使用 Token 和 Presence 连接到 Phoenix Socket
Connect to Phoenix Socket with Token and Presence
我正在尝试将 Phoenix Channel、Token 和 Presence 捆绑在一起向我的 Phoenix 1.3 应用程序添加聊天功能的模块。我无法让所有 3 个模块一起工作。最后一个错误是 connection to websocket closed before handshake
。现在,我没有收到任何错误,但它也没有连接到套接字。
我认为问题出在 player_socket.ex 中的 "connect" 函数。 (我有一个播放器资源)。这是函数:
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
我正在 app.html.eex 的元标记中签署令牌。 <%= tag :meta, name: "channel_token", content: Phoenix.Token.sign(@conn, "player auth", :player_id) %>
然后在 lobby_channel.ex 我正在尝试加入频道:
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
我阅读了文档,但似乎无法弄清楚为什么我无法使用 "current_player" 连接到 websocket,以便我可以使用 Presence 来显示谁在线以及玩家的名字与他们的聊天消息相关联。非常感谢任何见解!我这里有回购协议:
https://github.com/EssenceOfChaos/gofish
更新
我正在使用 "current_player" 插件将 conn 中的播放器结构存储为“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,
这是我的更新 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
你的 player_socket.ex
没问题。不过你确实有一些问题:
在您的 layout/app.eex
模板中:
Phoenix.Token.sign(@conn, "player auth", :player_id)
字面上写的是一个原子:player_id
而不是玩家的ID。为了写入播放器的 ID,您应该使用 @player_id
并添加一个插件,将值全局分配给您的 router.ex
,如下所示:
pipeline :browser do
[...]
plug :fetch_current_user
end
...
def fetch_current_user(conn, _) do
assigns(conn, :current_player, get_session(conn, :current_player)
end
这将使 @current_player
在您的所有模板中可用,然后您可以在 app.eex
:
中使用
<%= tag :meta, name: "channel_token", content: Phoenix.Token.sign(@conn, "player auth", @current_player) %>
(如果 @current_player
不是 nil
,你应该有条件地写这个,如果是,则阻止你的 JS 客户端尝试 websocket 连接,顺便说一句)
这一更改将立即解决您在登录后无法连接到 websocket 的问题,但您还有一个问题:
loby_channel.ex
中的 {:ok, assign(socket, :player_id, :current_player)}
是按字面意思分配原子 :current_player
而不是使用当前玩家 ID 的实际值,但你根本不需要这一行。相反,在你的 :after_join
中,你应该做
{:ok, _} = Presence.track(socket, socket.assigns.current_player.username, %{
online_at: inspect(System.system_time(:seconds))
})
注意我将 socket.assigns.current_player
更改为 socket.assigns.current_player.username
。这是因为您不能将结构指定为 Presence 键。
或者你可以做
{:ok, _} = Presence.track(socket, socket.assigns.current_player.id, %{
username: socket.assigns.current_player.username,
online_at: inspect(System.system_time(:seconds))
})
并且在您的 socket.js
中,您将在 renderOnlineUsers
中使用 first.username
而不是 id
我正在尝试将 Phoenix Channel、Token 和 Presence 捆绑在一起向我的 Phoenix 1.3 应用程序添加聊天功能的模块。我无法让所有 3 个模块一起工作。最后一个错误是 connection to websocket closed before handshake
。现在,我没有收到任何错误,但它也没有连接到套接字。
我认为问题出在 player_socket.ex 中的 "connect" 函数。 (我有一个播放器资源)。这是函数:
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
我正在 app.html.eex 的元标记中签署令牌。 <%= tag :meta, name: "channel_token", content: Phoenix.Token.sign(@conn, "player auth", :player_id) %>
然后在 lobby_channel.ex 我正在尝试加入频道:
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
我阅读了文档,但似乎无法弄清楚为什么我无法使用 "current_player" 连接到 websocket,以便我可以使用 Presence 来显示谁在线以及玩家的名字与他们的聊天消息相关联。非常感谢任何见解!我这里有回购协议: https://github.com/EssenceOfChaos/gofish
更新
我正在使用 "current_player" 插件将 conn 中的播放器结构存储为“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,
这是我的更新 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
你的 player_socket.ex
没问题。不过你确实有一些问题:
在您的 layout/app.eex
模板中:
Phoenix.Token.sign(@conn, "player auth", :player_id)
字面上写的是一个原子:player_id
而不是玩家的ID。为了写入播放器的 ID,您应该使用 @player_id
并添加一个插件,将值全局分配给您的 router.ex
,如下所示:
pipeline :browser do
[...]
plug :fetch_current_user
end
...
def fetch_current_user(conn, _) do
assigns(conn, :current_player, get_session(conn, :current_player)
end
这将使 @current_player
在您的所有模板中可用,然后您可以在 app.eex
:
<%= tag :meta, name: "channel_token", content: Phoenix.Token.sign(@conn, "player auth", @current_player) %>
(如果 @current_player
不是 nil
,你应该有条件地写这个,如果是,则阻止你的 JS 客户端尝试 websocket 连接,顺便说一句)
这一更改将立即解决您在登录后无法连接到 websocket 的问题,但您还有一个问题:
loby_channel.ex
中的 {:ok, assign(socket, :player_id, :current_player)}
是按字面意思分配原子 :current_player
而不是使用当前玩家 ID 的实际值,但你根本不需要这一行。相反,在你的 :after_join
中,你应该做
{:ok, _} = Presence.track(socket, socket.assigns.current_player.username, %{
online_at: inspect(System.system_time(:seconds))
})
注意我将 socket.assigns.current_player
更改为 socket.assigns.current_player.username
。这是因为您不能将结构指定为 Presence 键。
或者你可以做
{:ok, _} = Presence.track(socket, socket.assigns.current_player.id, %{
username: socket.assigns.current_player.username,
online_at: inspect(System.system_time(:seconds))
})
并且在您的 socket.js
中,您将在 renderOnlineUsers
first.username
而不是 id