import { S as SFrameError } from '../errors-BmXaR_x0.js'; /** Context passed to seal/unseal identifying the room and sender. */ interface SealContext { roomId: string; senderUid: string; } /** Options for createChatProvider. */ interface ChatProviderOptions { /** * Return an HKDF base-key with usages `['deriveKey', 'deriveBits']`. * The library uses this to derive per-(roomId, senderUid) AES-128-GCM keys * via HKDF-SHA-256 with room-scoped salt and sender-scoped info strings. */ getKey: (roomId: string) => Promise; /** * CTR allocation strategy. * - `'random-64'` (default): 64-bit random CTR per frame. Stateless. * Birthday bound: ~2^32 messages before collision risk becomes non-negligible. * Replay protection is bounded-set only (not monotonic — random CTRs are * non-ordered). Cross-session replay is possible (page reload wipes * replay state). Use monotonic-idb for stronger guarantees. * - `'monotonic-idb'`: IDB-backed atomic counter. Requires `ctrKeyspace`. * Multi-tab safe via navigator.locks (when available). Falls back to * single-tab mode in environments without navigator.locks (Node.js). */ ctrStrategy?: 'random-64' | 'monotonic-idb'; /** Required when ctrStrategy is 'monotonic-idb'. Namespaces the IDB store. */ ctrKeyspace?: string; /** * Replay window size (number of recent CTRs to track per sender per room). * Default: 1024. Set to 0 to disable replay protection (debug only). * * Under random-64 strategy: bounded-set semantics only (no high-watermark * check), since random CTRs are non-monotonic and HWM checks would * incorrectly reject most messages. */ replayWindow?: number; /** * Enable durable, cross-reload receiver-side anti-replay (CWE-294). * Default: `false` (opt-in). When enabled, accepted CTRs are persisted to * IndexedDB so the replay defense survives a page reload — without it, a * malicious / compromised app-server can re-serve an OLD authentic sealed * frame under a fresh msg_id and it verifies (the ciphertext is genuinely * authentic, just old). * * Requires BOTH IndexedDB AND the Web Locks API. Degrades to a no-op * (with a one-time warning) when either is unavailable (SSR / Node / * legacy Safari <15.4). The in-memory `replayWindow` remains the * session-scoped backstop in that case. * * `namespace` is REQUIRED when enabled — it isolates independent * deployments sharing the same origin. Two deployments with the same * namespace and a colliding (roomId, senderUid) would share a replay * window and could false-reject each other. * * Defaults to `true` when a `namespace` is provided (issue #41: the * previous default of `false` left the cross-reload replay vulnerability * CWE-294 open by default). Set to `false` to explicitly opt out. */ durableReplay?: boolean; /** * Namespace for the durable replay IDB store. REQUIRED when * `durableReplay` is `true`. Isolates independent deployments sharing the * same origin. Use a per-tenant identifier (e.g. appId or tenantId). */ namespace?: string; /** * Durable replay window size (distinct recent CTRs per sender per room, * persisted). Default: equals `replayWindow`. Must be <= `replayWindow` * — the in-memory window is the session-scoped backstop, and a durable * window LARGER than the in-memory one removes that backstop for the * extra span (reopens a narrow in-session replay window). `0` disables * the durable window (mirrors `replayWindow: 0`). */ durableReplayWindow?: number; /** Called synchronously when rotate(roomId) is invoked. */ onKeyRotated?: (roomId: string) => void; } /** The provider returned by createChatProvider. */ interface ChatSFrameProvider { /** * Encrypt plaintext into an SFrame buffer for the given (roomId, senderUid). * Derives AEAD key via HKDF (cached per provider instance, max 256 entries). */ seal(plaintext: Uint8Array, ctx: SealContext): Promise; /** * Decrypt an SFrame buffer. Validates AEAD, checks replay window. * Throws ReplayError on replay; throws AEADAuthError on key/uid/room mismatch. */ unseal(sealed: Uint8Array, ctx: SealContext): Promise; /** * Evict derived-key cache and replay state for roomId. * Does NOT clear CTR allocator state — CTR space is independent of crypto key. * Calls onKeyRotated if provided. */ rotate(roomId: string): void; /** Release any resources (no-op in v0.5; reserved for future cleanup). */ dispose(): void; } /** * Thrown when unseal detects a replayed CTR value. * Extends SFrameError for uniform error handling. */ declare class ReplayError extends SFrameError { readonly context?: { roomId?: string; senderUid?: string; ctr?: bigint; } | undefined; readonly code: "REPLAY"; constructor(message: string, context?: { roomId?: string; senderUid?: string; ctr?: bigint; } | undefined); } /** * Create a chat-mode SFrame provider. * * All state (key cache, replay windows) is scoped to the returned provider * instance — concurrent providers do NOT share state. * * @example * ```ts * const key = await crypto.subtle.importKey( * 'raw', sharedSecret32Bytes, 'HKDF', false, ['deriveKey', 'deriveBits'] * ); * const provider = createChatProvider({ * getKey: async (roomId) => key, * }); * const sealed = await provider.seal(plaintext, { roomId, senderUid }); * const plain = await provider.unseal(sealed, { roomId, senderUid }); * ``` */ declare function createChatProvider(opts: ChatProviderOptions): ChatSFrameProvider; export { type ChatProviderOptions, type ChatSFrameProvider, ReplayError, type SealContext, createChatProvider };