Skip to content

Commit f576bad

Browse files
committed
fix: enable presence on track message
currently the user would need to have enabled from the beginning of the channel. this will enable users to enable presence later in the flow by sending a track message which will enable presence messages for them
1 parent bd2c141 commit f576bad

File tree

6 files changed

+143
-22
lines changed

6 files changed

+143
-22
lines changed

lib/realtime/api.ex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,9 @@ defmodule Realtime.Api do
187187
end
188188

189189
defp list_extensions(type) do
190-
from(e in Extensions, where: e.type == ^type, select: e)
191-
|> Repo.all()
190+
query = from(e in Extensions, where: e.type == ^type, select: e)
191+
192+
Repo.all(query)
192193
end
193194

194195
def rename_settings_field(from, to) do

lib/realtime_web/channels/realtime_channel.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,7 @@ defmodule RealtimeWeb.RealtimeChannel do
376376
end
377377

378378
def handle_in("presence", payload, %{assigns: %{private?: false}} = socket) do
379-
with {:ok, socket} <- PresenceHandler.handle(payload, socket) do
379+
with {:ok, socket} <- PresenceHandler.handle(payload, nil, socket) do
380380
{:reply, :ok, socket}
381381
else
382382
{:error, :rate_limit_exceeded} ->

lib/realtime_web/channels/realtime_channel/presence_handler.ex

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,28 +52,25 @@ defmodule RealtimeWeb.RealtimeChannel.PresenceHandler do
5252
end
5353
end
5454

55-
@spec handle(map(), Socket.t()) ::
56-
{:ok, Socket.t()} | {:error, :rls_policy_error | :unable_to_set_policies | :rate_limit_exceeded}
57-
def handle(_, %{assigns: %{presence_enabled?: false}} = socket), do: {:ok, socket}
58-
def handle(payload, socket) when not is_private?(socket), do: handle(payload, nil, socket)
59-
6055
@spec handle(map(), pid() | nil, Socket.t()) ::
6156
{:ok, Socket.t()}
6257
| {:error, :rls_policy_error | :unable_to_set_policies | :rate_limit_exceeded | :unable_to_track_presence}
63-
def handle(_, _, %{assigns: %{presence_enabled?: false}} = socket), do: {:ok, socket}
64-
6558
def handle(%{"event" => event} = payload, db_conn, socket) do
6659
event = String.downcase(event, :ascii)
6760
handle_presence_event(event, payload, db_conn, socket)
6861
end
6962

70-
def handle(_payload, _db_conn, socket), do: {:ok, socket}
63+
def handle(_, _, socket), do: {:ok, socket}
7164

72-
defp handle_presence_event("track", payload, _db_conn, socket) when not is_private?(socket) do
73-
track(socket, payload)
65+
defp handle_presence_event("track", payload, _, socket) when not is_private?(socket) do
66+
with :ok <- limit_presence_event(socket) do
67+
socket = assign(socket, :presence_enabled?, true)
68+
track(socket, payload)
69+
end
7470
end
7571

76-
defp handle_presence_event("track", payload, db_conn, socket) when is_nil(socket.assigns.policies.presence.write) do
72+
defp handle_presence_event("track", payload, db_conn, socket)
73+
when is_private?(socket) and is_nil(socket.assigns.policies.presence.write) do
7774
%{assigns: %{authorization_context: authorization_context, policies: policies}} = socket
7875

7976
case Authorization.get_write_authorizations(policies, db_conn, authorization_context) do
@@ -92,7 +89,10 @@ defmodule RealtimeWeb.RealtimeChannel.PresenceHandler do
9289
end
9390

9491
defp handle_presence_event("track", payload, _, socket) when can_write_presence?(socket) do
95-
track(socket, payload)
92+
with :ok <- limit_presence_event(socket) do
93+
socket = assign(socket, :presence_enabled?, true)
94+
track(socket, payload)
95+
end
9696
end
9797

9898
defp handle_presence_event("track", _, _, socket) when not can_write_presence?(socket) do

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ defmodule Realtime.MixProject do
44
def project do
55
[
66
app: :realtime,
7-
version: "2.47.1",
7+
version: "2.47.2",
88
elixir: "~> 1.17.3",
99
elixirc_paths: elixirc_paths(Mix.env()),
1010
start_permanent: Mix.env() == :prod,

test/integration/rt_channel_test.exs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -909,6 +909,56 @@ defmodule Realtime.Integration.RtChannelTest do
909909
assert_receive %Message{event: "phx_reply", payload: %{"status" => "ok"}}, 500
910910
refute_receive %Message{event: "presence_state"}, 500
911911
end
912+
913+
test "presence automatically enabled when user sends track message for public channel", %{tenant: tenant} do
914+
{socket, _} = get_connection(tenant)
915+
config = %{presence: %{key: "", enabled: false}, private: false}
916+
topic = "realtime:any"
917+
918+
WebsocketClient.join(socket, topic, %{config: config})
919+
920+
assert_receive %Message{event: "phx_reply", payload: %{"status" => "ok"}, topic: ^topic}, 300
921+
refute_receive %Message{event: "presence_state"}, 500
922+
923+
payload = %{
924+
type: "presence",
925+
event: "TRACK",
926+
payload: %{name: "realtime_presence_96", t: 1814.7000000029802}
927+
}
928+
929+
WebsocketClient.send_event(socket, topic, "presence", payload)
930+
931+
assert_receive %Message{event: "presence_diff", payload: %{"joins" => joins, "leaves" => %{}}, topic: ^topic}
932+
933+
join_payload = joins |> Map.values() |> hd() |> get_in(["metas"]) |> hd()
934+
assert get_in(join_payload, ["name"]) == payload.payload.name
935+
assert get_in(join_payload, ["t"]) == payload.payload.t
936+
end
937+
938+
@tag policies: [:authenticated_read_broadcast_and_presence, :authenticated_write_broadcast_and_presence]
939+
test "presence automatically enabled when user sends track message for private channel",
940+
%{tenant: tenant, topic: topic} do
941+
{socket, _} = get_connection(tenant, "authenticated")
942+
config = %{presence: %{key: "", enabled: false}, private: true}
943+
topic = "realtime:#{topic}"
944+
945+
WebsocketClient.join(socket, topic, %{config: config})
946+
assert_receive %Message{event: "phx_reply", payload: %{"status" => "ok"}, topic: ^topic}, 300
947+
refute_receive %Message{event: "presence_state"}, 500
948+
949+
payload = %{
950+
type: "presence",
951+
event: "TRACK",
952+
payload: %{name: "realtime_presence_96", t: 1814.7000000029802}
953+
}
954+
955+
WebsocketClient.send_event(socket, topic, "presence", payload)
956+
957+
assert_receive %Message{event: "presence_diff", payload: %{"joins" => joins, "leaves" => %{}}, topic: ^topic}, 500
958+
join_payload = joins |> Map.values() |> hd() |> get_in(["metas"]) |> hd()
959+
assert get_in(join_payload, ["name"]) == payload.payload.name
960+
assert get_in(join_payload, ["t"]) == payload.payload.t
961+
end
912962
end
913963

914964
describe "token handling" do

test/realtime_web/channels/realtime_channel/presence_handler_test.exs

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ defmodule RealtimeWeb.RealtimeChannel.PresenceHandlerTest do
9999
end
100100
end
101101

102-
describe "handle/2" do
102+
describe "handle/3" do
103103
test "with true policy and is private, user can track their presence and changes", %{
104104
tenant: tenant,
105105
topic: topic,
@@ -142,7 +142,7 @@ defmodule RealtimeWeb.RealtimeChannel.PresenceHandlerTest do
142142
policies = %Policies{presence: %PresencePolicies{read: false, write: false}}
143143
socket = socket_fixture(tenant, topic, key, policies: policies, private?: false)
144144

145-
assert {:ok, _socket} = PresenceHandler.handle(%{"event" => "track"}, socket)
145+
assert {:ok, _socket} = PresenceHandler.handle(%{"event" => "track"}, nil, socket)
146146

147147
topic = socket.assigns.tenant_topic
148148
assert_receive %Broadcast{topic: ^topic, event: "presence_diff", payload: %{joins: joins, leaves: %{}}}
@@ -229,6 +229,7 @@ defmodule RealtimeWeb.RealtimeChannel.PresenceHandlerTest do
229229
assert {:ok, socket} =
230230
PresenceHandler.handle(
231231
%{"event" => "track", "payload" => %{"metadata" => random_string()}},
232+
nil,
232233
socket
233234
)
234235

@@ -248,20 +249,20 @@ defmodule RealtimeWeb.RealtimeChannel.PresenceHandlerTest do
248249
assert log =~ "UnknownPresenceEvent"
249250
end
250251

251-
test "socket with presence enabled false will ignore presence events in public channel", %{
252+
test "socket with presence enabled false will ignore non-track presence events in public channel", %{
252253
tenant: tenant,
253254
topic: topic
254255
} do
255256
key = random_string()
256257
policies = %Policies{presence: %PresencePolicies{read: true, write: true}}
257258
socket = socket_fixture(tenant, topic, key, policies: policies, private?: false, enabled?: false)
258259

259-
assert {:ok, _socket} = PresenceHandler.handle(%{"event" => "track"}, socket)
260+
assert {:ok, _socket} = PresenceHandler.handle(%{"event" => "untrack"}, nil, socket)
260261
topic = socket.assigns.tenant_topic
261262
refute_receive %Broadcast{topic: ^topic, event: "presence_diff"}
262263
end
263264

264-
test "socket with presence enabled false will ignore presence events in private channel", %{
265+
test "socket with presence enabled false will ignore non-track presence events in private channel", %{
265266
tenant: tenant,
266267
topic: topic,
267268
db_conn: db_conn
@@ -270,11 +271,80 @@ defmodule RealtimeWeb.RealtimeChannel.PresenceHandlerTest do
270271
policies = %Policies{presence: %PresencePolicies{read: true, write: true}}
271272
socket = socket_fixture(tenant, topic, key, policies: policies, private?: false, enabled?: false)
272273

273-
assert {:ok, _socket} = PresenceHandler.handle(%{"event" => "track"}, db_conn, socket)
274+
assert {:ok, _socket} = PresenceHandler.handle(%{"event" => "untrack"}, db_conn, socket)
274275
topic = socket.assigns.tenant_topic
275276
refute_receive %Broadcast{topic: ^topic, event: "presence_diff"}
276277
end
277278

279+
test "socket with presence disabled will enable presence on track message for public channel", %{
280+
tenant: tenant,
281+
topic: topic
282+
} do
283+
key = random_string()
284+
policies = %Policies{presence: %PresencePolicies{read: true, write: true}}
285+
socket = socket_fixture(tenant, topic, key, policies: policies, private?: false, enabled?: false)
286+
287+
refute socket.assigns.presence_enabled?
288+
289+
assert {:ok, updated_socket} = PresenceHandler.handle(%{"event" => "track"}, nil, socket)
290+
291+
assert updated_socket.assigns.presence_enabled?
292+
topic = socket.assigns.tenant_topic
293+
assert_receive %Broadcast{topic: ^topic, event: "presence_diff", payload: %{joins: joins, leaves: %{}}}
294+
assert Map.has_key?(joins, key)
295+
end
296+
297+
test "socket with presence disabled will enable presence on track message for private channel", %{
298+
tenant: tenant,
299+
topic: topic,
300+
db_conn: db_conn
301+
} do
302+
key = random_string()
303+
policies = %Policies{presence: %PresencePolicies{read: true, write: true}}
304+
socket = socket_fixture(tenant, topic, key, policies: policies, private?: true, enabled?: false)
305+
306+
refute socket.assigns.presence_enabled?
307+
308+
assert {:ok, updated_socket} = PresenceHandler.handle(%{"event" => "track"}, db_conn, socket)
309+
310+
assert updated_socket.assigns.presence_enabled?
311+
topic = socket.assigns.tenant_topic
312+
assert_receive %Broadcast{topic: ^topic, event: "presence_diff", payload: %{joins: joins, leaves: %{}}}
313+
assert Map.has_key?(joins, key)
314+
end
315+
316+
test "socket with presence disabled will not enable presence on untrack message", %{
317+
tenant: tenant,
318+
topic: topic,
319+
db_conn: db_conn
320+
} do
321+
key = random_string()
322+
policies = %Policies{presence: %PresencePolicies{read: true, write: true}}
323+
socket = socket_fixture(tenant, topic, key, policies: policies, enabled?: false)
324+
325+
refute socket.assigns.presence_enabled?
326+
327+
assert {:ok, updated_socket} = PresenceHandler.handle(%{"event" => "untrack"}, db_conn, socket)
328+
329+
refute updated_socket.assigns.presence_enabled?
330+
topic = socket.assigns.tenant_topic
331+
refute_receive %Broadcast{topic: ^topic, event: "presence_diff"}
332+
end
333+
334+
test "socket with presence disabled will not enable presence on unknown event", %{
335+
tenant: tenant,
336+
topic: topic,
337+
db_conn: db_conn
338+
} do
339+
key = random_string()
340+
policies = %Policies{presence: %PresencePolicies{read: true, write: true}}
341+
socket = socket_fixture(tenant, topic, key, policies: policies, enabled?: false)
342+
343+
refute socket.assigns.presence_enabled?
344+
345+
assert {:error, :unknown_presence_event} = PresenceHandler.handle(%{"event" => "unknown"}, db_conn, socket)
346+
end
347+
278348
@tag policies: [:authenticated_read_broadcast_and_presence, :authenticated_write_broadcast_and_presence]
279349
test "rate limit is checked on private channel", %{tenant: tenant, topic: topic, db_conn: db_conn} do
280350
key = random_string()

0 commit comments

Comments
 (0)