Skip to content

Commit 18dac56

Browse files
committed
implement everything needed for debouncing websocket message
This can be used with `WebsocketDemoWeb.Endpoint.broadcast!("demo:32211", "debounce_ping", %{})` It requires that demo:id (which is dynamic based on id) is grabbed from the frontend manually
1 parent 6ed70a0 commit 18dac56

File tree

2 files changed

+127
-2
lines changed

2 files changed

+127
-2
lines changed

lib/websocket_demo_web/channels/demo_channel.ex

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ defmodule WebsocketDemoWeb.DemoChannel do
22
require Logger
33
use Phoenix.Channel
44

5-
intercept []
5+
intercept ["debounce_ping"]
66

77
def join("demo:" <> _id, _params, socket) do
88
timer_ref = Process.send(self(), :tick, [])
@@ -11,6 +11,7 @@ defmodule WebsocketDemoWeb.DemoChannel do
1111
socket
1212
|> assign(:current_tick, 0)
1313
|> assign(:tick_timer, timer_ref)
14+
|> assign(:debounce_ping_debounce_state, :idle)
1415

1516
{:ok, socket}
1617
end
@@ -45,4 +46,38 @@ defmodule WebsocketDemoWeb.DemoChannel do
4546

4647
{:noreply, new_socket}
4748
end
49+
50+
def handle_info(:debounce_ping, socket = %{assigns: %{debounce_ping_debounce_state: :debouncing}}) do
51+
new_socket =
52+
socket
53+
|> assign(:debounce_ping_debounce_state, :idle)
54+
|> assign(:debounce_ping_debounce_timer, nil)
55+
56+
{:noreply, new_socket}
57+
end
58+
59+
def handle_info(:debounce_ping, socket = %{assigns: %{debounce_ping_debounce_state: :called}}) do
60+
{:noreply, handle_debounce_ping_call(socket)}
61+
end
62+
63+
def handle_out("debounce_ping", _, socket = %{assigns: %{debounce_ping_debounce_state: :idle}}) do
64+
{:noreply, handle_debounce_ping_call(socket)}
65+
end
66+
67+
def handle_out("debounce_ping", _, socket = %{assigns: %{debounce_ping_debounce_state: :debouncing}}) do
68+
{:noreply, assign(socket, :debounce_ping_debounce_state, :called)}
69+
end
70+
71+
def handle_out("debounce_ping", _, socket = %{assigns: %{debounce_ping_debounce_state: :called}}) do
72+
{:noreply, socket}
73+
end
74+
75+
defp handle_debounce_ping_call(socket) do
76+
push socket, "debounce_ping", %{}
77+
timer = Process.send_after(self(), :debounce_ping, 3000)
78+
79+
socket
80+
|> assign(:debounce_ping_debounce_state, :debouncing)
81+
|> assign(:debounce_ping_debounce_timer, timer)
82+
end
4883
end

test/websocket_demo_web/channels/demo_channel_test.exs

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ defmodule WebsocketDemoWeb.DemoChannelTest do
3131
assert :sys.get_state(socket.channel_pid).assigns.current_tick == 1
3232
Process.send(socket.channel_pid, :tick, [])
3333
assert :sys.get_state(socket.channel_pid).assigns.current_tick == 2
34-
assert Process.read_timer(socket.assigns.tick_timer) == 5000
34+
assert_in_delta Process.read_timer(socket.assigns.tick_timer), 5000, 10
3535
end
3636
end
3737

@@ -65,4 +65,94 @@ defmodule WebsocketDemoWeb.DemoChannelTest do
6565
leave(socket)
6666
end
6767
end
68+
69+
describe "handle_out debounce_ping" do
70+
test "state=idle, the socket receives an immediate push" do
71+
{:ok, _, socket} =
72+
socket(nil, %{})
73+
|> subscribe_and_join(DemoChannel, "demo:1")
74+
75+
broadcast_from! socket, "debounce_ping", %{}
76+
assert_push "debounce_ping", %{}
77+
end
78+
79+
test "state=idle, the timer is setup for 3s from now, and sets the next state" do
80+
{:ok, _, socket} =
81+
socket(nil, %{})
82+
|> subscribe_and_join(DemoChannel, "demo:1")
83+
84+
broadcast_from! socket, "debounce_ping", %{}
85+
assert_push "debounce_ping", %{}
86+
87+
state = :sys.get_state(socket.channel_pid).assigns
88+
assert_in_delta Process.read_timer(state.debounce_ping_debounce_timer), 3000, 10
89+
assert state.debounce_ping_debounce_state == :debouncing
90+
end
91+
92+
test "state=debouncing, the state is set to called" do
93+
{:ok, _, socket} =
94+
socket(nil, %{})
95+
|> subscribe_and_join(DemoChannel, "demo:1")
96+
97+
broadcast_from! socket, "debounce_ping", %{}
98+
assert_push "debounce_ping", %{}
99+
broadcast_from! socket, "debounce_ping", %{}
100+
101+
state = :sys.get_state(socket.channel_pid).assigns
102+
assert_in_delta Process.read_timer(state.debounce_ping_debounce_timer), 3000, 10
103+
assert state.debounce_ping_debounce_state == :called
104+
end
105+
106+
test "state=called, the state is not changed" do
107+
{:ok, _, socket} =
108+
socket(nil, %{})
109+
|> subscribe_and_join(DemoChannel, "demo:1")
110+
111+
broadcast_from! socket, "debounce_ping", %{}
112+
assert_push "debounce_ping", %{}
113+
broadcast_from! socket, "debounce_ping", %{}
114+
broadcast_from! socket, "debounce_ping", %{}
115+
116+
state = :sys.get_state(socket.channel_pid).assigns
117+
assert_in_delta Process.read_timer(state.debounce_ping_debounce_timer), 3000, 10
118+
assert state.debounce_ping_debounce_state == :called
119+
end
120+
end
121+
122+
describe "handle_info :debounce_ping" do
123+
test "from state=debouncing, sets the state to idle without a push" do
124+
{:ok, _, socket} =
125+
socket(nil, %{})
126+
|> subscribe_and_join(DemoChannel, "demo:1")
127+
128+
broadcast_from! socket, "debounce_ping", %{}
129+
assert_push "debounce_ping", %{}
130+
131+
send socket.channel_pid, :debounce_ping
132+
133+
state = :sys.get_state(socket.channel_pid).assigns
134+
assert state.debounce_ping_debounce_timer == nil
135+
assert state.debounce_ping_debounce_state == :idle
136+
end
137+
138+
test "from state=called, sets the state to debouncing with a push" do
139+
{:ok, _, socket} =
140+
socket(nil, %{})
141+
|> subscribe_and_join(DemoChannel, "demo:1")
142+
143+
broadcast_from! socket, "debounce_ping", %{}
144+
assert_push "debounce_ping", %{}
145+
broadcast_from! socket, "debounce_ping", %{}
146+
147+
state = :sys.get_state(socket.channel_pid).assigns
148+
assert state.debounce_ping_debounce_state == :called
149+
150+
send socket.channel_pid, :debounce_ping
151+
assert_push "debounce_ping", %{}
152+
153+
state = :sys.get_state(socket.channel_pid).assigns
154+
assert_in_delta Process.read_timer(state.debounce_ping_debounce_timer), 3000, 10
155+
assert state.debounce_ping_debounce_state == :debouncing
156+
end
157+
end
68158
end

0 commit comments

Comments
 (0)