# `Raxol.Gateway.Session`
[🔗](https://github.com/DROOdotFOO/raxol/blob/raxol_gateway-v0.2.0/lib/raxol/gateway/session.ex#L1)

One process per chat.

A session is created by `Raxol.Gateway.SessionRouter` for a `Route` and runs a
`Raxol.Gateway.Handler`. Each inbound event is dispatched to the handler; a
`{:reply, rendered, state}` result is delivered back through the session's
`:deliver` function (the router builds it from the adapter and connection). A
session stops itself after `:idle_timeout` ms of inactivity.

A session has a `conversation_id` (defaulting to its route key) that is stable
across a platform handoff: the router can start a session on a new route with
an existing `conversation_id`, so a configured `:log` resumes the same history.

## Handler startup

`start_link/1` returns as soon as the process exists; the handler's `init/2`
runs in a `handle_continue` after it. This keeps an expensive handler (one
starting a per-chat TEA app, say) from blocking the router that started it,
which starts sessions synchronously inside its own `handle_call`.

Two consequences for callers. A handler that fails to initialize stops the
session *after* `start_link/1` has already returned `{:ok, pid}`, so the
failure arrives as a DOWN rather than a return value -- `SessionRouter` emits
`[:raxol_gateway, :session, :down]` for it. And "the handler has started" is
not implied by `start_link/1` returning: any `GenServer.call/3` to the session
is serialized behind the continue and can be used as the barrier.

Because startup is no longer reported through a return value, it is reported
through telemetry instead: `[:raxol_gateway, :session, :ready]` once the
handler has initialized, and `[..., :init_timeout]` when it never does. A
`:started` from the router with no `:ready` behind it is a broken handler.

## Handler init is bounded

A handler stuck in `init/2` parks the session inside its own continue, where
it can read no message -- not a queued event, and not the idle timer, which is
armed only once init succeeds. Nothing would ever reap it, while the router
goes on routing that chat to it. So `init/1` spawns a watchdog that kills the
session if the handler has not returned within `:handler_init_timeout`. The
kill has to be brutal (a wedged process cannot honour a graceful exit), which
is why the diagnosis is emitted as telemetry before it lands.

This matters most for a handler that starts a per-chat TEA app: `start_link`
on deployment-authored `init/1` code has no timeout of its own.

## Options

  * `:route` (required) -- the `Raxol.Gateway.Route` this session serves
  * `:handler` (required) -- `{module, opts}` implementing `Gateway.Handler`
  * `:deliver` -- `(Route.t(), rendered -> any())`, default a no-op.
    `{:error, reason}`, a raised exception, or an exit reports
    `[:raxol_gateway, :session, :delivery_failed]`; the session remains alive.
  * `:idle_timeout` -- ms before the session stops (default 10 minutes)
  * `:handler_init_timeout` -- ms the handler's `init/2` may take before the
    session is killed (default 30 seconds), or `:infinity` to wait forever
  * `:conversation_id` -- a stable id for this chat (default `Route.key/1`)
  * `:log` -- `{module, server}` whose `append(server, conversation_id, items)`
    records each inbound event and each successfully delivered outbound reply
    (e.g. `{Raxol.Agent.Conversation.Log, log_server}`); default none

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `conversation_id`

```elixir
@spec conversation_id(GenServer.server(), timeout()) :: String.t()
```

The session's stable conversation id.

# `dispatch`

```elixir
@spec dispatch(GenServer.server(), term()) :: :ok
```

Dispatch an inbound event to the session.

# `route`

```elixir
@spec route(GenServer.server()) :: Raxol.Gateway.Route.t()
```

The route this session serves.

# `start_link`

```elixir
@spec start_link(keyword()) :: GenServer.on_start()
```

---

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