# `Issho.Live`
[🔗](https://github.com/hattoriai/issho/blob/v0.4.0/lib/issho/live.ex#L2)

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.

# `target`

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

# `aware`

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

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

# `awareness`

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

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

# `close_stream`

```elixir
@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`

```elixir
@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`

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

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

# `interrupt`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

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

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

# `read`

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

The document's current snapshot, from its authority.

# `release`

```elixir
@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`

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

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

# `states`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
