1

I'm trying to tie together the Phoenix Channel, Token, and Presence modules to add chat functionality to my Phoenix 1.3 application. I haven't been able to get all 3 modules working together. The last error was connection to websocket closed before handshake. Now, I'm not getting any errors but it's also not connecting to the socket.

I believe the issue is the "connect" function in the player_socket.ex. ( I have a player resource ). Here is the function:

  def connect(%{"token" => token}, socket) do
      case Phoenix.Token.verify(socket, "player auth", token, max_age: @max_age) do
        {:ok, player_id} ->
          player = Repo.get!(Player, player_id)
          {:ok, assign(socket, :current_player, player)}
          {:error, _reason} ->
           :error
      end
  end

I'm signing the token in a meta tag in app.html.eex. <%= tag :meta, name: "channel_token", content: Phoenix.Token.sign(@conn, "player auth", :player_id) %>

Then in the lobby_channel.ex I'm trying to join the channel:

  def join("lobby:lobby", _params, socket) do
    send(self(), :after_join)
    {:ok, assign(socket, :player_id, :current_player)}
  end

  def handle_info(:after_join, socket) do
    push socket, "presence_state", Presence.list(socket)
    {:ok, _} = Presence.track(socket, socket.assigns.current_player, %{
      online_at: inspect(System.system_time(:seconds))
    })
    {:noreply, socket}
  end

I read the docs but can't seem to figure out why I'm unable to connect to the websocket with the "current_player" so that I can use Presence to display who is online and the player's names to associate with their chat messages. Any insight is greatly appreciated! I have the repo here: https://github.com/EssenceOfChaos/gofish

UPDATE

I am using a "current_player" plug to store the player struct in the conn as "current_player.

%Plug.Conn{adapter: {Plug.Adapters.Cowboy.Conn, :...},
 assigns: %{current_player: %Gofish.Accounts.Player{__meta__: #Ecto.Schema.Metadata<:loaded, "players">,
    email: "example@aol.com", id: 6,

Here is my updated lobby_channel.ex:

  def join("lobby:lobby", _params, socket) do
    send(self(), :after_join)
    {:ok, socket}
  end

  def handle_info(:after_join, socket) do
    push socket, "presence_state", Presence.list(socket)
    {:ok, _} = Presence.track(socket, socket.assigns.current_player.id, %{
      username: socket.assigns.current_player.username,
      online_at: inspect(System.system_time(:seconds))
    })
    {:noreply, socket}
  end
Frederick John
  • 517
  • 2
  • 6
  • 14

1 Answers1

1

Your player_socket.ex is fine. You do have a few issues though:

In your layout/app.eex template:

Phoenix.Token.sign(@conn, "player auth", :player_id) is literally writing an atom :player_id instead of the ID of the player. In order to write the ID of the player, you should use @player_id and add a plug that assigns the value globally to your router.ex like so:

pipeline :browser do
  [...]
  plug :fetch_current_user
end

...

def fetch_current_user(conn, _) do
  assigns(conn, :current_player, get_session(conn, :current_player)
end

This will make @current_player available in all your templates, which you can then use in app.eex:

<%= tag :meta, name: "channel_token", content: Phoenix.Token.sign(@conn, "player auth", @current_player) %>

(you should write this conditionally if @current_player isn't nil and stop your JS client from attempting websocket connections if it is, btw)

This change will immediately fix your inability to connect to the websocket as long as you've signed in, but you still have one more issue: {:ok, assign(socket, :player_id, :current_player)} in your loby_channel.ex is assigning the atom :current_player literally instead of using the actual value of the current player's ID, but you don't need this line at all. Instead, in your :after_join, you should do

{:ok, _} = Presence.track(socket, socket.assigns.current_player.username, %{
  online_at: inspect(System.system_time(:seconds))
})

Notice I changed socket.assigns.current_player to socket.assigns.current_player.username. This is because you cannot assign a struct as a Presence key.

Alternatively you could do

{:ok, _} = Presence.track(socket, socket.assigns.current_player.id, %{
  username: socket.assigns.current_player.username,
  online_at: inspect(System.system_time(:seconds))
})

and in your socket.js you'd use first.username instead of id inside renderOnlineUsers

Mario
  • 1,349
  • 11
  • 16
  • Thank you for taking the time to answer! You were right I was assigning the atom "player_id" and not the actual id. I didn't notice because I was still getting a value for the token. I am connected to the websocket now so I've definitely made some progress : --- transport: connected to ws://localhost:4000/socket/websocket?token=SFMyNTY.g3QAAAACZAAEZGF0YWEGZAAGc2lnbmVkbgYAcXFphWAB.JzSzRcaWpebtBk2kH5ooeqKr8ez3Lxi8IuyRkuh0qxU&vsn=2.0.0 undefined --- but the Presence module isn't displaying the online users. I am trying to use "first.username" in socket.js – Frederick John Dec 23 '17 at 22:09
  • Ah, that's because there's another error I forgot to point out: in your `socket.js` you have `let chatInput = document.querySelector("#chat-input");` but it should actually be `let chatInput = document.querySelector("#chatInput");` as that's how you've written the ID in your template :) – Mario Dec 23 '17 at 22:25
  • Now it is displaying a logged in user. Thank you for your answer! If I log in with another browser and register a new account it displays the same user logged in twice, any idea why? – Frederick John Dec 23 '17 at 23:38
  • It must have something to do with me using " @current_player.id" in the meta tag to sign the token – Frederick John Dec 24 '17 at 00:07
  • Are you sure there's an issue? I see two separate users as it should be without changing anything in your repo: https://i.imgur.com/shxViKI.png – Mario Dec 24 '17 at 02:43