defmodule BackendWeb.ConnectedUserChannel do @moduledoc """ Manages requests that come in from browser """ use BackendWeb, :channel require Logger @impl true def join("user:" <> user_name, _params, %{assigns: %{user_name: socket_user}} = socket) when user_name == socket_user do Logger.info("WebSocket connected to #{node()}, user: #{user_name}") Phoenix.PubSub.subscribe(Backend.PubSub, "game_state") Phoenix.PubSub.subscribe(Backend.PubSub, "user_sessions:#{user_name}") # Notify other sessions for this user Phoenix.PubSub.broadcast_from( Backend.PubSub, self(), "user_sessions:#{user_name}", :new_browser_connection ) current_state = Backend.GameRunner.get_state() {:ok, %{game_state: current_state}, socket} end def join("user:" <> user_name, _params, %{assigns: %{user_name: socket_user}}) do Logger.warning("User #{socket_user} attempted to join channel for #{user_name}") {:error, %{reason: "unauthorized"}} end def join(_topic, _params, _socket) do {:error, %{reason: "authentication required"}} end @impl true def handle_info({:game_state_updated, state}, socket) do push(socket, "game_state", %{game_state: state}) {:noreply, socket} end @impl true def handle_info(:new_browser_connection, socket) do Logger.warning("New browser connection detected for user: #{socket.assigns.user_name}") push(socket, "new_browser_connection", %{}) {:noreply, socket} end @impl true @spec handle_in(<<_::48, _::_*8>>, map(), any()) :: {:noreply, any()} | {:reply, :ok, Phoenix.Socket.t()} def handle_in("join_game", %{"name" => name}, socket) do Logger.info("Player '#{name}' joining game on #{node()}") socket = socket |> assign(:player_name, name) |> assign(:keys_pressed, MapSet.new()) Backend.GameRunner.add_player(name) {:reply, :ok, socket} end @impl true def handle_in("key_down", %{"key" => key}, socket) do case socket.assigns[:player_name] do nil -> Logger.warning("Key down attempted without joining game") {:noreply, socket} player_name -> keys_pressed = MapSet.put(socket.assigns[:keys_pressed] || MapSet.new(), key) socket = assign(socket, :keys_pressed, keys_pressed) Logger.debug( "Player '#{player_name}' key down: #{key}, keys: #{inspect(MapSet.to_list(keys_pressed))}" ) Backend.GameRunner.update_player_keys(player_name, MapSet.to_list(keys_pressed)) {:noreply, socket} end end @impl true def handle_in("key_up", %{"key" => key}, socket) do case socket.assigns[:player_name] do nil -> Logger.warning("Key up attempted without joining game") {:noreply, socket} player_name -> keys_pressed = MapSet.delete(socket.assigns[:keys_pressed] || MapSet.new(), key) socket = assign(socket, :keys_pressed, keys_pressed) Logger.debug( "Player '#{player_name}' key up: #{key}, keys: #{inspect(MapSet.to_list(keys_pressed))}" ) Backend.GameRunner.update_player_keys(player_name, MapSet.to_list(keys_pressed)) {:noreply, socket} end end @impl true def terminate(_reason, socket) do case socket.assigns[:player_name] do nil -> Logger.info("WebSocket disconnected from #{node()}") player_name -> Logger.info("Player '#{player_name}' disconnected from #{node()}") Backend.GameRunner.remove_player(player_name) end :ok end end