# `stream-session` _(no submodules)_ ## Purpose `StreamSession` is the public face of the stream-tier session core. It wraps the FSM, the transport adapter, the interruption classifier, and the per-peer `StreamSourcePlugin` registry into one orchestration object. Consumers in chunks B/C (transports, hooks, components) talk to this class — they never reach into the FSM or the classifier directly. The class is intentionally a thin shell. Every lifecycle method (`connect`, `join`, `leave`, `disconnect`) is a `dispatchSessionEvent` followed by the matching transport call followed by another `dispatchSessionEvent`. Transport faults flow through `reportInterruption`, which classifies the cause, emits the `'interruption'` event, drives the FSM to `disconnected`, and updates telemetry. Auto-reconnect (chunk D) subscribes to `'interruption'` and decides whether to retry — the session itself never retries, keeping the retry policy at the right layer for testability. ## Features - Eight `'state-changed'` emissions covering the canonical happy path. - `'peer-stream-added'` / `'peer-stream-removed'` emissions for every peer the transport reports. - Deterministic `'interruption'` emission via the `InterruptionClassifier`. - Injectable transport via constructor — no globals, no singletons. - Telemetry: peer-connect / peer-disconnect counters, active-session gauge, RTT histogram (the latter wired by chunk B). - `dispose()` for clean teardown including peer chain removal. - Typed `EventBus` surface so consumers in chunk C can write `useEventListener(session.bus, "peer-stream-added", …)` without any `as` casts. ## Usage: Basic ```ts import { createStreamSession } from "@suaveplan/react-audio-stream/session"; const session = createStreamSession({ transport }); await session.connect("room-1"); await session.join(); // ...stream activity... await session.leave(); await session.disconnect(); ``` ## Usage: Advanced ```ts import { createStreamSession, type StreamTransportAdapter, } from "@suaveplan/react-audio-stream/session"; const session = createStreamSession({ transport }); session.bus.on("state-changed", ({ from, to }) => { metrics.set("ras_state", to); }); session.bus.on("peer-stream-added", ({ participantId, stream }) => { console.log("attaching peer", participantId); engine.attachSource(`peer:${participantId}`, stream); }); session.bus.on("interruption", (verdict) => { if (verdict.class === "network") { scheduleReconnect(); } }); try { await session.connect("room-1"); await session.join(); } catch (err) { // The session has already emitted 'interruption' and driven the FSM // to 'disconnected'. The error is re-thrown so the caller can decide. console.error(err); } ``` ## API Reference - `class StreamSession` - `constructor(options: StreamSessionOptions)` - `bus: EventBus` — public event surface. - `state`, `roomId`, `peers()` — read-only inspection. - `connect(roomId)`, `join()`, `leave()`, `disconnect()` — lifecycle methods that drive the FSM and the transport in lock-step. - `reportInterruption(cause)` — classify + emit + transition to `disconnected`. Used by chunk-B transports. - `dispose()` — unsubscribe transport listeners + tear down peers. - `createStreamSession(options)` — factory wrapper. - Type re-exports: `StreamSessionEventMap`, `StreamSessionOptions`, `StreamTransportAdapter`, `PeerStreamEvent`, `InterruptionCause`. ## Implementation Notes The session takes the classifier in the constructor rather than the options object having to know about it, so tests can stub the verdict without subclassing. Peer storage is a plain `Map` — the chain integration in chunk B will wire each plugin into the engine's timeline, but at chunk-A scope the plugin just holds the `MediaStream` reference. `tearDownAllPeers` runs on every transition to `disconnected` (both explicit and via FAIL) so the engine sees consistent peer-stream-removed events regardless of how the session ended. `extractReason` keeps the failure path allocation-free for the common `Error` case while still surfacing a useful string when the transport throws a non-Error. The `active` flag guards the OTel gauge against double-decrement when both an explicit disconnect and an interruption land back-to-back.