Is broadcasting a list (not inside an object) in a channel impossible?

Hi,

Using:

  • phoenix 1.3.0-rc.2 (Hex package) (mix)
  • ecto 2.1.4 (Hex package) (mix)
  • phoenix_ecto 3.2.3 (Hex package) (mix)

I use a channel with a join function like this:

  def join("positions:" <> tenant, payload, socket) do
      positions = Accounts.list_positions(tenant)
      {:ok, positions, socket}
  end

positions is just a list of Position.

It seems to me there is no point to add a surrounding object to the list in a ok response as we know what to expect (this is also a common practice in GET or POST HTTP requests/responses).

This works correctly, the list of positions is serialized and sent in the response:

{"status":"ok","response":[]},"event":"phx_reply"}

However when I want to broadcast the list of positions from a controller:

      positions = Accounts.list_positions(tenant)
      Endpoint.broadcast("positions:" <> tenant, "positions", positions)

This fails with the following error:

** (ArgumentError) topic and event must be strings, message must be a map

because broadcast/3 which uses Server.broadcast only accepts maps for the payload:

  def broadcast(pubsub_server, topic, event, payload)
      when is_binary(topic) and is_binary(event) and is_map(payload) do
  ...
  end
  def broadcast(_, _, _, _), do: raise_invalid_message()

So is there another way to broadcast directly a list like in the join response ? Does it makes sense or did I miss something ?

Thanks

It looks like it’s actually a bug that you can reply to join with a list.

So no, there’s not another a way and the fact that you did could with join is a bug.

Now, that’s not to say serializing a list won’t work. It’s just not the way the API for broadcasting was implemented in Phoenix.

1 Like

This is by design to keep the channel client contract simple. To get what you want, you can simply wrap the payload in a key, as you alluded to. For example:

      positions = Accounts.list_positions(tenant)
      Endpoint.broadcast("positions:" <> tenant, "positions", %{positions: positions})
channel.on("positions", ({positions}) => ...)

With destructing assignment on the client, it makes the wrapped object a non issues and nice to “match” on.

4 Likes

First of all let me thank you @chrismccord for Phoenix, I’m really enjoying the experience so far.

As I’m using elm for my frontend (with https://github.com/saschatimme/elm-phoenix for channels), and therefore not socket.js, I overlooked the fact that the phoenix channels API may have been influenced by the client API (elm-phoenix does not really rely on matching the wrapping key, it just requires a standard JSON decoder after matching the message).

Also as the following is possible:

channel.on("new_msg", payload => {
  ...
}

and because it seems redundant to have positions both as the message name and the payload wrapping key, it did not seem illogical to me to be able to send a serialized list.

Anyway I get your point. Thank you very much.

Thank you @Azolo, I was puzzled by the inconsistency between the join response and broadcast/3 parameters. If this is a bug, this explains it.