Search code examples
socketsphoenix-frameworkelixirphoenix-channels

Phoenix Channels - Multiple channels per socket


I'm writing an application using Elixir Channels to handle realtime events. I understand that there will be 1 socket open per client and can multiplex multiple channels over it. So my app is a chat application where users are part of multiple group chats. I have 1 Phoenix Channel called MessageChannel where the join method will handle dynamic topics.

def join("groups:" <> group_id, payload, socket) do
....

Let's say John joins groups/topics A and B while Bob only join group/topic B. When john sends a message to group/topic A, broadcast!/3 will also send that message to Bob too correct? Because handle_in doesn't have a context of which topic/group the message was sent to.

How would I handle it so that Bob doesn't receive the events that was sent to group A. Am I designing this right?


Solution

  • Because handle_in doesn't have a context of which topic/group the message was sent to.

    When Phoenix.Channel.broadcast/3 is called, apparently it does have the topic associated with the message (which is not obvious from the signature). You can see the code starting on this line of channel.ex:

    def broadcast(socket, event, message) do
        %{pubsub_server: pubsub_server, topic: topic} = assert_joined!(socket)
        Server.broadcast pubsub_server, topic, event, message
    end
    

    So when the call to broadcast/3 is made using the socket, it pattern matches out the current topic, and then makes a call to the underlying Server.broadcast/4.

    (If you're curious like I was, this in turn makes a call to the underlying PubSub.broadcast/3 which does some distribution magic to route the call to your configured pubsub implementation server, most likely using pg2 but I digress...)

    So, I found this behavior not obvious from reading the Phoenix.Channel docs, but they do state it explicitly in the phoenixframework channels page in Incoming Events:

    broadcast!/3 will notify all joined clients on this socket's topic and invoke their handle_out/3 callbacks.

    So it's only being broadcasted "on this socket's topic". They define topic on that same page as:

    topic - The string topic or topic:subtopic pair namespace, for example “messages”, “messages:123”

    So in your example, the "topics" are actually the topic:subtopic pair namespace strings: "groups:A" and "groups:B". John would have to subscribe to both of these topics separately on the client, so you would actually have references to two different channels, even though they're using the same socket. So assuming you're using the javascript client, the channel creation looks something like this:

    let channelA = this.socket.channel("groups:A", {});
    let channelB = this.socket.channel("groups:B", {});
    

    Then when you go to send a message on the channel from a client, you are using only the channel that has a topic that gets pattern matched out on the server as we saw above.

    channelA.push(msgName, msgBody);