first commit
This commit is contained in:
65
docs/api-stack/api/api-ws-bun/src/handshake/handshake.md
Normal file
65
docs/api-stack/api/api-ws-bun/src/handshake/handshake.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# `handshake`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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.
|
||||
Reference in New Issue
Block a user