Joining a channel from within handle_in

I wonder if I can do something like this:

defmodule Web.RoomChannel do
  use Web, :channel

  def join("rooms:lobby", _params, socket) do
    rooms = Rooms.list()
    {:ok, {:ok, rooms}, socket}
  end

  def join("rooms:" <> room_id, _params, socket) do
    {:ok, socket}
  end


  def handle_in("room:create", %{"title" => title},
                %{assigns: %{user_id: owner_id}} = socket) do
    {:ok, room_id} = Rooms.create(title, owner_id)
    {:ok, socket} = join("rooms:" <> room_id, nil, socket)
    {:reply, {:ok, %{room_id: room_id}}, socket}
  end
end

Or is it better to reply with room_id and rejoin from the client side?

Two ways:

  1. If you want to have the different channels have very different functions, have the client rejoin.
  2. If the channels have identical interfaces and such, then you can have multiple channels go to ‘this’ singular process by just subscribing to the other names (I do this heavily in my app since I have many things broadcast to specific names all handled in a single place so I do not get, say, 40 processes per user, which is a bit excessive ^.^):
MyServer.Endpoint.subscribe("rooms:" <> room_id)
1 Like

@OvermindDL1 is it possible to track presence when directly subscribing to a channel?

Since this doesn’t trigger the other channel’s join/2 callback I’m not sure how to initiate presence tracking on the other channel. Maybe I need a different approach altogether.

You should already be getting such events?

Unsure about join but I’d think it should get the notification that something subscribed to it. Do you have simplified code replicating the issue you have?

Sure. Here’s the most relevant snippets:

defmodule PresenceTestWeb.UserChannel do
  use PresenceTestWeb, :channel

  alias PresenceTestWeb.Presence

  def join("user:lobby", payload, socket) do
    if authorized?(payload) do
      socket.endpoint.subscribe("user_chat:lobby")
      send(self(), :after_join)
      {:ok, socket}
    else
      {:error, %{reason: "unauthorized"}}
    end
  end

  def handle_info(:after_join, socket) do
    push socket, "presence_state", Presence.list(socket)
    {:ok, _} = Presence.track(socket, 42, %{
      online_at: inspect(System.system_time(:seconds))
    })
    {:noreply, socket}
  end

  # other code
end
defmodule PresenceTestWeb.UserChatChannel do
  use PresenceTestWeb, :channel

  alias PresenceTestWeb.Presence

  def join("user_chat:lobby", payload, socket) do
    if authorized?(payload) do
      send(self(), :after_join)
      {:ok, socket}
    else
      {:error, %{reason: "unauthorized"}}
    end
  end

  def handle_info(:after_join, socket) do
    push socket, "presence_state", Presence.list(socket)
    {:ok, _} = Presence.track(socket, socket.assigns.user_id, %{
      online_at: inspect(System.system_time(:seconds))
    })
    {:noreply, socket}
  end
  # other code
end

After a client joins the user:lobby topic I want to track them as present in the user_chat:lobby topic as well. But currently when there is a user joined I see the following:

iex(5)> PresenceTestWeb.Presence.list("user_chat:lobby")
%{}
iex(6)> PresenceTestWeb.Presence.list("user:lobby")
%{"42" => %{metas: [%{online_at: "1525307553", phx_ref: "8314Df2yf4Q="}]}}

Here’s a link to the full code: https://github.com/axelson/repro_presence_test

Of course after typing all this out I notice that there is a Presence.track/4 callback (although it’s light on documentation): https://hexdocs.pm/phoenix/Phoenix.Presence.html#c:track/4

It initially through me for a loop because I didn’t recognize that it needed a straight pid instead of a process, but inside the UserChannel I was able to add secondary presence tracking with Presence.track/4:

{:ok, _} = Presence.track(self(), "user_chat:lobby", 42, %{
  online_at: inspect(System.system_time(:seconds))
})