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
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