Files
2026-05-27 23:47:53 +01:00

2.3 KiB

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

import { bootWsServer } from "@suaveplan/websocket-gateway";

const { server, address, port } = await bootWsServer(resolved, callbacks, signal);

Usage: Advanced Example

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<AuthContext>

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.