# `reconnect-token` ## Purpose Wraps the api-ws contract's `signReconnectToken` / `verifyReconnectToken` HMAC token helpers so the engine has a stateful issuer that enforces single-use semantics and maps the contract's single `WS.RECONNECT_TOKEN_INVALID` code onto the WS_BUN-namespace pair (`WS_BUN.RECONNECT_TOKEN_INVALID` vs `WS_BUN.RECONNECT_TOKEN_EXPIRED`). This gives the engine a clean place to plumb the HMAC secret, the TTL, and the clock without threading them through every handshake call. The issuer tracks consumed tokens in a `Set` so a replayed token (same wire string presented twice) is rejected with the same `INVALID` code as a tampered token — replay protection matches the spec scenario. ## Features - Async `sign` / `verify` over a pre-bound secret + TTL + clock. - Replay defence — every successful verify marks the token consumed; second presentation throws `WS_BUN.RECONNECT_TOKEN_INVALID`. - Distinguishes expiry from generic invalidity at the WS_BUN namespace so engines can map onto different HTTP status codes (401 vs 419). - `consumedCount` accessor for test assertions and capacity tracking. ## Usage: Basic ```ts import { ReconnectTokenIssuer } from "@suaveplan/api-ws-bun"; const issuer = new ReconnectTokenIssuer(new Uint8Array(32).fill(7)); const token = await issuer.sign({ tenant: "t1", principalId: "u1", lastEventId: null, }); const payload = await issuer.verify(token); console.log(payload.tenant); // "t1" ``` ## Usage: Advanced ```ts import { ReconnectTokenIssuer, WS_BUN_CODES } from "@suaveplan/api-ws-bun"; const clock = { value: 0 }; const issuer = new ReconnectTokenIssuer( new Uint8Array(32).fill(13), 1_000, () => clock.value ); const token = await issuer.sign({ tenant: "t1", principalId: "u1", lastEventId: "evt-9", }); clock.value += 2_000; try { await issuer.verify(token); } catch (err) { // (err as { code }).code === WS_BUN_CODES.RECONNECT_TOKEN_EXPIRED } ``` ## API Reference - `new ReconnectTokenIssuer(secret, ttlMs?, clock?)` — constructor. - `issuer.sign(payload): Promise` — sign a token. - `issuer.verify(token): Promise` — verify + consume. - `issuer.consumedCount: number` — total tokens consumed. ## Implementation Notes The contract `signReconnectToken` already encodes `exp = clock() + ttlMs` into the payload, so the issuer does not need to maintain its own expiry index. Single-use replay defence, by contrast, is engine-specific (multi-host deployments would need a Redis SET-NX instead of an in-process `Set`), so the issuer owns that state directly. The expiry detection in `verify` inspects the contract error message rather than re-running an expiry check. The contract error path is the canonical place where "expired" is differentiated from "bad MAC" / "malformed token"; mirroring that decision tree here would risk drift across versions.