first commit
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
# `stream-session`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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<StreamSessionEventMap>` — 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<string,
|
||||
StreamSourcePlugin>` — 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.
|
||||
Reference in New Issue
Block a user