Broadcast to different channel Phoenix 1.1.6

2019-06-24 11:55发布

问题:

I'm trying to broadcast to a different channel in my app but I can not get it to work. Also I trying to write a test but I'm not sure how.

From what I can gather I succeed in broadcasting the message from the notification_channel but it's not received in chat_channel.

Notification should send to chat.

notification_channel.ex

  def handle_in("new:group:recommendation", msg, socket) do
    payload = %{
        message: msg["message"],
        url: msg["url"],
        title: msg["title"],
        user_name: get_name_of_user(socket.assigns.user_grapqhl_id),
        user_grapqhl_id: socket.assigns.user_grapqhl_id
    }

    IO.puts "incomming"
    IO.inspect msg
    Enum.map(msg["groups"], fn(x) ->
        App.Endpoint.broadcast_from! self(), "chat:"<>x,
            "new:recommendation", payload
        end)
    {:reply, :ok, socket}

  end

chat_channel.ex

 def handle_in("new:recommendation", msg, socket) do
      IO.puts "i am a recommendation !"
      IO.inspect msg
      chat_msg = %{
         "creator_id" => msg["user_grapqhl_id"],
         "text" => msg["message"],
         "creator_name" => msg["user_name"]
      }

     broadcast! socket, "new:msg", create_chat_msg(chat_msg,socket)
     {:reply, :ok, socket}
  end

test

  test "do we send a new:recommendation to chat ?", %{guardian_token: guardian_token} do
      nils_base_64 = Base.encode64("user:nils")

      {:ok, socket} = connect(UserSocket, %{})
      {:ok, _, socket1} = subscribe_and_join(socket, "notifications:"<>nils_base_64, %{"guardian_token" => guardian_token})
      {:ok, _, socket} = subscribe_and_join(socket1, "chat:Y2hhdDpjaGF0Mw==", %{"guardian_token" => guardian_token})

      payload = %{
          "message" => "look at this cool thing!",
          "url" => "link to stuff",
          "title" => "AWESOME EVENT",
          "groups" => ["Y2hhdDpjaGF0Mw==", "Y2hhdDpwdWJsaWM="]
      }

      reply = %{message: "look at this cool thing!", title: "AWESOME EVENT", url: "link to stuff", user_grapqhl_id: nils_base_64, user_name: "Nils Eriksson"}

      ref = push socket1, "new:group:recommendation", payload
      assert_reply ref, :ok
      assert_broadcast "new:recommendation", ^reply
  end

This test passes and i can get it to fail by changing the reply or commenting out the broadcast. I can not get it to fail by changing the handle_in to receive fail:please in chat_channel. That is something that it would complain on if i would send change this ref = push socket1, "new:group:recommendation", payload to ref = push socket, "new:group:recommendation", payload not supprising in that case.

This is what is on the wire.

     Process mailbox:
   %Phoenix.Socket.Message{event: "init:msgs", payload: %{messages: []}, ref: nil, topic: "chat:Y2hhdDpjaGF0Mw=="}
   %Phoenix.Socket.Broadcast{event: "new:recommendation", payload: %{message: "look at this cool thing!", title: "AWESOME EVENTs", url: "link to stuff", user_grapqhl_id: "dXNlcjpuaWxz", user_name: "Nils Eriksson"}, topic: "chat:Y2hhdDpjaGF0Mw=="}
   %Phoenix.Socket.Message{event: "new:recommendation", payload: %{message: "look at this cool thing!", title: "AWESOME EVENTs", url: "link to stuff", user_grapqhl_id: "dXNlcjpuaWxz", user_name: "Nils Eriksson"}, ref: nil, topic: "chat:Y2hhdDpjaGF0Mw=="}

I use channel authentication since the elm package i use does not support authentication on socket level yet. so this is what it looks like in chat

  def join("chat:" <> chat_id, %{"guardian_token" => token}, socket) do
  IO.puts chat_id
  case sign_in(socket, token) do
     {:ok, authed_socket, _guardian_params} ->
         Process.flag(:trap_exit, true)
         send(self, {:after_join})
         [_type, node_chat_id] = Node.from_global_id(chat_id)
         {:ok, assign(authed_socket, :chat_id, node_chat_id)}
     {:error, reason} ->
         IO.puts "Can't join channel cuz: " <> reason
       # handle error TODO
   end

end

回答1:

Since you use broadcast_from/4 from your Endpoint.You should use handle_info/2 in your chat_channel:

alias Phoenix.Socket.Broadcast
  ...

def handle_info(%Broadcast{topic: _, event: ev, payload: payload}, socket) do
    IO.puts ev
    IO.inspect payload
    # do something with ev and payload( push or broadcast)
    {:noreply, socket}
  end

Or you can listen that event from your client:

chatChannel.on("new:recommendation", resp => {
   // doSomething with response
}

Edit:

Let's explain a bit about how the channel and PubSub system work.

When you want to broadcast or push an event with payload.First it will send to the PubSub system and then the PubSub system will send it to all the subscriber process (channel) with the topic that channel registered itself with the PubSub system.

And when you use Endpoint.broadcast_from/4 to broadcast an event from your server.The PubSub system will receive an event with payload and broadcast that event to the topic that channel registered.

The channel will trigger handle_out callback and push the message to the client. So in your chat_channel you don't need to handle_in "new:recommendation" event.Your client just need to listen to that event.

chatChannel.on("new:recommendation", resp => {
   // do something with response
}

And let me rewrite your test:

setup do
    nils_base_64 = Base.encode64("user:nils")
    {:ok, socket} = connect(UserSocket, %{})
    {:ok, _, socket} = subscribe_and_join(socket, "notifications:"<>nils_base_64, %{"guardian_token" => guardian_token})
    {:ok, socket: socket}
  end


test "do we send a new:recommendation to chat ?", %{socket: socket} do
      MyApp.Endpoint.subscribe("chat:Y2hhdDpjaGF0Mw==")

      payload = %{
          "message" => "look at this cool thing!",
          "url" => "link to stuff",
          "title" => "AWESOME EVENT",
          "groups" => ["Y2hhdDpjaGF0Mw==", "Y2hhdDpwdWJsaWM="]
      }



      reply = %Phoenix.Socket.Broadcast{message: "look at this cool thing!",
              title: "AWESOME EVENT",
              url: "link to stuff",
              user_grapqhl_id: nils_base_64,
              user_name: "Nils Eriksson"}

      ref = push socket, "new:group:recommendation", payload
      assert_reply ref, :ok
      assert_receive ^reply
  end

By subscribe to the topic that you want to listen you can make sure that your channel is received the message with assert_receive. That is the way to test broadcast to a different channel.

Give it a try and tell me.The test will pass.



回答2:

Use App.Endpoint.broadcast topic, event, payload with the topic of your chat channel. It should work.