# `handshake` _(no submodules)_ ## Purpose Freezes the per-request pipeline result onto a per-connection `HandshakeOutcome`. The host runs the standard pipeline steps 1–7 (auth + tenant resolution + capability scoping) and passes the resolved principal, tenant id, capability set, and token expiry into `runHandshake`. The function snapshots that result into an immutable record the rest of the connection lifecycle treats as the source of truth — the principal cannot mutate from this point on. Subprotocol negotiation also happens here, following RFC 6455 §4.1: the server picks the first protocol from its supported list that the client requested. If the client requested a subprotocol the server does not support, the handshake throws `WS_BUN.SUBPROTOCOL_UNSUPPORTED`. ## Features - Immutable snapshot of principal + tenant + capabilities. - RFC 6455 subprotocol negotiation with explicit unsupported-rejection. - Throws `WS_BUN.HANDSHAKE_REJECTED` for the obvious "missing principal / tenant" failure modes so the engine never has to second-guess pipeline output. ## Usage: Basic ```ts import { runHandshake } from "@suaveplan/api-ws-bun"; const outcome = runHandshake({ request, principal, tenantId, capabilities: new Set([]), tokenExpiresAt: Date.now() + 3600_000, }); ``` ## Usage: Advanced ```ts import { resolveSubprotocol, runHandshake } from "@suaveplan/api-ws-bun"; const outcome = runHandshake({ request, principal, tenantId, capabilities: new Set(["ws.broadcast-cross-tenant"]), tokenExpiresAt: Date.now() + 3600_000, supportedSubprotocols: ["suaveplan.v1", "graphql-transport-ws"], }); console.log(outcome.chosenSubprotocol); // Or use the lower-level negotiation directly: const choice = resolveSubprotocol( ["graphql-transport-ws"], ["suaveplan.v1", "graphql-transport-ws"] ); ``` ## API Reference - `HandshakeInput` — `{ request, principal, tenantId, capabilities, tokenExpiresAt, supportedSubprotocols? }`. - `HandshakeOutcome` — `{ principal, tenantId, capabilities, tokenExpiresAt, chosenSubprotocol }`. - `runHandshake(input): HandshakeOutcome` — freeze the pipeline result. - `resolveSubprotocol(requested, supported): string | null` — RFC 6455 §4.1 negotiation. ## Implementation Notes The function rejects missing principal or tenant with `WS_BUN.HANDSHAKE_REJECTED` even though the pipeline upstream is expected to guarantee both. This redundant guard exists because the engine treats this module as a hard wall against malformed input from a misconfigured pipeline — defending the rest of the connection lifecycle from `undefined`-propagation bugs is cheaper here than at every downstream call site. Capability sets are copied into a fresh `Set` rather than referenced directly so the engine cannot accidentally mutate the host-supplied capability bag. The cost is one allocation per handshake, dwarfed by everything else the connection setup is doing.