# Gateway lifecycle helpers ## Purpose Centralises three pieces of lifecycle plumbing the daemon needs but doesn't want inline: WS-server boot, start-completion accounting (OTel histogram + bus event), and the per-connection auth handshake. Splitting these out of `gateway.ts` keeps the daemon file under the 300-line cap and makes each phase independently unit-testable. ## Features - `bootWsServer(resolved, callbacks, signal)` — calls the injected factory, invokes `start()`, and translates every failure into a typed `WsGatewayError(START_FAILED)` with the original cause attached. Always best-effort-stops the (possibly-partially-created) server before rethrowing. - `recordStartCompleted(resolved, address, port, startedAt, t0, emit)` — records the start-duration histogram and emits the typed `ws_gateway:started` event. - `runAuthHandshake(resolved, socketId, handshake, now)` — runs the injected `authVerifier`, normalises the resulting auth context (handles missing client-ids), audits both outcomes, raises `AUTH_REJECTED` on failure. ## Usage: Basic Example ```ts import { bootWsServer } from "@suaveplan/websocket-gateway"; const { server, address, port } = await bootWsServer(resolved, callbacks, signal); ``` ## Usage: Advanced Example ```ts import { runAuthHandshake } from "@suaveplan/websocket-gateway"; try { const ctx = await runAuthHandshake(resolved, socketId, handshake, Date.now()); // ctx.clientId guaranteed to be non-empty } catch (err) { // err is a WsGatewayError(AUTH_REJECTED); audit log has already received // the "denied" record. } ``` ## API Reference - `bootWsServer(resolved, callbacks, signal): Promise<{server, address, port}>` - `recordStartCompleted(resolved, address, port, startedAt, t0, emit): void` - `runAuthHandshake(resolved, socketId, handshake, now): Promise` ## Implementation Notes `bootWsServer` always awaits `server?.stop()` before rethrowing because the WS factory may throw mid-`start()` after attaching internal state — letting that state leak would prevent a retry. `runAuthHandshake` defaults a missing `ctx.clientId` to the socket id so consumers that adopt async auth don't need to invent ids twice. Auditing fires on both branches because the audit log is the source of truth for "did this handshake even reach us" alerts.