Current section
Files
Jump to
Current section
Files
lib/musubi/transport/socket.ex
defmodule Musubi.Transport.Socket do
@moduledoc """
Phoenix socket adapter generated by `use Musubi.Socket`.
Application modules should prefer `use Musubi.Socket`; this module owns the
Phoenix-specific expansion and connect context plumbing.
"""
# Phoenix socket assign containing the connected Musubi socket context.
@musubi_socket_key :__musubi_socket__
@doc """
Declares the Phoenix socket adapter for a Musubi socket.
## Examples
defmodule MyAppWeb.MusubiSocket do
use Musubi.Socket, roots: [MyApp.Stores.DashboardStore]
end
"""
@spec __using__(keyword()) :: Macro.t()
defmacro __using__(opts) do
roots =
opts
|> Keyword.fetch!(:roots)
|> Musubi.Socket.__normalize_roots__(__CALLER__)
quote bind_quoted: [roots: roots] do
use Phoenix.Socket
@behaviour Musubi.Socket
channel("musubi:*", Musubi.Transport.ConnectionChannel)
channel("musubi_upload:*", Musubi.Transport.UploadChannel)
@__musubi_roots__ roots
@impl Musubi.Socket
def handle_connect(_params, socket), do: {:ok, socket}
@impl Musubi.Socket
def handle_join(_params, socket), do: {:ok, socket}
defoverridable handle_connect: 2, handle_join: 2
@impl Phoenix.Socket
def connect(params, phoenix_socket, connect_info) do
Musubi.Transport.Socket.__connect__(__MODULE__, params, phoenix_socket, connect_info)
end
@impl Phoenix.Socket
def id(_socket), do: nil
@doc false
@spec __musubi_roots__() :: Musubi.Socket.roots()
def __musubi_roots__, do: @__musubi_roots__
end
end
@doc """
Builds the Musubi socket context for a Phoenix connect callback.
## Examples
iex> socket = Musubi.Transport.Socket.build_connect_socket(%{"token" => "t"}, %{session: %{"user_id" => "u1"}})
iex> Musubi.Socket.session(socket)
%{"user_id" => "u1"}
"""
@spec build_connect_socket(map(), map()) :: Musubi.Socket.t()
def build_connect_socket(params, connect_info) when is_map(params) and is_map(connect_info) do
session = Map.get(connect_info, :session, %{})
%Musubi.Socket{}
|> Musubi.Socket.put_session(session)
|> Musubi.Socket.put_connect_info(connect_info)
end
@doc false
@spec __connect__(module(), map(), Phoenix.Socket.t(), map()) ::
{:ok, Phoenix.Socket.t()} | :error
def __connect__(socket_module, params, %Phoenix.Socket{} = phoenix_socket, connect_info)
when is_atom(socket_module) and is_map(params) and is_map(connect_info) do
musubi_socket = build_connect_socket(params, connect_info)
case socket_module.handle_connect(params, musubi_socket) do
{:ok, %Musubi.Socket{} = connected_socket} ->
{:ok, put_connect_socket(phoenix_socket, connected_socket)}
:error ->
:error
{:error, _reason} ->
:error
end
end
@doc false
@spec assign_connect_context(Phoenix.Socket.t(), map(), map()) :: Phoenix.Socket.t()
def assign_connect_context(%Phoenix.Socket{} = socket, params, connect_info)
when is_map(params) and is_map(connect_info) do
put_connect_socket(socket, build_connect_socket(params, connect_info))
end
@doc false
@spec put_connect_socket(Phoenix.Socket.t(), Musubi.Socket.t()) :: Phoenix.Socket.t()
def put_connect_socket(%Phoenix.Socket{} = socket, %Musubi.Socket{} = musubi_socket) do
Phoenix.Socket.assign(socket, @musubi_socket_key, musubi_socket)
end
@doc false
@spec fetch_connect_socket(Phoenix.Socket.t()) ::
{:ok, Musubi.Socket.t()} | {:error, :missing_socket}
def fetch_connect_socket(%Phoenix.Socket{assigns: assigns}) do
case Map.fetch(assigns, @musubi_socket_key) do
{:ok, %Musubi.Socket{} = socket} -> {:ok, socket}
_other -> {:error, :missing_socket}
end
end
end