Broadcast to socket without channel

I checked on the forum but still didn’t find a response
Is there any way to send a push to socket without channels. LIke my UserSocket is connected and I want to make something Endpoint.brocast("user_socket:foo", "bar", %{"foo" => "bar"})

It may be easier to find an answer if you share what effect you are trying to achieve.

Are you trying to send a message to exactly one of the user’s connections? i.e. they have multiple browsers / mobile app connections and you want to send a message to a specific one, but not all the others? Or something else?

Let’s say each client connects to server using a token, token is self contained according to Phoenix.Token

  1. Assign a user connected to socket
def connect(%{"token" => token}, socket) do
  {:ok, data} = Phoenix.Token.verify(secret, user_salt, token, max_age: 86400)
  {:ok, assign(socket, :user, %{id: token})}
end
  1. Generate a socket id via
def id(socket), do: "socket.users:#{socket.assigns[:user].id}"

Then somewhere in code send to socket.users:[user_id] some data. Let’s say I opened mobile app and web page with user_id equals to 1 then when I call something like Endpoint.broadcast_to_socket("socket:users:[user_id]")

Sorry, but I do not quite understand what the question is :confused:

What you described is how it works: given the id/1 function, you can use that in Endpoint.broadcast, e.g.:

MyApp.Endpoint.broadcast("users_socket:" <> user_id, "mymessage", %{})

And then all the connections with user_id would receive that message.

1 Like

Are you sure that I am able to do this without connection to any channel?

1 Like

Here are the docs for Phoenix.Socket.id/1. It has that exact example there :slight_smile: Give it a try!

Yeah. I did
If I send hardcoded disconnect as per example it drops connection
But if it looks like

TheAPp.Web.Endpoint.broadcast("socket.users:1", "Hello", %{})

nothing happens
Still the question is that possible to send some piece of data to the client without channels using a socket id
P.S. Without channel means no channel modules at all and no joining

Looking into it more, you do apparently need to connect to a channel. :frowning: sorry for the noise.

However, you can easily create a users: channel that does not require any special auth:

  def join(<<"user:", id :: binary>>, payload, socket) do
    if socket.assigns.user.id == id do
      send(self(), :after_join)
      {:ok, socket}
    else
      {:error, %{reason: "unauthorized"}}
    end
  end

… or similar.

and then you can broadcast to the channel. Is there a specific reason you are avoiding connecting to the channel?

2 Likes

That’s the point that it hasn’t an extra “connection” when it is already opened socket there for any data.

Channels do not create an extra network connection. They are multiplexed over the socket. On the server-side channels are quite cheap. So I wouldn’t worry about that.

2 Likes
MyApp.Endpoint.broadcast("users_socket:" <> user_id, "mymessage", %{})

I think it would only work for "disconnect". I remember struggling with it here How to put something in a socket's assigns from another process

1 Like

I don’t worry about that. I just don’t need the channel’s logic

  1. When it goes down for some reason the client has to rejoin to the channel not to the socket
  2. When socket goes down then all the channels have to rejoin
    Like why do the client app has to worry about double connection. So In my case it doesn’t make sense. I agree channels are great when you want to implement observing feature like Youtube comments on the right or event here for new replies

I googled through the forum and was hoping that may be something changed :slight_smile:
According to the code it is just a hardcoded event name. So actually I believe it is a limitation for some reason to give an interface to interact only via channels but then the question is why :thinking:

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

disconnect is of course the only hard-coded message, but you can listen for others on the socket (not channel) PID by subscribing it to what it needs.

Sorry didn’t understand the idea. Can you explain it more detailed?

Literally just that, subscribe to a pubsub topic then handle such messages, though I’m unsure how to setup such a receive in a socket (if there is no good API then one really should be PR’d in to handle arbitrary unhandled messages to the socket PID).

You might try running <YourApp>.Endpoint.subscribe("<some_topic>") from within the connect/2 callback. Supposedly, it would run inside the process handling the socket. Then you would be able to broadcast to all socket processes subscribed to "<some_topic>" with <YourApp>.Endpoint.broadcast("<some_topic>", some_event, some_msg) and see where it gets you. If phoenix’s socket is just a handler module for cowboy, you might be able to use cowboy’s callbacks to handle incoming messages, which IIRC is websocket_info, which seems to be forwarded to handler’s ws_info in phoenix v1.3 and to handle_info in phoenix master.

if there is no good API then one really should be PR’d in to handle arbitrary unhandled messages to the socket PID

It seems like handle_info has been added as a possible callback to phoenix.socket in v1.4. But in v1.3 the messages are simply ignored unless you pack them in a {:socket_push, _, _encoded_payload} tuple.

2 Likes

Awesome! Good to know! :slight_smile:

What all @idi527 said looks right though, you subscribe from inside the connect callback. :slight_smile:

1 Like

I tried to subscribe in connect but yeah for some reason it returns :ok but nothing happens.
1.4 is not released yet, so it is better to wait I think before using it in production.

What do you mean by “nothing happens”? Have you tried sending messages to that topic?