bezzoon
bezzoon

Reputation: 2019

Why am I getting this error when I try to broadcast to one socket

so I have

socket.endpoint.subscribe("user_socket:" <> socket.id)

And when I try to broadcast to it

      RealmWeb.Endpoint.broadcast(
            "user_socket:#{player_state.socket_id}",
            "quest completed",
            %{})

I get this error

* (FunctionClauseError) no function clause matching in RealmWeb.WorldChannel.handle_info/2
(realm 0.1.0) lib/realm_web/channels/world_channel.ex:24: RealmWeb.WorldChannel.handle_info(%Phoenix.Socket.Broadcast{event: "quest completed", payload: %{}, topic: "user_socket:0fef857e-b003-4f86-b571-4a212be9beaa"}, %Phoenix.Socket{assigns: %{}, channel: RealmWeb.WorldChannel, channel_pid: #PID<0.661.0>, endpoint: RealmWeb.Endpoint, handler: RealmWeb.UserSocket, id: "0fef857e-b003-4f86-b571-4a212be9beaa", join_ref: "3", joined: true, private: %{log_handle_in: :debug, log_join: :info}, pubsub_server: Realm.PubSub, ref: nil, serializer: Phoenix.Socket.V2.JSONSerializer, topic: "world:lobby", transport: :websocket, transport_pid: #PID<0.656.0>})
(phoenix 1.6.11) lib/phoenix/channel/server.ex:343: Phoenix.Channel.Server.handle_info/2
(stdlib 3.16.1) gen_server.erl:695: :gen_server.try_dispatch/4
(stdlib 3.16.1) gen_server.erl:771: :gen_server.handle_msg/6
(stdlib 3.16.1) proc_lib.erl:226: :proc_lib.init_p_do_apply/3


Last message: %Phoenix.Socket.Broadcast{event: "quest completed", payload: %{}, topic: "user_socket:0fef857e-b003-4f86-b571-4a212be9beaa"}
State: %Phoenix.Socket{assigns: %{}, channel: RealmWeb.WorldChannel, channel_pid: #PID<0.661.0>, endpoint: RealmWeb.Endpoint, handler: RealmWeb.UserSocket, id: "0fef857e-b003-4f86-b571-4a212be9beaa", join_ref: "3", joined: true, private: %{log_handle_in: :debug, log_join: :info}, pubsub_server: Realm.PubSub, ref: nil, serializer: Phoenix.Socket.V2.JSONSerializer, topic: "world:lobby", transport: :websocket, transport_pid: #PID<0.656.0>}

I don't know why this isn't working.

Why is it trying to do a handle_info call. I am expecting the code to send a message directly to a specific client.

Upvotes: -1

Views: 336

Answers (1)

JasonTrue
JasonTrue

Reputation: 19599

I'm going to make some assumptions based on the typical implementation of a Phoenix app that are probably correct, but could be different depending on other decisions you've made.

Given the code:

socket.endpoint.subscribe("user_socket:" <> socket.id)

Assuming the socket that is in-scope is a %Phoenix.Socket{}, and its endpoint field is set to RealmWeb.Endpoint, and assuming RealmWeb.Endpoint, implements the behavior Phoenix.Endpoint, will subscribe the current process to messages on that topic.

Based on the stack trace, it looks like you're subscribing your Phoenix.Channel process to these messages. There may be a case for doing that; in our codebase we have a specialized channel that is used by certain clients that happens to be interested in messages from another channel. So we subscribe to those events within the channel within the OurChannel.join/3 callback, and within that channel module, do something very similar to this:

  @impl true
  def handle_info(%{event: event, payload: payload}, socket) do
    push(socket, event, payload)
    {:noreply, socket}
  end

Which simply pushes those events to parties interested in them, but subscribed through this channel. We also listen for other types of GenServer-ish events (in our case, a regular check for a particular condition that decides whether the client should stay connected or not, and pushes a phx_close event in appropriate cases.

If subscribing to an external topic is indeed what you're doing, then implementing handle_info in a similar manner is basically all you need to do. See the Subscribing to external topics section of the Phoenix.Channel docs for more context.

My suspicion, though, is that you meant to have the client join the topic rather than have the channel subscribe to an external channel.

If that's the case, then basically you need to have the client do that (more on that in a moment) and then have your channel implement a join/3 to decide whether it's acceptable for the requesting client to join or not.

The join function could look like something this, if you have an is_authorized/2 function defined:

def join("user_socket:" <> socket_id, _payload, socket) do
  if is_authorized?(socket_id, socket) do
    {:ok, socket}
  else
    {:error,  %{reason: "unauthorized"}}
  end
end

def join(_any_channel_without_strict_authorization_rules, _payload, socket) do
  {:ok, socket}
end

Then, have your client join the "user_socket:#{user_id}" channel in "the usual way." In the case of LiveView, you can simply subscribe to the channel in your on_mount callback; if you're using client-side javascript you can check the Phoenix js library documentation.

Upvotes: 0

Related Questions