Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
# `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<string>` 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<ReconnectTokenString>` — sign a token.
- `issuer.verify(token): Promise<ReconnectTokenPayload>` — 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.