Issho.Live (Issho v0.4.0)

Copy Markdown View Source

A LiveView in a room: the same session as a browser's channel or an agent's process, in the LiveView's own process. Presence, awareness, the room's documents and the policy work the same; the room's events reach the LiveView's components with send_update/2, or the LiveView itself.

live_session :default, on_mount: [Issho.Live] do

# In a LiveView or one of its components, once connected:
{:ok, _session} = Issho.Live.join(MyApp.Issho, current_user, "board:42")
{:ok, snapshot} = Issho.Live.open("board:42", "board:42", notify: {MyComponent, "board"})

A component named in notify gets update/2 with an :issho assign for each event, {event, key, payload}:

  • {"tx", document_id, accepted_transaction}: a transaction accepted in a document it opened;
  • {"aware", room, frame}: the others' awareness;
  • {"presence_diff", room, diff}: people who came and went;
  • {"bye", room, reason}: the session ended (revoked, refused);
  • {"event", room, %Issho.Event{}}: an event of the room, for a component subscribed to its name (subscribe/3's :events).

notify: :self sends them to the LiveView as {Issho.Live, event, key, payload} messages instead.

Grants: rooms the browser joins

A component whose browser half joins the room itself (a text editor, which applies its own changes in the browser) needs a grant: a short token for the room, signed by the LiveView for the actor it authenticated. grant/4 signs it and remembers it, and the hook renews it when the browser asks (the "issho:grant" event, with the room), after asking the policy again, so that a person whose access went away gets no new grant. One grant serves every component of the page in that room.

grant = Issho.Live.grant(MyApp.Issho, current_user, "notebook:7", can: :write)

The components of a LiveView share one session per room: joining a room the LiveView is in already gives the same session. A LiveView may be in several rooms (a document, and a canvas of another room in it). A component that goes while the LiveView stays releases its room (release/2): the LiveView leaves it with its last component.

Join only in a connected LiveView (connected?(socket)): the static render has no process that lasts.

Summary

Functions

Sets this session's awareness state (nil clears it).

This session's own awareness state (an empty map for none).

Closes a stream of the room, for the person of this LiveView (Accept, Reject): when they may see it (Issho.Streams.visible?/2).

A grant for the browser to join the room as the actor: %{"room", "token", "can", "self", "socket", "max_channels"}, for a component's browser half. The LiveView renews it when the browser asks. Options: :can (:write or :read), :claims, :on_behalf_of, :socket (the Issho socket's path, "/issho/socket") and :max_channels (that socket's :max_channels_per_transport, 100).

Who this LiveView is in the room (Issho.Identity), or nil.

Pauses, resumes or stops an agent at work in the room (Issho.Work), as this LiveView's person: the person it acts for may, and anyone the policy allows :interrupt.

Joins a room as an actor, from this LiveView, or gives the session the LiveView already has there. Options: :can (:write), :claims, :on_behalf_of.

Leaves a room while the LiveView goes on: its people see the actor go (unless a tab of theirs is still there), its awareness of them clears, and the documents it opened are closed for it. Joining again later is a new session. A room the LiveView is not in is left already.

Merges keys into this session's awareness state: for the components of a LiveView that share its session in a room, each setting its own keys (a grid its place, another component its own key).

Merges a component's own keys into this session's state, under its document ("blocks", see Issho.Awareness), so that the LiveView's components, and Issho's own keys, never write over each other's; a key set to nil goes.

Opens a document of the room. Options: :notify, a component ({module, id}) or :self, for its transactions and the room's events; and :spec, what the document is if this opens it first and your policy's document/2 does not say (see Issho.Documents.open/4).

The people of the room now: Phoenix.Presence's list, by actor key.

The document's current snapshot, from its authority.

Stops sending the room's events, and its documents', to a component (or the LiveView, :self); when none is left in the room, leaves it (leave/1). For a component that goes from the page while the LiveView stays: a canvas deleted from a document, a panel closed.

The session id of this LiveView in the room, or nil.

The awareness states of the room's sessions, from every node: %{session_id => %{"key" => actor_key, "state" => state}}, for a component that draws the others itself.

The streams of the room this LiveView may see (Issho.Streams), as they are now, with what they hold so far; from then on, the LiveView gets what comes as messages, and the room's new streams it may see

Sends the room's messages to a component, or the LiveView, without opening a document. Options

Commits to an open document: a list of operations (on the document's latest revision) or a whole transaction.

Types

target()

@type target() :: {module(), term()} | :self

Functions

aware(room, state)

@spec aware(String.t(), map() | nil) :: :ok | {:error, term()}

Sets this session's awareness state (nil clears it).

awareness(room)

@spec awareness(String.t()) :: map()

This session's own awareness state (an empty map for none).

close_stream(room, id, outcome)

@spec close_stream(String.t(), String.t(), String.t() | atom()) ::
  :ok | {:error, term()}

Closes a stream of the room, for the person of this LiveView (Accept, Reject): when they may see it (Issho.Streams.visible?/2).

grant(instance, actor, room, opts \\ [])

@spec grant(atom(), Issho.Actor.t(), String.t(), keyword()) :: map()

A grant for the browser to join the room as the actor: %{"room", "token", "can", "self", "socket", "max_channels"}, for a component's browser half. The LiveView renews it when the browser asks. Options: :can (:write or :read), :claims, :on_behalf_of, :socket (the Issho socket's path, "/issho/socket") and :max_channels (that socket's :max_channels_per_transport, 100).

identity(room)

@spec identity(String.t()) :: Issho.Identity.t() | nil

Who this LiveView is in the room (Issho.Identity), or nil.

interrupt(room, key, action)

@spec interrupt(String.t(), String.t(), :pause | :resume | :stop) ::
  :ok | {:error, term()}

Pauses, resumes or stops an agent at work in the room (Issho.Work), as this LiveView's person: the person it acts for may, and anyone the policy allows :interrupt.

join(instance, actor, room, opts \\ [])

@spec join(atom(), Issho.Actor.t(), String.t(), keyword()) ::
  {:ok, String.t()} | {:error, term()}

Joins a room as an actor, from this LiveView, or gives the session the LiveView already has there. Options: :can (:write), :claims, :on_behalf_of.

leave(room)

@spec leave(String.t()) :: :ok

Leaves a room while the LiveView goes on: its people see the actor go (unless a tab of theirs is still there), its awareness of them clears, and the documents it opened are closed for it. Joining again later is a new session. A room the LiveView is not in is left already.

merge(room, changes)

@spec merge(String.t(), map()) :: :ok | {:error, term()}

Merges keys into this session's awareness state: for the components of a LiveView that share its session in a room, each setting its own keys (a grid its place, another component its own key).

merge_block(room, document, changes)

@spec merge_block(String.t(), String.t(), map()) :: :ok | {:error, term()}

Merges a component's own keys into this session's state, under its document ("blocks", see Issho.Awareness), so that the LiveView's components, and Issho's own keys, never write over each other's; a key set to nil goes.

open(room, document_id, opts \\ [])

@spec open(String.t(), String.t(), keyword()) :: {:ok, map()} | {:error, term()}

Opens a document of the room. Options: :notify, a component ({module, id}) or :self, for its transactions and the room's events; and :spec, what the document is if this opens it first and your policy's document/2 does not say (see Issho.Documents.open/4).

people(room)

@spec people(String.t()) :: map()

The people of the room now: Phoenix.Presence's list, by actor key.

read(room, document_id)

@spec read(String.t(), String.t()) :: {:ok, map()} | {:error, term()}

The document's current snapshot, from its authority.

release(room, target)

@spec release(String.t(), target()) :: :ok

Stops sending the room's events, and its documents', to a component (or the LiveView, :self); when none is left in the room, leaves it (leave/1). For a component that goes from the page while the LiveView stays: a canvas deleted from a document, a panel closed.

A LiveComponent hears nothing when it goes, so its browser half says it, from its hook's destroyed, and Issho's hook releases the room for the component of that id:

destroyed() {
  this.pushEvent("issho:release", { room: "board:42", component: "board" })
}

session_id(room)

@spec session_id(String.t()) :: String.t() | nil

The session id of this LiveView in the room, or nil.

states(room)

@spec states(String.t()) :: map()

The awareness states of the room's sessions, from every node: %{session_id => %{"key" => actor_key, "state" => state}}, for a component that draws the others itself.

streams(room)

@spec streams(String.t()) :: {:ok, [Issho.Stream.t()]} | {:error, term()}

The streams of the room this LiveView may see (Issho.Streams), as they are now, with what they hold so far; from then on, the LiveView gets what comes as messages, and the room's new streams it may see:

def mount(_params, _session, socket) do
  {:ok, outputs} = if connected?(socket), do: Issho.Live.streams(room), else: {:ok, []}
  {:ok, assign(socket, :outputs, Map.new(outputs, &{&1.id, &1}))}
end

def handle_info({Issho, :stream, id, {:started, stream}}, socket), do: ...
def handle_info({Issho, :stream, id, {:chunk, text}}, socket), do: ...
def handle_info({Issho, :stream, id, :finished}, socket), do: ...

and {:failed, reason}, {:closed, outcome} and :gone. A page that mounts while a stream is written picks it up where it got to. The LiveView is in the room (join/4). (Not LiveView's own streams of a list, stream/4, and not their assign: :streams is LiveView's.)

subscribe(key, target, opts \\ [])

@spec subscribe(String.t(), target(), keyword()) :: true

Sends the room's messages to a component, or the LiveView, without opening a document. Options:

  • :events, the names of the room's events (Issho.Event) the component gets as {"event", room, event}: a list of names, a name ending in ".*" for all of a kind ("comment.*"), or :all. Without it, none.
  • :on_release, a function of no argument, runs when the component is released (release/2), before the LiveView leaves the room if it was the last: for what the component put in the session's awareness, which stays while the LiveView is in the room.

transact(room, document_id, ops)

@spec transact(String.t(), String.t(), [term()] | map()) ::
  {:ok, map()} | {:error, term()}

Commits to an open document: a list of operations (on the document's latest revision) or a whole transaction.