View Source Synq.Channel behaviour (synq v0.2.0)

To start a Synq, you'll want to form a channel that uses this module.

  use Synq.Channel, web_module: MyAppWeb, max_version: 100

State Management:

  • Specify which assigns to track (sync) using the :track option
  • All other assigns remain in the ethereal plane (server-only)
  • Changes to tracked assigns are automatically synchronized using JSON patches

Example:

use Synq.Channel,
  web_module: MyAppWeb,
  track: [:spirits, :energy, :mana]
  • track optional, defaults to []. The keys in assigns to sync with the client.
  • message_builder optional, defaults to {Synq.MessageBuilder, ignore_keys: [:__meta__]}. If set, should be a tuple whose first element is a module that defines update_state_message/4 and new_state_message/3 and and second element contains any options. Options are passed as final arg to both functions when invoked. See Synq.MessageBuilder for details
  • max_version optional, defaults to 1000. This is the maximum version number, after which it will reset to 0 and begin incrementing again. Version numbers are used to detect a patch message arriving out of order. If such a condition is detected by phx-live-state a new copy of state is requested.

System Events

  • synq:error - Sent when an error occurs
  • synq:change - Sent for full state changes
  • synq:patch - Sent for incremental state updates using JSON patches

Summary

Types

Possible responses to send back to handle_event/3

Payload types that can be sent between client and server

Return value for event reply callbacks. Can be

Represents the tracked state that will be synced with the client

Version number for state updates, used to detect out-of-order messages

Callbacks

Called from join to authorize the connection. Return {:ok, socket} to authorize or {:error, reason} to deny. Default implementation returns {:ok, socket}

Invoked when the channel process is about to exit.

Receives an event from the client.

Handle regular Elixir process messages. Will send events and sync state based on the returned socket.

Returns the initial application state. Called just after connection

Types

@type event_response() ::
  {:reply, response :: reply(), socket :: Phoenix.Socket.t()}
  | {:ok, payload :: payload(), socket :: Phoenix.Socket.t()}
  | {:error, error :: Synq.Error.t(), socket :: Phoenix.Socket.t()}
  | {:noreply, socket :: Phoenix.Socket.t()}

Possible responses to send back to handle_event/3

@type message_builder() :: {module(), keyword()} | module()
@type payload() :: map() | term() | {:binary, binary()}

Payload types that can be sent between client and server

@type reply() :: status :: atom() | {status :: atom(), response :: payload()}

Return value for event reply callbacks. Can be:

  • status :: atom() # e.g., :ok, :error
  • {status :: atom(), response :: payload()} # e.g., {:ok, %{data: ...}}
@type track_map() :: map()

Represents the tracked state that will be synced with the client

@type version() :: non_neg_integer()

Version number for state updates, used to detect out-of-order messages

Callbacks

Link to this callback

authorize(topic, payload, socket)

View Source
@callback authorize(topic :: binary(), payload :: term(), socket :: Phoenix.Socket.t()) ::
  {:ok, socket :: Phoenix.Socket.t()} | {:error, Synq.Error.t()}

Called from join to authorize the connection. Return {:ok, socket} to authorize or {:error, reason} to deny. Default implementation returns {:ok, socket}

@callback before_close(
  reason :: :normal | :shutdown | {:shutdown, :left | :closed | term()},
  Phoenix.Socket.t()
) :: term()

Invoked when the channel process is about to exit.

See GenServer.terminate/2.

Link to this callback

handle_event(event, payload, socket)

View Source
@callback handle_event(event :: binary(), payload :: term(), socket :: Phoenix.Socket.t()) ::
  event_response()

Receives an event from the client.

If you choose not to reply to the message, you can return back {:noreply, socket} and an :ok response will be sent back to confirm the event was received.

If you choose to respond to the event, you can respond with {:reply, reply, socket}. The reply needs to be in the form of:

  • atom() - status
  • {atom(), payload()} - status with payload

You can also use one of these return types for convenience:

  • -> {:reply, {:ok, payload}, socket}
  • -> {:reply, {:error, error}, socket}

Any changes to the track will automatically materialize on the client after the response is sent back.

Examples

No reply:

def handle_event("spirit_movement", %{"direction" => direction}, socket) do

socket =
  socket
  |> update_spirit_position(direction)
  |> assign(:spirit_position, new_position)
{:noreply, socket}

end

Reply back with result:

def handle_event("summon_spirit", %{"type" => type}, socket) do

case SpirtRegistry.summon(type) do
  {:ok, spirit} ->
    socket = assign(socket, :spirits, socket.assigns.spirits ++ [spirit])
    {:ok, %{spirit: spirit}, socket}

  {:error, error} ->
  {:error, error}
end

end

Link to this callback

handle_message(msg, socket)

View Source
@callback handle_message(msg :: term(), socket :: Phoenix.Socket.t()) ::
  {:noreply, Phoenix.Socket.t()}

Handle regular Elixir process messages. Will send events and sync state based on the returned socket.

See GenServer.handle_info/2.

Link to this callback

init(topic, params, socket)

View Source
@callback init(topic :: binary(), params :: map(), socket :: Phoenix.Socket.t()) ::
  {:ok, socket :: Phoenix.Socket.t()} | {:error, error :: Synq.Error.t()}

Returns the initial application state. Called just after connection