A few channels questions that I need clarification on

Ok I have a few channel/websocket questions I hope I can get cleared up.

  1. How do I put a wildcard in join function. I currently have it hard coded to “user:8” for testing and it works fine, but I want that integer value to be dynamic and I will need access to it inside the function for a lookup.

    def join("user:8", _params, socket) do
      Logger.info ("join called..")
      send(self(), :after_join)
      {:ok, socket}
    end
    
  2. When I receive a message, I am broadcasting it and this works fine.

    def handle_in("message:new", message, socket) do
      broadcast! socket, "message:new", %{
        user: socket.assigns.user,
        body: message,
        timestamp: :os.system_time(:milli_seconds)
      }
      {:noreply, socket}
    end
    

So I am going to create a API endpoint where my Rails application can post to this endpoint, and then I want this to broadcast to my channel. How would I handle this?

You can use pattern matching… :slight_smile:

def join(“user:” <> id, _params, socket) do 
  IO.puts id
end
1 Like

As a side not, is there a way to output the contents of a type like how rails has .inspect ? i.e. output like

Logger.info “conn: #{conn.inspect}”

Yes, but forget about dot chaining, as in Ruby…

The way to do it in Elixir is to apply a function on structure.

Logger.info "conn: #{inspect conn}"

#or

iex> IO.inspect conn

As a side note, and because I have been doing Rails lately, I try to see dot chaining as a pipe…

# Ruby
conn
.inspect

# Elixir
conn
|> IO.inspect()
3 Likes

Thanks everyone.

So my 2nd question is basically, from another api controller I want to publish a message to go on my channel controller so it broadcasts to all the active socket clients. Is there a way to hook into my channel from a controller?

You can broadcast from your endpoint…

YourProjectNameWeb.Endpoint.broadcast! ...
2 Likes

I tried this, it doesn’t crash but I don’t see a message in my UI:

defmodule RealtimeWeb.MessageController do
  use RealtimeWeb, :controller
  require Logger

  def create(conn, _params) do
    Logger.info "api#create called"
    RealtimeWeb.Endpoint.broadcast!("board:8", "messages:new", %{
      user: "asdf",
      body: "some message",
      timestamp: :os.system_time(:milli_seconds)
    })
    json(conn, %{id: 123})
  end

end

My logs show that I am joined to the topic:

JOIN “board:8” to RealtimeWeb.BoardChannel

I’m not sure if you meant for me to use RealtimeWeb.Endpoint.broadcast! or RealtimeWeb.BoardChannel.broadcast! it with my real endpoint. When I put BoardChannel I got an error:

** (UndefinedFunctionError) function RealtimeWeb.BoardChannel.broadcast!/3 is undefined or private
(realtime) RealtimeWeb.BoardChannel.broadcast!(“board:8”, “messages:new”, %{body: “some message”, timestamp: 1543411781378, user: “asdf”})

I was thinking of

RealtimeWeb.Endpoint.broadcast!

I cannot really tell why it’s not working for You, but here is some code I use to notify by websocket (with Phoenix 1.3) …

defmodule GameWeb.Notifier do
  @moduledoc false
  require Logger

  # Notification Hub for application
  # Used by GameEngine to notify worker's exit

  def notify(%{payload: payload, type: :game_created}) do
    GameWeb.Endpoint.broadcast!("lobby", "game_added", %{game: payload})
  end

  def notify(%{payload: payload, type: :game_stopped}) do
    GameWeb.Endpoint.broadcast!("lobby", "game_removed", %{uuid: payload})
    
    # Notify remaining guest the game has ended!
    GameWeb.Endpoint.broadcast!("game:#{payload}", "game_force_quit", %{uuid: payload})
  end

  def notify(%{type: :request_created} = message) do
    # Do nothing, notification is done in lobby channel
    Logger.debug(fn -> "No op #{inspect(message)}" end)
  end

  def notify(%{payload: payload, type: :request_stopped}) do
    GameWeb.Endpoint.broadcast!("lobby", "request_cancelled", %{uuid: payload})
  end

  def notify(message) do
    Logger.debug(fn -> "Unknown notification #{inspect(message)}" end)
  end
end

I have to say it’s kind of easier by enabling logs in your client code… I do it like this.

const socketOptions = token => ({
  params: { token },
  logger: (kind, msg, data) => (
    // eslint-disable-next-line no-console
    console.log(`${kind}: ${msg}`, data)
  ),
});


socket = new Socket(`${ROOT_SOCKET}/socket`, socketOptions(token));
1 Like

Thanks for that debugging tip. I basically changed the way I was setting up my channel.

I had this:

let channel = socket.channel(channelId, {params: {token: authToken}})

I changed it to this and it works:

let channel = socket.channel(channelId)

I’m not sure why I was passing in token when creating a token, but since I have a socket I am already authenticated so passing it very a channel subscription doesn’t make sense.

Given that the socket has already a token, it’s not useful to pass it in the channel, as You mentionned. But I do have some access control in the join function of my channels.