LocalLiveView.Mirror behaviour (LocalLiveView v0.1.0)

Copy Markdown View Source

Behaviour for server-side mirror modules that receive synced assigns from a LocalLiveView.

A mirror module is automatically discovered by naming convention: Mirror.<ViewName>. It receives synced payloads from the local runtime via the handle_sync/3 callback.

defmodule Mirror.MyLocal do
  use LocalLiveView.Mirror

  @impl true
  def handle_sync(local_assigns, _mirror_assigns, %{mirror_id: mirror_id}) do
    Phoenix.PubSub.broadcast(MyApp.PubSub, "llv_mirror:MyLive:#{mirror_id}", {:llv_attrs, local_assigns})
    {:ok, local_assigns}
  end
end

Summary

Callbacks

Acts as a conflict resolution point between the local LiveView and its server-side mirror. Receives local_assigns (the map of synced assigns from the local runtime), mirror_assigns (the current state stored in the mirror channel), and session (a map containing :llv_id — the unique id of the LLV instance). Must return {:ok, new_mirror_assigns}.

Functions

Returns the mirror assigns currently held for mirror_id.

Callbacks

handle_sync(local_assigns, mirror_assigns, session)

@callback handle_sync(local_assigns :: map(), mirror_assigns :: map(), session :: map()) ::
  {:ok, map()}

Acts as a conflict resolution point between the local LiveView and its server-side mirror. Receives local_assigns (the map of synced assigns from the local runtime), mirror_assigns (the current state stored in the mirror channel), and session (a map containing :llv_id — the unique id of the LLV instance). Must return {:ok, new_mirror_assigns}.

Functions

get_mirror_assigns(mirror_id)

Returns the mirror assigns currently held for mirror_id.

These are the assigns last returned by the view's LocalLiveView.Mirror.handle_sync/3. Returns an empty map when no local live view is currently joined under that id, for example before the WASM runtime has started.

The browser is the source of truth here — mirror assigns are the server's view of the state that LocalLiveView last synced, kept in the channel process. A temporary disconnect does not lose it: the channel rejoins under the same mirror_id, because the id is derived from the host LiveView's socket.id, which survives reconnects. The mirror is then brought up to date by the next LocalLiveView.mirror_sync/2. Reloading the page starts a new mirror_id, since the LocalLiveView starts from scratch too.

def handle_info({:synced, mirror_id}, socket) do
  users = LocalLiveView.Mirror.get_mirror_assigns(mirror_id) |> Map.get("users", [])
  {:noreply, assign(socket, :users, users)}
end