arnodirlam
Subscribe all clients/presences in one channel to an external topic
We have a use case where we want to route an incoming call to all online users within a channel (“lobby”). Each of these users (“candidate pool”) can either accept or decline the incoming call. The first user accepting the incoming call will be connected and the selection process ends (first come first serve, max. 1).
Before anyone accepted the call, each candidate still in the pool (= not declined yet) should know who else is still in the pool.
Since channels and Presence are made for that, my intuition is to implement the system using it.
My idea is to create an external topic (new channel) for the incoming call, then add all candidates (from the “lobby” channel) to it, and use broadcast Presence diffs whenever the pool’s state changes. When a user accepts the incoming call, the external channel is immediately closed.
However, I’d like to avoid broadcasting to all users in “lobby” to join the external channel (client-side roundtrip), because of performance, reliability and security. I’d like to keep the logic server-side, ideally preserving the multi-node attributes that channels+presence gives me out-of-the box.
If I understand correctly, this forbids approaches using the “lobby” subscribers’ PIDs directly.
So my approach would be to broadcast a message to “lobby” with the external channel’s topic, then intercept the broadcast, let handle_out have each subscriber join the new channel, and cancel the message going to the client. All server-side. Once all candidates have joined the external topic, standard channels+Presence broadcasting can handle the rest.
Is this the best approach for the given use case?
Am I overlooking something?
On a sidenote, the phoenix JS-client will not “know” it’s subscribed to the external channel, so I have to either use its onMessage callback, or manipulate the topic of each outgoing message server-side using intercept and handle_out, correct?
Thanks a lot in advance for any feedback! ![]()
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #hex
- #performance










First 10 of 12 Posts
OvermindDL1
You can have more than one presence setup per channel as well, just have to give each a unique ID, but I use this quite often, just as a note.
But say you have N users in a lobby/pool, a call is made to this pool so a message is ‘broadcast’ to the topic id of the lobby/pool to pop up a message on the users interface or so. The first to accept it sends a message back to the call handler thing that has it join the call process while also sending a message to the other clients that it was accepted elsewhere with whatever other useful data. You may not even need presence for this unless you want to know who all is waiting rather than just broadcasting all willy-nilly (which is fine really ^.^).
But yeah, I’d have a user connect to a call when it is accepted, not when it is offered. You could even have them all join a presence so you know who is waiting for a response, and as they deny it they leave the presence, until one accepts then they all drop off when they get the message that it was accepted by someone else.
arnodirlam
Thanks for your fast reply and also commenting on the use case
Nice to know! I guess you pass in the original channel pid and another topic, similar to here?
You might be right that the info is not needed. However, I think it would be really useful for the users to know, and I’m also eager to wrap my head around this use case of Phoenix, testing whether it could really work like I’ve described in my question, keeping all the multi-node niceties in place
This is exactly what I’m trying to achieve. Ideally with broadcasts to only those users in that second presence.
You’re talking about another Presence within the same channel, not a separate channel, right?
So I assume that
OvermindDL1
Precisely!
Also indeed.
Presence is for presence information, not for broadcasting. If you want user channel to listen for specific things then have them join another specially named topic then broadcast to that. I do not mean have them join a new topic from the javascript side or even to spawn a new process, but literally just call
MyServer.Endpoint.subscribe("new:topic:thing:o'whatever"), and when you want them to stop listening on that topic then just callMyServer.Endpoint.unsubscribe("new:topic:thing:o'whatever"). Anything broadcast to those new topics and not the ‘channel topic’ will not appear inhandle_in/handle_out, but rather will appear inhandle_infowith aPhoenix.Socket.Broadcaststructure, use it like this:For example I use this in my notifications channel topic, then the notification internally registers to certain other things depending on which notification groups they are assigned to.
But yeah, presence is literally just for tracking presence in the group, not to communicating to it, for that you should subscribe to the new topic as well and broadcast to that.
arnodirlam
Thanks very much for the elaboration
If I understand correctly, for each incoming call, we now have one additional Presence and one additional topic (not channel) that’s “attached” to the main lobby channel but with a different topic each (probably best to use the same topic for both).
MyServer.Endpoint.subscribesubscribes the already existing lobby channel process to the new topic, in addition to the main topic it’s subscribed to by default.For some reason I’ve assumed that “subscribing to external topics” in the docs subscribes an individual client to another topic. Having re-read your answers, a background article and some source code I’ve realized that it’s actually the channel process itself that’s subscribed to topics and that just forwards all broadcasts to
PubSub. So it’s enough to subscribe the channel in order to “subscribe all clients in one channel to an external topic” (as I put it in the subject).Judging from the source code,
handle_infoin your example is used just likehandle_out. So I can easily lookup if the assigned user is in the Presence associated to the topic (as in Intercepting outgoing events) in order to broadcast to only those users/clients. That’s exactly what I wanted to achieveAfter all, what was decisive to understand is that
handle_in,handle_outetc.) I have a reference to both the individual client (socket) and the channel as a whole (self())Thanks so much for the explanations and bearing with me. It really helped - and hopefully will help others when they read this

OvermindDL1
Exactly on both.
Yep,
handle_infojust handles messages to this process that it does not know what they are, so that ‘you’ can handle them straight.You might want to have a catch-all
handle_infoat the bottom of thehandle_info’s too so a process does not crash if it happens to get a message it does not know (I log them).No problem, I delved into the code for them when I started using them a while back, so it is good to spread the knowledge.
arnodirlam
I’ve started implementing the approach we discussed, but have problems with the subscription.
The presence part works nicely: I create a presence on an external topic, I can access it in all the channel callbacks, and it even broadcasts
presence_diffevents to that topic whenever the presence changes.However, the only client who’s ever receiving anything broadcast to the external topic is the one who triggered the subscription (within a
handle_insent by her).Here’s a test that shows what I’ve tried, reduced to the broadcasting aspects:
which yields the output:
This shows that intercepted broadcasts to
room:lobbydo reach all clients, but broadcasts to the external topic reach only the first client.Am I overlooking something?
Do I need to broadcast the message to the main topic within handle_info or something like that?
OvermindDL1
To broadcast to the external topic (the one the others joined but did not initialize within) I think you need to use
Vira.Endpoint.broadcast.Search ‘broadcast’ at (because it has no anchor to link to ;-)): Phoenix.Endpoint — Phoenix v1.8.8
arnodirlam
That’s exactly the same as what
broadcast_from!does in tests. To make sure I’ve triedVira.Endpoint.broadcast!but with the same result. Broadcasts to the external topic are only sent to one clientIn your notifications example, do you subscribe each client individually or subscribe the whole channel once and every client in the channel gets every message sent to the external topic? I’m just wondering, because you have no filter mechanism in there, and the same approach doesn’t work for me
OvermindDL1
Each client has to subscribe itself.
pacoguzman
Hi all, I’m trying to implement something similar to this, so current clients (sockets) when they send a message that creates a record on the storage are notified later about any update to that record.
So I do the following in the channel module →
socket.endpoint.subscribe("records:#{record.id}"). The problem is that I’m not able to receive any notification on to that topic.And I’m not sure what I’m missing. I’m just calling
MyApp.Endpoint.broadcast("records:#{record_id}", "update", %{})from another process after updates happened.When you talk about
Is the part that I don’t understand I guess, in my example the subscriptions is done through the current channel process (self()) which is what is subscribed. Phoenix has deprecated passing a pid to subscribe and only allow the parent to be subscribed.