@mcpwarp/ws-mixer 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/errors.ts","../src/conn.ts","../src/frame.ts","../src/control.ts","../src/stream.ts","../src/util.ts"],"sourcesContent":["/** Public API of @mcpwarp/ws-mixer: the ws-mixer.v1 JS/TypeScript client SDK. */\nexport { connect, MixerClient, fullJitterDelay, SUBPROTOCOL, SDK_VERSION } from \"./client.js\";\nexport type {\n ConnectOptions,\n ReconnectOptions,\n TokenProvider,\n DisconnectReason,\n DisconnectPhase,\n DisconnectPayload,\n CloseOptions,\n} from \"./client.js\";\n\nexport { MixerConn } from \"./conn.js\";\nexport type { ConnOptions, WSLike } from \"./conn.js\";\n\nexport { MixerStream } from \"./stream.js\";\nexport type { StreamState } from \"./stream.js\";\n\nexport { ErrorCode, WsMixerError, ConnError, StreamError, TokenUnavailableError, codeName, parseErrorCode, closeCode } from \"./errors.js\";\nexport type { ErrorCodeValue } from \"./errors.js\";\n\nexport {\n FrameType,\n decodeFrame,\n encodeFrame,\n encodeOpen,\n encodeData,\n encodeWindow,\n encodeClose,\n encodeReset,\n windowIncrement,\n resetCode,\n resetMessage,\n frameTypeName,\n MAX_MESSAGE_SIZE,\n MAX_STREAM_ZERO_PAYLOAD,\n MAX_CHUNK,\n MAX_SEND_WINDOW,\n} from \"./frame.js\";\nexport type { Frame, FrameTypeValue } from \"./frame.js\";\n\nexport { parseControl, encodeControl, knownDrainReason } from \"./control.js\";\nexport type {\n ControlMessage,\n AgentInfo,\n ServerInfo,\n HelloMsg,\n WelcomeMsg,\n PingMsg,\n PongMsg,\n DrainMsg,\n ErrorMsg,\n AppMsg,\n} from \"./control.js\";\n","/**\n * Public client entry point: `connect(url, opts)`. Owns the WebSocket\n * handshake (subprotocol, headers, size limits), and the reconnect/backoff\n * policy from docs/research/2026-08-26-control-channel-and-connection-lifecycle.md\n * and WIRE.md section 2.9's reconnect table. `MixerConn` (conn.ts) owns\n * everything about one already-connected socket; this file owns the loop\n * that replaces it.\n *\n * State machine (single source of truth for \"are we allowed to dial /\n * reconnect right now\"): idle -> dialing -> connected -> backoff -> ...,\n * terminating in closed (either `close()` was called, a fatal close code\n * was hit, or maxAttempts was exhausted). `dialing` and `backoff` both loop\n * back to `dialing` on success; every terminal transition happens exactly\n * once, in `terminal()`/`goFatal()`/`giveUp()`.\n */\nimport { EventEmitter } from \"node:events\";\nimport { WebSocket } from \"ws\";\nimport type { AgentInfo, DrainMsg, WelcomeMsg } from \"./control.js\";\nimport { ErrorCode, StreamError, TokenUnavailableError, WsMixerError, closeCode, codeName } from \"./errors.js\";\nimport { MixerConn, type WSLike } from \"./conn.js\";\nimport { MAX_MESSAGE_SIZE } from \"./frame.js\";\nimport type { MixerStream } from \"./stream.js\";\n\nexport const SUBPROTOCOL = \"ws-mixer.v1\";\n// Kept in sync with package.json's \"version\" by hand (test/version.test.ts\n// asserts the two match, so a release bump that forgets this one fails CI\n// instead of silently going stale on the wire in hello.agent.sdk_version /\n// the User-Agent header).\nexport const SDK_VERSION = \"0.6.0\";\n\nexport type ClientState = \"idle\" | \"dialing\" | \"connected\" | \"backoff\" | \"closed\";\n\nexport interface ReconnectOptions {\n /** Base delay for exponential backoff, ms. Default 1000. */\n base?: number;\n /** Backoff cap, ms. Default 60000. */\n cap?: number;\n /** Per-attempt connect timeout, ms. Default 10000. */\n connectTimeout?: number;\n /**\n * Max reconnect attempts before giving up. Default Infinity. Counts\n * consecutive reconnects *without a stable connection in between*\n * (`stableAfter` below) -- since `attempt` itself only resets at\n * stability now, a server that welcomes and then always disconnects\n * before a connection ever proves stable exhausts this ceiling and goes\n * fatal exactly like a server that never welcomes at all.\n */\n maxAttempts?: number;\n /**\n * How long (ms) a connection must stay up past `welcome` before it's\n * considered stable (WIRE.md section 2.9's `stable`). The backoff attempt\n * counter, and every once-only retry budget re-armed alongside it (e.g.\n * 4013 KEEPALIVE_TIMEOUT's one immediate retry, and the one-time\n * token-provider refresh-retry for a pre-welcome auth rejection), are\n * reset only once a connection has stayed up this long -- not on\n * `welcome` itself -- so a server that welcomes and then immediately\n * closes can't reset them every cycle and turn backoff (or a refresh-retry\n * loop against the token endpoint) into a redial-roughly-once-a-second\n * loop forever. Default 10000.\n */\n stableAfter?: number;\n}\n\n/** Structural subset of `ws`'s WebSocket needed during the dial (before a MixerConn exists). Lets tests inject a fake transport via `_wsFactory`. */\n/**\n * Structural subset of Node's `http.ClientRequest`, as seen on `ws`'s\n * `unexpected-response` event. `ws` only calls its own `abortHandshake`\n * cleanup when nothing is listening for `unexpected-response` -- since this\n * SDK does listen (to read `statusCode`/`Retry-After`), it owns draining and\n * destroying `req`/`res` itself, or the request/socket leaks.\n */\nexport interface DialRequest {\n destroy?(err?: Error): void;\n}\n\n/** Structural subset of Node's `http.IncomingMessage`, as seen on `ws`'s `unexpected-response` event. */\nexport interface DialResponse {\n statusCode?: number;\n headers: Record<string, string | string[] | undefined>;\n resume?(): void;\n destroy?(err?: Error): void;\n}\n\nexport interface DialSocket extends WSLike {\n readonly protocol: string;\n once(event: \"open\", listener: () => void): this;\n once(\n event: \"unexpected-response\",\n listener: (req: DialRequest, res: DialResponse) => void,\n ): this;\n once(event: \"error\", listener: (err: Error) => void): this;\n}\n\nexport interface WSFactoryOptions {\n perMessageDeflate: boolean;\n maxPayload: number;\n headers: Record<string, string>;\n}\n\n/** Test-only dial hook: replaces `new WebSocket(url, [SUBPROTOCOL], options)`. */\nexport type WSFactory = (url: string, protocols: string[], options: WSFactoryOptions) => DialSocket;\n\n/**\n * A ws-mixer token: either a static string or a callback returning a fresh\n * token (sync or `Promise`). Per CLIENT-SDK.md's \"Token provider\" and \"Provider failure\" rows, the callback MUST\n * be invoked on every dial, never cached across reconnects, and a\n * throw/rejection is fatal -- surfaced verbatim as `DisconnectReason.cause` --\n * UNLESS it's a `TokenUnavailableError` (thrown directly, or reachable by\n * following `.cause`): that marks a temporary failure to OBTAIN a token\n * (network still down, auth server briefly unreachable) and is instead\n * treated like a failed dial -- normal backoff, no fatal.\n */\nexport type TokenProvider = string | (() => Promise<string> | string);\n\n/** Where a disconnect originated, per CLIENT-SDK.md's \"Disconnect reason shape\" row. */\nexport type DisconnectPhase = \"dial\" | \"handshake\" | \"connected\";\n\n/**\n * The shape every disconnect (recoverable or fatal) is reported with, per\n * CLIENT-SDK.md's \"Disconnect reason shape\" row. `httpStatus` is set only for a dial failure whose\n * response was an HTTP status (401/403/404/429); `cause` is set only when a\n * token provider threw/rejected.\n */\nexport interface DisconnectReason {\n phase: DisconnectPhase;\n /**\n * The WebSocket close code, when a WS close occurred. When derived from a\n * ws-mixer error (this side's own `WsMixerError`/`ConnError`, or a peer's\n * `error{code}`), this is always the semantic `4000+error_code` -- for an\n * `error_code` outside the legal WS close range (>= 0x1000_0000, an\n * application-layer code that is legal for a stream RESET but never was\n * for a connection close), that can differ from the *actual* bytes this\n * side puts on the wire, which are clamped to `4000+INTERNAL_ERROR` (4002)\n * instead (conn.ts's `wireCloseCode`, mirroring go/wsmixer's\n * `wsCloseCode`) -- `errorCode` always keeps the real, unclamped code\n * either way. When instead observed directly from a bare close frame\n * (`onSocketClose`, no preceding `error{}`), `wsCode` is exactly what was\n * on the wire, since there is nothing else it could be.\n */\n wsCode?: number;\n errorCode?: number;\n errorName?: string;\n httpStatus?: number;\n fatal: boolean;\n message: string;\n /**\n * The reason field of the close frame *received from the peer*, verbatim\n * -- never this side's own outgoing reason (CLIENT-SDK.md's `closeReason`\n * row). Absent or empty whenever no reason was received from the peer:\n * this includes an abnormal closure (no close frame at all), this side\n * having initiated the close itself (a peer's echo carries no information\n * and RFC 6455 doesn't require it to copy the reason), and the SDK closing\n * on a peer's `error{}` without reading whatever close frame follows it,\n * as WIRE.md section 2.7 allows (\"logs, surfaces and closes\"). The\n * human-readable text is in `message` for all of those cases instead --\n * consumers SHOULD prefer `closeReason` and fall back to `message`.\n */\n closeReason?: string;\n cause?: unknown;\n}\n\n/**\n * The actual runtime payload for every disconnect: `DisconnectReason` plus\n * the pre-existing loud \"SDK bug\" fields (item 7) for a protocol-bug close\n * (4001/4003/4004) -- kept alongside `errorCode`/`errorName` rather than\n * replacing them, so existing `protocolError`/`code`/`name` consumers keep\n * working.\n */\nexport type DisconnectPayload = DisconnectReason & (\n | { protocolError: true; code: number; name: string }\n | { protocolError?: false }\n);\n\n/**\n * Everything known about one disconnect *before* we know whether it will be\n * retried or is the last one (exhaustion/fatal): every field `DisconnectReason`\n * carries except `fatal`, built from local data at the call site (the\n * `DialError`, the handshake rejection, or the `MixerConn` close `info`) --\n * never from shared mutable instance state. This is what makes it safe for a\n * concurrent old-connection close and a new dial's failure to report\n * independently without clobbering each other (blocker 3).\n */\ninterface DisconnectContext {\n phase: DisconnectPhase;\n wsCode?: number;\n errorCode?: number;\n errorName?: string;\n httpStatus?: number;\n cause?: unknown;\n message: string;\n closeReason?: string;\n protocolError?: boolean;\n code?: number;\n name?: string;\n}\n\n/**\n * The outcome of one `dialAndHandshakeOnce` attempt: either it landed\n * (`ok: true`), was cancelled by a racing `close()` with nothing left to\n * report (`cancelled: true`), or failed with everything connectOnce's retry\n * loop and failure-reporting path need -- `unauthorized` marks exactly the\n * two pre-welcome rejection shapes eligible for the one-time token-provider\n * refresh-retry (CLIENT-SDK.md's \"Rejected token\" row): an HTTP 401 on the upgrade, or a\n * handshake-phase UNAUTHORIZED (4011, with or without a preceding\n * `error{}`).\n */\ntype AttemptResult =\n | { ok: true; cancelled?: false; conn: MixerConn; welcome: WelcomeMsg }\n | { ok: false; cancelled: true }\n | {\n ok: false;\n cancelled?: false;\n ctx: DisconnectContext;\n fatal: boolean;\n unauthorized: boolean;\n retryAfterMs?: number;\n };\n\n/** Resolves a `TokenProvider` to a token string. Calling a throwing/rejecting provider propagates its error verbatim. */\nasync function resolveToken(token: TokenProvider): Promise<string> {\n return typeof token === \"function\" ? await token() : token;\n}\n\nexport interface ConnectOptions {\n token: TokenProvider;\n meta?: unknown;\n agent?: Partial<AgentInfo>;\n window?: number;\n maxStreams?: number;\n capabilities?: string[];\n headers?: Record<string, string>;\n\n /**\n * Delivered, along with `onApp`/`onDrain`, in wire order from one delivery\n * loop per connection, one at a time -- never concurrently with each other\n * or with themselves (CLIENT-SDK.md's \"Handler delivery\" row). An event\n * already received when the connection ends is still owed to this\n * callback: it MAY therefore still fire shortly after `close()`/\n * `close({message})`'s own promise has resolved, for a stream/message/drain\n * that arrived before whatever ended the connection.\n */\n onStream?: (stream: MixerStream) => void;\n /** See `onStream`'s doc comment: same wire-order/post-close-flush guarantees. */\n onApp?: (body: Record<string, unknown>) => void;\n /** See `onStream`'s doc comment: same wire-order/post-close-flush guarantees. */\n onDrain?: (msg: { reason: string; deadlineMs?: number; message?: string; lastStreamId: number }) => void;\n onConnect?: (welcome: WelcomeMsg) => void;\n /**\n * Fires on every disconnect, recoverable or not (see stats() note in\n * docs/DESIGN.md). This is the SDK's loud, always-invoked channel\n * -- unlike `'error'`, it is not gated on a listener being attached, so a\n * `4001`/`4003`/`4004` close (WIRE.md section 2.9: \"these mean an SDK\n * bug and a silent retry loop hides it\") always reaches an `onDisconnect`\n * the caller supplied, with `protocolError: true` plus the offending\n * `code`/`name` to make it impossible to miss.\n */\n onDisconnect?: (reason: DisconnectPayload) => void;\n\n reconnect?: ReconnectOptions;\n\n /** Test-only: injects a fake WebSocket-like transport for the dial. Never used in production. */\n _wsFactory?: WSFactory;\n\n /**\n * @internal Test-only: forwarded verbatim to `MixerConn`'s `ConnOptions._timing`\n * (see conn.ts) so a scaled-clock test harness (e.g. the conformance\n * runner's `--time-scale`) can run a real `connect()` against `welcome`\n * values below the wire's keepalive floors. Never set this in production;\n * stripped from the public `.d.ts` surface is not enforced today, but no\n * production code path should ever pass it.\n */\n _timing?: {\n minPingInterval?: number;\n minPingTimeout?: number;\n helloTimeout?: number;\n };\n}\n\n/** Options for an application-initiated `MixerClient.close()` (CLIENT-SDK.md's \"Application close\" row). See `close()`'s doc comment. */\nexport interface CloseOptions {\n /**\n * The mode is chosen by presence, not value: supplying `message` (even\n * `\"\"`) closes the connection with `error{code: ErrorCode.APPLICATION_CLOSE,\n * message}` then WS close `4014`, instead of the default graceful drain --\n * WIRE.md section 2.8 makes `APPLICATION_CLOSE` the only code an\n * application may close a *connection* with, so there is no caller-chosen\n * code (D-2026-09-25-01). Truncated to 123 UTF-8 bytes on a character\n * boundary for the WS close reason. Leaving `message` undefined -- `close()`\n * or `close({})` -- takes the graceful path instead. A `code` key (e.g. a\n * plain-JS caller still passing `{code: 14}`) makes `close()` throw a\n * `TypeError` synchronously, with or without `message`.\n */\n message?: string;\n}\n\nconst DEFAULT_RECONNECT: Required<ReconnectOptions> = {\n base: 1000,\n cap: 60000,\n connectTimeout: 10000,\n maxAttempts: Infinity,\n stableAfter: 10000,\n};\n\n/** Close codes the client must never retry after (WIRE.md section 2.9's reconnect table). */\nconst FATAL_WS_CODES = new Set([4000 + ErrorCode.UNSUPPORTED, 4000 + ErrorCode.UNAUTHORIZED]);\nconst GOING_AWAY_WS_CODE = 4000 + ErrorCode.GOING_AWAY; // 4012\nconst KEEPALIVE_TIMEOUT_WS_CODE = 4000 + ErrorCode.KEEPALIVE_TIMEOUT; // 4013\nconst ENHANCE_YOUR_CALM_WS_CODE = 4000 + ErrorCode.ENHANCE_YOUR_CALM; // 4009\nconst APPLICATION_CLOSE_WS_CODE = 4000 + ErrorCode.APPLICATION_CLOSE; // 4014\nconst ABNORMAL_CLOSURE_WS_CODE = 1001; // non-ws-mixer close treated as 4012, WIRE.md section 2.8\n/**\n * Close codes that mean \"SDK bug\", per WIRE.md section 2.9's reconnect\n * table: \"normal backoff and a loud developer-facing error -- these mean an\n * SDK bug and a silent retry loop hides it.\" Backoff already happens via the\n * normal scheduleReconnect() path below; onDisconnect (never gated on a\n * listener, unlike 'error') is the loud part.\n */\nconst PROTOCOL_BUG_WS_CODES = new Set([\n 4000 + ErrorCode.PROTOCOL_ERROR, // 4001\n 4000 + ErrorCode.FLOW_CONTROL_ERROR, // 4003\n 4000 + ErrorCode.FRAME_SIZE_ERROR, // 4004\n]);\n// A connected-phase 4014 (APPLICATION_CLOSE) gets 4009's own \"start at cap\"\n// treatment below (WIRE.md section 2.9): both are, by their own nature,\n// refusals the server sends on purpose (an operator-driven session cap, or\n// \"back off, you're overloading me\") rather than an ordinary transient\n// failure -- an explicit \"back off hard immediately\" signal, not plain\n// full-jitter backoff's usual \"assume this might just be a blip\". This is\n// unrelated to the attempt counter's own reset timing (armStabilityTimer):\n// even now that the counter only resets once a connection has proven itself\n// stable, a 4009/connected-phase-4014 still deserves to skip straight to the\n// cap instead of climbing there gradually. A handshake-phase 4014 has no\n// such problem -- it never reset anything -- so it stays on the ordinary\n// handshake-failure path (connectOnce's catch), attempt counter and all.\n\n/**\n * Derives the disconnect reason's `errorCode`/`errorName` pair (CLIENT-SDK.md's\n * \"Disconnect reason shape\" row, D-2026-09-20-09): when `knownErrorCode` is\n * already known (this side's own `WsMixerError`, or a peer's `error{}` that\n * preceded the close), it's used verbatim; otherwise, for a BARE close whose\n * `wsCode` falls in ws-mixer's private-use range 4001-4999, `errorCode` is\n * derived mechanically (`wsCode - 4000`) and `errorName` from the WIRE.md\n * §2.8 table (unknown -> INTERNAL_ERROR). Anything else -- no `wsCode`, or a\n * non-ws-mixer code (1000, 1001, 1006, 1009, 1011, ...), or bare `4000`\n * itself (never legitimately on the wire; NO_ERROR closes as 1000) -- gets\n * neither, so an HTTP upgrade rejection or an abnormal closure never gets a\n * fabricated ws-mixer code.\n */\nfunction deriveErrorCodeName(\n wsCode: number | undefined,\n knownErrorCode: number | undefined,\n): { errorCode: number | undefined; errorName: string | undefined } {\n const errorCode = knownErrorCode ?? (wsCode !== undefined && wsCode >= 4001 && wsCode <= 4999 ? wsCode - 4000 : undefined);\n return { errorCode, errorName: errorCode !== undefined ? codeName(errorCode) : undefined };\n}\n\nexport declare interface MixerClient {\n on(event: \"welcome\", listener: (welcome: WelcomeMsg) => void): this;\n on(event: \"stream\", listener: (stream: MixerStream) => void): this;\n on(event: \"app\", listener: (body: Record<string, unknown>) => void): this;\n on(event: \"drain\", listener: (msg: DrainMsg) => void): this;\n on(event: \"reconnecting\", listener: (info: { attempt: number; delayMs: number; cause: string }) => void): this;\n on(event: \"close\", listener: (info: DisconnectPayload) => void): this;\n on(event: \"error\", listener: (err: WsMixerError) => void): this;\n on(event: \"fatal\", listener: (err: WsMixerError) => void): this;\n on(event: \"pong\", listener: (info: { id: number; rttMs: number }) => void): this;\n}\n\n/**\n * MixerClient owns the reconnect loop: it replaces `conn` with a fresh\n * MixerConn on every disconnect, per the policy in\n * docs/research/2026-08-26-control-channel-and-connection-lifecycle.md.\n *\n * In-flight streams are lost on reconnect -- there is no resumption\n * (WIRE.md section 2.9). A handler must tell \"the response ended\" (EOF)\n * from \"the tunnel died\" (its stream is destroyed with an error) itself;\n * this SDK does not paper over the difference.\n */\nexport class MixerClient extends EventEmitter {\n private readonly url: string;\n private readonly opts: ConnectOptions;\n private readonly reconnectOpts: Required<ReconnectOptions>;\n\n private state: ClientState = \"idle\";\n /** The confirmed-live connection (welcome received). Null while dialing/backing off. */\n private conn: MixerConn | null = null;\n /** A MixerConn mid-handshake, not yet promoted to `conn`. Tracked so close() can tear it down too. */\n private dialingConn: MixerConn | null = null;\n /**\n * Set only while a `drain`-triggered parallel reconnect is in flight: the\n * connection that sent `drain`, still alive and finishing in-flight\n * streams. Torn down (fail()) as soon as its replacement's `welcome`\n * lands, so a superseded connection never lingers past that point.\n */\n private retiringConn: MixerConn | null = null;\n\n private attempt = 0;\n private everConnected = false;\n private closing = false;\n private fatal = false;\n /** Set when `drain` already started a parallel reconnect, so the close that follows it doesn't schedule a second one. */\n private drainReconnectScheduled = false;\n /**\n * Set when `drain` arrives with reconnect disabled (`maxAttempts:0`):\n * WIRE.md section 2.9 says in-flight streams finish normally until the\n * server's own deadline, at which point it closes with 4012 -- so the conn\n * stays up and this flag just tells the eventual close handler to report\n * that close as the one fatal \"drained; reconnect disabled\" disconnect\n * instead of treating 4012 as an ordinary going-away reconnect trigger.\n */\n private drainedNoReconnect = false;\n /** WIRE.md section 2.9: close 4013 gets exactly one immediate retry before falling back to normal backoff. Re-armed at stability, not at welcome -- see armStabilityTimer. */\n private keepaliveImmediateRetryUsed = false;\n /**\n * CLIENT-SDK.md's \"Rejected token\" row: a pre-welcome token rejection (an HTTP 401 on\n * the upgrade, or a handshake-phase UNAUTHORIZED/4011, with or without a\n * preceding `error{}`) gets exactly one immediate provider refresh-retry\n * -- ONE budget shared across both rejection shapes, and across every\n * `connectOnce()` call, not reset on every dial/redial. Re-armed only at\n * stability (armStabilityTimer), same as `keepaliveImmediateRetryUsed`:\n * without that, a server that welcomes and then closes shortly after could\n * make the client hit the token endpoint again on every single reconnect\n * cycle forever, instead of only once per genuinely-unstable run. After a\n * long healthy (stable) session, the budget is available again -- an\n * ordinary token expiry on some later reconnect still gets its retry.\n */\n private unauthorizedRetryUsed = false;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n /** Resolves connectOnce's backoff-delay await; settled directly by clearReconnectTimer() so close()/goFatal()/giveUp() during backoff don't leave that await dangling forever. */\n private reconnectTimerResolve: (() => void) | null = null;\n /**\n * One-shot timer armed on `welcome` (armStabilityTimer): fires once the\n * *current* conn has stayed up for `reconnectOpts.stableAfter` ms, at\n * which point `attempt` and the once-only retry budgets it gates\n * (`keepaliveImmediateRetryUsed`, `unauthorizedRetryUsed`) reset.\n * `unref()`'d so it never keeps the process alive, and cleared on every\n * path that ends a conn or the client itself (conn close, close()/\n * close({message}), goFatal, a drain hand-over's retired conn) so a stale\n * timer can never fire against a conn that's no longer the active one --\n * though the `this.conn === conn` check inside it is also always\n * re-verified regardless, belt and suspenders. Per-connection: a\n * drain-superseded predecessor closing well after its replacement's own\n * `welcome` must never clear the replacement's still-ticking timer (see\n * the `wasActive` guard in wireConn's 'close' handler below).\n */\n private stabilityTimer: ReturnType<typeof setTimeout> | null = null;\n\n private startResolve: (() => void) | null = null;\n private startReject: ((err: Error) => void) | null = null;\n\n constructor(url: string, opts: ConnectOptions) {\n super();\n this.url = url;\n this.opts = opts;\n this.reconnectOpts = { ...DEFAULT_RECONNECT, ...opts.reconnect };\n // Validated synchronously, before anything else happens (RangeError):\n // silently letting a non-finite or\n // negative value through would either never fire (NaN: Node clamps\n // setTimeout's delay to a minimum of 1ms rather than rejecting it, so a\n // NaN stableAfter would still schedule a timer -- but comparing `attempt`\n // resets against \"NaN ms\" is meaningless and not what a caller passing\n // NaN could have intended) or misrepresent what actually happens (a\n // negative value also just clamps to ~1ms in practice). `0` itself stays\n // legal and well-defined: reset on the next tick after `welcome`, i.e.\n // the pre-`stableAfter` (pre-0.4) behaviour.\n if (!Number.isFinite(this.reconnectOpts.stableAfter) || this.reconnectOpts.stableAfter < 0) {\n throw new RangeError(\n `ws-mixer: reconnect.stableAfter must be a finite number >= 0; got ${this.reconnectOpts.stableAfter}`,\n );\n }\n }\n\n /** Starts the first connection attempt and resolves once `welcome` completes (or rejects if it never gets there and reconnecting is pointless: a fatal auth failure, or maxAttempts exhausted before the first success). */\n async start(): Promise<void> {\n return new Promise((resolve, reject) => {\n this.startResolve = resolve;\n this.startReject = reject;\n void this.connectOnce(0, \"initial\");\n });\n }\n\n /** The currently active MixerConn, if connected. */\n currentConn(): MixerConn | null {\n return this.conn;\n }\n\n /** Current reconnect state machine position: idle/dialing/connected/backoff/closed. */\n currentState(): ClientState {\n return this.state;\n }\n\n /**\n * Writes an `app` frame; resolves once `ws.send`'s callback confirms it\n * actually reached the socket (docs/DESIGN.md), or rejects with the\n * connection's terminal error if it fails first -- conn.ts's control queue\n * carries the same per-write resolver `sendData()` already uses for stream\n * bytes.\n */\n sendApp(body: Record<string, unknown>): Promise<void> {\n if (!this.conn) return Promise.reject(new Error(\"ws-mixer: sendApp called with no active connection\"));\n return this.conn.sendApp(body);\n }\n\n /** \"Ignore and count\" counters for the active connection (item 11); `undefined` when there is none. */\n stats(): ReturnType<MixerConn[\"stats\"]> | undefined {\n return this.conn?.stats();\n }\n\n /**\n * Graceful client shutdown: stops reconnecting for good and closes every\n * live/in-flight connection. A dial already in flight when this is called\n * is closed as soon as it resolves (see connectOnce); nothing reconnects\n * after this returns.\n *\n * With `opts.message`, this is instead an application-initiated close\n * (CLIENT-SDK.md's \"Application close\" row): `error{code:\n * ErrorCode.APPLICATION_CLOSE, message}` on stream 0, WS close `4014`\n * (message truncated to 123 UTF-8 bytes on a character boundary), then\n * the socket -- `MixerConn.fail()`'s `teardownConn` already performs\n * exactly those three steps in order. No `drain`, no grace period.\n * `APPLICATION_CLOSE` is the only code an application may close a\n * *connection* with (WIRE.md section 2.8), so there is no caller-chosen\n * code here (D-2026-09-25-01): an `opts` with a `code` key (a plain-JS\n * caller still on the old `close({code, message})` shape) throws a\n * `TypeError` synchronously, before either connection is touched.\n *\n * A callback for a stream/`app`/`drain` event received before this close\n * can still fire shortly after this promise resolves: it doesn't wait for\n * `onStream`/`onApp`/`onDrain`'s delivery loop to finish flushing whatever\n * was already queued (CLIENT-SDK.md's \"Handler delivery\" row) -- see those\n * options' own doc comments.\n */\n close(opts?: CloseOptions): Promise<void> {\n if (typeof opts === \"object\" && opts !== null && Object.prototype.hasOwnProperty.call(opts, \"code\")) {\n throw new TypeError(\n \"ws-mixer: close() no longer takes a code (D-2026-09-25-01); use close({message}) -- it always closes with APPLICATION_CLOSE\",\n );\n }\n return this.closeImpl(opts);\n }\n\n private async closeImpl(opts?: CloseOptions): Promise<void> {\n // Marked closing/closed FIRST, before either connection is touched, so\n // nothing below -- the 'close' handler's own reconnect-scheduling\n // branches -- ever schedules a reconnect for this shutdown.\n this.closing = true;\n this.state = \"closed\";\n // An app-initiated close() always wins over a pending drain deadline:\n // once this returns, the drain handler's own close (4012) must be\n // reported as an ordinary graceful close, not the drained/fatal report\n // below (blocker: drainedNoReconnect branch runs unconditionally).\n this.drainedNoReconnect = false;\n // If this races an in-flight first dial (never connected once), start()\n // must settle rather than hang forever: it's not a fatal error, just a\n // client-initiated shutdown before any connection ever completed.\n this.rejectStartIfNeverConnected(new Error(\"ws-mixer: closed before the first connection completed\"));\n this.clearReconnectTimer();\n this.clearStabilityTimer();\n // Built once and reused for every conn this call tears down, so a\n // message given to close() isn't silently dropped for a\n // retiring/dialing conn just because it wasn't yet the primary `conn`\n // (previously these two always hard-coded NO_ERROR/\"client closing\"\n // regardless of opts).\n const closeErr =\n opts?.message !== undefined ? new WsMixerError(ErrorCode.APPLICATION_CLOSE, opts.message) : new WsMixerError(ErrorCode.NO_ERROR, \"client closing\");\n if (this.retiringConn) {\n const old = this.retiringConn;\n this.retiringConn = null;\n old.fail(closeErr);\n }\n if (this.dialingConn) {\n const dialing = this.dialingConn;\n this.dialingConn = null;\n dialing.fail(closeErr);\n }\n if (this.conn) {\n if (opts?.message !== undefined) {\n this.conn.fail(closeErr);\n } else {\n await this.conn.close();\n }\n }\n }\n\n // this.state is a plain string-literal-union property, and TS's control\n // flow narrowing does *not* invalidate a narrowed `this.state === \"x\"`\n // across an `await` (even though the awaited call can, and here does,\n // reassign it) -- so connectOnce below reads it through this indirection\n // to force a fresh, unnarrowed check every time.\n private isClosed(): boolean {\n return this.state === \"closed\";\n }\n\n /** Cancels a pending backoff timer, if any, and settles connectOnce's awaited delay promise so it doesn't dangle. */\n private clearReconnectTimer(): void {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.reconnectTimerResolve) {\n const resolve = this.reconnectTimerResolve;\n this.reconnectTimerResolve = null;\n resolve();\n }\n }\n\n /** Cancels the pending stability timer, if any (see the field's own doc comment). Idempotent. */\n private clearStabilityTimer(): void {\n if (this.stabilityTimer) {\n clearTimeout(this.stabilityTimer);\n this.stabilityTimer = null;\n }\n }\n\n /**\n * Arms the one-shot stability timer for `conn`, replacing (and so\n * implicitly clearing) whatever timer was pending before -- there is only\n * ever one conn worth timing at once: a drain-superseded predecessor is\n * torn down as soon as the replacement's own `welcome` lands (connectOnce),\n * which is exactly the moment this is called for the replacement, so the\n * predecessor's own now-irrelevant timer never outlives this call.\n */\n private armStabilityTimer(conn: MixerConn): void {\n this.clearStabilityTimer();\n const timer = setTimeout(() => {\n this.stabilityTimer = null;\n // The conn this timer was armed for might already be gone (replaced\n // by a reconnect, or the client closed) by the time it fires -- only\n // reset if it's still the one and only active connection.\n if (this.conn === conn) {\n this.attempt = 0;\n this.keepaliveImmediateRetryUsed = false;\n this.unauthorizedRetryUsed = false;\n }\n }, this.reconnectOpts.stableAfter);\n timer.unref?.();\n this.stabilityTimer = timer;\n }\n\n private async connectOnce(delayMs: number, cause: string): Promise<void> {\n if (this.closing || this.isClosed()) return;\n if (delayMs > 0) {\n this.state = \"backoff\";\n this.emit(\"reconnecting\", { attempt: this.attempt, delayMs, cause });\n await new Promise<void>((resolve) => {\n this.reconnectTimerResolve = resolve;\n this.reconnectTimer = setTimeout(resolve, delayMs);\n });\n this.reconnectTimer = null;\n this.reconnectTimerResolve = null;\n if (this.closing || this.isClosed()) return;\n }\n\n this.state = \"dialing\";\n\n // One retry, covering EITHER pre-welcome rejection shape (CLIENT-SDK.md's\n // \"Rejected token\" row): an HTTP 401 on the upgrade, or a handshake-phase\n // UNAUTHORIZED (4011, with or without a preceding `error{}`) --\n // `dialAndHandshakeOnce` surfaces both as `attempt.unauthorized` so this\n // one loop covers both instead of two parallel mechanisms. Only a token\n // PROVIDER is eligible (retrying a static string can only ever hand back\n // the same, already-rejected token). The budget (`unauthorizedRetryUsed`)\n // is a CLIENT-level flag, not local to this call -- it's the same kind of\n // once-only budget as `keepaliveImmediateRetryUsed`, re-armed only at\n // stability (armStabilityTimer), not on every redial: 401 then 4011, or\n // 4011 then 401, on this or a later connectOnce() call before the client\n // has ever gone stable, is still \"a second rejection\" and falls straight\n // through to the ordinary failure handling below, which is fatal for\n // both shapes regardless of order.\n //\n // Deliberately unconditional on `maxAttempts`/reconnect being disabled --\n // matches ../ws-mixer-go's dialAndHandshake (isUnauthorized +\n // isRealProvider, no MaxAttempts/Disabled check at all): the rejected\n // token row applies to the *initial* connect too, where `maxAttempts:0`\n // (\"this SDK will never reconnect\") says nothing about retrying within\n // one still-in-progress connect attempt. Nor does it increment `attempt`\n // -- the retry redials immediately, within this same connect attempt, so\n // it isn't itself \"a reconnect since the last stable connection\" the way\n // `attempt` counts (see `stableAfter`'s doc); only the ordinary failure\n // paths below (goFatal/scheduleReconnect) touch `attempt`. No disconnect\n // is reported for the first rejection -- only the final outcome is\n // (mirrors the pre-existing HTTP-401-only refresh-retry's own silence on\n // its first rejection).\n const isProvider = typeof this.opts.token === \"function\";\n let attempt: AttemptResult;\n for (;;) {\n attempt = await this.dialAndHandshakeOnce(this.reconnectOpts.connectTimeout);\n if (attempt.ok || attempt.cancelled) break;\n if (!this.unauthorizedRetryUsed && isProvider && attempt.unauthorized) {\n // The retiring conn this dial was hedging against can have gone\n // fatal (e.g. its own 4011) and failed `this.dialingConn` -- see the\n // `fatal`/`drainedNoReconnect` branches -- while this retry loop was\n // still awaiting the dial above; don't spend the retry budget\n // re-dialing with a fresh token into a client that already decided\n // to close.\n if (this.closing || this.isClosed()) break;\n this.unauthorizedRetryUsed = true;\n continue;\n }\n break;\n }\n\n if (attempt.ok) {\n const { conn, welcome } = attempt;\n // A drain hand-over's parallel dial can land its welcome just as (or\n // just after) the retiring conn it's replacing closes fatally: the\n // close handler's own `dialingConn.fail()` (see the `fatal`/\n // `drainedNoReconnect` branches below) only helps when it runs BEFORE\n // this `handshake()` await settles -- once welcome has already been\n // validated there is no rejection left to race, so this promoted\n // `conn` would otherwise flip the client straight back to \"connected\"\n // on top of the `fatal` event already emitted. Guard it here too:\n // don't promote into a client that's already done, and close the\n // now-abandoned connection instead of leaking it.\n if (this.closing || this.isClosed()) {\n if (this.dialingConn === conn) this.dialingConn = null;\n conn.fail(new WsMixerError(ErrorCode.NO_ERROR, \"client closing\"));\n return;\n }\n // Reset backoff (and the once-only budgets it gates, e.g. 4013's one\n // immediate retry and the token-refresh retry above) only once THIS\n // conn has stayed up for `stableAfter` ms past welcome, never on\n // welcome itself or on a bare dial/101 success (WIRE.md\n // section 2.9's `stable`) -- see armStabilityTimer's own doc comment\n // for why.\n this.armStabilityTimer(conn);\n this.everConnected = true;\n this.dialingConn = null;\n this.conn = conn;\n this.state = \"connected\";\n\n // A `drain`-triggered parallel reconnect just succeeded: the old\n // connection is no longer needed, so tear it down now instead of\n // waiting on the server's own deadline.\n if (this.retiringConn && this.retiringConn !== conn) {\n const old = this.retiringConn;\n this.retiringConn = null;\n // Blocker 3: this drain cycle's parallel reconnect has now landed,\n // so its \"suppress one extra reconnect\" job is done. Clearing it\n // here -- not in old's close handler below -- matters because\n // `old.fail()` fires that handler synchronously, and by then\n // `this.retiringConn` is already null (just cleared above), so the\n // handler can no longer tell \"this was the retiring conn\" from\n // `this.retiringConn === conn`. Left uncleared, the flag would still\n // be true the next time the (now active) replacement disconnects for\n // an unrelated reason, silently swallowing that reconnect too.\n this.drainReconnectScheduled = false;\n // In-flight streams on the superseded conn are still live -- they\n // didn't fail, the *connection* they were riding on was replaced.\n // Give each one a stream-scoped CANCEL (\"connection drained\"), not\n // the connection-level NO_ERROR used to tear the socket itself down,\n // so an app distinguishes \"this stream was cancelled\" from \"the\n // whole conn errored\" (see MixerStream.terminateNoThrow).\n old.fail(\n new WsMixerError(ErrorCode.NO_ERROR, \"superseded by a new connection\"),\n (streamId) => new StreamError(ErrorCode.CANCEL, streamId, \"connection drained\"),\n );\n }\n\n this.opts.onConnect?.(welcome);\n if (this.startResolve) {\n const resolve = this.startResolve;\n this.startResolve = null;\n this.startReject = null;\n resolve();\n }\n return;\n }\n\n if (attempt.cancelled) return; // close() raced the dial/handshake: nothing left to do here\n\n if (attempt.ctx.phase === \"dial\") {\n if (this.closing || this.isClosed()) return;\n if (attempt.fatal) {\n this.goFatal(attempt.ctx);\n return;\n }\n if (attempt.retryAfterMs !== undefined) {\n // HTTP 429 with Retry-After: honour the server's hint verbatim instead of our own jitter.\n this.reportAndSchedule(attempt.ctx, attempt.ctx.message, () => attempt.retryAfterMs!);\n return;\n }\n this.scheduleReconnect(attempt.ctx, attempt.ctx.message);\n return;\n }\n\n // Handshake-phase failure.\n if (attempt.fatal) {\n this.goFatal(attempt.ctx);\n return;\n }\n if (this.closing || this.isClosed()) return;\n // Non-fatal handshake failure (e.g. no `welcome` within the hello\n // timeout, or the peer closing the socket before it ever arrived):\n // still \"every disconnect...is reported\" (CLIENT-SDK.md's \"Disconnect reason shape\" row) --\n // exactly once, whether that's this retry's report or (if maxAttempts\n // is already exhausted) the single merged exhaustion report\n // scheduleReconnect emits via giveUp instead.\n this.scheduleReconnect(attempt.ctx, \"handshake failed: \" + attempt.ctx.message);\n }\n\n /**\n * One dial + handshake attempt: resolves the token provider (fresh, per\n * CLIENT-SDK.md's \"Token provider\" row), opens the socket, and -- if that succeeds --\n * builds a `MixerConn` and waits for `welcome`. Returns a discriminated\n * result rather than throwing, so connectOnce's retry loop above can\n * decide what to do with a failure (retry once, or finalize it) without a\n * second round of reclassifying the same error: `unauthorized` is true for\n * exactly the two pre-welcome rejection shapes eligible for that retry (an\n * HTTP 401 on the upgrade, or a handshake-phase UNAUTHORIZED, 4011, with\n * or without a preceding `error{}`); `cancelled` is true when `close()`\n * raced the dial/handshake and there is nothing left to report.\n */\n private async dialAndHandshakeOnce(connectTimeoutMs: number): Promise<AttemptResult> {\n let token: string;\n try {\n token = await resolveToken(this.opts.token);\n } catch (err) {\n const e = providerDialError(err);\n return {\n ok: false,\n ctx: { phase: \"dial\", cause: e.cause, message: e.message },\n // Fatal by default (CLIENT-SDK.md's \"Provider failure\" row) -- UNLESS the provider\n // explicitly marked this as a temporary failure to obtain a token\n // (TokenUnavailableError, thrown directly or reachable via `cause`),\n // in which case it's treated exactly like a failed dial: normal\n // backoff via the ordinary phase-\"dial\" path below, same as every\n // other non-auth dial failure.\n fatal: !isTokenUnavailable(err),\n unauthorized: false,\n };\n }\n\n let ws: DialSocket;\n try {\n ws = await dialWebSocket(this.url, token, this.opts, connectTimeoutMs);\n } catch (e) {\n if (this.closing || this.isClosed()) return { ok: false, cancelled: true };\n const err = e as DialError;\n return {\n ok: false,\n ctx: {\n phase: \"dial\",\n httpStatus: err.httpStatus,\n cause: err.cause,\n message: err.message,\n errorCode: err.errorCode,\n errorName: err.errorCode !== undefined ? codeName(err.errorCode) : undefined,\n },\n fatal: err.fatal,\n // Only an HTTP 401 is eligible for the refresh-retry -- 403/404 are\n // fatal always, provider or not (dialWebSocket already marks both\n // `fatal`, this just decides which of them also gets one retry).\n unauthorized: err.httpStatus === 401,\n retryAfterMs: err.retryAfterMs,\n };\n }\n\n if (this.closing || this.isClosed()) {\n // close() during an in-flight dial: close the socket once the dial\n // resolves, never reconnect.\n try {\n ws.close(1000, \"client closing\");\n } catch {\n ws.terminate?.();\n }\n return { ok: false, cancelled: true };\n }\n\n const conn = new MixerConn(ws as unknown as WSLike, {\n token,\n agent: {\n sdk: \"ws-mixer-js\",\n sdk_version: SDK_VERSION,\n runtime: `node/${process.version.replace(/^v/, \"\")}`,\n os: `${process.platform}/${process.arch}`,\n ...this.opts.agent,\n },\n meta: this.opts.meta,\n window: this.opts.window,\n maxStreams: this.opts.maxStreams,\n capabilities: this.opts.capabilities,\n _timing: this.opts._timing,\n });\n this.dialingConn = conn;\n this.wireConn(conn);\n\n try {\n const welcome = await conn.handshake();\n return { ok: true, conn, welcome };\n } catch (e) {\n this.dialingConn = null;\n const err = e as WsMixerError;\n // A raw peer close (onSocketClose) carries `err.wsCode`: derive\n // errorCode/wsCode from it via deriveErrorCodeName(), exactly like the\n // connected-phase 'close' handler below does, rather than trusting\n // onSocketClose's generic INTERNAL_ERROR placeholder -- a bare\n // 4010/4011 close before welcome must classify (and go fatal)\n // the same way a post-welcome one does. A *locally* raised failure\n // (hello timeout, welcome validation, ...) has no observed wsCode: its\n // `err.code` is already the genuine classification, so derive the WS\n // code it's about to close with instead (CLIENT-SDK.md's\n // \"Handshake-phase close\" row: the welcome timeout carries the locally\n // generated wsCode 4001, no closeReason).\n let wsCode = err.wsCode;\n let knownErrorCode: number | undefined;\n if (wsCode === undefined) {\n knownErrorCode = err.code;\n wsCode = closeCode(err.code);\n }\n const { errorCode, errorName } = deriveErrorCodeName(wsCode, knownErrorCode);\n return {\n ok: false,\n ctx: {\n phase: \"handshake\",\n wsCode,\n errorCode,\n errorName,\n message: err.message,\n closeReason: err.closeReason,\n },\n fatal: err.fatal || errorCode === ErrorCode.UNSUPPORTED || errorCode === ErrorCode.UNAUTHORIZED,\n unauthorized: errorCode === ErrorCode.UNAUTHORIZED,\n };\n }\n }\n\n private wireConn(conn: MixerConn): void {\n conn.on(\"welcome\", (w) => this.emit(\"welcome\", w));\n // These three return their handler's result (possibly a Promise) so\n // MixerConn's delivery loop (emitOrdered, item 2) actually awaits\n // onStream/onApp/onDrain before moving to the next queued event, and so\n // a throw/rejection there is caught by emitOrdered instead of becoming\n // an unhandled rejection.\n conn.on(\"stream\", (s) => {\n this.emit(\"stream\", s);\n return this.opts.onStream?.(s);\n });\n conn.on(\"app\", (b) => {\n this.emit(\"app\", b);\n return this.opts.onApp?.(b);\n });\n conn.on(\"pong\", (p) => this.emit(\"pong\", p));\n conn.on(\"drain\", (d) => {\n // A second `drain` on the same connection (the server re-sending it,\n // or a duplicate) must not spawn a SECOND parallel reconnect on top of\n // one already scheduled for this conn -- mirrors the Go SDK, which\n // ignores a repeat drain the same way (handleServerDrain's own\n // drainReconnectScheduled check). Without this, two `connectOnce`\n // calls would both be racing to redial (and, since Change 1's\n // refresh-retry budget is now client-level, could both spend it).\n //\n // `this.conn === conn` guards against a flushed `drain` (CLIENT-SDK.md's\n // \"Handler delivery\" row: a queued event is still delivered after the\n // connection that queued it has already died) arriving for a conn\n // that's no longer live: its own 'close' handler already ran\n // synchronously and set `this.conn = null` (and, for an ordinary\n // disconnect, already scheduled the real reconnect) before this\n // flushed `drain` is ever delivered -- without this guard, a `drain`\n // queued behind a blocked handler on a conn that then dies for an\n // unrelated reason would still see `!this.closing && this.state !==\n // \"closed\" && !this.drainReconnectScheduled` all true and spawn a\n // SECOND, parallel reconnect against the already-dead conn, latching\n // `drainReconnectScheduled` and wrongly suppressing the next\n // legitimate close-driven reconnect.\n if (this.conn === conn && !this.closing && this.state !== \"closed\" && !this.drainReconnectScheduled) {\n if (this.reconnectOpts.maxAttempts > 0) {\n // Reconnect immediately and in parallel, before the old connection closes (WIRE.md section 2.9).\n this.drainReconnectScheduled = true;\n this.retiringConn = conn;\n void this.connectOnce(Math.random() * 2000, \"drain\");\n } else {\n // maxAttempts:0 means this SDK will never reconnect (WIRE.md section\n // 2.9's bounded retry count), but that doesn't make `drain` an\n // immediate fatal event: WIRE.md section 2.9 says in-flight streams finish\n // normally until the server's own deadline, then the server closes\n // with 4012. So we let the conn run to that close instead of\n // tearing it down here -- the close handler below reports that\n // 4012 as the one fatal \"drained; reconnect disabled\" disconnect.\n this.drainedNoReconnect = true;\n }\n }\n // Emitted only after the reconnect decision above: a user 'drain'\n // listener that throws is caught by MixerConn's emitOrdered (item 2 of\n // the delivery loop this handler's own return value feeds), never by\n // this handler itself, so it must not be able to skip the parallel\n // reconnect (or the maxAttempts:0 drainedNoReconnect flag) by throwing\n // before this handler ever gets there.\n this.emit(\"drain\", d);\n return this.opts.onDrain?.({ reason: d.reason, deadlineMs: d.deadline_ms, message: d.message, lastStreamId: d.last_stream_id });\n });\n // Guarded like MixerConn's own emit('error') (item 2): 'close' below\n // always fires and reaches onDisconnect(reason), so nothing is silent.\n conn.on(\"error\", (err) => {\n if (this.listenerCount(\"error\") > 0) this.emit(\"error\", err);\n });\n conn.on(\"close\", (info) => {\n const wasActive = this.conn === conn;\n if (this.conn === conn) this.conn = null;\n if (this.dialingConn === conn) this.dialingConn = null;\n if (this.retiringConn === conn) this.retiringConn = null;\n // The stability timer only ever belongs to the currently-active conn\n // (armStabilityTimer replaces, and so implicitly clears, a\n // predecessor's own timer the moment a replacement welcomes) -- so\n // only clear it here when THIS conn was that active one; a retiring/\n // never-promoted conn's close (e.g. a drain hand-over's old conn,\n // ending well after its replacement's own welcome already re-armed\n // the timer for the new conn) is never the timer's current owner and\n // must NOT clear the replacement's still-ticking one.\n if (wasActive) this.clearStabilityTimer();\n\n // A conn that never became the active connection either had its\n // handshake() promise already handled in connectOnce's catch (a normal\n // dial/handshake failure), or was fail()'d directly by close()/the\n // retiring-conn teardown above (which already know why) -- either way,\n // scheduling a second reconnect here would race the one already in flight.\n if (!wasActive) return;\n\n const fatal = info.wsCode !== undefined && FATAL_WS_CODES.has(info.wsCode);\n const protocolBug = info.wsCode !== undefined && PROTOCOL_BUG_WS_CODES.has(info.wsCode);\n // A post-handshake close is always phase \"connected\"; it never carries\n // an HTTP status or a token-provider cause (those only apply to a dial)\n // -- built fresh from `info` (this specific close), never from shared\n // instance state, so a concurrent dial's own report can't clobber it\n // and vice versa (blocker 3).\n const ctx: DisconnectContext = protocolBug\n ? // errorCode is only set when MixerConn itself detected the failure\n // (fail()'s teardownConn); a raw WS-level close (onSocketClose)\n // never carries one, even though wsCode = 4000 + code still tells\n // us exactly which one it was -- deriveErrorCodeName() derives it\n // rather than falling back to something less informative than the\n // wire already told us (PROTOCOL_BUG_WS_CODES is always a bare\n // ws-mixer code, so this always agrees with `code`/`name` below).\n (() => {\n const { errorCode, errorName } = deriveErrorCodeName(info.wsCode, info.errorCode);\n const code = errorCode!;\n return {\n phase: \"connected\",\n wsCode: info.wsCode,\n errorCode,\n errorName,\n message: info.message,\n closeReason: info.closeReason,\n protocolError: true,\n code,\n name: codeName(code),\n };\n })()\n : {\n phase: \"connected\",\n wsCode: info.wsCode,\n // Same derivation as the protocolBug branch above: a raw\n // ws-mixer close with no preceding `error` control frame (e.g.\n // 4011 from onSocketClose) still tells us exactly which one it\n // was, so deriveErrorCodeName() fills both errorCode/errorName\n // from it instead of leaving them undefined just because\n // MixerConn itself never set one.\n ...deriveErrorCodeName(info.wsCode, info.errorCode),\n message: info.message,\n closeReason: info.closeReason,\n };\n\n if (fatal) {\n // Exactly one report for this close: it's fatal, so there's no\n // separate \"exhaustion\" report to merge it with.\n this.notifyDisconnect(this.buildPayload(ctx, true));\n if (this.closing || this.fatal || this.state === \"closed\") return;\n this.state = \"closed\";\n this.fatal = true;\n this.closing = true;\n this.clearReconnectTimer();\n const err = new WsMixerError(ctx.errorCode ?? ErrorCode.INTERNAL_ERROR, info.message, { fatal: true });\n // A drain hand-over's parallel dial can still be mid-handshake when\n // the retiring conn (this one) closes fatally -- left alone, that\n // dial finishes later, connectOnce's `attempt.ok` branch promotes it,\n // and the client flips back to \"connected\" on top of a `fatal` event\n // it already emitted. Fail it so its handshake() rejects and the\n // abandoned socket actually closes instead of leaking -- with\n // NO_ERROR/\"client closing\" (same as connectOnce's own `attempt.ok`\n // guard), not `err` itself: when `err` is UNAUTHORIZED (4011), that\n // code is server-side only (WIRE.md), and failing the dial with it\n // would both misreport the dial's own failure and, if the dial is a\n // token-provider retry still inside connectOnce's unauthorized-retry\n // loop, race that loop into redialing with a fresh token after this\n // client has already gone fatal.\n if (this.dialingConn && this.dialingConn !== conn) {\n const dialing = this.dialingConn;\n this.dialingConn = null;\n dialing.fail(new WsMixerError(ErrorCode.NO_ERROR, \"client closing\"));\n }\n this.emit(\"fatal\", err);\n this.rejectStartIfNeverConnected(err);\n return;\n }\n\n if (this.fatal) {\n // Already reported this disconnect fatally -- the wsCode branch\n // above on this very call. This conn's close finally catching up is\n // not a second disconnect. (A *superseded* conn's close never\n // reaches this far at all: it's caught by the `!wasActive` return\n // above, since `this.conn` was already reassigned to its replacement\n // before old.fail() ran.)\n return;\n }\n if (this.closing || this.state === \"closed\") {\n // Already terminating for an unrelated reason (client-initiated\n // close()) -- still one report, just no scheduling decision to make.\n // Checked before drainedNoReconnect below: an app-initiated close()\n // during a drain deadline is a normal graceful close, not a fatal\n // \"drained; reconnect disabled\" report (close() also clears\n // drainedNoReconnect itself, but this ordering is what actually\n // matters if that ever changes).\n this.notifyDisconnect(this.buildPayload(ctx, false));\n return;\n }\n\n if (this.drainedNoReconnect) {\n // The drain handler above saw maxAttempts:0 and let this conn run to\n // its own deadline instead of reconnecting; this is that deadline's\n // close (4012 per WIRE.md section 2.9) arriving now. One report,\n // fatal, with a message that says why -- not a going-away reconnect.\n this.drainedNoReconnect = false;\n this.state = \"closed\";\n this.fatal = true;\n this.closing = true;\n this.clearReconnectTimer();\n this.notifyDisconnect(this.buildPayload(ctx, true, \"drained; reconnect disabled\"));\n const err = new WsMixerError(ctx.errorCode ?? ErrorCode.GOING_AWAY, \"drained; reconnect disabled\", { fatal: true });\n // Same defensive cleanup as the `fatal` branch above, and unreachable\n // for the same reason it's still kept: maxAttempts:0 means the drain\n // handler never itself starts a parallel dial, and no dial runs while\n // a conn is already active, so `dialingConn` should never be set\n // here. Kept only as a harmless belt-and-braces guard, using the same\n // NO_ERROR/\"client closing\" fail as the `fatal` branch above rather\n // than `err` itself, which may carry a server-side-only code.\n if (this.dialingConn && this.dialingConn !== conn) {\n const dialing = this.dialingConn;\n this.dialingConn = null;\n dialing.fail(new WsMixerError(ErrorCode.NO_ERROR, \"client closing\"));\n }\n this.emit(\"fatal\", err);\n this.rejectStartIfNeverConnected(err);\n return;\n }\n if (this.drainReconnectScheduled) {\n // Already reconnecting in parallel from the `drain` handler above.\n this.notifyDisconnect(this.buildPayload(ctx, false));\n this.drainReconnectScheduled = false;\n return;\n }\n if (info.wsCode === GOING_AWAY_WS_CODE || info.wsCode === ABNORMAL_CLOSURE_WS_CODE) {\n this.reconnectImmediately(ctx, \"going_away\");\n return;\n }\n if (info.wsCode === KEEPALIVE_TIMEOUT_WS_CODE) {\n this.scheduleReconnectAfterKeepaliveTimeout(ctx, \"keepalive_timeout\");\n return;\n }\n if (info.wsCode === ENHANCE_YOUR_CALM_WS_CODE) {\n this.scheduleReconnectAtCap(ctx, \"enhance_your_calm\");\n return;\n }\n if (info.wsCode === APPLICATION_CLOSE_WS_CODE) {\n // WIRE.md section 2.9: a connected-phase 4014 is, by nature, sent\n // *after* welcome -- same \"explicit refuse-on-purpose, back off\n // hard\" treatment as 4009 above, for the same reason (see the\n // const's own comment). Covers both the error{14}+close and\n // bare-4014 variants; a *handshake-phase* 4014 never reaches this\n // handler at all (it's a connectOnce catch/goFatal-or-\n // scheduleReconnect concern, where `attempt` does climb normally).\n this.scheduleReconnectAtCap(ctx, \"application_close\");\n return;\n }\n this.scheduleReconnect(ctx, info.message);\n });\n }\n\n private notifyDisconnect(payload: DisconnectPayload): void {\n this.opts.onDisconnect?.(payload);\n this.emit(\"close\", payload);\n }\n\n /** Builds the final `DisconnectPayload` for a `DisconnectContext`, filling in `fatal` and, when present, the exhaustion message override. */\n private buildPayload(ctx: DisconnectContext, fatal: boolean, messageOverride?: string): DisconnectPayload {\n const base = {\n phase: ctx.phase,\n wsCode: ctx.wsCode,\n errorCode: ctx.errorCode,\n errorName: ctx.errorName,\n httpStatus: ctx.httpStatus,\n cause: ctx.cause,\n message: messageOverride ?? ctx.message,\n closeReason: ctx.closeReason,\n fatal,\n };\n if (ctx.protocolError) {\n return { ...base, protocolError: true, code: ctx.code!, name: ctx.name! };\n }\n return { ...base, protocolError: false };\n }\n\n private rejectStartIfNeverConnected(err: Error): void {\n if (!this.everConnected && this.startReject) {\n const reject = this.startReject;\n this.startResolve = null;\n this.startReject = null;\n reject(err);\n }\n }\n\n /** Fatal per WIRE.md section 2.9: never reconnect, surface loudly, and fail start() if it never got a first connection. One report only. */\n private goFatal(ctx: DisconnectContext): void {\n if (this.state === \"closed\") return;\n this.state = \"closed\";\n this.fatal = true;\n this.closing = true;\n this.clearReconnectTimer();\n this.clearStabilityTimer();\n // Defensive: no current caller reaches goFatal() with a live `this.conn`\n // (dial/handshake failures only ever have a `dialingConn`), but if one\n // ever does, detach it before failing it so its own 'close' handler sees\n // `wasActive === false` and doesn't produce a second report -- this call\n // is still the one report for this fatal disconnect.\n const conn = this.conn;\n this.conn = null;\n const err = new WsMixerError(ctx.errorCode ?? ErrorCode.INTERNAL_ERROR, ctx.message, { fatal: true });\n if (conn) conn.fail(err);\n this.emit(\"fatal\", err);\n this.notifyDisconnect(this.buildPayload(ctx, true));\n this.rejectStartIfNeverConnected(err);\n }\n\n /**\n * maxAttempts exhausted: stop, but connect() must reject if it never\n * succeeded once. Reports exactly one `DisconnectReason` -- the merged\n * exhaustion report, `fatal: true` with the exhaustion `message`, but\n * carrying the underlying failure's `wsCode`/`errorCode`/`httpStatus`/\n * `cause` from `ctx` -- never a second report on top of the one the\n * failure itself would otherwise have gotten (blocker 1).\n *\n * Mirrors goFatal: clears the stability timer too (a live connection can\n * still have one ticking, e.g. a drain hand-over's parallel dial failing\n * while the old connection it was replacing is still up), and detaches\n * every live/in-flight conn -- `conn`, `dialingConn`, `retiringConn`,\n * which a drain hand-over can leave all pointing at the very same live\n * connection, hence the reference-dedup below -- before failing it, so\n * each one's own 'close' handler sees `this.conn`/`this.retiringConn`/\n * `this.dialingConn` already cleared and produces no report of its own;\n * this call is still the one report. Without this, an exhausted client\n * left a still-live socket (and its ping/watchdog timers, and the\n * now-orphaned stability timer) running past \"closed\".\n */\n private giveUp(ctx: DisconnectContext, message: string): void {\n if (this.state === \"closed\") return;\n this.state = \"closed\";\n this.closing = true;\n this.clearReconnectTimer();\n this.clearStabilityTimer();\n const conn = this.conn;\n const dialing = this.dialingConn;\n const retiring = this.retiringConn;\n this.conn = null;\n this.dialingConn = null;\n this.retiringConn = null;\n const err = new WsMixerError(ctx.errorCode ?? ErrorCode.INTERNAL_ERROR, message, { fatal: true });\n if (conn) conn.fail(err);\n if (dialing && dialing !== conn) dialing.fail(err);\n if (retiring && retiring !== conn && retiring !== dialing) retiring.fail(err);\n this.notifyDisconnect(this.buildPayload(ctx, true, message));\n this.rejectStartIfNeverConnected(new Error(message));\n }\n\n /**\n * The single point where a recoverable disconnect either gets its one\n * report and a scheduled retry, or -- if `maxAttempts` is already\n * exhausted -- gets folded into `giveUp`'s one merged exhaustion report\n * instead. Exhaustion is checked *before* any report goes out, which is\n * what makes \"exactly one `DisconnectReason` per disconnect\" (blocker 1)\n * hold: the two outcomes are mutually exclusive, never both.\n */\n private reportAndSchedule(ctx: DisconnectContext, cause: string, computeDelay: () => number): void {\n if (this.closing || this.fatal || this.state === \"closed\") return;\n if (this.attempt >= this.reconnectOpts.maxAttempts) {\n this.giveUp(ctx, `max reconnect attempts (${this.reconnectOpts.maxAttempts}) exhausted: ${cause}`);\n return;\n }\n this.notifyDisconnect(this.buildPayload(ctx, false));\n this.attempt++;\n const delay = computeDelay();\n void this.connectOnce(delay, cause);\n }\n\n /**\n * Close 4012 / close 1001 with no preceding `drain`: reconnect\n * immediately, jitter random(0, 2000)ms only -- but still counts toward\n * `attempt`/`maxAttempts`, so a 4012 flap loop (a server stuck\n * accepting-then-immediately-draining) still respects the ceiling instead\n * of retrying forever. (The `drain`-triggered parallel reconnect is a\n * separate, direct connectOnce() call in the `drain` handler above and\n * does not bump `attempt` -- it is the server telling us to move, not a\n * failure being retried.)\n */\n private reconnectImmediately(ctx: DisconnectContext, cause: string): void {\n this.reportAndSchedule(ctx, cause, () => Math.random() * 2000);\n }\n\n /**\n * Close 4013 KEEPALIVE_TIMEOUT: one immediate attempt, then normal backoff\n * (WIRE.md section 2.9). The immediate attempt goes through\n * reportAndSchedule -- same as the 4012 path (reconnectImmediately) -- so\n * it respects the maxAttempts ceiling too: a 4013 flap loop with no budget\n * left gives up (one merged `fatal: true` report) instead of dialing a\n * socket reportAndSchedule would otherwise have refused to allow.\n *\n * `keepaliveImmediateRetryUsed` is a once-only budget re-armed ONLY at\n * stability (armStabilityTimer) -- deliberately never un-set here on the\n * second (or any later) 4013: doing that would let the budget \"spend then\n * immediately un-spend itself\" on the very next 4013, so four 4013s in a\n * row with no intervening stable connection would go immediate, backoff,\n * immediate, backoff... forever, instead of immediate once and backoff\n * every time after that until the client actually goes stable.\n */\n private scheduleReconnectAfterKeepaliveTimeout(ctx: DisconnectContext, cause: string): void {\n if (!this.keepaliveImmediateRetryUsed) {\n this.keepaliveImmediateRetryUsed = true;\n this.reportAndSchedule(ctx, cause, () => 0);\n return;\n }\n this.scheduleReconnect(ctx, cause);\n }\n\n /** Close 4009 ENHANCE_YOUR_CALM: start backoff at the cap, not at base. */\n private scheduleReconnectAtCap(ctx: DisconnectContext, cause: string): void {\n this.reportAndSchedule(ctx, cause, () => Math.random() * this.reconnectOpts.cap);\n }\n\n /** Normal AWS full-jitter backoff: everything not covered by a more specific rule above. */\n private scheduleReconnect(ctx: DisconnectContext, cause: string): void {\n this.reportAndSchedule(ctx, cause, () => fullJitterDelay(this.attempt, this.reconnectOpts.base, this.reconnectOpts.cap));\n }\n}\n\n/** AWS \"full jitter\": random(0, min(cap, base * 2^attempt)). */\nexport function fullJitterDelay(attempt: number, base: number, cap: number): number {\n const exp = Math.min(cap, base * 2 ** attempt);\n return Math.random() * exp;\n}\n\ninterface DialError extends Error {\n fatal: boolean;\n /**\n * Which ws-mixer error code a fatal dial failure maps to (WIRE.md\n * section 2.9). Only ever set for a missing/mismatched subprotocol echo\n * (UNSUPPORTED) -- an SDK-raised, local ws-mixer error, no close frame or\n * HTTP status involved. `undefined` for everything else: a plain network\n * failure (no wire error occurred), an HTTP upgrade rejection of any kind\n * including 401/403/404 (never a ws-mixer wire code -- D-2026-09-20-09),\n * and a token-provider throw/reject (an application error, not a wire\n * error) -- none of those get `errorCode`/`errorName` on the disconnect\n * reason, only `httpStatus`/`cause` as applicable (nit 5).\n */\n errorCode?: number;\n /** Present only for HTTP 429 with a parseable Retry-After header. */\n retryAfterMs?: number;\n /** The HTTP status of the upgrade response, when the dial failed at the HTTP layer (401/403/404/429). */\n httpStatus?: number;\n /** The token provider's thrown/rejected error, when the dial failed there instead of at the socket. */\n cause?: unknown;\n}\n\nfunction dialError(\n message: string,\n fatal: boolean,\n opts?: { retryAfterMs?: number; errorCode?: number; httpStatus?: number; cause?: unknown },\n): DialError {\n const err = new Error(message) as DialError;\n err.fatal = fatal;\n if (opts?.errorCode !== undefined) err.errorCode = opts.errorCode;\n if (opts?.retryAfterMs !== undefined) err.retryAfterMs = opts.retryAfterMs;\n if (opts?.httpStatus !== undefined) err.httpStatus = opts.httpStatus;\n if (opts?.cause !== undefined) err.cause = opts.cause;\n return err;\n}\n\n/**\n * Wraps a token provider's thrown/rejected error as a dial failure\n * (CLIENT-SDK.md's \"Provider failure\" row: \"the thrown/rejected error is surfaced\n * verbatim\"), fatal by default -- the caller (dialAndHandshakeOnce) is what\n * actually decides `fatal` via `isTokenUnavailable`, this just builds the\n * `message`/`cause` pair either outcome shares. Deliberately carries no\n * `errorCode`: this is an application-level failure, not a ws-mixer wire\n * error (nit 5) -- `cause` is how the caller gets at it.\n */\nfunction providerDialError(err: unknown): DialError {\n let message: string;\n try {\n message = err instanceof Error ? err.message : String(err);\n } catch {\n // A throwing `message` getter must not escape as an unhandled rejection\n // -- fall back to a fixed string so this still yields one fatal report.\n message = \"token provider failed\";\n }\n return dialError(message, /* overridden by dialAndHandshakeOnce's isTokenUnavailable check */ true, { cause: err });\n}\n\n/**\n * Bounded, cycle-safe walk of `err` and its `cause` chain looking for a\n * `TokenUnavailableError`. Detection is `instanceof` ONLY -- never a\n * duck-typed property or method check -- so an unrelated library's error\n * that happens to share some shape can never accidentally turn a genuinely\n * fatal provider failure into an endless retry loop. Providers are free to\n * wrap the real cause (`new Error(\"refresh failed\", { cause: new\n * TokenUnavailableError(...) })`); the depth cap and `seen` set both guard\n * against a pathological or cyclic `cause` chain hanging this walk.\n */\nconst MAX_CAUSE_CHAIN_DEPTH = 10;\nfunction isTokenUnavailable(err: unknown): boolean {\n const seen = new Set<unknown>();\n let cur: unknown = err;\n for (let depth = 0; depth < MAX_CAUSE_CHAIN_DEPTH && cur != null && !seen.has(cur); depth++) {\n if (cur instanceof TokenUnavailableError) return true;\n seen.add(cur);\n try {\n cur = cur instanceof Error ? cur.cause : undefined;\n } catch {\n // A throwing `cause` getter must not escape this walk -- treat it as\n // unmarked (fatal) rather than letting the throw wedge the client.\n return false;\n }\n }\n return false;\n}\n\n/** Parses a `Retry-After` header: either delta-seconds or an HTTP-date (RFC 9110 section 10.2.3). */\nfunction parseRetryAfter(value: string | string[] | undefined): number | undefined {\n const v = Array.isArray(value) ? value[0] : value;\n if (v === undefined) return undefined;\n const seconds = Number(v);\n if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000);\n const dateMs = Date.parse(v);\n if (!Number.isNaN(dateMs)) return Math.max(0, dateMs - Date.now());\n return undefined;\n}\n\nfunction defaultWsFactory(url: string, protocols: string[], options: WSFactoryOptions): DialSocket {\n return new WebSocket(url, protocols, options) as unknown as DialSocket;\n}\n\n/**\n * Opens the WebSocket per WIRE.md section 2.1: binary, no compression,\n * `ws-mixer.v1` subprotocol offered and required to be echoed, Bearer auth\n * header. Fatal (never retried by this function itself) on a missing/\n * mismatched subprotocol echo or an HTTP 401/403/404 handshake response --\n * connectOnce's retry loop is what decides whether a 401 gets the one\n * token-provider refresh-retry before treating that fatal verdict as final;\n * HTTP 429 carries `Retry-After` (if present) back to the caller instead of\n * our own jittered delay.\n */\nfunction dialWebSocket(url: string, token: string, opts: ConnectOptions, connectTimeoutMs: number): Promise<DialSocket> {\n return new Promise((resolve, reject) => {\n const factory = opts._wsFactory ?? defaultWsFactory;\n const ws = factory(url, [SUBPROTOCOL], {\n perMessageDeflate: false,\n maxPayload: MAX_MESSAGE_SIZE,\n headers: {\n Authorization: `Bearer ${token}`,\n \"User-Agent\": `ws-mixer-js/${SDK_VERSION} (node/${process.version.replace(/^v/, \"\")} ${process.platform}/${process.arch})`,\n ...opts.headers,\n },\n });\n\n const timer = setTimeout(() => {\n ws.terminate?.();\n reject(dialError(`connect timed out after ${connectTimeoutMs}ms`, false));\n }, connectTimeoutMs);\n\n let settled = false;\n ws.once(\"unexpected-response\", (req, res) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n try {\n // `ws` skips its own abortHandshake cleanup whenever an\n // 'unexpected-response' listener is attached (ours is, right here) --\n // it assumes we'll do it ourselves. Drain the response so its\n // underlying socket can be reused/closed cleanly, then destroy the\n // request and the `ws` wrapper so nothing lingers (item 2). `req`/\n // `res` are optional-chained throughout: some transports/mocks omit\n // one or both, and a defensive cleanup here must never itself throw.\n res?.resume?.();\n req?.destroy?.();\n res?.destroy?.();\n ws.terminate?.();\n const status = res?.statusCode;\n if (status === 429) {\n reject(\n dialError(`HTTP 429 (rate limited) during handshake`, false, {\n retryAfterMs: parseRetryAfter(res?.headers?.[\"retry-after\"]),\n httpStatus: 429,\n }),\n );\n return;\n }\n if (status === 401 || status === 403) {\n // Auth/authorization failure (WIRE.md section 2.9 groups it\n // with UNAUTHORIZED, 4011, for retry/fatal purposes only). Fatal by\n // default -- connectOnce's retry loop is the one place that knows\n // whether a token provider is present and this connect attempt\n // hasn't already used its one refresh-retry, and overrides this by\n // retrying instead of treating it as final (401 only; 403 is fatal\n // always). No `errorCode`: an HTTP upgrade rejection is never a\n // ws-mixer wire error, so it's never entitled to one (D-2026-09-20-09)\n // -- httpStatus alone.\n reject(\n dialError(`unexpected HTTP response during handshake: ${status}`, true, {\n httpStatus: status,\n }),\n );\n return;\n }\n if (status === 404) {\n // Not found is fatal (retrying the same URL can't fix it). Like\n // every HTTP upgrade rejection -- 401/403 included -- it's never a\n // ws-mixer wire error (D-2026-09-20-09): no errorCode, only\n // httpStatus.\n reject(dialError(`unexpected HTTP response during handshake: ${status}`, true, { httpStatus: status }));\n return;\n }\n // Any other status (5xx, or none at all) looks transient rather than\n // a hard rejection: retry with normal backoff instead of giving up.\n // No errorCode -- this never came from a ws-mixer wire error.\n reject(dialError(`unexpected HTTP response during handshake: ${status}`, false, { httpStatus: status }));\n } catch (e) {\n // Never let a throw escape out of `ws`'s emit (item 3): always\n // settle the dial with something, even when we don't know what the\n // response actually was.\n reject(dialError(e instanceof Error ? e.message : String(e), false));\n }\n });\n ws.once(\"error\", (err) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n reject(dialError(err.message, false));\n });\n ws.once(\"open\", () => {\n if (settled) return;\n if (ws.protocol !== SUBPROTOCOL) {\n settled = true;\n clearTimeout(timer);\n ws.close(1002);\n // Missing/mismatched subprotocol echo is UNSUPPORTED (4010), not\n // UNAUTHORIZED (4011) -- WIRE.md section 2.9 lists it alongside\n // 4010 in the \"fatal, never reconnect\" row, distinct from the\n // 401/403/404 auth-failure row above.\n reject(\n dialError(`server did not echo the ${SUBPROTOCOL} subprotocol; failing fatally, do not retry`, true, {\n errorCode: ErrorCode.UNSUPPORTED,\n }),\n );\n return;\n }\n settled = true;\n clearTimeout(timer);\n resolve(ws);\n });\n });\n}\n\n/** Connects to a ws-mixer.v1 server and starts the reconnect-managed client. Resolves once the first `welcome` lands. */\nexport async function connect(url: string, opts: ConnectOptions): Promise<MixerClient> {\n const client = new MixerClient(url, opts);\n await client.start();\n return client;\n}\n","/**\n * ws-mixer.v1 error code table (WIRE.md section 2.8). Shared by stream\n * RESET frames and the connection-level `error` control message.\n *\n * 0x0000_0000-0x0000_0fff is reserved for ws-mixer; codes >= 0x1000_0000 are\n * free for the layer above and are never produced by this package. Unknown\n * codes received on the wire are treated as INTERNAL_ERROR (see codeName).\n */\nexport const ErrorCode = {\n NO_ERROR: 0x00,\n PROTOCOL_ERROR: 0x01,\n INTERNAL_ERROR: 0x02,\n FLOW_CONTROL_ERROR: 0x03,\n FRAME_SIZE_ERROR: 0x04,\n STREAM_CLOSED: 0x05,\n REFUSED_STREAM: 0x06,\n CANCEL: 0x07,\n STREAM_LIMIT: 0x08,\n ENHANCE_YOUR_CALM: 0x09,\n UNSUPPORTED: 0x0a,\n UNAUTHORIZED: 0x0b,\n GOING_AWAY: 0x0c,\n KEEPALIVE_TIMEOUT: 0x0d,\n // Connection-level only, never emitted by ws-mixer itself: exists for the\n // application above to close a connection for its own reason, carried in\n // error.message / the WS close reason (WS close 4014).\n APPLICATION_CLOSE: 0x0e,\n} as const;\n\nexport type ErrorCodeValue = (typeof ErrorCode)[keyof typeof ErrorCode];\n\nconst codeNames: Record<number, string> = {\n [ErrorCode.NO_ERROR]: \"NO_ERROR\",\n [ErrorCode.PROTOCOL_ERROR]: \"PROTOCOL_ERROR\",\n [ErrorCode.INTERNAL_ERROR]: \"INTERNAL_ERROR\",\n [ErrorCode.FLOW_CONTROL_ERROR]: \"FLOW_CONTROL_ERROR\",\n [ErrorCode.FRAME_SIZE_ERROR]: \"FRAME_SIZE_ERROR\",\n [ErrorCode.STREAM_CLOSED]: \"STREAM_CLOSED\",\n [ErrorCode.REFUSED_STREAM]: \"REFUSED_STREAM\",\n [ErrorCode.CANCEL]: \"CANCEL\",\n [ErrorCode.STREAM_LIMIT]: \"STREAM_LIMIT\",\n [ErrorCode.ENHANCE_YOUR_CALM]: \"ENHANCE_YOUR_CALM\",\n [ErrorCode.UNSUPPORTED]: \"UNSUPPORTED\",\n [ErrorCode.UNAUTHORIZED]: \"UNAUTHORIZED\",\n [ErrorCode.GOING_AWAY]: \"GOING_AWAY\",\n [ErrorCode.KEEPALIVE_TIMEOUT]: \"KEEPALIVE_TIMEOUT\",\n [ErrorCode.APPLICATION_CLOSE]: \"APPLICATION_CLOSE\",\n};\n\nconst namesToCode: Record<string, number> = Object.fromEntries(\n Object.entries(codeNames).map(([code, name]) => [name, Number(code)]),\n);\n\n/** Renders an error code's wire name, e.g. \"FLOW_CONTROL_ERROR\". Unrecognized codes render \"INTERNAL_ERROR\". */\nexport function codeName(code: number): string {\n return codeNames[code] ?? \"INTERNAL_ERROR\";\n}\n\n/** Looks up an error code by its wire name (e.g. \"STREAM_LIMIT\"). Returns undefined if unknown. */\nexport function parseErrorCode(name: string): number | undefined {\n return namesToCode[name];\n}\n\n/** ws_close = 4000 + error_code, with NO_ERROR mapping to 1000 (WIRE.md section 2.8). */\nexport function closeCode(code: number): number {\n return code === ErrorCode.NO_ERROR ? 1000 : 4000 + code;\n}\n\n/**\n * WsMixerError is the single error type this package throws or emits.\n * `fatal` marks a close code the client SDK must never reconnect after\n * (UNSUPPORTED always; UNAUTHORIZED once it's actually final -- after\n * `welcome`, or before `welcome` once the one provider refresh-retry has\n * already been spent (or immediately, with a static token that has nothing\n * to refresh) -- see client.ts's connectOnce/dialAndHandshakeOnce; and the\n * client's own auth/handshake failures).\n */\nexport class WsMixerError extends Error {\n readonly code: number;\n override name: string = \"WsMixerError\";\n readonly fatal: boolean;\n readonly streamId?: number;\n /**\n * The peer's observed WS close-frame code, set only when this error was\n * built from an actually-observed close frame (e.g. MixerConn.onSocketClose)\n * rather than a locally-raised protocol violation.\n */\n readonly wsCode?: number;\n /** The peer's observed WS close-frame reason, verbatim, under the same condition as `wsCode`. */\n readonly closeReason?: string;\n\n constructor(\n code: number,\n message: string,\n opts?: { fatal?: boolean; streamId?: number; wsCode?: number; closeReason?: string },\n ) {\n super(message);\n this.code = code;\n this.fatal = opts?.fatal ?? false;\n this.streamId = opts?.streamId;\n this.wsCode = opts?.wsCode;\n this.closeReason = opts?.closeReason;\n }\n\n get codeName(): string {\n return codeName(this.code);\n }\n\n toString(): string {\n return `WsMixerError[${this.codeName}]: ${this.message}`;\n }\n}\n\n/** A connection-fatal error: desynchronizes shared connection state. Always error{code} + WS close. */\nexport class ConnError extends WsMixerError {\n readonly lastStreamId?: number;\n constructor(code: number, message: string, opts?: { streamId?: number; lastStreamId?: number; fatal?: boolean }) {\n super(code, message, opts);\n this.name = \"ConnError\";\n this.lastStreamId = opts?.lastStreamId;\n }\n}\n\n/** A stream-scoped error: produces RESET(code, message) on one stream; the connection stays up. */\nexport class StreamError extends WsMixerError {\n constructor(code: number, streamId: number, message: string) {\n super(code, message, { streamId });\n this.name = \"StreamError\";\n }\n}\n\n/**\n * A token provider's explicit signal that it could not OBTAIN a token for a\n * temporary reason (a laptop waking before the network is back, the auth\n * server briefly unreachable while refreshing an expired token) -- as\n * opposed to a genuinely fatal provider failure (a malformed credential, a\n * permanently revoked client). Throwing/rejecting with an instance of this\n * (directly, or wrapped via `cause`) is how a provider opts a dial failure\n * into client.ts's ordinary non-fatal dial-retry path instead of the\n * fatal-by-default one every other provider throw/reject still gets --\n * detection is `instanceof` only, deliberately not duck-typed on any\n * property or method, so an accidental match on some unrelated library's\n * error can never turn a genuinely fatal provider failure into an endless\n * retry loop.\n */\nexport class TokenUnavailableError extends Error {\n override name = \"TokenUnavailableError\";\n constructor(message?: string, options?: { cause?: unknown }) {\n super(message, options);\n }\n}\n","/**\n * MixerConn: the per-connection ws-mixer.v1 protocol engine. Owns the\n * handshake, the read-dispatch loop, the control-priority + round-robin\n * write scheduler (WIRE.md section 2.6 rule 3), keepalive and drain.\n * The JS SDK is always the answering peer (client): it never calls\n * OpenStream, only receives OPEN from the server.\n *\n * Mirrors `go/wsmixer/conn.go`, `dispatch.go`, `sched.go`, `keepalive.go` and\n * `drain.go`, adapted to Node's single-threaded event loop (no goroutines:\n * one write scheduler driven by a wake/notify queue instead of channels).\n */\nimport { EventEmitter } from \"node:events\";\nimport {\n type AgentInfo,\n type ControlMessage,\n type DrainMsg,\n type WelcomeMsg,\n encodeControl,\n knownDrainReason,\n parseControl,\n} from \"./control.js\";\nimport { ConnError, ErrorCode, StreamError, WsMixerError, closeCode } from \"./errors.js\";\nimport {\n FrameType,\n decodeFrame,\n encodeData,\n encodeReset,\n frameTypeName,\n resetCode,\n resetMessage,\n windowIncrement,\n} from \"./frame.js\";\nimport { MixerStream, type StreamHost } from \"./stream.js\";\nimport { truncateUtf8 } from \"./util.js\";\n\n/** Structural subset of `ws`'s WebSocket that MixerConn depends on (keeps the transport swappable). */\nexport interface WSLike {\n readonly readyState: number;\n send(data: Uint8Array, cb?: (err?: Error) => void): void;\n close(code?: number, reason?: string): void;\n terminate?(): void;\n on(event: \"message\", listener: (data: Uint8Array | Buffer, isBinary: boolean) => void): void;\n on(event: \"close\", listener: (code: number, reason: Buffer) => void): void;\n on(event: \"error\", listener: (err: Error) => void): void;\n}\n\nexport interface ConnOptions {\n token: string;\n agent: AgentInfo;\n meta?: unknown;\n window?: number;\n maxStreams?: number;\n capabilities?: string[];\n /** Timeout waiting for `welcome` after sending `hello`. Default 10000ms per WIRE.md section 2.9. */\n helloTimeoutMs?: number;\n /**\n * @internal Test-only: lets a test's `welcome` use keepalive values below\n * the wire floors (WIRE.md section 2.7: `ping_interval` floor 5000ms,\n * `ping_timeout` >= 2x `ping_interval`) so keepalive/sequence tests can run\n * with real timers in well under a second. Never set this in production.\n */\n _timing?: {\n minPingInterval?: number;\n minPingTimeout?: number;\n helloTimeout?: number;\n };\n}\n\nconst DEFAULT_WINDOW = 262144;\nconst DEFAULT_MAX_STREAMS = 64;\nconst DEFAULT_HELLO_TIMEOUT_MS = 10000;\nconst DEFAULT_PING_INTERVAL_FLOOR_MS = 5000;\n/** Stream-0 flood limit token bucket defaults, mirroring go/wsmixer's Options.Stream0RateLimit/Stream0Burst. */\nconst STREAM0_RATE_PER_SEC = 50;\nconst STREAM0_BURST = 100;\n/** Delivery queue capacity floor, mirroring go/wsmixer's deliveryQueueSize. */\nconst DELIVERY_QUEUE_MIN = 128;\n/** Added on top of maxStreams when that alone would exceed DELIVERY_QUEUE_MIN, mirroring deliveryQueueMargin. */\nconst DELIVERY_QUEUE_MARGIN = 64;\n\n/**\n * Clamps a would-be WS close code to one actually legal to send on the wire\n * -- 1000, or 4000-4999, pass through unchanged; anything else (an\n * application-layer error code >= 0x1000_0000 is a legal RESET code,\n * errors.ts, but was never a legal *connection*-close code) is clamped to\n * INTERNAL_ERROR's mapped code (4002) instead, mirroring go/wsmixer's\n * wsCloseCode (conn.go). `ws`'s Sender.close() throws a RangeError for an\n * out-of-range code rather than sending nothing, which this file's own\n * catch would otherwise turn into `ws.terminate()` -- a bare 1006 abnormal\n * closure that tells the peer nothing at all. `error{}` (sent separately,\n * unclamped) still carries the real code either way, and the *reported*\n * `wsCode` (the 'close' event below, DisconnectReason) stays the real\n * 4000+code too -- only the bytes actually put on the wire are clamped.\n */\nfunction wireCloseCode(wsCode: number): number {\n return wsCode === 1000 || (wsCode >= 4000 && wsCode <= 4999) ? wsCode : 4000 + ErrorCode.INTERNAL_ERROR;\n}\n\ninterface OutboxItem {\n chunk: Uint8Array;\n resolve: () => void;\n reject: (err: Error) => void;\n}\n\n/** One queued control-channel (stream-0) frame, paired with the promise that resolves once it's actually written. */\ninterface ControlItem {\n frame: Uint8Array;\n resolve: () => void;\n reject: (err: Error) => void;\n}\n\n/** One OnStream/OnApp/OnDrain invocation queued for the delivery loop, in wire order. Mirrors go/wsmixer's deliveryEvent. */\ntype DeliveryEvent =\n | { kind: \"stream\"; stream: MixerStream }\n | { kind: \"app\"; body: Record<string, unknown> }\n | { kind: \"drain\"; msg: DrainMsg };\n\n/** A small token bucket rate limiter (no locking needed: Node's single-threaded), mirroring go/wsmixer's tokenBucket (conn.go). */\nclass TokenBucket {\n private tokens: number;\n private last = Date.now();\n constructor(\n private readonly capacity: number,\n private readonly ratePerSecond: number,\n ) {\n this.tokens = capacity;\n }\n\n /** Reports whether one token is available and, if so, consumes it. */\n allow(): boolean {\n const now = Date.now();\n this.tokens = Math.min(this.capacity, this.tokens + ((now - this.last) / 1000) * this.ratePerSecond);\n this.last = now;\n if (this.tokens < 1) return false;\n this.tokens -= 1;\n return true;\n }\n}\n\n/**\n * Fires (and clears) once for every welcome/close event; used for the\n * `welcome`-then-timeout handshake race and to unblock `close()`'s grace wait.\n */\nexport declare interface MixerConn {\n on(event: \"welcome\", listener: (welcome: WelcomeMsg) => void): this;\n on(event: \"stream\", listener: (stream: MixerStream) => void): this;\n on(event: \"app\", listener: (body: Record<string, unknown>) => void): this;\n on(event: \"drain\", listener: (msg: DrainMsg) => void): this;\n on(event: \"pong\", listener: (info: { id: number; rttMs: number }) => void): this;\n on(event: \"error\", listener: (err: WsMixerError) => void): this;\n on(event: \"fatal\", listener: (err: WsMixerError) => void): this;\n on(\n event: \"close\",\n listener: (info: { wsCode: number; errorCode?: number; message: string; closeReason?: string }) => void,\n ): this;\n /** Fires when a `'stream'`/`'app'`/`'drain'` listener throws or rejects; delivery continues with the next queued event regardless (see stats().handlerErrors). */\n on(event: \"handlerError\", listener: (info: { event: string; error: Error }) => void): this;\n}\n\n/** One live, handshaken (or handshaking) ws-mixer connection. */\nexport class MixerConn extends EventEmitter {\n private readonly ws: WSLike;\n private readonly opts: ConnOptions;\n\n private closed = false;\n private handshakeDone = false;\n session = \"\";\n peerWindow = DEFAULT_WINDOW;\n ourWindow: number;\n maxStreams = DEFAULT_MAX_STREAMS;\n pingIntervalMs = 30000;\n pingTimeoutMs = 90000;\n welcomeMeta: unknown;\n private draining = false;\n /** The `last_stream_id` from the most recently received `drain`; set only once draining. */\n private lastStreamId?: number;\n\n private readonly streams = new Map<number, MixerStream>();\n private highestOpened = 0;\n\n /**\n * Minimal \"ignore and count\" counters (item 11 of the review: full\n * escalation/STREAM_LIMIT bucketing is intentionally out of scope for v1 --\n * see README.md and docs/DESIGN.md). Exposed via `stats()`.\n */\n private readonly counters = {\n unknownFrameTypes: 0,\n staleFrames: 0,\n duplicatePongs: 0,\n refusedOpens: 0,\n /**\n * OPENs received above the drain's `last_stream_id` (WIRE.md section\n * 2.9 / the 2026-08-27 decision log): connection-fatal `PROTOCOL_ERROR`,\n * not a stream-scoped refusal -- the server already promised not to send\n * one, so this is it breaking that promise, not a benign race.\n */\n drainViolations: 0,\n /** `drain.reason` values outside the closed enum, normalized to \"maintenance\" (WIRE.md section 2.7). */\n unknownDrainReasons: 0,\n /** A `'stream'`/`'app'`/`'drain'` listener that threw or rejected; delivery continued with the next event regardless. */\n handlerErrors: 0,\n /** ConnError-triggered connection failures: malformed/out-of-sequence control traffic (CLIENT-SDK.md's \"Stats / counters\" row). */\n protocolViolations: 0,\n /**\n * Raw WS message bytes received/sent, cumulative (CLIENT-SDK.md's\n * \"Stats / counters\" row): the full ws-mixer frame on the wire, header included, for every\n * message -- not just DATA payload bytes. This differs from Go's\n * `BytesTransferred` metric, which counts payload only; do not compare\n * the two directly.\n */\n bytesIn: 0,\n bytesOut: 0,\n };\n\n /** Snapshot of the counters above. */\n stats(): Readonly<typeof this.counters> {\n return { ...this.counters };\n }\n\n /**\n * The `StreamHost` a `MixerStream` actually talks to: a plain object of\n * bound closures, not `this` -- so `sendData`/`sendControlFrame`/\n * `retireStream` stay private implementation details of MixerConn instead\n * of leaking onto its public (and `.d.ts`) surface (item 8).\n */\n readonly #streamHost: StreamHost = {\n sendData: (streamId, chunk) => this.sendData(streamId, chunk),\n sendControlFrame: (frame) => this.sendControlFrame(frame),\n retireStream: (streamId) => this.retireStream(streamId),\n };\n\n // --- write scheduler state ---\n private readonly controlQueue: ControlItem[] = [];\n private readonly rotation: number[] = [];\n private readonly inRotation = new Set<number>();\n private readonly outbox = new Map<number, OutboxItem[]>();\n private wakeWaiters: Array<() => void> = [];\n private writerRunning = false;\n /** Resolved once the stream table is empty; used by close() instead of polling. */\n private idleWaiters: Array<() => void> = [];\n\n // --- ordered async delivery (item 2): stream/app/drain handlers fire in wire\n // order, off one queue, so a slow handler cannot stall frame parsing. ---\n private readonly deliveryQueue: DeliveryEvent[] = [];\n private deliveryRunning = false;\n\n // --- stream-0 flood limit (item 4), mirrors go/wsmixer's Conn.stream0Bucket ---\n private readonly stream0Bucket = new TokenBucket(STREAM0_BURST, STREAM0_RATE_PER_SEC);\n\n // --- keepalive state ---\n private nextPingId = 0;\n /** Watermark: every id below this has been acked (or pruned as stale) at least once. Mirrors go/wsmixer's Conn.lowestUnacked. */\n private lowestUnacked = 0;\n private lastPongAt = 0;\n private readonly outstandingPings = new Map<number, number>();\n private pingTimer: ReturnType<typeof setInterval> | null = null;\n private watchdogTimer: ReturnType<typeof setInterval> | null = null;\n private handshakeTimer: ReturnType<typeof setTimeout> | null = null;\n private jitterTimer: ReturnType<typeof setTimeout> | null = null;\n /**\n * The most recent pre-welcome `'error'` event's message, recorded so\n * `onSocketClose`'s report -- the sole reporter, see its own doc comment\n * -- can use it as `message` when `ws`'s own close carries no reason of\n * its own.\n */\n private pendingHandshakeErrorMessage: string | undefined;\n\n constructor(ws: WSLike, opts: ConnOptions) {\n super();\n this.ws = ws;\n this.opts = opts;\n this.ourWindow = opts.window ?? DEFAULT_WINDOW;\n this.maxStreams = opts.maxStreams ?? DEFAULT_MAX_STREAMS;\n ws.on(\"message\", (data) => this.onMessage(data));\n ws.on(\"close\", (code, reason) => this.onSocketClose(code, reason));\n ws.on(\"error\", (err) => this.onSocketError(err));\n }\n\n // --- handshake --------------------------------------------------------------\n\n /** Sends `hello` and resolves once `welcome` is validated, or rejects (a WsMixerError, `fatal` set for UNSUPPORTED). */\n handshake(): Promise<WelcomeMsg> {\n return new Promise((resolve, reject) => {\n const hello: ControlMessage = {\n t: \"hello\",\n v: 1,\n token: this.opts.token,\n agent: this.opts.agent,\n window: this.ourWindow,\n max_streams: this.maxStreams,\n ...(this.opts.capabilities ? { capabilities: this.opts.capabilities } : {}),\n ...(this.opts.meta !== undefined ? { meta: this.opts.meta } : {}),\n } as ControlMessage;\n\n const timeoutMs = this.opts.helloTimeoutMs ?? this.opts._timing?.helloTimeout ?? DEFAULT_HELLO_TIMEOUT_MS;\n this.handshakeTimer = setTimeout(() => {\n this.handshakeTimer = null;\n const err = new ConnError(ErrorCode.PROTOCOL_ERROR, `no welcome within ${timeoutMs}ms of hello`);\n this.fail(err);\n reject(err);\n }, timeoutMs);\n\n this.once(\"__welcome_internal\", (welcome: WelcomeMsg) => {\n if (this.handshakeTimer) {\n clearTimeout(this.handshakeTimer);\n this.handshakeTimer = null;\n }\n resolve(welcome);\n });\n this.once(\"__handshake_failed_internal\", (err: WsMixerError) => {\n if (this.handshakeTimer) {\n clearTimeout(this.handshakeTimer);\n this.handshakeTimer = null;\n }\n reject(err);\n });\n\n // Fire-and-forget here: a write failure surfaces via\n // __handshake_failed_internal (onSocketClose/onSocketError/fail all\n // emit it), so this promise's rejection would just be redundant.\n void this.enqueueControl(encodeData(0, encodeControl(hello))).catch(() => {});\n this.startWriter();\n });\n }\n\n private applyWelcome(welcome: WelcomeMsg): void {\n const minInterval = this.opts._timing?.minPingInterval ?? DEFAULT_PING_INTERVAL_FLOOR_MS;\n if (welcome.ping_interval < minInterval) {\n throw new ConnError(ErrorCode.PROTOCOL_ERROR, `welcome.ping_interval ${welcome.ping_interval} is below the ${minInterval}ms floor`);\n }\n const minTimeout = this.opts._timing?.minPingTimeout ?? 2 * welcome.ping_interval;\n if (welcome.ping_timeout < minTimeout) {\n throw new ConnError(\n ErrorCode.PROTOCOL_ERROR,\n `welcome.ping_timeout (${welcome.ping_timeout}) must be at least ${minTimeout}ms`,\n );\n }\n this.session = welcome.session;\n this.peerWindow = welcome.window;\n this.maxStreams = this.opts.maxStreams\n ? Math.min(this.opts.maxStreams, welcome.max_streams)\n : welcome.max_streams;\n this.pingIntervalMs = welcome.ping_interval;\n this.pingTimeoutMs = welcome.ping_timeout;\n this.welcomeMeta = welcome.meta;\n this.handshakeDone = true;\n this.lastPongAt = Date.now();\n this.emit(\"welcome\", welcome);\n this.emit(\"__welcome_internal\", welcome);\n this.startKeepalive();\n }\n\n // --- read dispatch (never blocks on application code) ------------------------\n\n private onMessage(data: Uint8Array | Buffer): void {\n this.counters.bytesIn += data.length;\n try {\n this.dispatchFrame(data instanceof Uint8Array ? data : new Uint8Array(data));\n } catch (e) {\n this.handleDispatchError(e);\n }\n }\n\n private handleDispatchError(e: unknown): void {\n if (e instanceof ConnError) {\n this.counters.protocolViolations++;\n this.fail(e);\n return;\n }\n if (e instanceof StreamError) {\n const stream = this.streams.get(e.streamId!);\n if (stream) {\n // The SDK itself detected this violation (not application code), so\n // abort() -- not the app-facing reset() -- both tears the stream down\n // and emits 'reset', the same way a peer-sent RESET would (mirrors\n // handleReset()): the app didn't choose this, so it needs to hear\n // about it the same way.\n stream.abort(e.code, e.message);\n } else {\n // The stream is already gone locally (e.g. a race with retireStream),\n // but the peer still needs to hear RESET for it -- mirrors Go's\n // resetStream (dispatch.go), which always sends regardless of\n // whether its local Stream struct still exists.\n this.sendControlFrame(encodeReset(e.streamId!, e.code, e.message));\n }\n return;\n }\n this.fail(new ConnError(ErrorCode.INTERNAL_ERROR, e instanceof Error ? e.message : String(e)));\n }\n\n private dispatchFrame(msg: Uint8Array): void {\n const frame = decodeFrame(msg);\n\n const known = Object.values(FrameType).includes(frame.type as never);\n if (!known) {\n this.counters.unknownFrameTypes++;\n return;\n }\n\n if (frame.streamId === 0) {\n if (frame.type !== FrameType.DATA) {\n // decodeFrame already rejects OPEN/CLOSE/WINDOW/RESET on stream 0.\n return;\n }\n this.dispatchControl(frame.payload);\n return;\n }\n\n if (!this.handshakeDone) {\n // \"The server may send nothing at all before welcome\" (WIRE.md\n // section 2.9): any non-stream-0 frame here means the server jumped\n // the gun on the handshake.\n throw new ConnError(ErrorCode.PROTOCOL_ERROR, `${frameTypeName(frame.type)} frame received before welcome completed the handshake`);\n }\n\n const id = frame.streamId;\n if (frame.type === FrameType.OPEN) {\n if (id <= this.highestOpened) {\n throw new ConnError(ErrorCode.PROTOCOL_ERROR, `duplicate or out-of-order OPEN for stream ${id}`);\n }\n this.highestOpened = id;\n // Drain enforcement (WIRE.md section 2.9 and the 2026-08-27\n // decision log): \"endpoints MUST NOT increase last_stream_id\" and\n // \"after sending drain the server MUST NOT send OPEN\". An OPEN above\n // the announced last_stream_id is the server breaking a boundary it\n // already promised -- strict connection PROTOCOL_ERROR, not a\n // stream-scoped REFUSED_STREAM (only the server opens streams, so only\n // the client is ever in a position to detect this).\n if (this.draining && this.lastStreamId !== undefined && id > this.lastStreamId) {\n this.counters.drainViolations++;\n throw new ConnError(\n ErrorCode.PROTOCOL_ERROR,\n `OPEN for stream ${id} received after drain (last_stream_id=${this.lastStreamId})`,\n );\n }\n // OPEN beyond the negotiated max_streams: stream-scoped STREAM_LIMIT,\n // not a connection error (WIRE.md section 2.8's error table). Only\n // the server opens streams, so only the client is ever in a position\n // to detect this (mirrors spec/fixtures/sequences/max_streams_exceeded.json).\n if (this.streams.size >= this.maxStreams) {\n this.counters.refusedOpens++;\n this.sendControlFrame(encodeReset(id, ErrorCode.STREAM_LIMIT, `max_streams=${this.maxStreams} exceeded by stream ${id}`));\n return;\n }\n const stream = new MixerStream(id, this.#streamHost, this.ourWindow, this.peerWindow);\n this.streams.set(id, stream);\n this.enqueueDelivery({ kind: \"stream\", stream });\n return;\n }\n\n if (id > this.highestOpened) {\n throw new ConnError(ErrorCode.PROTOCOL_ERROR, `${frame.type === FrameType.DATA ? \"DATA\" : \"frame\"} for stream ${id}, which was never opened`);\n }\n\n const stream = this.streams.get(id);\n if (!stream) {\n // Benign race: frames for a stream that was open and is now gone.\n this.counters.staleFrames++;\n return;\n }\n\n switch (frame.type) {\n case FrameType.DATA:\n stream.handleData(frame.payload);\n break;\n case FrameType.WINDOW:\n stream.handleWindow(windowIncrement(frame));\n break;\n case FrameType.CLOSE:\n stream.handleClose();\n break;\n case FrameType.RESET:\n stream.handleReset(resetCode(frame), resetMessage(frame));\n break;\n }\n }\n\n private dispatchControl(payload: Uint8Array): void {\n // Stream-0 flood limit (item 4, WIRE.md section 2.7): checked before\n // parsing, mirroring go/wsmixer's handleControlData, so a flood of junk\n // can't burn CPU on top of exhausting the bucket.\n if (!this.stream0Bucket.allow()) {\n throw new ConnError(ErrorCode.ENHANCE_YOUR_CALM, `stream-0 message rate exceeded ${STREAM0_RATE_PER_SEC}/s (burst ${STREAM0_BURST})`);\n }\n const msg = parseControl(payload);\n switch (msg.t) {\n case \"welcome\":\n if (this.handshakeDone) throw new ConnError(ErrorCode.PROTOCOL_ERROR, \"duplicate welcome after handshake completed\");\n this.applyWelcome(msg);\n return;\n case \"hello\":\n throw new ConnError(ErrorCode.PROTOCOL_ERROR, \"unexpected hello: only the client sends hello\");\n case \"ping\":\n // Item 6: a ping before welcome completes is treated like any other\n // pre-welcome control frame (WIRE.md sections 2.9/2.10: \"the\n // server may send nothing at all before welcome\").\n if (!this.handshakeDone) {\n throw new ConnError(ErrorCode.PROTOCOL_ERROR, \"ping received before welcome completed the handshake\");\n }\n // pong jumps to the head of the control queue (item 6 / WIRE.md\n // section 2.7: \"A pong MUST ... jump ahead of queued DATA\").\n void this.sendControlPriority({ t: \"pong\", id: msg.id, ...(msg.ts !== undefined ? { ts: msg.ts } : {}) } as ControlMessage).catch(() => {});\n return;\n case \"pong\":\n // WIRE.md section 2.7 defines two verdicts for an incoming pong\n // -- id never sent -> PROTOCOL_ERROR, duplicate -> ignore + count --\n // and nothing beyond \"MUST carry the same id and ts\" about a\n // mismatched `ts` specifically. RTT here is measured off this side's\n // own recorded send time (handlePong's `sentAt`), never off the\n // peer's echoed `ts`, so a wrong echo can't skew it; treating it as\n // fatal isn't required by the spec and would misidentify a\n // clock-skewed-but-otherwise-healthy peer as a protocol violation\n // (confirmed by spec/fixtures/sequences/duplicate_pong_from_server.json,\n // which replays a pong carrying a fixed, unrelated `ts` and expects\n // no error). `ts` is therefore accepted but not verified; only `id`\n // governs match/duplicate detection.\n this.handlePong(msg.id);\n return;\n case \"drain\": {\n // Item 1: like ping/app above, drain before welcome completes the\n // handshake is a protocol violation (WIRE.md sections 2.9/2.10:\n // \"the server may send nothing at all before welcome\"), mirroring\n // Go dispatch.go's blanket !handshakeDone check.\n if (!this.handshakeDone) {\n throw new ConnError(ErrorCode.PROTOCOL_ERROR, \"drain received before welcome completed the handshake\");\n }\n // Unknown reason: tolerated, degrade to \"maintenance\" and count\n // (WIRE.md section 2.7) -- both for the connection's own\n // draining decision and for whatever the app sees via the 'drain'\n // event, so a consumer never has to recognize a reason outside the\n // documented closed enum.\n let reason = msg.reason;\n if (!knownDrainReason(reason)) {\n this.counters.unknownDrainReasons++;\n reason = \"maintenance\";\n }\n const normalized: DrainMsg = reason === msg.reason ? msg : { ...msg, reason };\n this.draining = true;\n // Endpoints MUST NOT increase last_stream_id across drains\n // (WIRE.md section 2.9); keep the smaller value if we somehow\n // saw more than one.\n this.lastStreamId = this.lastStreamId === undefined ? normalized.last_stream_id : Math.min(this.lastStreamId, normalized.last_stream_id);\n this.enqueueDelivery({ kind: \"drain\", msg: normalized });\n return;\n }\n case \"error\":\n // The normative pre-welcome rejection shape (OVERVIEW.md section\n // 3.4's Authenticate hook, WIRE.md section 2.7/2.10 step 12): the\n // server MAY reject `hello` with `error{code,message}` + close\n // *before* ever sending `welcome` -- confirmed by\n // spec/fixtures/sequences/auth_failure.json (error{UNAUTHORIZED} +\n // close 4011, no welcome). Unlike drain/ping/app/hello above, this is\n // never a protocol violation on the peer's part even pre-welcome, so\n // it always routes to handlePeerError -- which never replies with\n // another error{} (WIRE.md section 2.7) and closes with 4000+code --\n // surfacing the peer's own code/message to the handshake rejection\n // instead of masking it as a local PROTOCOL_ERROR.\n this.handlePeerError(msg.code, msg.message, msg.stream_id);\n return;\n case \"app\":\n if (!this.handshakeDone) throw new ConnError(ErrorCode.PROTOCOL_ERROR, \"app message received before welcome\");\n this.enqueueDelivery({ kind: \"app\", body: msg.body });\n return;\n }\n }\n\n // --- ordered async delivery (item 2) -------------------------------------\n\n /**\n * Queues one `'stream'`/`'app'`/`'drain'` event for in-order, async\n * delivery, mirroring go/wsmixer's deliveryLoop: the read/dispatch path\n * above never blocks on application code, but all three still fire in\n * wire order, off a single loop, one at a time. Handlers registered via\n * `on('stream'|'app'|'drain', ...)` must not block for long -- a handler\n * that never returns/resolves permanently wedges this loop, exactly like\n * the Go side. Throws ConnError(ENHANCE_YOUR_CALM) if the bounded queue\n * (`max(128, max_streams + 64)`) is full, which the caller (dispatchFrame/\n * dispatchControl, inside onMessage's try/catch) turns into fail().\n *\n * Refuses once `this.closed` (CLIENT-SDK.md's \"Handler delivery\" row):\n * `dispatchFrame`/`dispatchControl` -- the only callers -- always run\n * before whatever ends the connection sets `this.closed` (every\n * fail()/close()/handlePeerError path runs teardownConn synchronously,\n * `error` is always the last message on the wire), so nothing legitimate\n * should ever reach here once closed; this is the defensive backstop that\n * makes runDeliveryLoop's post-close drain below provably bounded and\n * terminating regardless.\n */\n private enqueueDelivery(ev: DeliveryEvent): void {\n if (this.closed) return;\n const capacity = Math.max(DELIVERY_QUEUE_MIN, this.maxStreams + DELIVERY_QUEUE_MARGIN);\n if (this.deliveryQueue.length >= capacity) {\n throw new ConnError(ErrorCode.ENHANCE_YOUR_CALM, `application delivery queue full (>${capacity} pending stream/app/drain callbacks)`);\n }\n this.deliveryQueue.push(ev);\n if (!this.deliveryRunning) {\n this.deliveryRunning = true;\n void this.runDeliveryLoop();\n }\n }\n\n /**\n * Drains `deliveryQueue` until empty -- deliberately NOT gated on\n * `!this.closed` (CLIENT-SDK.md's \"Handler delivery\" row): an event\n * already queued when the connection ends is still owed to its handler\n * (`error` is the last message on the wire, so a stream OPEN/`app`/`drain`\n * queued ahead of it arrived before the connection ended), so this loop\n * finishes flushing that backlog even after `teardownConn` has already set\n * `this.closed` and emitted `'close'` -- a handler MAY therefore run\n * shortly after `MixerClient.close()`/`MixerConn.close()`'s own promise has\n * already resolved (see those methods' doc comments and README). `if\n * (!ev) break` is what actually ends the loop; since `enqueueDelivery`\n * above refuses once closed, the queue can only shrink from here, so this\n * always terminates.\n */\n private async runDeliveryLoop(): Promise<void> {\n for (;;) {\n const ev = this.deliveryQueue.shift();\n if (!ev) break;\n switch (ev.kind) {\n case \"stream\":\n await this.emitOrdered(\"stream\", ev.stream);\n break;\n case \"app\":\n await this.emitOrdered(\"app\", ev.body);\n break;\n case \"drain\":\n await this.emitOrdered(\"drain\", ev.msg);\n break;\n }\n }\n this.deliveryRunning = false;\n }\n\n /**\n * Invokes every listener for `event` in registration order, awaiting each\n * one's return value before starting the next -- unlike EventEmitter's own\n * synchronous `emit`, which fires listeners back to back without waiting\n * for a returned Promise to settle.\n *\n * A listener that throws synchronously or returns a rejected promise is\n * caught here, counted (stats().handlerErrors) and surfaced via a guarded\n * `'handlerError'` emit -- never an unhandled rejection, and never fatal\n * to delivery: the next queued stream/app/drain event still runs.\n */\n private async emitOrdered(event: string, arg: unknown): Promise<void> {\n for (const listener of this.listeners(event) as Array<(a: unknown) => unknown>) {\n try {\n const result = listener.call(this, arg);\n if (result instanceof Promise) await result;\n } catch (e) {\n this.counters.handlerErrors++;\n const error = e instanceof Error ? e : new Error(String(e));\n if (this.listenerCount(\"handlerError\") > 0) this.emit(\"handlerError\", { event, error });\n }\n }\n }\n\n // --- outbound: control priority + round-robin DATA scheduler ------------------\n\n /**\n * Queues one control-channel frame and returns a promise that resolves\n * once `ws.send`'s callback confirms it was actually written (or rejects\n * with the connection's terminal error if the conn fails first) --\n * mirroring how `sendData()`'s per-stream outbox already works.\n */\n private enqueueControl(frame: Uint8Array, priority = false): Promise<void> {\n return new Promise((resolve, reject) => {\n if (this.closed) {\n reject(new ConnError(ErrorCode.INTERNAL_ERROR, \"connection closed\"));\n return;\n }\n const item: ControlItem = { frame, resolve, reject };\n if (priority) this.controlQueue.unshift(item);\n else this.controlQueue.push(item);\n this.wake();\n });\n }\n\n /**\n * Sends a control message (hello/ping/pong/drain/app/error) as stream-0\n * DATA, queued ahead of any DATA. Resolves once the frame is actually\n * written to the socket.\n */\n sendControl(msg: ControlMessage): Promise<void> {\n return this.enqueueControl(encodeData(0, encodeControl(msg)));\n }\n\n /**\n * Sends a control message at the head of the control queue, ahead of\n * anything already queued (item 6: \"A pong MUST ... jump ahead of queued\n * DATA\" -- and ahead of other queued control frames too).\n */\n private sendControlPriority(msg: ControlMessage): Promise<void> {\n return this.enqueueControl(encodeData(0, encodeControl(msg)), true);\n }\n\n /** Backs `streamHost.sendControlFrame`: send a control-priority frame (WINDOW/CLOSE/RESET) immediately. */\n private sendControlFrame(frame: Uint8Array): void {\n void this.enqueueControl(frame).catch(() => {\n // A stream-level WINDOW/CLOSE/RESET write failing means the connection\n // is already going down; that failure is reported through the normal\n // 'close'/'fatal' path, not here (this method's callers, StreamHost's\n // fire-and-forget contract, don't await it).\n });\n }\n\n /** Backs `streamHost.sendData`: enqueue a DATA chunk for round-robin transmission. */\n private sendData(streamId: number, chunk: Uint8Array): Promise<void> {\n return new Promise((resolve, reject) => {\n if (this.closed) {\n reject(new ConnError(ErrorCode.INTERNAL_ERROR, \"connection closed\"));\n return;\n }\n let q = this.outbox.get(streamId);\n if (!q) {\n q = [];\n this.outbox.set(streamId, q);\n }\n q.push({ chunk, resolve, reject });\n this.markReady(streamId);\n });\n }\n\n /** Backs `streamHost.retireStream`: retire a fully-closed stream's id. */\n private retireStream(streamId: number): void {\n this.streams.delete(streamId);\n this.outbox.delete(streamId);\n if (this.streams.size === 0) this.notifyIdle();\n }\n\n /** Resolves every pending close()'s wait for the stream table to empty, instead of polling. */\n private notifyIdle(): void {\n const waiters = this.idleWaiters;\n this.idleWaiters = [];\n for (const w of waiters) w();\n }\n\n private waitForEmpty(): Promise<void> {\n return new Promise((resolve) => {\n if (this.streams.size === 0) {\n resolve();\n return;\n }\n this.idleWaiters.push(resolve);\n });\n }\n\n private markReady(streamId: number): void {\n if (!this.inRotation.has(streamId)) {\n this.inRotation.add(streamId);\n this.rotation.push(streamId);\n }\n this.wake();\n }\n\n private wake(): void {\n const waiters = this.wakeWaiters;\n this.wakeWaiters = [];\n for (const w of waiters) w();\n }\n\n private waitForWork(): Promise<void> {\n return new Promise((resolve) => this.wakeWaiters.push(resolve));\n }\n\n private nextChunk(): { streamId: number; item: OutboxItem } | null {\n while (this.rotation.length > 0) {\n const id = this.rotation.shift()!;\n this.inRotation.delete(id);\n const q = this.outbox.get(id);\n if (!q || q.length === 0) continue;\n const item = q.shift()!;\n const stream = this.streams.get(id);\n if (stream) {\n const { done, error } = stream.sendDone();\n if (done) {\n item.reject(error!);\n continue;\n }\n }\n if (q.length > 0) this.markReady(id);\n return { streamId: id, item };\n }\n return null;\n }\n\n private startWriter(): void {\n if (this.writerRunning) return;\n this.writerRunning = true;\n void this.runWriter();\n }\n\n private async runWriter(): Promise<void> {\n while (!this.closed) {\n while (this.controlQueue.length > 0) {\n const item = this.controlQueue.shift()!;\n try {\n await this.writeRaw(item.frame);\n item.resolve();\n } catch (e) {\n item.reject(e as Error);\n return; // socket failure: onSocketClose/onSocketError handles teardown\n }\n }\n const next = this.nextChunk();\n if (next) {\n try {\n await this.writeRaw(encodeData(next.streamId, next.item.chunk));\n next.item.resolve();\n } catch (e) {\n next.item.reject(e as Error);\n return;\n }\n continue;\n }\n await this.waitForWork();\n }\n }\n\n /**\n * Writes one frame and resolves once `ws.send`'s callback reports it\n * flushed. This is how rule 2 (§2.6, \"gate the write loop on the socket\n * send buffer\") is satisfied without polling `bufferedAmount` on a timer:\n * `ws`'s `send(data, cb)` callback fires only once the chunk has actually\n * been handed to the socket, so awaiting it before writing the next frame\n * already keeps exactly one write in flight and applies backpressure for\n * free (runWriter's loop awaits writeRaw() before picking the next chunk).\n */\n private writeRaw(frame: Uint8Array): Promise<void> {\n return new Promise((resolve, reject) => {\n if (this.closed) {\n reject(new Error(\"connection closed\"));\n return;\n }\n try {\n this.ws.send(frame, (err) => {\n if (err) {\n reject(err);\n return;\n }\n this.counters.bytesOut += frame.length;\n resolve();\n });\n } catch (e) {\n reject(e as Error);\n }\n });\n }\n\n // --- app messages -------------------------------------------------------------\n\n /** Resolves once the `app` frame is actually written to the socket, or rejects with the connection's terminal error if it fails first. */\n sendApp(body: Record<string, unknown>): Promise<void> {\n return this.sendControl({ t: \"app\", body } as ControlMessage);\n }\n\n // --- keepalive ------------------------------------------------------------------\n\n private startKeepalive(): void {\n // First ping jittered by random(0, ping_interval) to avoid a herd after a mass reconnect.\n const jitter = Math.random() * this.pingIntervalMs;\n this.jitterTimer = setTimeout(() => {\n this.jitterTimer = null;\n if (this.closed) return;\n this.sendPing();\n this.pingTimer = setInterval(() => this.sendPing(), this.pingIntervalMs);\n }, jitter);\n // Checked at most every 1000ms in production, but no less often than\n // ping_timeout itself so a test-injected tiny ping_timeout (see\n // ConnOptions._timing) is still detected promptly instead of waiting out\n // a fixed 1s tick.\n this.watchdogTimer = setInterval(() => this.checkWatchdog(), Math.min(1000, this.pingTimeoutMs));\n }\n\n private sendPing(): void {\n if (this.closed) return;\n const id = this.nextPingId++;\n this.outstandingPings.set(id, Date.now());\n void this.sendControl({ t: \"ping\", id, ts: Date.now() } as ControlMessage).catch(() => {});\n }\n\n /**\n * Pong watermark scheme, mirroring go/wsmixer/dispatch.go's handlePong:\n * every id below lowestUnacked has been acked (or pruned as stale) at\n * least once, and no id >= nextPingId has ever been sent.\n * - id >= nextPingId: never sent -> PROTOCOL_ERROR, connection-fatal.\n * - id < lowestUnacked, or in-range but already absent from the map\n * (already acked, or pruned by the watchdog as stale/late): a\n * duplicate or late pong -- tolerated, just counted.\n */\n private handlePong(id: number): void {\n if (id >= this.nextPingId) {\n throw new ConnError(ErrorCode.PROTOCOL_ERROR, `pong for id ${id} that was never sent`);\n }\n if (id < this.lowestUnacked) {\n this.counters.duplicatePongs++;\n return;\n }\n const sentAt = this.outstandingPings.get(id);\n if (sentAt === undefined) {\n this.counters.duplicatePongs++;\n return;\n }\n this.outstandingPings.delete(id);\n if (id === this.lowestUnacked) {\n this.lowestUnacked++;\n while (this.lowestUnacked < this.nextPingId && !this.outstandingPings.has(this.lowestUnacked)) {\n this.lowestUnacked++;\n }\n }\n this.lastPongAt = Date.now();\n this.emit(\"pong\", { id, rttMs: Date.now() - sentAt });\n }\n\n private checkWatchdog(): void {\n if (this.closed) return;\n const elapsed = Date.now() - this.lastPongAt;\n for (const [id, sentAt] of this.outstandingPings) {\n if (Date.now() - sentAt > this.pingTimeoutMs) this.outstandingPings.delete(id);\n }\n if (elapsed > this.pingTimeoutMs) {\n this.fail(new ConnError(ErrorCode.KEEPALIVE_TIMEOUT, `no pong received for ${elapsed}ms`));\n }\n }\n\n // --- shutdown -----------------------------------------------------------------\n\n /**\n * Shared teardown: WS close `4000 + code`, reject everything outstanding,\n * emit `'fatal'`/`'error'` (guarded) then `'close'`, synchronously and in\n * that order every time. `sendErrorFrame` controls whether an\n * `error{code,message}` control frame is sent first -- true for a\n * locally-detected failure (fail()), false when the peer already sent its\n * own `error` and WIRE.md section 2.7 forbids replying with another\n * one (handlePeerError()). Either way `this.closed` is set *before*\n * `ws.close()` is called, so the peer's echo of this close (or any close\n * frame it happens to send around the same time) is ignored by\n * onSocketClose below rather than reported as `closeReason` -- per\n * CLIENT-SDK.md's `closeReason` row, an outgoing reason this side sent is\n * never legitimate `closeReason` data, and there is nothing to gain by\n * waiting for the peer's own close frame here: WIRE.md section 2.7\n * already allows closing on `error` \"without reading the close frame that\n * followed\".\n */\n private teardownConn(err: WsMixerError, sendErrorFrame: boolean, streamErrorFactory?: (streamId: number) => WsMixerError): void {\n if (this.closed) return;\n this.closed = true;\n this.stopTimers();\n if (sendErrorFrame) {\n try {\n const frame = encodeData(0, encodeControl({ t: \"error\", code: err.code, message: err.message } as ControlMessage));\n this.ws.send(frame);\n // Best-effort like the send() call itself (no callback to confirm an\n // actual flush): still counted in bytesOut so this farewell frame\n // isn't invisible to stats() (item 3).\n this.counters.bytesOut += frame.length;\n } catch {\n // best-effort: the socket may already be gone\n }\n }\n const wsCode = closeCode(err.code);\n const reason = truncateUtf8(err.message, 123);\n try {\n this.ws.close(wireCloseCode(wsCode), reason);\n } catch {\n this.ws.terminate?.();\n }\n this.rejectOutstanding(err, streamErrorFactory);\n if (!this.handshakeDone) this.emit(\"__handshake_failed_internal\", err);\n const fatal = err.code === ErrorCode.UNSUPPORTED || err.code === ErrorCode.UNAUTHORIZED;\n // Guarded (item 2): 'close' below always fires and carries the same\n // {errorCode, message}, so a caller with no 'error'/'fatal' listener\n // still learns why via onDisconnect(reason) instead of an uncaught throw.\n const evt = fatal ? \"fatal\" : \"error\";\n if (this.listenerCount(evt) > 0) this.emit(evt, err);\n // closeReason is deliberately absent here in both branches: this side\n // initiated the close (either directly, or -- sendErrorFrame:false, from\n // handlePeerError -- reacting to error{} without reading whatever close\n // frame the peer sends behind it, per WIRE.md section 2.7). Only\n // onSocketClose, for a close frame this side actually *received*, ever\n // has a real closeReason to report.\n this.emit(\"close\", { wsCode, errorCode: err.code, message: err.message });\n }\n\n /**\n * Connection-fatal failure path: error{code,message} on stream 0, WS close\n * 4000+code, teardown. Three steps, in order (WIRE.md section 2.8).\n *\n * `streamErrorFactory`, when given, overrides the error every live stream\n * is torn down with (`err` is still used for the connection-level frame/\n * close/rejects) -- used by MixerClient when it fails a *superseded*\n * (drained) conn: the streams on it aren't erroring, the conn they were\n * riding on is being replaced, so each gets a stream-scoped CANCEL\n * (\"connection drained\") instead of the connection's own NO_ERROR.\n *\n * Synchronous, but any stream/app/drain event still queued for delivery at\n * this point is not: runDeliveryLoop keeps flushing it after this returns\n * (see close()'s doc comment above for why).\n */\n fail(err: WsMixerError, streamErrorFactory?: (streamId: number) => WsMixerError): void {\n this.teardownConn(err, true, streamErrorFactory);\n }\n\n /**\n * The peer sent `error{code,message}`: record it and close with\n * `4000 + code` immediately, without waiting for the peer to do anything\n * else -- mirrors go/wsmixer/dispatch.go's handlePeerError. `error` is\n * always the last message on the wire (WIRE.md section 2.7), so\n * there is nothing left to negotiate.\n */\n private handlePeerError(code: number, message: string, streamId?: number): void {\n const err = new WsMixerError(code, message, { streamId });\n this.teardownConn(err, false);\n }\n\n /**\n * Graceful client shutdown: drain{client_requested}, brief grace period,\n * then close(NO_ERROR). This resolves once teardownConn's synchronous\n * steps are done (frame/WS close/rejects/'close' emitted) -- it does NOT\n * wait for runDeliveryLoop to finish flushing whatever stream/app/drain\n * backlog was still queued at that point. A handler for an event received\n * before this close MAY therefore still be invoked shortly after this\n * promise (and 'close') has already resolved/fired (CLIENT-SDK.md's\n * \"Handler delivery\" row).\n */\n async close(graceMs = 5000): Promise<void> {\n if (this.closed) return;\n if (this.handshakeDone) {\n void this.sendControl({ t: \"drain\", reason: \"client_requested\", last_stream_id: this.highestOpened } as ControlMessage).catch(() => {});\n if (this.streams.size > 0) {\n // No polling: wait for whichever happens first -- the stream table\n // draining to empty (notifyIdle, from retireStream), the grace\n // deadline, or the connection closing out from under us. Both the\n // timer and the 'close' listener are cleaned up afterwards\n // regardless of which branch won the race, so neither dangles.\n let graceTimer: ReturnType<typeof setTimeout> | undefined;\n let onClose: (() => void) | undefined;\n try {\n await Promise.race([\n this.waitForEmpty(),\n new Promise<void>((resolve) => {\n graceTimer = setTimeout(resolve, graceMs);\n }),\n new Promise<void>((resolve) => {\n onClose = () => resolve();\n this.once(\"close\", onClose);\n }),\n ]);\n } finally {\n if (graceTimer) clearTimeout(graceTimer);\n if (onClose) this.off(\"close\", onClose);\n }\n }\n }\n if (this.closed) return;\n this.fail(new WsMixerError(ErrorCode.NO_ERROR, \"client closing\"));\n }\n\n /**\n * A close frame this side actually *received* from `ws`'s 'close' event.\n * `code`/`reason` here are always the peer's, per `ws`'s own contract\n * (`receiverOnConclude` in `ws/lib/websocket.js` only ever updates\n * `_closeCode`/`_closeMessage` from a frame it parsed off the wire, never\n * from what this side sent via `.close()`) -- so whenever `this.closed` is\n * already `true`, this is a close frame arriving after teardownConn()\n * already ran and reported (never our own echoed close winning a race),\n * and is correctly ignored rather than folded in after the fact.\n *\n * Pre-welcome, this is also the SOLE reporter for a transport failure\n * before `welcome` -- measured against real `ws` (8.21): a TCP reset,\n * half-close or peer close frame after the 101 delivers a bare 'close'\n * with no 'error' at all, and a protocol-level failure `ws` itself\n * detects (an invalid frame: 1002, an oversized message: 1009, ...)\n * delivers 'error' immediately followed by 'close' a macrotask or more\n * later -- 'close' is never racing 'error' for which one wins; it always\n * arrives after it, if it arrives at all. `onSocketError` below therefore\n * never finalizes anything itself, only records the error's message here\n * for `message` to fall back on when the close carries no reason of its\n * own -- so this always reports the *actual* close code `ws` delivers\n * (1006, or whatever real close code it sent), never a wsCode fabricated\n * for a close that never happened.\n */\n private onSocketClose(code: number, reason: Buffer): void {\n if (this.closed) return;\n this.closed = true;\n this.stopTimers();\n const reasonStr = reason.toString();\n const handshakePhase = !this.handshakeDone;\n const message = reasonStr || (handshakePhase ? this.pendingHandshakeErrorMessage : undefined) || `socket closed with code ${code}`;\n const err = new WsMixerError(ErrorCode.INTERNAL_ERROR, message, {\n wsCode: code,\n closeReason: reasonStr || undefined,\n });\n this.rejectOutstanding(err);\n if (handshakePhase) this.emit(\"__handshake_failed_internal\", err);\n this.emit(\"close\", { wsCode: code, message, closeReason: reasonStr || undefined });\n }\n\n /**\n * Pre-welcome, `'error'` never arrives in the same microtask as (or after)\n * a 'close' that never comes -- see onSocketClose's own doc comment for\n * what real `ws` actually does. So this deliberately does NOT reject or\n * finalize the handshake itself; it just records the error's message for\n * onSocketClose (the sole reporter) to use as `message` when the close\n * that follows carries no reason of its own. If `'close'` somehow never\n * follows at all (a transport that violates `ws`'s own contract), nothing\n * here is left waiting on it: `handshake()`'s own hello/welcome timeout\n * (`handshakeTimer`, 10s default) still fires and produces the one report\n * (`wsCode` 4001) regardless -- deliberately the only fallback for that\n * case, not a second mechanism grafted on here.\n */\n private onSocketError(err: Error): void {\n if (!this.handshakeDone && !this.closed) {\n this.pendingHandshakeErrorMessage = err.message;\n }\n // Guarded (item 2): onSocketClose (or the hello/welcome timeout above)\n // still follows and emits 'close', so onDisconnect(reason) still\n // reports this even with no 'error' listener.\n if (this.listenerCount(\"error\") > 0) this.emit(\"error\", new WsMixerError(ErrorCode.INTERNAL_ERROR, err.message));\n }\n\n private stopTimers(): void {\n if (this.pingTimer) clearInterval(this.pingTimer);\n if (this.watchdogTimer) clearInterval(this.watchdogTimer);\n if (this.handshakeTimer) clearTimeout(this.handshakeTimer);\n if (this.jitterTimer) clearTimeout(this.jitterTimer);\n this.pingTimer = null;\n this.watchdogTimer = null;\n this.handshakeTimer = null;\n this.jitterTimer = null;\n }\n\n private rejectOutstanding(err: Error, streamErrorFactory?: (streamId: number) => WsMixerError): void {\n for (const q of this.outbox.values()) {\n for (const item of q) item.reject(err);\n }\n this.outbox.clear();\n // Any control frame (including a pending sendApp()) still queued when\n // the connection dies never reaches writeRaw(), so it must be rejected\n // here instead -- otherwise its promise would hang forever.\n for (const item of this.controlQueue.splice(0, this.controlQueue.length)) {\n item.reject(err);\n }\n this.wake();\n for (const stream of this.streams.values()) {\n // Same \"don't crash a consumer with no 'error' listener\" rule as a\n // stream-level RESET (item 2 / stream.ts handleReset): a connection\n // failure aborts every live stream the same way -- unless the caller\n // gave a per-stream override (fail()'s streamErrorFactory), e.g. a\n // superseded conn's streams getting stream-scoped CANCEL instead of\n // the connection's own terminal error.\n stream.terminateNoThrow(\n streamErrorFactory\n ? streamErrorFactory(stream.id)\n : err instanceof WsMixerError\n ? err\n : new ConnError(ErrorCode.INTERNAL_ERROR, err.message),\n );\n }\n }\n\n isDraining(): boolean {\n return this.draining;\n }\n\n liveStreamCount(): number {\n return this.streams.size;\n }\n}\n","/**\n * ws-mixer.v1 mux frame codec (WIRE.md section 2.2-2.4). Mirrors\n * `go/wsmixer/frame.go` exactly: same header layout, same validation, same\n * error split between connection-fatal (ConnError) and stream-scoped\n * (StreamError).\n */\nimport { ConnError, ErrorCode, StreamError } from \"./errors.js\";\n\nexport const FrameType = {\n OPEN: 0x00,\n DATA: 0x01,\n WINDOW: 0x02,\n CLOSE: 0x03,\n RESET: 0x04,\n} as const;\n\nexport type FrameTypeValue = (typeof FrameType)[keyof typeof FrameType];\n\nconst frameTypeNames: Record<number, string> = {\n [FrameType.OPEN]: \"OPEN\",\n [FrameType.DATA]: \"DATA\",\n [FrameType.WINDOW]: \"WINDOW\",\n [FrameType.CLOSE]: \"CLOSE\",\n [FrameType.RESET]: \"RESET\",\n};\n\n/** Renders a frame type's wire name, or \"UNKNOWN(0xNN)\" outside the v1 set. */\nexport function frameTypeName(t: number): string {\n const name = frameTypeNames[t];\n if (name) return name;\n return `UNKNOWN(0x${t.toString(16).padStart(2, \"0\")})`;\n}\n\nfunction frameTypeKnown(t: number): boolean {\n return t in frameTypeNames;\n}\n\nconst FRAME_HEADER_SIZE = 8;\n/** Largest legal WebSocket message: 8-byte header plus a 64 KiB payload. */\nexport const MAX_MESSAGE_SIZE = FRAME_HEADER_SIZE + 65536;\n/** Control-channel (stream 0) message size cap. */\nexport const MAX_STREAM_ZERO_PAYLOAD = 16384;\n/** Recommended DATA chunk size: a sender-side default, not a wire limit. */\nexport const MAX_CHUNK = 16384;\nconst STREAM_ID_HIGH_BIT = 0x8000_0000;\n/** Largest legal cumulative send-credit window: 2^31-1 (WIRE.md section 2.6 decision 1). */\nexport const MAX_SEND_WINDOW = 0x7fff_ffff;\n\n/** One decoded ws-mixer mux frame: the 8-byte header plus its payload. */\nexport interface Frame {\n type: number;\n flags: number;\n streamId: number;\n payload: Uint8Array;\n}\n\n/**\n * Decodes one WebSocket message into a Frame per WIRE.md sections\n * 2.2-2.4. Throws ConnError (connection-fatal) or StreamError (scoped to the\n * frame's stream id).\n */\nexport function decodeFrame(msg: Uint8Array): Frame {\n if (msg.length < FRAME_HEADER_SIZE) {\n throw new ConnError(\n ErrorCode.PROTOCOL_ERROR,\n `frame header too short: ${msg.length} bytes, need at least ${FRAME_HEADER_SIZE}`,\n );\n }\n if (msg.length > MAX_MESSAGE_SIZE) {\n throw new ConnError(\n ErrorCode.FRAME_SIZE_ERROR,\n `message of ${msg.length} bytes exceeds the ${MAX_MESSAGE_SIZE} byte limit`,\n );\n }\n\n const view = new DataView(msg.buffer, msg.byteOffset, msg.byteLength);\n const type = view.getUint8(0);\n const flags = view.getUint8(1);\n // bytes 2..4 are reserved; ignore on receive.\n const streamId = view.getUint32(4);\n const payload = msg.subarray(FRAME_HEADER_SIZE);\n\n if ((streamId & STREAM_ID_HIGH_BIT) !== 0) {\n throw new ConnError(ErrorCode.PROTOCOL_ERROR, `stream id 0x${streamId.toString(16)} has the reserved high bit set`);\n }\n\n const f: Frame = { type, flags, streamId, payload };\n\n if (!frameTypeKnown(type)) {\n // Unknown type: structurally accepted. Caller ignores it and counts it.\n return f;\n }\n\n if (\n (type === FrameType.OPEN || type === FrameType.CLOSE || type === FrameType.WINDOW || type === FrameType.RESET) &&\n streamId === 0\n ) {\n throw new ConnError(ErrorCode.PROTOCOL_ERROR, `${frameTypeName(type)} is not legal on stream 0 (control channel)`);\n }\n\n switch (type) {\n case FrameType.OPEN:\n if (streamId % 2 === 0) {\n throw new ConnError(\n ErrorCode.PROTOCOL_ERROR,\n `OPEN for even stream id ${streamId}: only the server opens streams and server ids are always odd`,\n );\n }\n break;\n\n case FrameType.DATA:\n if (streamId === 0 && payload.length > MAX_STREAM_ZERO_PAYLOAD) {\n throw new ConnError(\n ErrorCode.ENHANCE_YOUR_CALM,\n `stream 0 payload of ${payload.length} bytes exceeds the ${MAX_STREAM_ZERO_PAYLOAD} byte control-channel limit`,\n );\n }\n break;\n\n case FrameType.WINDOW: {\n if (payload.length !== 4) {\n throw new ConnError(ErrorCode.FRAME_SIZE_ERROR, `WINDOW payload is ${payload.length} bytes, must be exactly 4`);\n }\n const increment = new DataView(payload.buffer, payload.byteOffset, payload.byteLength).getUint32(0);\n if (increment === 0) {\n throw new StreamError(ErrorCode.PROTOCOL_ERROR, streamId, \"WINDOW increment of 0 is not legal (range is 1..2^31-1)\");\n }\n if ((increment & STREAM_ID_HIGH_BIT) !== 0) {\n throw new ConnError(ErrorCode.FLOW_CONTROL_ERROR, `WINDOW increment ${increment} would push the send window past 2^31-1`);\n }\n break;\n }\n\n case FrameType.RESET:\n if (payload.length < 4) {\n throw new ConnError(ErrorCode.FRAME_SIZE_ERROR, `RESET payload is ${payload.length} bytes, must be at least 4`);\n }\n break;\n }\n\n return f;\n}\n\n/** Returns the 4-byte increment carried by a WINDOW frame. Caller must have checked f.type === FrameType.WINDOW. */\nexport function windowIncrement(f: Frame): number {\n return new DataView(f.payload.buffer, f.payload.byteOffset, f.payload.byteLength).getUint32(0);\n}\n\n/** Returns the error code carried by a RESET frame. Caller must have checked f.type === FrameType.RESET. */\nexport function resetCode(f: Frame): number {\n return new DataView(f.payload.buffer, f.payload.byteOffset, 4).getUint32(0);\n}\n\nconst utf8Decoder = new TextDecoder(\"utf-8\", { fatal: false });\n\n/**\n * Returns the (UTF-8 sanitized) message carried by a RESET frame. Invalid\n * UTF-8 is replaced with U+FFFD by TextDecoder, matching the wire spec's\n * \"replace chars, do not kill the connection\" rule.\n */\nexport function resetMessage(f: Frame): string {\n return utf8Decoder.decode(f.payload.subarray(4));\n}\n\n/** Serializes a Frame to its wire form: 8-byte header followed by the payload. */\nexport function encodeFrame(f: { type: number; streamId: number; payload?: Uint8Array }): Uint8Array {\n const payload = f.payload ?? new Uint8Array(0);\n const out = new Uint8Array(FRAME_HEADER_SIZE + payload.length);\n const view = new DataView(out.buffer);\n view.setUint8(0, f.type);\n view.setUint8(1, 0);\n view.setUint16(2, 0);\n view.setUint32(4, f.streamId >>> 0);\n out.set(payload, FRAME_HEADER_SIZE);\n return out;\n}\n\nexport function encodeOpen(streamId: number): Uint8Array {\n return encodeFrame({ type: FrameType.OPEN, streamId });\n}\n\nexport function encodeData(streamId: number, payload: Uint8Array): Uint8Array {\n return encodeFrame({ type: FrameType.DATA, streamId, payload });\n}\n\nexport function encodeWindow(streamId: number, increment: number): Uint8Array {\n const payload = new Uint8Array(4);\n new DataView(payload.buffer).setUint32(0, increment >>> 0);\n return encodeFrame({ type: FrameType.WINDOW, streamId, payload });\n}\n\nexport function encodeClose(streamId: number): Uint8Array {\n return encodeFrame({ type: FrameType.CLOSE, streamId });\n}\n\nconst utf8Encoder = new TextEncoder();\n\nexport function encodeReset(streamId: number, code: number, message = \"\"): Uint8Array {\n const msgBytes = utf8Encoder.encode(message);\n const payload = new Uint8Array(4 + msgBytes.length);\n new DataView(payload.buffer).setUint32(0, code >>> 0);\n payload.set(msgBytes, 4);\n return encodeFrame({ type: FrameType.RESET, streamId, payload });\n}\n","/**\n * ws-mixer.v1 control channel (stream 0) message types and hand-written\n * validators (WIRE.md section 2.7). Mirrors `go/wsmixer/control.go` and\n * `control_messages.go`: dispatch on `t`, then explicit per-field checks —\n * never a JSON Schema validator on the runtime path (decision 9).\n */\nimport { ConnError, ErrorCode } from \"./errors.js\";\nimport { MAX_STREAM_ZERO_PAYLOAD } from \"./frame.js\";\n\nconst T_HELLO = \"hello\";\nconst T_WELCOME = \"welcome\";\nconst T_PING = \"ping\";\nconst T_PONG = \"pong\";\nconst T_DRAIN = \"drain\";\nconst T_ERROR = \"error\";\nconst T_APP = \"app\";\n\n// Field bounds from WIRE.md section 2.7's field tables.\nconst MAX_TOKEN_LEN = 4096;\nconst MAX_AGENT_FIELD_LEN = 128;\nconst WINDOW_MIN = 16384;\nconst WINDOW_MAX = 0x7fff_ffff;\nconst MAX_STREAMS_MIN = 1;\nconst MAX_STREAMS_MAX = 100000;\nconst MAX_SESSION_LEN = 64;\nconst PING_INTERVAL_MIN = 5000;\nconst MAX_ERROR_MESSAGE = 1024;\nconst MAX_DRAIN_MESSAGE = 256;\nconst MAX_ID_VALUE = Number.MAX_SAFE_INTEGER; // 2^53-1\nconst MAX_STREAM_ID_VALUE = 0x7fff_ffff; // 31-bit stream ids\n\nconst CAPABILITY_RE = /^[a-z0-9_.-]{1,64}$/;\n\nexport interface AgentInfo {\n sdk: string;\n sdk_version: string;\n runtime?: string;\n os?: string;\n}\n\nexport interface ServerInfo {\n name?: string;\n version?: string;\n node?: string;\n}\n\nexport interface HelloMsg {\n t: \"hello\";\n v: number;\n token: string;\n agent: AgentInfo;\n window?: number;\n max_streams?: number;\n capabilities?: string[];\n meta?: unknown;\n}\n\nexport interface WelcomeMsg {\n t: \"welcome\";\n v: number;\n session: string;\n window: number;\n max_streams: number;\n ping_interval: number;\n ping_timeout: number;\n server?: ServerInfo;\n capabilities?: string[];\n meta?: unknown;\n}\n\nexport interface PingMsg {\n t: \"ping\";\n id: number;\n ts?: number;\n}\n\nexport interface PongMsg {\n t: \"pong\";\n id: number;\n ts?: number;\n}\n\nexport interface DrainMsg {\n t: \"drain\";\n reason: string;\n last_stream_id: number;\n deadline_ms?: number;\n retry_after_ms?: number;\n message?: string;\n}\n\nexport interface ErrorMsg {\n t: \"error\";\n code: number;\n message: string;\n stream_id?: number;\n last_stream_id?: number;\n}\n\nexport interface AppMsg {\n t: \"app\";\n body: Record<string, unknown>;\n}\n\nexport type ControlMessage = HelloMsg | WelcomeMsg | PingMsg | PongMsg | DrainMsg | ErrorMsg | AppMsg;\n\nconst KNOWN_DRAIN_REASONS = new Set([\"rollout\", \"overload\", \"id_exhausted\", \"replaced\", \"maintenance\", \"client_requested\"]);\n\n/** Reports whether reason is one of the documented drain reasons. An unknown reason is tolerated at runtime. */\nexport function knownDrainReason(reason: string): boolean {\n return KNOWN_DRAIN_REASONS.has(reason);\n}\n\nfunction connErr(message: string, code: number = ErrorCode.PROTOCOL_ERROR): ConnError {\n return new ConnError(code, message);\n}\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\nfunction jsonKind(v: unknown): string {\n if (v === null) return \"null\";\n if (typeof v === \"boolean\") return \"boolean\";\n if (typeof v === \"number\") return \"number\";\n if (typeof v === \"string\") return \"string\";\n if (Array.isArray(v)) return \"array\";\n if (typeof v === \"object\") return \"object\";\n return typeof v;\n}\n\nfunction requireField(top: Record<string, unknown>, name: string): unknown {\n if (!(name in top)) throw connErr(`missing required field \"${name}\"`);\n return top[name];\n}\n\nfunction fieldString(raw: unknown, name: string): string {\n if (typeof raw !== \"string\") throw connErr(`field \"${name}\" must be a string`);\n return raw;\n}\n\n/**\n * A JSON integer field. Rejects floats and non-finite values (\"No floats\n * anywhere in the control channel\", WIRE.md section 2.7) and rejects\n * anything outside Number's safe integer range.\n */\nfunction fieldInt(raw: unknown, name: string): number {\n if (typeof raw !== \"number\" || !Number.isFinite(raw)) throw connErr(`field \"${name}\" must be an integer`);\n if (!Number.isInteger(raw)) throw connErr(`field \"${name}\" must be an integer, not a float`);\n return raw;\n}\n\nfunction fieldObject(raw: unknown, name: string): Record<string, unknown> {\n if (!isPlainObject(raw)) throw connErr(`field \"${name}\" must be an object`);\n return raw;\n}\n\nfunction fieldStringArray(raw: unknown, name: string): string[] {\n if (!Array.isArray(raw)) throw connErr(`field \"${name}\" must be an array of strings`);\n return raw.map((item) => {\n if (typeof item !== \"string\") throw connErr(`field \"${name}\" must be an array of strings`);\n return item;\n });\n}\n\nfunction requireString(top: Record<string, unknown>, name: string): string {\n return fieldString(requireField(top, name), name);\n}\n\nfunction requireInt(top: Record<string, unknown>, name: string): number {\n return fieldInt(requireField(top, name), name);\n}\n\nfunction validateCapabilities(caps: string[]): void {\n for (const c of caps) {\n if (!CAPABILITY_RE.test(c)) throw connErr(`capability \"${c}\" does not match ^[a-z0-9_.-]{1,64}$`);\n }\n}\n\nfunction parseAgentInfo(obj: Record<string, unknown>): AgentInfo {\n const sdk = fieldString(requireField(obj, \"sdk\"), \"agent.sdk\");\n const sdk_version = fieldString(requireField(obj, \"sdk_version\"), \"agent.sdk_version\");\n if ([...sdk].length > MAX_AGENT_FIELD_LEN || [...sdk_version].length > MAX_AGENT_FIELD_LEN) {\n throw connErr(`agent.sdk / agent.sdk_version must be <= ${MAX_AGENT_FIELD_LEN} characters`);\n }\n const a: AgentInfo = { sdk, sdk_version };\n if (\"runtime\" in obj) a.runtime = fieldString(obj.runtime, \"agent.runtime\");\n if (\"os\" in obj) a.os = fieldString(obj.os, \"agent.os\");\n return a;\n}\n\nfunction parseHello(top: Record<string, unknown>): HelloMsg {\n const v = requireInt(top, \"v\");\n if (v !== 1) throw connErr(`hello.v=${v} does not match ws-mixer.v1`, ErrorCode.UNSUPPORTED);\n\n const token = requireString(top, \"token\");\n if (token.length < 1 || token.length > MAX_TOKEN_LEN) {\n throw connErr(`hello.token length ${token.length} out of range 1..${MAX_TOKEN_LEN}`);\n }\n\n const agent = parseAgentInfo(fieldObject(requireField(top, \"agent\"), \"agent\"));\n\n let window = 262144;\n if (\"window\" in top) {\n window = fieldInt(top.window, \"window\");\n if (window < WINDOW_MIN || window > WINDOW_MAX) {\n throw connErr(`hello.window ${window} out of range ${WINDOW_MIN}..${WINDOW_MAX}`);\n }\n }\n\n let max_streams = 64;\n if (\"max_streams\" in top) {\n max_streams = fieldInt(top.max_streams, \"max_streams\");\n if (max_streams < MAX_STREAMS_MIN || max_streams > MAX_STREAMS_MAX) {\n throw connErr(`hello.max_streams ${max_streams} out of range ${MAX_STREAMS_MIN}..${MAX_STREAMS_MAX}`);\n }\n }\n\n const m: HelloMsg = { t: \"hello\", v, token, agent, window, max_streams };\n if (\"capabilities\" in top) {\n const capabilities = fieldStringArray(top.capabilities, \"capabilities\");\n validateCapabilities(capabilities);\n m.capabilities = capabilities;\n }\n if (\"meta\" in top) m.meta = top.meta;\n return m;\n}\n\nfunction parseWelcome(top: Record<string, unknown>): WelcomeMsg {\n const v = requireInt(top, \"v\");\n if (v !== 1) throw connErr(`welcome.v=${v} does not match ws-mixer.v1`, ErrorCode.UNSUPPORTED);\n\n const session = requireString(top, \"session\");\n if (session.length > MAX_SESSION_LEN) throw connErr(`welcome.session length ${session.length} exceeds ${MAX_SESSION_LEN}`);\n\n const window = requireInt(top, \"window\");\n if (window < WINDOW_MIN || window > WINDOW_MAX) throw connErr(`welcome.window ${window} out of range ${WINDOW_MIN}..${WINDOW_MAX}`);\n\n const max_streams = requireInt(top, \"max_streams\");\n if (max_streams < MAX_STREAMS_MIN || max_streams > MAX_STREAMS_MAX) {\n throw connErr(`welcome.max_streams ${max_streams} out of range ${MAX_STREAMS_MIN}..${MAX_STREAMS_MAX}`);\n }\n\n const ping_interval = requireInt(top, \"ping_interval\");\n if (ping_interval < PING_INTERVAL_MIN) throw connErr(`welcome.ping_interval ${ping_interval} is below the ${PING_INTERVAL_MIN}ms floor`);\n\n const ping_timeout = requireInt(top, \"ping_timeout\");\n if (ping_timeout < 2 * ping_interval) {\n throw connErr(`welcome.ping_timeout (${ping_timeout}) must be at least 2x ping_interval (${ping_interval})`);\n }\n\n const m: WelcomeMsg = { t: \"welcome\", v, session, window, max_streams, ping_interval, ping_timeout };\n\n if (\"server\" in top) {\n const obj = fieldObject(top.server, \"server\");\n const si: ServerInfo = {};\n if (\"name\" in obj) si.name = fieldString(obj.name, \"server.name\");\n if (\"version\" in obj) si.version = fieldString(obj.version, \"server.version\");\n if (\"node\" in obj) si.node = fieldString(obj.node, \"server.node\");\n m.server = si;\n }\n if (\"capabilities\" in top) {\n const capabilities = fieldStringArray(top.capabilities, \"capabilities\");\n validateCapabilities(capabilities);\n m.capabilities = capabilities;\n }\n if (\"meta\" in top) m.meta = top.meta;\n return m;\n}\n\nfunction parsePingPong(top: Record<string, unknown>, t: \"ping\" | \"pong\"): PingMsg | PongMsg {\n const id = requireInt(top, \"id\");\n if (id < 0 || id > MAX_ID_VALUE) throw connErr(`${t}.id ${id} out of range 0..2^53-1`);\n const m: PingMsg | PongMsg = { t, id };\n if (\"ts\" in top) m.ts = fieldInt(top.ts, \"ts\");\n return m;\n}\n\nfunction parseDrain(top: Record<string, unknown>): DrainMsg {\n const reason = requireString(top, \"reason\");\n const lastStreamId = requireInt(top, \"last_stream_id\");\n if (lastStreamId < 0 || lastStreamId > MAX_STREAM_ID_VALUE) {\n throw connErr(`drain.last_stream_id ${lastStreamId} out of range 0..2^31-1`);\n }\n const m: DrainMsg = { t: \"drain\", reason, last_stream_id: lastStreamId };\n if (\"deadline_ms\" in top) m.deadline_ms = fieldInt(top.deadline_ms, \"deadline_ms\");\n if (\"retry_after_ms\" in top) m.retry_after_ms = fieldInt(top.retry_after_ms, \"retry_after_ms\");\n if (\"message\" in top) {\n m.message = fieldString(top.message, \"message\");\n if (m.message.length > MAX_DRAIN_MESSAGE) throw connErr(`drain.message length ${m.message.length} exceeds ${MAX_DRAIN_MESSAGE}`);\n }\n return m;\n}\n\nfunction parseErrorMsg(top: Record<string, unknown>): ErrorMsg {\n const code = requireInt(top, \"code\");\n if (code < 0 || code > 0xffffffff) throw connErr(`error.code ${code} out of range`);\n const message = requireString(top, \"message\");\n if (message.length > MAX_ERROR_MESSAGE) throw connErr(`error.message length ${message.length} exceeds ${MAX_ERROR_MESSAGE}`);\n const m: ErrorMsg = { t: \"error\", code, message };\n if (\"stream_id\" in top) {\n const v = fieldInt(top.stream_id, \"stream_id\");\n if (v < 0 || v > MAX_STREAM_ID_VALUE) throw connErr(`error.stream_id ${v} out of range 0..2^31-1`);\n m.stream_id = v;\n }\n if (\"last_stream_id\" in top) {\n const v = fieldInt(top.last_stream_id, \"last_stream_id\");\n if (v < 0 || v > MAX_STREAM_ID_VALUE) throw connErr(`error.last_stream_id ${v} out of range 0..2^31-1`);\n m.last_stream_id = v;\n }\n return m;\n}\n\nfunction parseApp(top: Record<string, unknown>): AppMsg {\n const body = requireField(top, \"body\");\n if (!isPlainObject(body)) throw connErr(`app.body must be a JSON object, got ${jsonKind(body)}`);\n return { t: \"app\", body };\n}\n\n/**\n * Validates and decodes one stream-0 DATA payload (already UTF-8 decoded to\n * a string). Implements WIRE.md section 2.7's envelope table plus every\n * message type's per-field checks. Every validation failure is a ConnError\n * with PROTOCOL_ERROR (or ENHANCE_YOUR_CALM for the oversize case).\n */\nexport function parseControl(raw: string | Uint8Array): ControlMessage {\n const bytes = typeof raw === \"string\" ? utf8Encoder.encode(raw) : raw;\n if (bytes.length > MAX_STREAM_ZERO_PAYLOAD) {\n throw new ConnError(\n ErrorCode.ENHANCE_YOUR_CALM,\n `stream 0 payload of ${bytes.length} bytes exceeds the ${MAX_STREAM_ZERO_PAYLOAD} byte control-channel limit`,\n );\n }\n let text: string;\n if (typeof raw === \"string\") {\n text = raw;\n } else {\n try {\n text = utf8Decoder.decode(raw);\n } catch (e) {\n throw connErr(`malformed UTF-8 on stream 0: ${(e as Error).message}`);\n }\n }\n\n let top: unknown;\n try {\n top = JSON.parse(text);\n } catch (e) {\n throw connErr(`malformed JSON on stream 0: ${(e as Error).message}`);\n }\n if (!isPlainObject(top)) {\n throw connErr(`control message must be a JSON object, got ${jsonKind(top)}`);\n }\n\n if (!(\"t\" in top)) throw connErr(`control message missing required field \"t\"`);\n const t = fieldString(top.t, \"t\");\n\n switch (t) {\n case T_HELLO:\n return parseHello(top);\n case T_WELCOME:\n return parseWelcome(top);\n case T_PING:\n return parsePingPong(top, \"ping\");\n case T_PONG:\n return parsePingPong(top, \"pong\");\n case T_DRAIN:\n return parseDrain(top);\n case T_ERROR:\n return parseErrorMsg(top);\n case T_APP:\n return parseApp(top);\n default:\n throw connErr(`unknown control message type \"${t}\"`);\n }\n}\n\nconst utf8Encoder = new TextEncoder();\nconst utf8Decoder = new TextDecoder(\"utf-8\", { fatal: true });\n\n/** Serializes a control message to its stream-0 DATA payload bytes. */\nexport function encodeControl(msg: ControlMessage): Uint8Array {\n return utf8Encoder.encode(JSON.stringify(msg));\n}\n","/**\n * MixerStream: one ws-mixer byte stream, exposed as a Node Duplex.\n * Mirrors `go/wsmixer/stream.go`'s state machine, credit accounting and\n * half-close semantics (WIRE.md section 2.5-2.6). The JS SDK is always\n * the answering peer: it never opens streams, only receives OPEN from the\n * server and reads/writes/closes/resets what arrives.\n *\n * Three ways a stream ends in RESET, all converging on the same terminal\n * state and `resetCode`, but differing in whether `'reset'` fires:\n * - the peer sends RESET (handleReset()) -- `'reset'` fires; the stream's\n * owner didn't choose this.\n * - the SDK itself detects a violation on this stream (abort(), called from\n * MixerConn's dispatch loop) -- `'reset'` fires, same reasoning as above:\n * the app didn't choose it either.\n * - the app calls the public reset() -- `'reset'` does NOT fire; the caller\n * already knows why, by definition.\n * `resetCode` is set by all three, always, whether or not an `'error'`\n * listener is attached.\n */\nimport { Duplex } from \"node:stream\";\nimport { ConnError, ErrorCode, StreamError, WsMixerError, parseErrorCode } from \"./errors.js\";\nimport { MAX_CHUNK, MAX_SEND_WINDOW, encodeClose, encodeReset, encodeWindow } from \"./frame.js\";\nimport { truncateUtf8 } from \"./util.js\";\n\nexport type StreamState = \"idle\" | \"open\" | \"half_closed_local\" | \"half_closed_remote\" | \"closed\";\n\n/** Attached by terminateNoThrow() (and drainWriteQueue's failed-write path) when nothing else is listening for 'error', purely so Node's own unlistened-'error' crash never fires -- see terminateNoThrow's doc comment. */\nconst NOOP_ERROR_LISTENER = () => {};\n\n/** Payload of the `'reset'` event: fired once, whenever this stream is RESET by something other than the app's own reset() call -- a peer RESET (handleReset()) or an SDK-detected violation (abort()) -- whether or not an `'error'` listener is attached. */\nexport interface StreamResetInfo {\n code: number;\n message: string;\n}\n\n/** Callback the stream uses to hand an outbound DATA/WINDOW/CLOSE/RESET frame to the connection. */\nexport interface StreamHost {\n /** Enqueue a DATA chunk for round-robin transmission; resolves once written (or rejects on failure). */\n sendData(streamId: number, chunk: Uint8Array): Promise<void>;\n /** Send a control-priority frame (WINDOW/CLOSE/RESET) immediately. */\n sendControlFrame(frame: Uint8Array): void;\n /** Called when the stream is fully closed so the connection can retire its id. */\n retireStream(streamId: number): void;\n}\n\nexport class MixerStream extends Duplex {\n readonly id: number;\n private readonly host: StreamHost;\n\n private state: StreamState = \"open\";\n private sendWindow: number;\n private recvWindow: number;\n private readonly initialWindow: number;\n private unacked = 0;\n\n private remoteClosed = false;\n private terminalError: WsMixerError | null = null;\n\n /** Resolved when sendWindow becomes > 0 or the stream can no longer send. */\n private sendWaiters: Array<() => void> = [];\n /** Bytes queued via _write, waiting on credit; drives backpressure. */\n private writeQueue: Array<{ chunk: Uint8Array; callback: (err?: Error | null) => void }> = [];\n private draining = false;\n /** Write callbacks (_write's own terminal-error branch, or drainWriteQueue's catch) whose error must wait until the read side has finished delivering buffered data and emitted 'end' -- see terminateNoThrow's doc comment. Non-null exactly while that wait is pending; flushed (and reset to null) from _destroy(), or from the 'end' listener drainWriteQueue's catch installs. */\n private heldWriteCallbacks: Array<(err?: Error | null) => void> | null = null;\n\n /** Set once this stream is RESET -- by the peer (handleReset), the SDK (abort()), or the app itself (reset()); undefined until then. Set even when no `'error'` listener is attached. */\n resetCode?: number;\n\n constructor(id: number, host: StreamHost, initialRecvWindow: number, initialSendWindow: number) {\n super({ autoDestroy: false, emitClose: true, readableHighWaterMark: initialRecvWindow });\n this.id = id;\n this.host = host;\n this.initialWindow = initialRecvWindow;\n this.recvWindow = initialRecvWindow;\n this.sendWindow = initialSendWindow;\n }\n\n getState(): StreamState {\n return this.state;\n }\n\n getSendWindow(): number {\n return this.sendWindow;\n }\n\n getRecvWindow(): number {\n return this.recvWindow;\n }\n\n // --- Duplex plumbing ------------------------------------------------------\n\n override _read(_size: number): void {\n // Nothing to do: handleData() pushes directly as bytes arrive. Node's\n // Duplex internally buffers what push() delivers, so this is a no-op\n // producer side; credit accounting happens in creditConsumed(), invoked\n // as the consumer actually reads out of that internal buffer (see\n // override of `read`/`_read` limitations below and the `data`/`resume`\n // driven credit hook installed in creditOnConsume()).\n }\n\n override _write(chunk: Uint8Array, _encoding: BufferEncoding, callback: (error?: Error | null) => void): void {\n if (this.terminalError) {\n // A write issued during or after termination: if the read side is\n // still mid-delivery of buffered data (heldWriteCallbacks non-null,\n // see terminateNoThrow), hold this callback too instead of calling it\n // with an error now -- same reasoning as drainWriteQueue's catch\n // below. Otherwise the read side is long done and there's nothing left\n // to block, so fail it immediately.\n if (this.heldWriteCallbacks) {\n this.heldWriteCallbacks.push(callback);\n } else {\n callback(this.terminalError);\n }\n return;\n }\n if (this.state !== \"open\" && this.state !== \"half_closed_remote\") {\n callback(new StreamError(ErrorCode.STREAM_CLOSED, this.id, \"write on a stream that is not open for sending\"));\n return;\n }\n this.writeQueue.push({ chunk, callback });\n this.pumpWriteQueue();\n }\n\n override _final(callback: (error?: Error | null) => void): void {\n this.closeWrite();\n callback();\n }\n\n override _destroy(err: Error | null, callback: (error?: Error | null) => void): void {\n if (this.state !== \"closed\") {\n this.reset(ErrorCode.CANCEL, err ? String(err.message) : \"local destroy\");\n }\n // Flush write callbacks terminateNoThrow held back for the read side (see\n // its doc comment) -- whether this destroy() is the one it scheduled via\n // 'end', or the read side never got there because something else (the\n // app's own destroy(), reset()) tore the stream down first.\n if (this.heldWriteCallbacks) {\n const held = this.heldWriteCallbacks;\n this.heldWriteCallbacks = null;\n for (const cb of held) cb(this.terminalError ?? err ?? undefined);\n }\n callback(err);\n }\n\n // --- outbound: send credit + chunking -------------------------------------\n\n private pumpWriteQueue(): void {\n if (this.draining) return;\n this.draining = true;\n void this.drainWriteQueue().finally(() => {\n this.draining = false;\n if (this.writeQueue.length > 0) this.pumpWriteQueue();\n });\n }\n\n private async drainWriteQueue(): Promise<void> {\n while (this.writeQueue.length > 0) {\n const item = this.writeQueue[0]!;\n let { chunk } = item;\n const { callback } = item;\n try {\n while (chunk.length > 0) {\n const n = await this.reserveSendCredit(chunk.length);\n const piece = chunk.subarray(0, n);\n chunk = chunk.subarray(n);\n await this.host.sendData(this.id, piece);\n }\n this.writeQueue.shift();\n callback();\n } catch (e) {\n this.writeQueue.shift();\n if (this.heldWriteCallbacks) {\n // terminateNoThrow is holding write errors until the read side\n // finishes delivering buffered data and emits 'end' (calling this\n // callback with an error right now would mark the readable side\n // errored too, per Node's own Writable/Duplex machinery, and block\n // that 'end' from ever firing) -- see its doc comment.\n this.heldWriteCallbacks.push(callback);\n continue;\n }\n if (this.state === \"half_closed_remote\") {\n // Same hazard, reached before terminateNoThrow() has run: the\n // peer's CLOSE already arrived, so the read side may still be\n // delivering buffered data, and the only way a send rejects here\n // is the connection's writer failing this chunk's socket write.\n // Once 'end' has fired there's nothing left to block, so fail the\n // callback now. Otherwise hold it until 'end', without relying on\n // teardown to flush it: the app's closeWrite() can retire the\n // stream before the socket's 'close' arrives, and then\n // rejectOutstanding() never reaches it. `failure` is the fallback\n // for a flush with no terminalError of its own.\n const failure = e as Error;\n if (!this.hasErrorListener()) {\n this.on(\"error\", NOOP_ERROR_LISTENER);\n }\n if (this.readableEnded) {\n callback(failure);\n continue;\n }\n this.heldWriteCallbacks = [(err) => callback(err ?? failure)];\n // Deferred a tick past 'end' so every 'end' listener -- the app's,\n // and Node's own for-await/pipeline() handling -- has run before\n // the callback's error can re-emit as 'error'.\n this.once(\"end\", () =>\n process.nextTick(() => {\n if (this.destroyed || !this.heldWriteCallbacks) return;\n // Closed by closeWrite(): already retired, so nothing else\n // will ever destroy it, and 'close' needs a destroy() on an\n // `autoDestroy: false` Duplex. _destroy() flushes, after\n // destroy has begun, so Node doesn't re-emit the error.\n if (this.state === \"closed\") {\n this.destroy();\n return;\n }\n // Still half_closed_remote: the teardown that's still coming\n // destroys it (right away, the buffer being empty now), but\n // the callback doesn't wait on it; _destroy() then finds the\n // array null.\n const held = this.heldWriteCallbacks;\n this.heldWriteCallbacks = null;\n for (const cb of held) cb(this.terminalError ?? undefined);\n }),\n );\n continue;\n }\n // Otherwise the write failed and its caller must hear that. Node's\n // Writable machinery re-emits a write callback's error as 'error', so\n // -- same rationale as terminateNoThrow() -- attach the internal no-op\n // listener first if nothing else is listening: this catch is also\n // reachable before terminateNoThrow() has run at all (the connection's\n // writer failed this chunk's socket write, and teardown follows from\n // the socket's own 'close'), and a dying connection must not crash a\n // consumer that never attached its own 'error'.\n if (!this.hasErrorListener()) {\n this.on(\"error\", NOOP_ERROR_LISTENER);\n }\n callback(e as Error);\n }\n }\n }\n\n /** Waits until send credit is available, then reserves and returns min(want, sendWindow, MAX_CHUNK). */\n private reserveSendCredit(want: number): Promise<number> {\n return new Promise((resolve, reject) => {\n const attempt = (): boolean => {\n if (this.terminalError) {\n reject(this.terminalError);\n return true;\n }\n if (this.state !== \"open\" && this.state !== \"half_closed_remote\") {\n reject(new StreamError(ErrorCode.STREAM_CLOSED, this.id, \"write on a stream that is not open for sending\"));\n return true;\n }\n if (this.sendWindow > 0) {\n const n = Math.min(want, this.sendWindow, MAX_CHUNK);\n this.sendWindow -= n;\n resolve(n);\n return true;\n }\n return false;\n };\n if (attempt()) return;\n this.sendWaiters.push(() => {\n if (attempt()) return;\n // still blocked; re-register (handleWindow/onTerminal calls waiters again)\n });\n });\n }\n\n private wakeSendWaiters(): void {\n const waiters = this.sendWaiters;\n this.sendWaiters = [];\n for (const w of waiters) w();\n }\n\n // --- inbound: DATA / credit-on-consume -------------------------------------\n\n /**\n * Applies a received DATA frame. Throws a ConnError (FLOW_CONTROL_ERROR) if it exceeds recv credit.\n * @internal\n */\n handleData(payload: Uint8Array): void {\n if (this.state === \"half_closed_remote\") {\n throw new StreamError(ErrorCode.STREAM_CLOSED, this.id, \"DATA received after this stream's CLOSE\");\n }\n if (this.state === \"closed\") {\n throw new StreamError(ErrorCode.STREAM_CLOSED, this.id, \"DATA received on a closed stream\");\n }\n const n = payload.length;\n if (n > this.recvWindow) {\n throw new ConnError(\n ErrorCode.FLOW_CONTROL_ERROR,\n `stream ${this.id}: received ${n} DATA bytes with ${this.recvWindow} credit remaining`,\n );\n }\n this.recvWindow -= n;\n // push() delivers bytes to the Duplex's readable side; credit is granted\n // back to the peer as the *application* consumes them (creditConsumed),\n // not on receipt (WIRE.md section 2.6). The read() override below\n // covers three of the four consumption modes (read(), pipe() and\n // for-await both drive Node's internal flow loop through the public\n // read() method) -- but Node has a \"fast path\" for on('data')/pipe()\n // consumers already in flowing mode with an empty internal buffer:\n // readableAddChunk emits 'data' synchronously from *inside* push(),\n // handing the chunk straight to the consumer without ever touching the\n // buffer or calling read(). Measuring readableLength across push() below\n // catches exactly that: whatever didn't end up buffered was delivered\n // (and thus consumed) right here, synchronously.\n const before = this.readableLength;\n this.push(Buffer.from(payload));\n const buffered = this.readableLength - before;\n const bypassed = n - buffered;\n if (bypassed > 0) this.creditConsumed(bypassed);\n }\n\n /**\n * Credits the peer back once bytes leave the internal buffer, mirroring Go\n * `Stream.creditConsumed` (stream.go) and its half-window threshold.\n */\n private creditConsumed(n: number): void {\n this.unacked += n;\n this.maybeDeliverEOF();\n const threshold = this.initialWindow / 2;\n if (this.unacked >= threshold && this.state !== \"closed\") {\n const increment = this.unacked;\n this.recvWindow += increment;\n this.unacked = 0;\n this.host.sendControlFrame(encodeWindow(this.id, increment));\n }\n }\n\n private eofDelivered = false;\n private internalBufferEmpty(): boolean {\n // readableLength is Duplex's own count of unread, buffered bytes.\n return this.readableLength === 0;\n }\n\n /**\n * Covers every consumption mode that goes through the buffer: explicit\n * paused-mode `.read()` calls, and Node's internal flow loop -- which\n * calls this same public `read()` method to back `on('data')`, `pipe()`\n * and `for await` once bytes are actually sitting in the buffer (as\n * opposed to handleData()'s synchronous fast-path bypass above, which\n * never reaches the buffer at all). Between the two, every byte is\n * credited exactly once: whatever left the buffer here, plus whatever\n * never entered it there.\n */\n override read(size?: number): unknown {\n const before = this.readableLength;\n const result: unknown = super.read(size);\n const consumed = before - this.readableLength;\n if (consumed > 0) this.creditConsumed(consumed);\n return result;\n }\n\n // --- inbound: WINDOW / CLOSE / RESET ---------------------------------------\n\n /**\n * Applies a received WINDOW frame's credit increment. Throws ConnError on overflow past 2^31-1.\n * @internal\n */\n handleWindow(increment: number): void {\n // WINDOW must be tolerated on a half-closed or fully-closed stream (the\n // one race the ordered transport does not remove, WIRE.md section\n // 2.5); only the cumulative overflow case is rejected.\n const newWindow = this.sendWindow + increment;\n if (newWindow > MAX_SEND_WINDOW) {\n throw new ConnError(ErrorCode.FLOW_CONTROL_ERROR, `stream ${this.id}: WINDOW would push the send window to ${newWindow}, past 2^31-1`);\n }\n if (this.state === \"closed\") return;\n this.sendWindow = newWindow;\n this.wakeSendWaiters();\n }\n\n /**\n * Applies a received CLOSE frame: half-close-remote (peer will send no more DATA).\n * @internal\n */\n handleClose(): void {\n switch (this.state) {\n case \"open\":\n this.state = \"half_closed_remote\";\n break;\n case \"half_closed_local\":\n this.state = \"closed\";\n this.host.retireStream(this.id);\n break;\n case \"half_closed_remote\":\n throw new StreamError(ErrorCode.STREAM_CLOSED, this.id, \"duplicate CLOSE received\");\n case \"closed\":\n return;\n }\n this.remoteClosed = true;\n this.maybeDeliverEOF();\n }\n\n // CLOSE preserves buffered data; EOF (push(null)) only follows once every\n // buffered byte has been delivered to the application (WIRE.md section\n // 2.5: \"deliver those bytes and *then* EOF\"). Called both right after\n // handleClose() (buffer may already be empty) and from creditConsumed() as\n // the application keeps draining the buffer.\n private maybeDeliverEOF(): void {\n if (!this.eofDelivered && this.remoteClosed && this.internalBufferEmpty()) {\n this.eofDelivered = true;\n this.push(null);\n }\n }\n\n /** True once something is listening for `'error'`: gates attaching the internal no-op `'error'` listener (terminateNoThrow, drainWriteQueue). */\n private hasErrorListener(): boolean {\n return this.listenerCount(\"error\") > 0;\n }\n\n /**\n * Applies a received RESET frame: any state, discard buffered data, go to\n * closed (WIRE.md: RESET discards buffered data, unlike CLOSE). Node\n * throws synchronously out of destroy()/a write callback when an 'error'\n * is emitted with no listener -- terminateNoThrow's internal no-op\n * listener is what keeps a peer RESET (including CANCEL, the ordinary path\n * for a client-initiated `close()`) from crashing a consumer that never\n * attached its own `'error'`, while still surfacing the code via\n * `resetCode`/`'reset'`/`stream.errored` instead of a false clean end.\n * @internal\n */\n handleReset(code: number, message: string): void {\n this.state = \"closed\";\n const err = new StreamError(code, this.id, message);\n this.terminalError = err;\n this.resetCode = code;\n this.wakeSendWaiters();\n this.emit(\"reset\", { code, message });\n this.terminateNoThrow(err, { discardBuffered: true });\n this.host.retireStream(this.id);\n }\n\n /**\n * Tears this stream down with `err`. Used by handleReset()/applyReset() (a\n * stream-level RESET, peer-sent or app-initiated -- both pass\n * `{discardBuffered: true}`, per WIRE.md: \"RESET discards buffered data\")\n * and by MixerConn.rejectOutstanding() (a connection-level failure aborts\n * every stream still live when it hit -- no `discardBuffered`).\n *\n * Two different endings, per CLIENT-SDK.md's \"Stream teardown on\n * disconnect\" row:\n *\n * Node's own 'error' event throws synchronously and crashes the process\n * when nothing is listening for it -- an internal no-op 'error' listener is\n * attached first, unconditionally, whenever nothing else is listening\n * (harmless -- the stream is terminating either way): needed by the\n * destroy(err) branch below directly, and by the push(null) branch too,\n * since even there the WRITE side is still live from Node's own Writable\n * machinery's point of view, and a later write's `_write` callback error\n * (`terminalError`, set above) still makes Node itself try to emit\n * 'error'.\n *\n * - `opts?.discardBuffered`, OR the peer's own CLOSE never arrived on this\n * stream (`!this.remoteClosed`): destroy(err) -- this stream's read side\n * never legitimately finished, so it ends with an error, never a clean\n * end-of-stream. 'end' is never emitted from this branch, so a consumer\n * with only 'data'/'end'/'close' listeners sees 'close' (and\n * `stream.errored`) but never a false clean end; pipe()/pipeline()/\n * for-await consumers see `err` exactly as they would with a real\n * 'error' listener attached.\n * - Otherwise (no `discardBuffered`, the peer's CLOSE already arrived --\n * `this.remoteClosed`): this stream's READ side genuinely ended cleanly\n * on the wire before the connection died, and CLIENT-SDK.md requires the\n * bytes it preserved to be delivered first, followed by 'end', followed\n * by 'close' -- never an 'error'. `push(null)` does NOT discard whatever\n * Node's Duplex still has buffered -- it marks EOF, and Node delivers the\n * buffered bytes to the consumer and only then emits 'end' (unlike\n * `destroy(err)`, which WOULD discard them). No 'error' event fires: the\n * response DID complete -- but `stream.errored` does NOT stay `null`\n * forever: once `heldWriteCallbacks` is flushed (see below), a write\n * callback is called with `err`, and Node's own Writable machinery marks\n * `stream.errored` from that alone, same as any other write error. Once\n * 'end' has actually been delivered (immediately, if the buffer was\n * already empty; otherwise once the consumer has drained it), this\n * stream is `destroy()`'d with no error -- required for 'close' to ever\n * fire on an `autoDestroy: false` Duplex, which this stream is\n * (`emitClose: true` alone only arms the event, it doesn't schedule it).\n * `eofDelivered` guards against calling `push(null)` a second time if\n * `maybeDeliverEOF()` already did (buffer was already empty when CLOSE\n * arrived); in that case the buffer is still (and can only still be)\n * empty here too -- nothing can add to it once `remoteClosed` is set --\n * so this destroys right away rather than waiting on an 'end' that may\n * already have fired, or may never (a consumer that never reads never\n * sees Node's own 'end', the same way it never would on any other\n * Readable).\n *\n * A write outstanding at this exact moment (`writeQueue` non-empty, one\n * item mid-`drainWriteQueue`, or one issued via `_write` after this call)\n * still needs its callback settled with `err` -- the caller is waiting on\n * it -- but calling it with an error *before* 'end' has fired would make\n * Node's own Writable/Duplex machinery mark the READABLE side `errored`\n * too, permanently blocking 'end' regardless of whether an 'error'\n * listener is attached. So instead of calling those callbacks here,\n * `heldWriteCallbacks` (non-null for exactly this stretch) collects them\n * -- pushed onto by `drainWriteQueue`'s catch and by `_write`'s\n * terminal-error branch -- and `_destroy()` flushes them with `err`\n * right after `destroy()` actually runs (whether that's the 'end'-\n * triggered `destroy()` below, or the read side never gets there because\n * something else destroys the stream first): by then the readable side\n * is already finished or was never going to finish anyway, so marking it\n * `errored` is no longer a concern.\n *\n * Accepted tradeoff: a held write callback is settled only once the read\n * side has actually been drained ('end') or the stream is destroyed some\n * other way (app destroy(), reset()). An app that awaits a write's\n * callback without ever reading this stream will wait until it does one\n * or the other.\n *\n * `this.state = \"closed\"` up front (not just `terminalError`) matters for\n * more than bookkeeping: `_destroy()`'s own guard (`if (this.state !==\n * \"closed\") this.reset(...)`) would otherwise re-enter here through\n * reset()/applyReset()/terminateNoThrow() a second time, overwriting the\n * real `err` with a generic CANCEL, in the destroy(err) branch above.\n *\n * Setting `terminalError` and waking every sendWaiter (not just calling\n * destroy()) is what makes a write already blocked on send credit\n * (drainWriteQueue's reserveSendCredit) -- and everything still queued\n * behind it -- fail promptly with `err` too, in EITHER branch: each\n * rejection drains the next queued item in turn via drainWriteQueue's own\n * loop, so no separate queue-flushing step is needed here.\n *\n * A stream already fully, cleanly closed (both directions CLOSE'd) has\n * already called `retireStream()` and is no longer tracked by MixerConn,\n * so `rejectOutstanding` never reaches it here at all -- only a stream\n * still genuinely open, or half-closed one way, ever is.\n *\n * `resetCode` is set here too, but only when `err` is a `StreamError`\n * (mirrors handleReset()/applyReset(), which construct one for exactly\n * this): the drain-hand-over `streamErrorFactory` override (client.ts)\n * hands a stream-scoped `StreamError(CANCEL, \"connection drained\")` for a\n * CONNECTION teardown (no `discardBuffered`) -- deliberately NOT what\n * gates the buffered-data decision above (that's `opts.discardBuffered`\n * alone), since a `StreamError` here doesn't mean \"discard\": it would\n * otherwise wrongly discard a response that had already completed on\n * every drain hand-over. From the stream's own perspective a `StreamError`\n * IS a fourth way it ends in RESET, on top of the three the class doc\n * above lists, distinct from an ordinary `WsMixerError`/`ConnError`\n * connection failure, which is not RESET-shaped and leaves `resetCode`\n * unset.\n * @internal\n */\n terminateNoThrow(err: WsMixerError, opts?: { discardBuffered?: boolean }): void {\n this.state = \"closed\";\n this.terminalError = err;\n if (err instanceof StreamError) this.resetCode = err.code;\n const deferWriteErrors = !opts?.discardBuffered && this.remoteClosed;\n if (deferWriteErrors) this.heldWriteCallbacks = this.heldWriteCallbacks ?? [];\n this.wakeSendWaiters();\n // Attached unconditionally, before either branch below: even in the\n // push(null) branch, the WRITE side is still live from Node's own\n // Writable machinery's point of view (only the readable side ends here)\n // -- a later write's `_write` callback error (terminalError, above)\n // still makes Node itself try to emit 'error', which would otherwise\n // crash a consumer that only ever cared about reading.\n if (!this.hasErrorListener()) {\n this.on(\"error\", NOOP_ERROR_LISTENER);\n }\n if (deferWriteErrors) {\n const hadBufferedData = !this.internalBufferEmpty();\n if (!this.eofDelivered) {\n this.eofDelivered = true;\n this.push(null);\n }\n if (hadBufferedData) {\n this.once(\"end\", () => this.destroy());\n } else {\n this.destroy();\n }\n return;\n }\n this.destroy(err);\n }\n\n /**\n * Reports whether this stream must no longer send DATA (CLOSE sent, or RESET).\n * @internal\n */\n sendDone(): { done: boolean; error?: WsMixerError } {\n if (this.state === \"half_closed_local\" || this.state === \"closed\") {\n if (this.terminalError) return { done: true, error: this.terminalError };\n return { done: true, error: new StreamError(ErrorCode.STREAM_CLOSED, this.id, \"DATA not sent: CLOSE already sent on this stream\") };\n }\n return { done: false };\n }\n\n // --- public half-close / reset API -----------------------------------------\n\n /** Sends CLOSE: \"I will send no more DATA on this stream.\" end() is sugar for this via _final. */\n closeWrite(): void {\n switch (this.state) {\n case \"open\":\n this.state = \"half_closed_local\";\n break;\n case \"half_closed_remote\":\n this.state = \"closed\";\n break;\n case \"half_closed_local\":\n case \"closed\":\n return; // already sent, or nothing to send to\n }\n const closedNow = this.state === \"closed\";\n this.host.sendControlFrame(encodeClose(this.id));\n if (closedNow) this.host.retireStream(this.id);\n }\n\n /**\n * Sends CLOSE (if not already sent) and stops delivering further reads. If\n * the peer has not yet half-closed its own send side, a plain closeWrite()\n * would leave it writing into a window nobody drains, so close() also\n * RESETs with CANCEL in that case (mirrors go/wsmixer/stream.go Close()).\n */\n close(): void {\n if (!this.remoteClosed) {\n this.closeWrite();\n this.reset(ErrorCode.CANCEL, \"local close: peer had not finished sending\");\n return;\n }\n this.closeWrite();\n this.destroy();\n }\n\n /**\n * Aborts the stream in both directions with the given error code and\n * message, discarding buffered data. `code` may be a numeric ws-mixer.v1\n * error code or its wire name (e.g. `\"CANCEL\"`); `message` is truncated to\n * 256 UTF-8 bytes on a character boundary (WIRE.md section 2.3: a RESET\n * message SHOULD be <= 256 B).\n *\n * Same \"don't throw with nobody listening\" rule as handleReset(): an\n * internal no-op 'error' listener (attached by terminateNoThrow() when\n * nothing else is) keeps this from crashing a consumer with no 'error' of\n * its own, while still surfacing the code via `resetCode`/`stream.errored`\n * (buffered data discarded, per RESET's own semantics: terminateNoThrow()\n * is called with `{discardBuffered: true}`).\n *\n * This is the app-facing API: calling code already knows why it's\n * resetting its own stream, so unlike a peer-sent RESET (handleReset()) or\n * an SDK-detected violation (abort()), `reset()` does NOT emit `'reset'`.\n */\n reset(code: number | string, message = \"\"): void {\n this.applyReset(code, message);\n }\n\n /**\n * Internal counterpart to reset(): used when the *connection* -- not\n * application code -- is what decided to tear this stream down (an\n * SDK-detected protocol violation surfaced as a `StreamError` from\n * MixerConn's dispatch loop). Identical effect to reset(), but also emits\n * `'reset'` the same way handleReset() does for a peer-sent RESET: in both\n * cases the stream's owner didn't choose this, so it needs to hear about\n * it. No-ops (and does not emit) if the stream is already closed.\n * @internal\n */\n abort(code: number | string, message = \"\"): void {\n if (this.applyReset(code, message)) {\n this.emit(\"reset\", { code: this.resetCode!, message: this.terminalError!.message });\n }\n }\n\n /** Shared implementation behind reset()/abort(); returns false (no-op) if the stream was already closed. */\n private applyReset(code: number | string, message: string): boolean {\n if (this.state === \"closed\") {\n // terminateNoThrow may have left this stream closed but not yet\n // destroy()'d, holding write callbacks back for the read side to drain\n // (see its doc comment). reset() is documented to flush those\n // callbacks, so nudge the stream the rest of the way there.\n if (this.heldWriteCallbacks && !this.destroyed) this.destroy();\n return false;\n }\n const numericCode = typeof code === \"string\" ? parseErrorCode(code) : code;\n if (numericCode === undefined) {\n throw new Error(`unknown ws-mixer error code name: ${code as string}`);\n }\n const truncated = truncateUtf8(message, 256);\n this.state = \"closed\";\n this.terminalError = new StreamError(numericCode, this.id, truncated);\n this.resetCode = numericCode;\n this.wakeSendWaiters();\n this.host.sendControlFrame(encodeReset(this.id, numericCode, truncated));\n this.host.retireStream(this.id);\n this.terminateNoThrow(this.terminalError, { discardBuffered: true });\n return true;\n }\n\n /**\n * Wraps this stream as Web Streams API `{ readable, writable }`, e.g. for\n * `fetch`'s `duplex` option or any other Web Streams consumer.\n */\n toWeb(): ReturnType<typeof Duplex.toWeb> {\n return Duplex.toWeb(this);\n }\n}\n","/** Small helpers shared by stream.ts and conn.ts. */\n\n/** Truncates `message` to at most `maxBytes` UTF-8 bytes, backing off to the nearest character boundary. */\nexport function truncateUtf8(message: string, maxBytes: number): string {\n const bytes = new TextEncoder().encode(message);\n if (bytes.length <= maxBytes) return message;\n let end = maxBytes;\n while (end > 0 && (bytes[end]! & 0xc0) === 0x80) end--;\n return new TextDecoder().decode(bytes.subarray(0, end));\n}\n"],"mappings":"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","names":["import_node_events","utf8Encoder","utf8Decoder","stream"]}