I am trying to track the presence of a user on the UserSocker
using Phoenix.Presence
without the need for the client to be present on a specific channel.
Later on I want to subscribe to the presence of a user in different channels to be informed about the users presence.
I got this working except receiving a presence_diff
when a user disconnects. What I am doing is tracking the presence from the UserSocket
on a distinct topic for each user:
defmodule MyAppWeb.UserSocket do
# ...
def connect(%{"user_id" => user_id, "password" => password}, socket) do
case Accounts.authenticate(user_id, password) do
{:ok, user} ->
track_user_presence(socket.transport_pid, user)
{:ok, assign(socket, :user, user)}
_error -> :error
end
end
defp user_presence_topic(user_id) do
"user_presence:#{user_id}"
end
defp track_user_presence(pid, user) do
MyApp.Presence.track(pid, user_presence_topic(user.id), user.id, %{
online_at: inspect(System.system_time(:seconds))
})
end
end
From my channel I am subscribing to the users distinct presence topics:
defmodule MyAppWeb.RoomChannel do
# ...
def join("room:" <> room_id, payload, socket) do
send(self(), :after_join)
{:ok, assign(socket, :room_id, room_id)}
end
def handle_info(:after_join, socket) do
user_ids = ~w(1 2)
presence_state = get_and_subscribe_to_user_presence(socket, user_ids)
push(socket, "presence_state", presence_state)
{:noreply, socket}
end
def get_and_subscribe_to_user_presence(socket, user_ids) do
user_ids
|> Enum.map(&user_presence_topic/1)
|> Enum.map(fn topic ->
Phoenix.PubSub.subscribe(
socket.pubsub_server,
topic,
fastlane: {socket.transport_pid, socket.serializer, []}
)
Presence.list(topic)
end)
|> Enum.reduce(%{}, fn map, acc -> Map.merge(acc, map) end)
end
defp user_presence_topic(user_id) do
"user_presence:#{user_id}"
end
end
I came to the conclusion that I somehow need to monitor the sockets transport_pid
and send the presence diff myself when the socket terminates.
Another idea was to join the client to a separate presence channel from the UserSocket.connect/2
function but I did not found out how to archive that so far.
I hacked together a simple phoenix app with tests outlining the issue: https://github.com/kbredemeier/socket_presence
Any advice on this would be appreciated.
Figured it out. Instead of using PubSub.subscribe/3
I ended up using
MyApp.Endpoint.subscribe/1
with the topic I wanted to subscribe to and implementing handle_info/2
on my subscribing channel. handle_info/2
will then receive the brodcasts from the subscribed channel which in my case were the presence_diff
s.
Chris McCord pointed out to me on slack that the fastlane
option for Phoenix.PubSub.subscribe/3
allows to message a different process than the subscriber when broadcasting. In my case this resulted in my client receiving the presence_diff
not on the channel I was expecting.