/** * hosting/webSocketFrames — the frame codec the conversation door speaks, and * nothing else. * * Pure bytes in, pure bytes out: no socket, no session, no handler, no port * vocabulary. That is what makes it testable the only way protocol code is * worth testing — against the byte sequences the specification itself * publishes, rather than against a client we also wrote. * * ── Why this file exists instead of a dependency ───────────────────────────── * The alternative was an optional peer dependency carrying a complete * implementation. It was rejected for a reason that is about honesty rather * than about lines of code: `capabilities` is declared at CONSTRUCTION and is * static thereafter, so a host whose conversation door only works when an * optional package happens to be installed can only either claim * `'conversation'` and then refuse to do it — the library declaring a promise * it cannot keep, which is the one thing the capability union forbids — or * probe `node_modules` and let feature detection depend on install state, so a * deployment that forgot the dependency silently gets a host that quietly does * not do conversations. The shipped adapter honours what it declares, always, * with nothing to install. That was worth these bytes. * * ── What is implemented, exactly ───────────────────────────────────────────── * The server half of RFC 6455 for a text channel: the handshake, text frames, * continuation frames, ping/pong, and close. Payloads are unmasked on the way * in (client frames MUST be masked) and written unmasked on the way out (server * frames MUST NOT be), which is the asymmetry the RFC requires. * * ── What is NOT, and how far the verification goes ─────────────────────────── * No extensions and no compression: `permessage-deflate` is negotiated, and * this door never negotiates it, so the RSV bits must always be zero and a peer * that sets one is refused. No binary frames — the port carries text, and * binary is a capability nobody has minted evidence for. No client role. * * Verification is: the byte vectors published in RFC 6455 §5.7, which were * authored by the specification and not by this repository, plus a live * exchange against the platform's own WebSocket client where the runtime has * one, plus the conversation conformance suite over a real socket. It is **not** * run against the Autobahn test suite, and this file does not claim more * coverage than the tests beside it actually execute. * * Pattern: pure codec. Role: innermost ring — imports nothing from this package. */ /// /// /** The frame kinds this door speaks. Numbers, because the wire uses numbers. */ export declare const OPCODE: { readonly continuation: 0; readonly text: 1; readonly binary: 2; readonly close: 8; readonly ping: 9; readonly pong: 10; }; /** * Close codes this door produces. The RFC's own numbers, named — a bare `1009` * in a branch is a fact nobody can check without opening the specification. */ export declare const CLOSE_CODE: { /** Ordinary end: the work is done. */ readonly normal: 1000; /** The host is shutting down. */ readonly goingAway: 1001; /** The peer broke the protocol. */ readonly protocolError: 1002; /** Data this endpoint cannot accept — a binary frame, here. */ readonly unsupportedData: 1003; /** Reserved: never sent on the wire, used only as "we were not told". */ readonly noStatus: 1005; /** Text that is not valid UTF-8. */ readonly invalidPayload: 1007; /** Past a declared ceiling. */ readonly tooBig: 1009; /** * Something went wrong on THIS side — the RFC's own word for a condition * that stopped this endpoint fulfilling the request. Used where the fault is * demonstrably not the peer's, so that a close code never blames them for it. */ readonly internalError: 1011; }; /** One frame, as it came off the wire, already unmasked. */ export interface WebSocketFrame { /** Last frame of its message? */ readonly fin: boolean; /** One of {@link OPCODE}. */ readonly opcode: number; /** The unmasked payload. */ readonly payload: Buffer; } /** * A peer that broke the protocol, carrying the close code the RFC names for * that breakage — so the caller does not have to re-derive which number means * what at the point it has to write one. */ export declare class FrameProtocolError extends Error { readonly closeCode: number; constructor(closeCode: number, message: string); } /** * The `Sec-WebSocket-Accept` value for a client's `Sec-WebSocket-Key`: * base64(sha1(key + GUID)). Deterministic, and pinned against the RFC's own * worked example. */ export declare function acceptKey(clientKey: string): string; /** * The 101 response, as bytes. * * `protocol` is echoed only when the caller selected one. Echoing a subprotocol * the client did not offer would make the client fail the connection, so * choosing one is the wire's job (it read the offer) and not this function's. */ export declare function handshakeResponse(clientKey: string, protocol?: string): Buffer; /** * One frame, ready to write. **Never masked**: a server that masks its frames * is a server the client closes on, per RFC 6455 §5.1. */ export declare function encodeFrame(opcode: number, payload: Buffer, fin?: boolean): Buffer; /** A text frame carrying one whole message. */ export declare function encodeText(text: string): Buffer; /** * A close frame carrying a code and, when there is one, a reason. * * The reason is truncated to 123 bytes because a control frame's payload may * not exceed 125 and the code takes two of them. Truncating a REASON is safe in * a way truncating a message never is: it is diagnostic prose about an ending * that has already been decided. */ export declare function encodeClose(code: number, reason?: string): Buffer; /** What a close frame said, pulled apart. */ export interface CloseFramePayload { /** The peer's code, or `noStatus` when it sent none — which is legal. */ readonly code: number; /** The peer's words, when it sent any. */ readonly reason?: string; } /** Read a close frame's payload. An empty payload is "no status", never an error. */ export declare function decodeClose(payload: Buffer): CloseFramePayload; /** Is this one of the three control opcodes? */ export declare function isControlFrame(opcode: number): boolean; /** * Turns a stream of TCP chunks into whole frames. * * Stateful by necessity — a frame arrives in as many pieces as the network * feels like, and two frames arrive in one piece just as often. Every branch * that gives up does so by throwing a {@link FrameProtocolError} carrying the * code to close with, so the layer above never has to invent one. */ export declare class FrameReader { private buffered; private readonly maxFrameBytes; /** * @param maxFrameBytes The declared ceiling, when there is one. Checked * against the length in the HEADER — before the payload is buffered, so an * announced 4GB frame costs nothing to refuse. */ constructor(maxFrameBytes?: number); /** Feed one chunk; get back every frame that completed. */ push(chunk: Buffer): WebSocketFrame[]; /** One frame, or `undefined` when the bytes for a whole one are not here yet. */ private readOne; } /** * Decode a whole message's bytes as UTF-8, refusing anything that is not. * * Strict on purpose (RFC 6455 §8.1): a text frame that is not valid UTF-8 is a * protocol violation, and the lenient alternative silently replaces the bad * bytes with `U+FFFD` — handing the consumer a string the far side never sent * and no way to know it happened. */ export declare function decodeText(payload: Buffer): string;