Skip to content

Commit 6cfe6e1

Browse files
authored
fix: enable presence on track message (#1527)
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 6cfe6e1

File tree

6 files changed

+137
-20
lines changed

6 files changed

+137
-20
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: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,28 +52,22 @@ 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
65+
defp handle_presence_event("track", payload, _, socket) when not is_private?(socket) do
7366
track(socket, payload)
7467
end
7568

76-
defp handle_presence_event("track", payload, db_conn, socket) when is_nil(socket.assigns.policies.presence.write) do
69+
defp handle_presence_event("track", payload, db_conn, socket)
70+
when is_private?(socket) and is_nil(socket.assigns.policies.presence.write) do
7771
%{assigns: %{authorization_context: authorization_context, policies: policies}} = socket
7872

7973
case Authorization.get_write_authorizations(policies, db_conn, authorization_context) do
@@ -111,6 +105,8 @@ defmodule RealtimeWeb.RealtimeChannel.PresenceHandler do
111105
end
112106

113107
defp track(socket, payload) do
108+
socket = assign(socket, :presence_enabled?, true)
109+
114110
%{assigns: %{presence_key: presence_key, tenant_topic: tenant_topic}} = socket
115111
payload = Map.get(payload, "payload", %{})
116112

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)