Connecting to Channel from within application

Hey everyone, here’s my use case: I have a channel that sends events to a GenServer running a simulation and that GenServer responds, as well as sends broadcast back to the channel. Now I want to create another GenServer that can connect to that same channel and send and receive messages like any other client.

I’ve done some searching and noticed there are libraries that implement a full client, as in use websockets to connect to the channel, but that seems overkill since my new GenServers are running within the same application. Is there anyway to achieve this without running a full client?

Failing this I think I could just have the two GenServers communicate directly with each other but I would like to avoid creating a different communication channel within my simulation.

You can simply use PubSub. Subscribe to the channel topic to “listen in” on the channel messages. Broadcast on the channel topic to send messages to channel processes.

def init(...) do
  MyApp.Endpoint.subscribe("room:123")
  ...
end

alias Phoenix.Socket.Broadcast
def handle_info(%Broadcast{topic: "new_msg"} = msg, state) do
  # do something on new chat message broadcasted by channel process
end

def broadcast_new_msg(body) do
  MyApp.Endpoint.broadcast_from(self(), "room:123", "new_msg", %{body: body})
end
2 Likes

Thanks for the quick reply, Chris! This looks like it will work for my needs. A couple more questions if you don’t mind. Does subscribing to the topic trigger the topics join callback? I’m presuming it does not as the join callback takes a socket (that’s easy enough to get around in my case). From looking at the docs for PubSub, it looks like after I subscribe I need to call Process.info at some interval to get notified of incoming messages, is this correct?

No. See Chris’s usage of handle_info/2

Ah! Thanks for pointing that out. I understand now.

Hi! Sorry to resurrect this. Is this solution still valid? I can’t get it to work. Do I need to set something else up? I believe I have a fairly default setup (see below) using the default generated @session_options in the endpoint. I have confirmed that the session_uuid sent to the genserver is correct. What can I be missing?

endpoint.ex

socket "/socket/session", MyApp.SessionSocket,
    websocket: [connect_info: [session: @session_options]

socket.ex

use Phoenix.Socket
channel "lobby:*", MyApp.Channels.LobbyChannel, websocket: true, longpoll: false

lobby_channel.ex

use Phoenix.Channel
@impl true
def join("lobby:" <> session_uuid, _payload, socket) do
  ...

genServer.ex

use GenServer
def init(session_uuid) do
  MyApp.Endpoint.subscribe("lobby:#{session_uuid}")
  ...
end

def handle_info(msg, state) do
  IO.puts("This never gets called")
end

Just to cover the basics: Are there messages broadcasted on that topic while the genserver is alive?

This is what I am referring to when I say “Do I need to set something else up?” Does this mean I need to re-broadcast the message from handle_in in LobbyChannel? It was my (perhaps misinformed) assumption that this was done automagically.

Yes you need to broadcast messages in handle_in. That’s not a “re-broadcast” though, as handle_in doesn’t deal with broadcast messages, but with events of the connected client. The server end of a channel does automatially forward pubsub messages it receives to its connected client, but events of the client are not automatically considered messages to broadcast.

Ok thank you for clarifying that. And “re-broadcasting” was just a poor choice of words, didn’t actually mean “broadcasting again” :smiley: I had solved it in a similar way, but with GenServer.call instead.