Search code examples
elixirphoenix-frameworkphoenix-channels

How to broadcast to one socket_id using phoenix


In the docs there is this example but it only works for disconnects https://hexdocs.pm/phoenix/Phoenix.Socket.html#module-examples

  use Phoenix.Socket

  channel "room:*", MyAppWeb.RoomChannel

  def connect(params, socket, _connect_info) do
    {:ok, assign(socket, :user_id, params["user_id"])}
  end

  def id(socket), do: "users_socket:#{socket.assigns.user_id}"
end

# Disconnect all user's socket connections and their multiplexed channels
MyAppWeb.Endpoint.broadcast("users_socket:" <> user.id, "disconnect", %{})

I have tried that and it only works for disconnects.

I've read in places that you can create a topic specific for that user but I could use a practical example. I am having trouble grokking the complexity and could use a practical example.

Thank you.

I was thinking if you could get the underlying socket in the ets table you can just push/4 to that socket but I don't know how to do that.


Solution

  • You need to actually subscribe to a topic in order to receive the broadcast.

    The "disconnect" message is special because it doesn't require subscription.

    Inside the Phoenix.Socket source code, there exists these three lines:

      def __info__(%Broadcast{event: "disconnect"}, state) do
        {:stop, {:shutdown, :disconnected}, state}
      end
    

    and they are the reason the socket state changes when the "disconnect" broadcast is received.

    The "fallback" function looks like this:

      def __info__(_, state) do
        {:ok, state}
      end
    

    which means for any and all "other" topics, the socket state is unchanged.

    You need to manually subscribe to "other" topics.

    It should look something like this:

    socket.endpoint.subscribe("some_topic:" <> socket.assigns.user_id)
    

    Then you can broadcast messages like this:

    MyAppWeb.Endpoint.broadcast("some_topic:" <> user.id, "some_event", %{foo: bar})
    

    If you are doing this with LiveView then the easiest way to get started is to put this in your mount function:

    if connected?(socket) do
      socket.endpoint.subscribe("some_topic:" <> socket.assigns.user_id)
    end
    

    and to then have a handle_info like this:

      @impl true
      def handle_info(%{event: "some_event"} = info, socket) do
        IO.inspect info, label: "INFO"
        {:noreply, socket}
      end