How to send a message to a user without having to join a channel client side

Is there a way to send a message to a specific user without having to join a channel client side? Especially without having to create a channel per user since the client doesn’t even know it’s user id (so no users:#{id} channels)

1 Like

As of now I’m doing this, in the client side I join channel notifications which is:

defmodule App.NotificationsChannel do
  use App.Web, :channel
  alias Phoenix.Socket.Broadcast

  def join("notifications", _payload, socket) do
    %App.User{id: id} = Guardian.Phoenix.Socket.current_resource(socket)
    :ok = App.Endpoint.subscribe("notifications:" <> id)
    {:ok, socket}
  end

  def handle_info(%Broadcast{topic: _, event: ev, payload: payload}, socket) do
    push(socket, ev, payload)
    {:noreply, socket}
  end
end

however a solution where the client can just connect without joining a channel and possibly without even using the phoenix js client would be better

The question becomes “How would you do this in normal javascript?” as this is a front-end issue (Phoenix can handle anything you’d need to do on the backend). :slight_smile:

Well then, backend side, how do I send a message to a specific user? Even if he has multiple tabs open on the same website

This is best handle with a user:id channel :slight_smile:

The server can send to any specific user, via its own channel like this.

socket.endpoint.broadcast!("user:#{id}", message, payload)

The user does not need to know the id, the server knows it. But for some reason, You don’t want to do this…

2 Likes

You give them a unique identifier. Generate a UUID, set it in a cookie, and use it to build the topic to join. No authentication and the user doesn’t need to know. You can even generate the UUID client side if you’re okay with the security implications.

You’re not going to be able to send a specific message to a specific user without giving them some kind of unique identifier.

1 Like

Well, both cases includes having a user joining a channel, which is what I’m doing now… That’s the “phoenix way”, routing websocket messages through channels, right? Which needs on the client side to either use phoenix.js or send a websocket message to join a channel (manually, but that’s what it is).

What I’m asking (since the client side it’s not our code, not our app, not our pages, not our js dependencies, no cookies etc…) is if there is a way to send a message to a specific user keeping all the channel joining or something else server-side.

So the user can just connect to the socket and listen to messages, no channel joining or anything else…
All this using just phoenix sockets (or channels internally) and not having to handle websockets with cowboy directly…

I’ve tried to use Endpoint.subscribe in the socket but it doesn’t receive any info message like the channel.

Can you be more specific about the thing that is to receive the messages and what it’s supposed to be using to receive them?

1 Like

Yeah sorry, I meant that the client (which is a javascript browser) might just use the default browser websocket API to receive a message… Like:

var exampleSocket = new WebSocket("ws://localhost:4000/socket");
exampleSocket.onmessage = function (event) {
  console.log(event.data);
}

So the backend could “theoretically” just authenticate the client that connects to its socket, see that the socket is user x and whenever I need, I can send a message to all the sockets belongings to user x

Are you okay with a modified version of Phoenix? I have a theory to test when I wake up and I think that there is a possibility ripping out topics/channels won’t be too difficult.

Oh no don’t worry, if it’s nothing that can be done easily, I’ll just use cowboy directly and manage my own websocket processes :slight_smile:

You can use cowboy websockets directly with phoenix just fine. Phoenix’s Channel’s wrapper is just a simple wrapper around both websockets and longpolling that supplies an abstract ‘room’ concept for ease of separation of work. If you just want a raw websocket then use cowboy’s API from within phoenix. :slight_smile:

Phoenix does not ‘replace’ functionality of the libraries it uses, only add to them, you still use the lower API’s when they exist, whether that is Plug, Cowboy, or whatever. :slight_smile:

Yeah sure, what I meant is that I will just not use the Phoenix Socket and Channel high level API.

Unless (I haven’t checked yet) there is a way either trough the Phoenix.Presence module or by manually tracking transport PIDs to send the messages directly through the sockets, maybe using this function

I don’t fully understand the requirements…

but SSE might be relevant if it’s oneway: https://github.com/mustafaturan/sse

He wants to make it so that all the client has to do is call

var exampleSocket = new WebSocket("ws://localhost:4000/socket");
exampleSocket.onmessage = function (event) {
  console.log(event.data);
}

and then have his server side code take it from there. In other words, the requirement is that the client side doesn’t have to have any concept of a channel. If that means setting up a “pseudochannel” on the server side to make Phoenix Channel API happy, he’s willing to do that.

1 Like

https://github.com/phoenixframework/phoenix/issues/234

This is probably what I need, thanks!

Actually you can, the Phoenix.Presence module is just a Phoenix.PubSub refinement, so you can take you cowboy-websocket PID and call the subscribe on the pubsub to get the messages, then just handle them by, oh, forwarding them over the websocket as normal. :slight_smile:

Yeah the Phoenix API is pretty pluggable at multiple levels too. :slight_smile:

An alternative also is to have every client join the same channel, i.e. users, and if you broadcast something like MyApp.Endpoint.broadcast("users", "message", %{for_user: 123, data: "here"}). You can then use phoenix’s intercept to determine within the channel if that message is “for you”, assuming you know server side the ID of the user who joined. This would mean your “broadcasts” would only go out to the parties which your intercept function determined it should go out. Be aware that intercept has significant performance penalties and you should heavily consider them before using it.
https://hexdocs.pm/phoenix/Phoenix.Channel.html#module-intercepting-outgoing-events

2 Likes

Well it’s the joining part that I would like to skip, at the moment as in my second post it works fine by using subscribe which shouldn’t have any performance penalty since it’s just a way of having the client join another channel