@orkestrel/websocket 0.0.10 → 0.0.12

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.
@@ -3,180 +3,259 @@ let node_crypto = require("node:crypto");
3
3
  let _orkestrel_emitter = require("@orkestrel/emitter");
4
4
  //#region src/server/constants.ts
5
5
  /**
6
- * The RFC 6455 GUID concatenated to a client's `Sec-WebSocket-Key` before the SHA-1
7
- * hash that yields the `Sec-WebSocket-Accept` response value.
6
+ * Names the accept GUID concatenated to a client's `Sec-WebSocket-Key` before the accept
7
+ * hash, '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'.
8
8
  *
9
9
  * @remarks
10
- * A fixed, spec-mandated constant (RFC 6455 §4.2.2) read only by
10
+ * The base64-encoded SHA-1 of that concatenation is the `Sec-WebSocket-Accept` response
11
+ * value. A fixed, spec-mandated constant (RFC 6455 §4.2.2) — read only by
11
12
  * {@link computeWebSocketAccept}.
12
13
  */
13
14
  var WEBSOCKET_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
14
- /** The WebSocket protocol version this wrapper speaks (`Sec-WebSocket-Version: 13`). */
15
+ /**
16
+ * Names the supported protocol version, '13'.
17
+ *
18
+ * @remarks
19
+ * The value this wrapper speaks, carried by the `Sec-WebSocket-Version` handshake header.
20
+ */
15
21
  var WEBSOCKET_VERSION = "13";
16
- /** Text frame opcode — a UTF-8 payload (RFC 6455 §5.6). */
22
+ /**
23
+ * Names the text frame opcode, 0x01.
24
+ *
25
+ * @remarks
26
+ * A UTF-8 payload (RFC 6455 §5.6).
27
+ */
17
28
  var WEBSOCKET_OPCODE_TEXT = 1;
18
- /** Binary frame opcode — a raw byte payload (RFC 6455 §5.6). */
29
+ /**
30
+ * Names the binary frame opcode, 0x02.
31
+ *
32
+ * @remarks
33
+ * A raw byte payload (RFC 6455 §5.6).
34
+ */
19
35
  var WEBSOCKET_OPCODE_BINARY = 2;
20
- /** Continuation frame opcode — the next fragment of an open data message (RFC 6455 §5.4). */
36
+ /**
37
+ * Names the continuation frame opcode, 0x00.
38
+ *
39
+ * @remarks
40
+ * The next fragment of an open data message (RFC 6455 §5.4).
41
+ */
21
42
  var WEBSOCKET_OPCODE_CONTINUATION = 0;
22
- /** Close frame opcode — a control frame ending the connection (RFC 6455 §5.5.1). */
43
+ /**
44
+ * Names the close frame opcode, 0x08.
45
+ *
46
+ * @remarks
47
+ * A control frame ending the connection (RFC 6455 §5.5.1).
48
+ */
23
49
  var WEBSOCKET_OPCODE_CLOSE = 8;
24
- /** Ping frame opcode — a control frame the peer must answer with a pong (RFC 6455 §5.5.2). */
50
+ /**
51
+ * Names the ping frame opcode, 0x09.
52
+ *
53
+ * @remarks
54
+ * A control frame the peer must answer with a pong (RFC 6455 §5.5.2).
55
+ */
25
56
  var WEBSOCKET_OPCODE_PING = 9;
26
- /** Pong frame opcode — a control frame answering a ping (RFC 6455 §5.5.3). */
57
+ /**
58
+ * Names the pong frame opcode, 0x0a.
59
+ *
60
+ * @remarks
61
+ * A control frame answering a ping (RFC 6455 §5.5.3).
62
+ */
27
63
  var WEBSOCKET_OPCODE_PONG = 10;
28
- /** Ready state for a connecting WebSocket (before the handshake completes). */
64
+ /**
65
+ * Names the connecting ready state, 0.
66
+ *
67
+ * @remarks
68
+ * The state a WebSocket holds before its handshake completes.
69
+ */
29
70
  var WEBSOCKET_READY_CONNECTING = 0;
30
- /** Ready state for an open WebSocket (the handshake completed; frames flow). */
71
+ /**
72
+ * Names the open ready state, 1.
73
+ *
74
+ * @remarks
75
+ * The state a WebSocket holds after the handshake completes and while frames flow.
76
+ */
31
77
  var WEBSOCKET_READY_OPEN = 1;
32
- /** Ready state for a closing WebSocket (a close frame was sent or received). */
78
+ /**
79
+ * Names the closing ready state, 2.
80
+ *
81
+ * @remarks
82
+ * The state a WebSocket holds after a close frame is sent or received.
83
+ */
33
84
  var WEBSOCKET_READY_CLOSING = 2;
34
- /** Ready state for a closed WebSocket (the socket ended). */
85
+ /**
86
+ * Names the closed ready state, 3.
87
+ *
88
+ * @remarks
89
+ * The state a WebSocket holds after the socket ends.
90
+ */
35
91
  var WEBSOCKET_READY_CLOSED = 3;
36
- /** Normal-closure status code (RFC 6455 §7.4.1) — the default `close` code. */
92
+ /**
93
+ * Names the normal-closure status code, 1000.
94
+ *
95
+ * @remarks
96
+ * The default `close` code (RFC 6455 §7.4.1).
97
+ */
37
98
  var WEBSOCKET_CLOSE_NORMAL = 1e3;
38
- /** Protocol-error status code (RFC 6455 §7.4.1) — a framing/state rule was violated. */
99
+ /**
100
+ * Names the protocol-error status code, 1002.
101
+ *
102
+ * @remarks
103
+ * Sent when a framing or state rule was violated (RFC 6455 §7.4.1).
104
+ */
39
105
  var WEBSOCKET_CLOSE_PROTOCOL = 1002;
40
- /** Unsupported-data status code (RFC 6455 §7.4.1) — the endpoint received a data type it cannot accept (e.g. binary on a text-only endpoint). */
106
+ /**
107
+ * Names the unsupported-data status code, 1003.
108
+ *
109
+ * @remarks
110
+ * Sent when the endpoint received a data type it cannot accept (RFC 6455 §7.4.1), for
111
+ * example binary on a text-only endpoint.
112
+ */
41
113
  var WEBSOCKET_CLOSE_UNSUPPORTED = 1003;
42
- /** Invalid-frame-payload-data status code (RFC 6455 §7.4.1) — e.g. non-UTF-8 text or an unparseable close reason. */
114
+ /**
115
+ * Names the invalid-frame-payload-data status code, 1007.
116
+ *
117
+ * @remarks
118
+ * Sent for non-UTF-8 text or an unparseable close reason (RFC 6455 §7.4.1).
119
+ */
43
120
  var WEBSOCKET_CLOSE_INVALID = 1007;
44
- /** Message-too-big status code (RFC 6455 §7.4.1) — a reassembled message exceeded the payload cap. */
45
- var WEBSOCKET_CLOSE_TOOBIG = 1009;
46
- /** The default maximum inbound single-frame length AND reassembled-message total byte count (100 MiB — the `ws` package default). */
121
+ /**
122
+ * Names the message-too-big status code, 1009.
123
+ *
124
+ * @remarks
125
+ * Sent when a reassembled message exceeded the payload cap (RFC 6455 §7.4.1).
126
+ */
127
+ var WEBSOCKET_CLOSE_TOO_BIG = 1009;
128
+ /**
129
+ * Names the default cap on both an inbound frame's declared length and a reassembled
130
+ * message's total byte count, 104,857,600 bytes (100 MiB).
131
+ *
132
+ * @remarks
133
+ * The same value the `ws` package defaults to. Either breach closes
134
+ * {@link WEBSOCKET_CLOSE_TOO_BIG}.
135
+ */
47
136
  var WEBSOCKET_MAX_PAYLOAD = 104857600;
48
- /** The default close-handshake timeout in milliseconds — how long `close()` waits for the peer's echo before tearing the socket down. */
137
+ /**
138
+ * Names the default close-handshake timeout, 30,000 milliseconds — how long `close` waits
139
+ * for the peer's echo.
140
+ *
141
+ * @remarks
142
+ * After it expires the wrapper tears the socket down, so a silent peer cannot leak the
143
+ * handle open.
144
+ */
49
145
  var WEBSOCKET_CLOSE_TIMEOUT_MS = 3e4;
50
- /** The post-`#fail` flush grace in milliseconds — how long a validation-breach close frame is given to flush through the socket's write buffer before the hard `destroy()` fallback fires (the normal path destroys sooner, on the `end()` flush callback). */
51
- var WEBSOCKET_FAIL_TIMEOUT_MS = 1e3;
52
- /** The maximum control-frame payload length in bytes (RFC 6455 §5.5). */
53
- var WEBSOCKET_CONTROL_MAXLEN = 125;
54
- /** The maximum UTF-8 close-reason length after the two-byte status code. */
55
- var WEBSOCKET_CLOSE_REASON_MAXLEN = 123;
56
- //#endregion
57
- //#region src/server/helpers.ts
58
146
  /**
59
- * Compute the `Sec-WebSocket-Accept` response value for an RFC 6455 upgrade.
147
+ * Names the flush grace, 1,000 milliseconds, a validation-breach close frame is given
148
+ * before the hard teardown fallback destroys the socket.
60
149
  *
61
150
  * @remarks
62
- * The base64-encoded SHA-1 of the client's `Sec-WebSocket-Key` concatenated with the
63
- * fixed {@link WEBSOCKET_GUID} (RFC 6455 §4.2.2) the proof the server understood the
64
- * handshake. Pure and deterministic.
151
+ * Armed after `#fail` writes the close frame, so the frame drains through the socket's
152
+ * write buffer rather than being discarded. The normal path destroys sooner, on the
153
+ * `end()` flush callback.
154
+ */
155
+ var WEBSOCKET_FAIL_TIMEOUT_MS = 1e3;
156
+ /**
157
+ * Names the maximum control-frame payload length, 125 bytes.
65
158
  *
66
- * @param key - The client's `Sec-WebSocket-Key` header value
67
- * @returns The base64 accept token to send back as `Sec-WebSocket-Accept`
159
+ * @remarks
160
+ * The cap RFC 6455 §5.5 sets on every control frame's payload.
68
161
  */
69
- function computeWebSocketAccept(key) {
70
- return (0, node_crypto.createHash)("sha1").update(key + WEBSOCKET_GUID).digest("base64");
71
- }
162
+ var WEBSOCKET_CONTROL_MAX_LENGTH = 125;
72
163
  /**
73
- * Whether a value is a canonical RFC 6455 `Sec-WebSocket-Key`.
164
+ * Names the maximum UTF-8 close-reason length after the two-byte status code, 123.
74
165
  *
75
166
  * @remarks
76
- * A valid key is exactly 16 random bytes encoded as 24 characters of base64, ending
77
- * in `==` (RFC 6455 §4.1). This predicate is suitable at an HTTP upgrade boundary:
78
- * malformed or non-canonical encodings return `false`; nothing is thrown.
167
+ * What is left of {@link WEBSOCKET_CONTROL_MAX_LENGTH} after the close frame's status
168
+ * code.
169
+ */
170
+ var WEBSOCKET_CLOSE_REASON_MAX_LENGTH = 123;
171
+ //#endregion
172
+ //#region src/server/errors.ts
173
+ /**
174
+ * Represents an error the WebSocket wrapper throws for a refused caller-supplied value,
175
+ * carrying a machine-readable `code` and an optional `context`.
79
176
  *
80
- * @param key - The proposed `Sec-WebSocket-Key` header value
81
- * @returns `true` when `key` is the canonical base64 encoding of 16 bytes
177
+ * @remarks
178
+ * The `code` is a {@link WebSocketErrorCode}; the `context` record holds the refused
179
+ * value under a key naming it: an `'OPTION'` carries the offending option (`payload`,
180
+ * `timeout`, `key`, or `protocol`), a `'LIMIT'` carries `size` and the `limit` it
181
+ * exceeded, a `'CLOSE'` carries the refused close `code`, and a `'FRAME'` carries
182
+ * `opcode` or the mask's `size`. Narrow a caught value with {@link isWebSocketError}.
82
183
  *
83
184
  * @example
84
185
  * ```ts
85
- * const key = request.headers['sec-websocket-key']
86
- * if (typeof key !== 'string' || !isWebSocketKey(key)) socket.destroy()
186
+ * import { createNodeWebSocket, isWebSocketError } from '@src/server'
187
+ *
188
+ * try {
189
+ * createNodeWebSocket({ socket, key: 'not-base64' })
190
+ * } catch (error) {
191
+ * if (isWebSocketError(error) && error.code === 'OPTION') socket.destroy()
192
+ * }
87
193
  * ```
88
194
  */
89
- function isWebSocketKey(key) {
90
- if (!/^[A-Za-z0-9+/]{22}==$/.test(key)) return false;
91
- return Buffer.from(key, "base64").length === 16;
92
- }
195
+ var WebSocketError = class extends Error {
196
+ code;
197
+ context;
198
+ /**
199
+ * Creates a WebSocket error carrying a machine-readable code.
200
+ *
201
+ * @param code - The machine-readable {@link WebSocketErrorCode} a `catch` branches on
202
+ * @param message - The human-readable description, carried as the `Error` message
203
+ * @param context - The refused value keyed by name; omitted leaves `context` `undefined`
204
+ */
205
+ constructor(code, message, context) {
206
+ super(message);
207
+ this.name = "WebSocketError";
208
+ this.code = code;
209
+ if (context !== void 0) this.context = context;
210
+ }
211
+ };
93
212
  /**
94
- * Whether a value is one valid WebSocket subprotocol token.
213
+ * Checks whether a caught value is a {@link WebSocketError}, narrowing it so a `catch` can
214
+ * branch on `error.code`.
95
215
  *
96
- * @remarks
97
- * Subprotocols use the HTTP `token` grammar. Whitespace, separators, commas, and
98
- * control characters are rejected, preventing an untrusted value from injecting a
99
- * second handshake header.
100
- *
101
- * @param protocol - The negotiated subprotocol to validate
102
- * @returns `true` when `protocol` is one non-empty HTTP token
216
+ * @param value - The value to test (typically a `catch` binding)
217
+ * @returns True if `value` is a `WebSocketError`; false otherwise
103
218
  *
104
219
  * @example
105
220
  * ```ts
106
- * if (!isWebSocketProtocol(protocol)) throw new RangeError('invalid protocol')
221
+ * import { isWebSocketError } from '@src/server'
222
+ *
223
+ * try {
224
+ * ws.close(1000.5)
225
+ * } catch (error) {
226
+ * if (isWebSocketError(error) && error.code === 'CLOSE') ws.close()
227
+ * }
107
228
  * ```
108
229
  */
109
- function isWebSocketProtocol(protocol) {
110
- return /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(protocol);
230
+ function isWebSocketError(value) {
231
+ return value instanceof WebSocketError;
111
232
  }
233
+ //#endregion
234
+ //#region src/server/helpers.ts
112
235
  /**
113
- * Decode a single RFC 6455 frame from the front of a buffer.
236
+ * Computes the `Sec-WebSocket-Accept` response value for an RFC 6455 upgrade.
114
237
  *
115
238
  * @remarks
116
- * Reads the FIN bit and opcode (byte 0), the mask bit and 7-bit payload length (byte
117
- * 1) extended to a 16-bit length when the 7-bit field is `126`, or a 64-bit length
118
- * when it is `127` — the optional 4-byte mask key, then the payload, XOR-unmasking it
119
- * against the key when the mask bit is set (client→server frames MUST be masked, RFC
120
- * 6455 §5.3; an unmasked frame still decodes, leaving the payload as-is, so the caller
121
- * can enforce policy). Returns `undefined` the moment the buffer is too short for the
122
- * part it is up to (the length prefix, the mask, or the full payload) — the signal to
123
- * the caller to read more bytes and retry, exactly like {@link SSEParser} on a partial
124
- * line. `consumed` is the total bytes the frame occupied, so the caller slices the
125
- * remainder. Pure; never throws on a short buffer.
239
+ * The base64-encoded SHA-1 of the client's `Sec-WebSocket-Key` concatenated with the
240
+ * fixed {@link WEBSOCKET_GUID} (RFC 6455 §4.2.2) the proof the server understood the
241
+ * handshake. Pure and deterministic.
126
242
  *
127
- * @param buffer - The accumulation buffer to decode the next frame from
128
- * @returns The parsed {@link WebSocketFrame}, or `undefined` when the buffer is incomplete
243
+ * @param key - The client's `Sec-WebSocket-Key` header value
244
+ * @returns The base64 accept token to send back as `Sec-WebSocket-Accept`
129
245
  */
130
- function parseWebSocketFrame(buffer) {
131
- if (buffer.length < 2) return void 0;
132
- const firstByte = buffer.readUInt8(0);
133
- const secondByte = buffer.readUInt8(1);
134
- const fin = (firstByte & 128) !== 0;
135
- const rsv = (firstByte & 112) >> 4;
136
- const opcode = firstByte & 15;
137
- const masked = (secondByte & 128) !== 0;
138
- let length = secondByte & 127;
139
- let offset = 2;
140
- if (length === 126) {
141
- if (buffer.length < offset + 2) return void 0;
142
- length = buffer.readUInt16BE(offset);
143
- offset += 2;
144
- } else if (length === 127) {
145
- if (buffer.length < offset + 8) return void 0;
146
- const high = buffer.readUInt32BE(offset);
147
- const low = buffer.readUInt32BE(offset + 4);
148
- length = high * 4294967296 + low;
149
- offset += 8;
150
- }
151
- let mask;
152
- if (masked) {
153
- if (buffer.length < offset + 4) return void 0;
154
- mask = buffer.subarray(offset, offset + 4);
155
- offset += 4;
156
- }
157
- if (buffer.length < offset + length) return void 0;
158
- const payload = Buffer.alloc(length);
159
- buffer.copy(payload, 0, offset, offset + length);
160
- if (mask !== void 0) for (let index = 0; index < length; index += 1) payload[index] = payload.readUInt8(index) ^ mask.readUInt8(index % 4);
161
- return {
162
- fin,
163
- opcode,
164
- payload,
165
- consumed: offset + length,
166
- masked,
167
- rsv
168
- };
246
+ function computeWebSocketAccept(key) {
247
+ return (0, node_crypto.createHash)("sha1").update(key + WEBSOCKET_GUID).digest("base64");
169
248
  }
170
249
  /**
171
- * Read the declared payload length off the front of a buffer, without buffering or
172
- * reading the payload itself.
250
+ * Reads the declared payload length off the front of a buffer without buffering or
251
+ * reading the payload itself, answering `undefined` until the length field is complete.
173
252
  *
174
253
  * @remarks
175
254
  * Decodes only byte 1's 7-bit length field, extended by the 16-bit (`126`) or 64-bit
176
- * (`127`) form exactly like {@link parseWebSocketFrame} — but stops there, so a caller
255
+ * (`127`) form exactly like `parseWebSocketFrame` — but stops there, so a caller
177
256
  * can reject an over-cap frame the moment its length is known, before the payload
178
- * bytes have even arrived. Returns `undefined` until the length field itself is fully
179
- * buffered (mirrors the parser's incomplete-buffer contract). Pure; never throws.
257
+ * bytes have even arrived. The incomplete-buffer contract mirrors the parser's. Pure;
258
+ * never throws.
180
259
  *
181
260
  * @param buffer - The accumulation buffer to read the next frame's length from
182
261
  * @returns The declared payload length, or `undefined` when the buffer is too short to know it yet
@@ -184,7 +263,7 @@ function parseWebSocketFrame(buffer) {
184
263
  * @example
185
264
  * ```ts
186
265
  * const declared = measureWebSocketFrame(buffer)
187
- * if (declared !== undefined && declared > limit) fail(WEBSOCKET_CLOSE_TOOBIG)
266
+ * if (declared !== undefined && declared > limit) fail(WEBSOCKET_CLOSE_TOO_BIG)
188
267
  * ```
189
268
  */
190
269
  function measureWebSocketFrame(buffer) {
@@ -203,22 +282,24 @@ function measureWebSocketFrame(buffer) {
203
282
  return length;
204
283
  }
205
284
  /**
206
- * Whether the next frame uses the shortest valid RFC 6455 payload-length encoding.
285
+ * Checks whether the next frame uses the shortest valid RFC 6455 payload-length
286
+ * encoding, answering `undefined` until its length prefix is complete.
207
287
  *
208
288
  * @remarks
209
- * Returns `undefined` until the complete length prefix is buffered. The 16-bit form
210
- * is canonical only for lengths at least 126; the 64-bit form only for lengths at
211
- * least 65,536 and with its most-significant bit clear (RFC 6455 §5.2).
289
+ * The 16-bit form is canonical only for lengths at least 126; the 64-bit form only for
290
+ * lengths at least 65,536 and with its most-significant bit clear (RFC 6455 §5.2). Reads
291
+ * the same length prefix as {@link measureWebSocketFrame}, under the same
292
+ * incomplete-buffer contract. Pure; never throws.
212
293
  *
213
294
  * @param buffer - The accumulation buffer containing the next frame header
214
295
  * @returns Its canonicality, or `undefined` while the length prefix is incomplete
215
296
  *
216
297
  * @example
217
298
  * ```ts
218
- * if (isWebSocketFrameCanonical(buffer) === false) fail(WEBSOCKET_CLOSE_PROTOCOL)
299
+ * if (matchesWebSocketCanonical(buffer) === false) fail(WEBSOCKET_CLOSE_PROTOCOL)
219
300
  * ```
220
301
  */
221
- function isWebSocketFrameCanonical(buffer) {
302
+ function matchesWebSocketCanonical(buffer) {
222
303
  if (buffer.length < 2) return void 0;
223
304
  const lengthCode = buffer.readUInt8(1) & 127;
224
305
  if (lengthCode < 126) return true;
@@ -233,31 +314,95 @@ function isWebSocketFrameCanonical(buffer) {
233
314
  return high > 0 || low >= 65536;
234
315
  }
235
316
  /**
236
- * Decode a byte sequence as strict UTF-8, or signal it is malformed.
317
+ * Encodes a single RFC 6455 frame to its wire bytes the inverse of
318
+ * `parseWebSocketFrame`.
237
319
  *
238
320
  * @remarks
239
- * Wraps `TextDecoder('utf-8', { fatal: true })` in a try/catch so a malformed sequence
240
- * returns `undefined` instead of throwing (AGENTS §14 a guard-adjacent coercer never
241
- * throws on bad input). Pure.
321
+ * Builds a final (FIN-set) frame: byte 0 is `0x80 | opcode`; the payload length uses
322
+ * the 7-bit form below 126, the `126` + 16-bit form below 65 536, or the `127` +
323
+ * 64-bit form beyond; when `masked` is set the mask bit is set, a 4-byte key (supplied
324
+ * through `options.mask`, else random) is written, and the payload is XOR-masked. Server→
325
+ * client frames are unmasked (the default); pass `masked: true` to encode a client
326
+ * frame (for example to feed the parser in a test). A `string` payload is encoded as
327
+ * UTF-8. Returns one contiguous `Buffer` (header + payload), so the wrapper writes it
328
+ * with a single `socket.write`. Pure.
242
329
  *
243
- * @param bytes - The raw bytes to decode
244
- * @returns The decoded string, or `undefined` when `bytes` is not valid UTF-8
330
+ * @param opcode - The frame opcode (a `WEBSOCKET_OPCODE_*` value)
331
+ * @param payload - The payload, a `Buffer` or a UTF-8 `string`
332
+ * @param options - Masking control ({@link WebSocketEncodeOptions}); defaults to unmasked
333
+ * @returns The complete frame as wire bytes
334
+ * @throws A {@link WebSocketError} coded `FRAME` when `opcode` is outside the four-bit wire field, when `options.mask` is not 4 bytes, or when `options.mask` is supplied without `masked: true`
335
+ */
336
+ function encodeWebSocketFrame(opcode, payload, options) {
337
+ if (!Number.isInteger(opcode) || opcode < 0 || opcode > 15) throw new WebSocketError("FRAME", "opcode must be an integer between 0 and 15", { opcode });
338
+ if (options?.mask !== void 0 && options.mask.length !== 4) throw new WebSocketError("FRAME", "mask must contain exactly 4 bytes", { size: options.mask.length });
339
+ if (options?.mask !== void 0 && options.masked !== true) throw new WebSocketError("FRAME", "mask requires masked: true");
340
+ const body = typeof payload === "string" ? Buffer.from(payload, "utf-8") : payload;
341
+ const length = body.length;
342
+ const masked = options?.masked === true;
343
+ const mask = masked ? options?.mask ?? (0, node_crypto.randomBytes)(4) : void 0;
344
+ const maskBit = masked ? 128 : 0;
345
+ const extended = length < 126 ? 0 : length < 65536 ? 2 : 8;
346
+ const header = Buffer.alloc(2 + extended + (mask !== void 0 ? 4 : 0));
347
+ header[0] = 128 | opcode;
348
+ if (length < 126) header[1] = maskBit | length;
349
+ else if (length < 65536) {
350
+ header[1] = maskBit | 126;
351
+ header.writeUInt16BE(length, 2);
352
+ } else {
353
+ header[1] = maskBit | 127;
354
+ header.writeUInt32BE(Math.floor(length / 4294967296), 2);
355
+ header.writeUInt32BE(length % 4294967296, 6);
356
+ }
357
+ if (mask === void 0) return Buffer.concat([header, body]);
358
+ mask.copy(header, header.length - 4);
359
+ const maskedBody = Buffer.alloc(length);
360
+ for (let index = 0; index < length; index += 1) maskedBody[index] = body.readUInt8(index) ^ mask.readUInt8(index % 4);
361
+ return Buffer.concat([header, maskedBody]);
362
+ }
363
+ /**
364
+ * Checks whether a value is a canonical RFC 6455 `Sec-WebSocket-Key`.
365
+ *
366
+ * @remarks
367
+ * A valid key is exactly 16 random bytes encoded as 24 characters of base64, ending
368
+ * in `==` (RFC 6455 §4.1). This predicate is suitable at an HTTP upgrade boundary:
369
+ * malformed or non-canonical encodings return `false`; nothing is thrown.
370
+ *
371
+ * @param key - The proposed `Sec-WebSocket-Key` header value
372
+ * @returns True if `key` is the canonical base64 encoding of 16 bytes; false otherwise
245
373
  *
246
374
  * @example
247
375
  * ```ts
248
- * const text = parseUTF8(payload)
249
- * if (text === undefined) fail(WEBSOCKET_CLOSE_INVALID)
376
+ * const key = request.headers['sec-websocket-key']
377
+ * if (typeof key !== 'string' || !isWebSocketKey(key)) socket.destroy()
250
378
  * ```
251
379
  */
252
- function parseUTF8(bytes) {
253
- try {
254
- return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
255
- } catch {
256
- return;
257
- }
380
+ function isWebSocketKey(key) {
381
+ if (!/^[A-Za-z0-9+/]{22}==$/.test(key)) return false;
382
+ return Buffer.from(key, "base64").length === 16;
383
+ }
384
+ /**
385
+ * Checks whether a value is one valid WebSocket subprotocol token.
386
+ *
387
+ * @remarks
388
+ * Subprotocols use the HTTP `token` grammar. Whitespace, separators, commas, and
389
+ * control characters are rejected, preventing an untrusted value from injecting a
390
+ * second handshake header.
391
+ *
392
+ * @param protocol - The negotiated subprotocol to validate
393
+ * @returns True if `protocol` is one non-empty HTTP token; false otherwise
394
+ *
395
+ * @example
396
+ * ```ts
397
+ * if (!isWebSocketProtocol(protocol)) socket.destroy()
398
+ * ```
399
+ */
400
+ function isWebSocketProtocol(protocol) {
401
+ return /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(protocol);
258
402
  }
259
403
  /**
260
- * Whether a numeric value is a valid RFC 6455 close status code to RECEIVE (§7.4.1).
404
+ * Checks whether a numeric value is a close status code an RFC 6455 endpoint may
405
+ * receive (§7.4.1).
261
406
  *
262
407
  * @remarks
263
408
  * True for `1000`–`1003`, `1007`–`1014`, and the application range `3000`–`4999`; false
@@ -270,7 +415,7 @@ function parseUTF8(bytes) {
270
415
  * never throws.
271
416
  *
272
417
  * @param code - The close status code to validate
273
- * @returns `true` when `code` is a valid RFC 6455 close code
418
+ * @returns True if `code` is a valid RFC 6455 close code; false otherwise
274
419
  *
275
420
  * @example
276
421
  * ```ts
@@ -284,73 +429,133 @@ function isCloseCode(code) {
284
429
  if (code >= 3e3 && code <= 4999) return true;
285
430
  return false;
286
431
  }
432
+ //#endregion
433
+ //#region src/server/parsers.ts
287
434
  /**
288
- * Encode a single RFC 6455 frame to its wire bytes the inverse of
289
- * {@link parseWebSocketFrame}.
435
+ * Decodes a single RFC 6455 frame from the front of a buffer, answering `undefined`
436
+ * while the buffer is incomplete so the caller accumulates and retries.
290
437
  *
291
438
  * @remarks
292
- * Builds a final (FIN-set) frame: byte 0 is `0x80 | opcode`; the payload length uses
293
- * the 7-bit form below 126, the `126` + 16-bit form below 65 536, or the `127` +
294
- * 64-bit form beyond; when `masked` is set the mask bit is set, a 4-byte key (supplied
295
- * via `options.mask`, else random) is written, and the payload is XOR-masked. Server→
296
- * client frames are unmasked (the default); pass `masked: true` to encode a CLIENT
297
- * frame (e.g. to feed the parser in a test). A `string` payload is encoded as UTF-8.
298
- * Returns one contiguous `Buffer` (header + payload), so the wrapper writes it with a
299
- * single `socket.write`. Pure.
439
+ * Reads the FIN bit and opcode (byte 0), the mask bit and 7-bit payload length (byte
440
+ * 1) — extended to a 16-bit length when the 7-bit field is `126`, or a 64-bit length
441
+ * when it is `127` the optional 4-byte mask key, then the payload, XOR-unmasking it
442
+ * against the key when the mask bit is set (client→server frames must be masked, RFC
443
+ * 6455 §5.3; an unmasked frame still decodes, leaving the payload as-is, so the caller
444
+ * can enforce policy). The incomplete answer comes the moment the buffer is too short
445
+ * for the part it is up to: the length prefix, the mask, or the full payload.
446
+ * `consumed` is the total bytes the frame occupied, so the caller slices the remainder.
447
+ * Pure; never throws on a short buffer.
300
448
  *
301
- * @param opcode - The frame opcode (a `WEBSOCKET_OPCODE_*` value)
302
- * @param payload - The payload, a `Buffer` or a UTF-8 `string`
303
- * @param options - Masking control ({@link WebSocketEncodeOptions}); defaults to unmasked
304
- * @returns The complete frame as wire bytes
449
+ * @param buffer - The accumulation buffer to decode the next frame from
450
+ * @returns The parsed {@link WebSocketFrame}, or `undefined` when the buffer is incomplete
451
+ *
452
+ * @example
453
+ * ```ts
454
+ * const frame = parseWebSocketFrame(buffer)
455
+ * if (frame === undefined) return // incomplete — wait for more bytes
456
+ * ```
305
457
  */
306
- function encodeWebSocketFrame(opcode, payload, options) {
307
- if (!Number.isInteger(opcode) || opcode < 0 || opcode > 15) throw new RangeError("opcode must be an integer between 0 and 15");
308
- if (options?.mask !== void 0 && options.mask.length !== 4) throw new RangeError("mask must contain exactly 4 bytes");
309
- if (options?.mask !== void 0 && options.masked !== true) throw new RangeError("mask requires masked: true");
310
- const body = typeof payload === "string" ? Buffer.from(payload, "utf-8") : payload;
311
- const length = body.length;
312
- const masked = options?.masked === true;
313
- const mask = masked ? options?.mask ?? (0, node_crypto.randomBytes)(4) : void 0;
314
- const maskBit = masked ? 128 : 0;
315
- const extended = length < 126 ? 0 : length < 65536 ? 2 : 8;
316
- const header = Buffer.alloc(2 + extended + (mask !== void 0 ? 4 : 0));
317
- header[0] = 128 | opcode;
318
- if (length < 126) header[1] = maskBit | length;
319
- else if (length < 65536) {
320
- header[1] = maskBit | 126;
321
- header.writeUInt16BE(length, 2);
322
- } else {
323
- header[1] = maskBit | 127;
324
- header.writeUInt32BE(Math.floor(length / 4294967296), 2);
325
- header.writeUInt32BE(length % 4294967296, 6);
458
+ function parseWebSocketFrame(buffer) {
459
+ if (buffer.length < 2) return void 0;
460
+ const firstByte = buffer.readUInt8(0);
461
+ const secondByte = buffer.readUInt8(1);
462
+ const fin = (firstByte & 128) !== 0;
463
+ const rsv = (firstByte & 112) >> 4;
464
+ const opcode = firstByte & 15;
465
+ const masked = (secondByte & 128) !== 0;
466
+ let length = secondByte & 127;
467
+ let offset = 2;
468
+ if (length === 126) {
469
+ if (buffer.length < offset + 2) return void 0;
470
+ length = buffer.readUInt16BE(offset);
471
+ offset += 2;
472
+ } else if (length === 127) {
473
+ if (buffer.length < offset + 8) return void 0;
474
+ const high = buffer.readUInt32BE(offset);
475
+ const low = buffer.readUInt32BE(offset + 4);
476
+ length = high * 4294967296 + low;
477
+ offset += 8;
478
+ }
479
+ let mask;
480
+ if (masked) {
481
+ if (buffer.length < offset + 4) return void 0;
482
+ mask = buffer.subarray(offset, offset + 4);
483
+ offset += 4;
484
+ }
485
+ if (buffer.length < offset + length) return void 0;
486
+ const payload = Buffer.alloc(length);
487
+ buffer.copy(payload, 0, offset, offset + length);
488
+ if (mask !== void 0) for (let index = 0; index < length; index += 1) payload[index] = payload.readUInt8(index) ^ mask.readUInt8(index % 4);
489
+ return {
490
+ fin,
491
+ opcode,
492
+ payload,
493
+ consumed: offset + length,
494
+ masked,
495
+ rsv
496
+ };
497
+ }
498
+ /**
499
+ * Decodes a byte sequence as strict UTF-8, answering `undefined` when the sequence is
500
+ * malformed.
501
+ *
502
+ * @remarks
503
+ * Wraps `TextDecoder('utf-8', { fatal: true })` in a try/catch, so a malformed sequence
504
+ * returns rather than throwing — a guard-adjacent coercer never throws on bad input.
505
+ * Pure.
506
+ *
507
+ * @param bytes - The raw bytes to decode
508
+ * @returns The decoded string, or `undefined` when `bytes` is not valid UTF-8
509
+ *
510
+ * @example
511
+ * ```ts
512
+ * const text = parseUTF8(payload)
513
+ * if (text === undefined) fail(WEBSOCKET_CLOSE_INVALID)
514
+ * ```
515
+ */
516
+ function parseUTF8(bytes) {
517
+ try {
518
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
519
+ } catch {
520
+ return;
326
521
  }
327
- if (mask === void 0) return Buffer.concat([header, body]);
328
- mask.copy(header, header.length - 4);
329
- const maskedBody = Buffer.alloc(length);
330
- for (let index = 0; index < length; index += 1) maskedBody[index] = body.readUInt8(index) ^ mask.readUInt8(index % 4);
331
- return Buffer.concat([header, maskedBody]);
332
522
  }
333
523
  //#endregion
334
524
  //#region src/server/NodeWebSocket.ts
335
525
  /**
336
- * A server-native WebSocket over a raw upgraded `node:stream` Duplex — the lean
337
- * wrapper around the RFC 6455 wire protocol.
526
+ * Implements the wrapper contract over a raw upgraded `node:stream` Duplex socket,
527
+ * driving the RFC 6455 handshake, the frame codec, auto-pong, and the close handshake,
528
+ * and surfacing every event on an owned `emitter`.
338
529
  *
339
530
  * @remarks
340
- * Created by `createNodeWebSocket`. When given a client `key` it runs in SERVER mode —
531
+ * Created by `createNodeWebSocket`. When given a client `key` it runs in server mode —
341
532
  * it writes the `101 Switching Protocols` handshake (`computeWebSocketAccept(key)`) and
342
- * emits `open`; given no key it runs in CLIENT mode (no handshake, frames masked). It
533
+ * emits `open`; given no key it runs in client mode (no handshake, frames masked). It
343
534
  * then listens on the socket's `data`, accumulating bytes in `#buffer` and decoding
344
535
  * every complete frame with {@link parseWebSocketFrame} (slicing `consumed` and
345
- * re-parsing the remainder): a TEXT frame — reassembling continuation fragments across
346
- * `fin: false` frames — decodes to UTF-8 and emits `message`; a PING is auto-answered
347
- * with a PONG and emits `ping`; a PONG emits `pong`; a CLOSE is echoed and ends the
536
+ * re-parsing the remainder): a text frame — reassembling continuation fragments across
537
+ * `fin: false` frames — decodes to UTF-8 and emits `message`; a ping is auto-answered
538
+ * with a pong and emits `ping`; a pong emits `pong`; a close frame is echoed and ends the
348
539
  * socket, emitting `close`. `send` writes a text frame, `ping` a ping, `close` a close
349
- * frame; `destroy` tears down immediately. It owns a typed `#emitter` (AGENTS §13) that
350
- * isolates a throwing listener and routes the error to its own `error` handler (the `error`
351
- * option) — the socket never crashes. An underlying socket error emits the domain
352
- * `error` event and terminates the wrapper. The untyped socket `data` is narrowed to a
353
- * `Buffer` with a guard, never an assertion (AGENTS §14).
540
+ * frame; `destroy` tears down immediately. It owns a typed `#emitter` by composition, and
541
+ * the emitter isolates a throwing listener and routes the error to its own `error` handler
542
+ * (the `error` option) — the socket never crashes. An underlying socket error emits the
543
+ * domain `error` event and terminates the wrapper. The untyped socket `data` is narrowed
544
+ * to a `Buffer` with a guard, never an assertion.
545
+ *
546
+ * @example
547
+ * ```ts
548
+ * import { NodeWebSocket } from '@src/server'
549
+ *
550
+ * // In a node:http 'upgrade' handler, over the socket the server already handed over:
551
+ * const key = request.headers['sec-websocket-key']
552
+ * if (typeof key !== 'string') {
553
+ * socket.destroy()
554
+ * return
555
+ * }
556
+ * const ws = new NodeWebSocket({ socket, key, head })
557
+ * ws.emitter.on('message', (text) => ws.send(`echo: ${text}`))
558
+ * ```
354
559
  */
355
560
  var NodeWebSocket = class {
356
561
  #emitter;
@@ -373,14 +578,25 @@ var NodeWebSocket = class {
373
578
  #closeTimer;
374
579
  #destroyed = false;
375
580
  #detached = false;
581
+ /**
582
+ * Creates a WebSocket wrapper over an already-upgraded Duplex socket.
583
+ *
584
+ * @remarks
585
+ * `key` selects the mode: present runs server mode and writes the `101 Switching
586
+ * Protocols` handshake, omitted runs client mode and masks every outgoing frame.
587
+ * {@link NodeWebSocketOptions} describes every member.
588
+ *
589
+ * @param options - The {@link NodeWebSocketOptions} the wrapper is built from
590
+ * @throws A {@link WebSocketError} coded `OPTION` when `payload`, `timeout`, `key`, or `protocol` is refused, or when `protocol` is supplied without a server `key`, thrown before the wrapper writes to or assumes ownership of the `socket`
591
+ */
376
592
  constructor(options) {
377
593
  const payload = options.payload ?? 104857600;
378
- if (!Number.isSafeInteger(payload) || payload < 0) throw new RangeError("payload must be a non-negative safe integer");
594
+ if (!Number.isSafeInteger(payload) || payload < 0) throw new WebSocketError("OPTION", "payload must be a non-negative safe integer", { payload });
379
595
  const timeout = options.timeout ?? 3e4;
380
- if (!Number.isSafeInteger(timeout) || timeout < 0) throw new RangeError("timeout must be a non-negative safe integer");
381
- if (options.key !== void 0 && !isWebSocketKey(options.key)) throw new RangeError("key must be the canonical base64 encoding of 16 bytes");
382
- if (options.protocol !== void 0 && !isWebSocketProtocol(options.protocol)) throw new RangeError("protocol must be a valid WebSocket subprotocol token");
383
- if (options.protocol !== void 0 && options.key === void 0) throw new RangeError("protocol requires a server key");
596
+ if (!Number.isSafeInteger(timeout) || timeout < 0) throw new WebSocketError("OPTION", "timeout must be a non-negative safe integer", { timeout });
597
+ if (options.key !== void 0 && !isWebSocketKey(options.key)) throw new WebSocketError("OPTION", "key must be the canonical base64 encoding of 16 bytes", { key: options.key });
598
+ if (options.protocol !== void 0 && !isWebSocketProtocol(options.protocol)) throw new WebSocketError("OPTION", "protocol must be a valid WebSocket subprotocol token", { protocol: options.protocol });
599
+ if (options.protocol !== void 0 && options.key === void 0) throw new WebSocketError("OPTION", "protocol requires a server key", { protocol: options.protocol });
384
600
  this.#emitter = new _orkestrel_emitter.Emitter({
385
601
  ...options.on === void 0 ? {} : { on: options.on },
386
602
  ...options.error === void 0 ? {} : { error: options.error }
@@ -422,19 +638,27 @@ var NodeWebSocket = class {
422
638
  get readyState() {
423
639
  return this.#readyState;
424
640
  }
425
- send(data) {
641
+ send(message) {
426
642
  if (this.#readyState !== 1) return;
427
- this.#write(1, Buffer.from(data, "utf-8"));
643
+ this.#write(1, Buffer.from(message, "utf-8"));
428
644
  }
429
- ping(data) {
645
+ ping(payload) {
430
646
  if (this.#readyState !== 1) return;
431
- if (data !== void 0 && Buffer.byteLength(data, "utf-8") > 125) throw new RangeError("ping payload exceeds 125 bytes");
432
- this.#write(9, data === void 0 ? Buffer.alloc(0) : Buffer.from(data, "utf-8"));
647
+ const size = payload === void 0 ? 0 : Buffer.byteLength(payload, "utf-8");
648
+ if (size > 125) throw new WebSocketError("LIMIT", `ping payload exceeds 125 bytes`, {
649
+ size,
650
+ limit: 125
651
+ });
652
+ this.#write(9, payload === void 0 ? Buffer.alloc(0) : Buffer.from(payload, "utf-8"));
433
653
  }
434
654
  close(code, reason) {
435
655
  if (this.#readyState === 2 || this.#readyState === 3) return;
436
- if (code !== void 0 && !isCloseCode(code)) throw new RangeError("invalid close code");
437
- if (reason !== void 0 && Buffer.byteLength(reason, "utf-8") > 123) throw new RangeError(`close reason exceeds 123 bytes`);
656
+ if (code !== void 0 && !isCloseCode(code)) throw new WebSocketError("CLOSE", "invalid close code", { code });
657
+ const size = reason === void 0 ? 0 : Buffer.byteLength(reason, "utf-8");
658
+ if (size > 123) throw new WebSocketError("LIMIT", `close reason exceeds 123 bytes`, {
659
+ size,
660
+ limit: 123
661
+ });
438
662
  this.#readyState = 2;
439
663
  this.#code = code ?? 1e3;
440
664
  this.#reason = reason === void 0 || reason.length === 0 ? void 0 : reason;
@@ -456,13 +680,13 @@ var NodeWebSocket = class {
456
680
  }
457
681
  #drain() {
458
682
  for (;;) {
459
- if (isWebSocketFrameCanonical(this.#buffer) === false) {
683
+ if (matchesWebSocketCanonical(this.#buffer) === false) {
460
684
  this.#fail(WEBSOCKET_CLOSE_PROTOCOL);
461
685
  return;
462
686
  }
463
687
  const declared = measureWebSocketFrame(this.#buffer);
464
688
  if (declared !== void 0 && declared > this.#payload) {
465
- this.#fail(WEBSOCKET_CLOSE_TOOBIG);
689
+ this.#fail(WEBSOCKET_CLOSE_TOO_BIG);
466
690
  return;
467
691
  }
468
692
  const frame = parseWebSocketFrame(this.#buffer);
@@ -516,7 +740,7 @@ var NodeWebSocket = class {
516
740
  this.#fragments.push(payload);
517
741
  this.#fragmentBytes += payload.length;
518
742
  if (this.#fragmentBytes > this.#payload) {
519
- this.#fail(WEBSOCKET_CLOSE_TOOBIG);
743
+ this.#fail(WEBSOCKET_CLOSE_TOO_BIG);
520
744
  return;
521
745
  }
522
746
  if (!fin) return;
@@ -645,33 +869,39 @@ var NodeWebSocket = class {
645
869
  //#endregion
646
870
  //#region src/server/factories.ts
647
871
  /**
648
- * Create a server-native WebSocket over a raw upgraded `node:stream` Duplex socket.
872
+ * Creates a server-native WebSocket over a raw upgraded `node:stream` Duplex socket
873
+ * server mode when a `key` is given, client mode otherwise.
649
874
  *
650
875
  * @remarks
651
- * The construction entry point for the {@link NodeWebSocketInterface} (AGENTS §8). Pass
652
- * the upgraded `socket` plus the client's `Sec-WebSocket-Key` as `key` to run in SERVER
653
- * mode the wrapper writes the `101 Switching Protocols` handshake and sends unmasked
654
- * frames; omit `key` for CLIENT mode (no handshake, masked frames). This is the
655
- * lean-native handle; it speaks only the WebSocket wire protocol — an MCP transport (the
656
- * later chunk) is built ON it. It is the WebSocket counterpart to
657
- * `createSQLiteDatabase` / `createIndexedDBDatabase`.
876
+ * The construction entry point for the {@link NodeWebSocketInterface}. In server mode the
877
+ * wrapper writes the `101 Switching Protocols` handshake and sends unmasked frames; in
878
+ * client mode it writes no handshake and masks every outgoing frame. This is the
879
+ * lean-native handle: it speaks the WebSocket wire protocol and nothing above it, so a
880
+ * message transport is built on it rather than into it.
658
881
  *
659
882
  * @param options - The {@link NodeWebSocketOptions} (`socket`, optional `key` / `head` /
660
883
  * `protocol` / `on`)
661
884
  * @returns A typed {@link NodeWebSocketInterface}
885
+ * @throws A `WebSocketError` coded `OPTION` when `payload`, `timeout`, `key`, or `protocol` is refused, thrown before the wrapper writes to or assumes ownership of the `socket`
662
886
  *
663
- * @example
887
+ * @example Accept an upgrade and echo messages (server mode)
664
888
  * ```ts
665
- * import { createNodeWebSocket } from '@src/server'
889
+ * import { createNodeWebSocket } from '@orkestrel/websocket'
666
890
  *
667
- * // In a node:http 'upgrade' handler — server mode, identified by the client key:
668
891
  * server.on('upgrade', (request, socket, head) => {
892
+ * const key = request.headers['sec-websocket-key']
893
+ * if (typeof key !== 'string') {
894
+ * socket.destroy()
895
+ * return
896
+ * }
669
897
  * const ws = createNodeWebSocket({
670
898
  * socket,
671
- * key: request.headers['sec-websocket-key'],
672
- * head,
673
- * on: { message: (text) => ws.send(`echo: ${text}`) },
899
+ * key,
900
+ * head, // any bytes already buffered after the upgrade headers
901
+ * on: { message: (text) => ws.send(`echo: ${text}`) }, // wired before the first frame arrives
674
902
  * })
903
+ * ws.emitter.on('message', (text) => log('echoed', text)) // a second observer of the same event
904
+ * ws.emitter.on('close', (code, reason) => log('closed', code, reason))
675
905
  * })
676
906
  * ```
677
907
  */
@@ -683,11 +913,11 @@ exports.NodeWebSocket = NodeWebSocket;
683
913
  exports.WEBSOCKET_CLOSE_INVALID = WEBSOCKET_CLOSE_INVALID;
684
914
  exports.WEBSOCKET_CLOSE_NORMAL = WEBSOCKET_CLOSE_NORMAL;
685
915
  exports.WEBSOCKET_CLOSE_PROTOCOL = WEBSOCKET_CLOSE_PROTOCOL;
686
- exports.WEBSOCKET_CLOSE_REASON_MAXLEN = WEBSOCKET_CLOSE_REASON_MAXLEN;
916
+ exports.WEBSOCKET_CLOSE_REASON_MAX_LENGTH = WEBSOCKET_CLOSE_REASON_MAX_LENGTH;
687
917
  exports.WEBSOCKET_CLOSE_TIMEOUT_MS = WEBSOCKET_CLOSE_TIMEOUT_MS;
688
- exports.WEBSOCKET_CLOSE_TOOBIG = WEBSOCKET_CLOSE_TOOBIG;
918
+ exports.WEBSOCKET_CLOSE_TOO_BIG = WEBSOCKET_CLOSE_TOO_BIG;
689
919
  exports.WEBSOCKET_CLOSE_UNSUPPORTED = WEBSOCKET_CLOSE_UNSUPPORTED;
690
- exports.WEBSOCKET_CONTROL_MAXLEN = WEBSOCKET_CONTROL_MAXLEN;
920
+ exports.WEBSOCKET_CONTROL_MAX_LENGTH = WEBSOCKET_CONTROL_MAX_LENGTH;
691
921
  exports.WEBSOCKET_FAIL_TIMEOUT_MS = WEBSOCKET_FAIL_TIMEOUT_MS;
692
922
  exports.WEBSOCKET_GUID = WEBSOCKET_GUID;
693
923
  exports.WEBSOCKET_MAX_PAYLOAD = WEBSOCKET_MAX_PAYLOAD;
@@ -702,13 +932,15 @@ exports.WEBSOCKET_READY_CLOSING = WEBSOCKET_READY_CLOSING;
702
932
  exports.WEBSOCKET_READY_CONNECTING = WEBSOCKET_READY_CONNECTING;
703
933
  exports.WEBSOCKET_READY_OPEN = WEBSOCKET_READY_OPEN;
704
934
  exports.WEBSOCKET_VERSION = WEBSOCKET_VERSION;
935
+ exports.WebSocketError = WebSocketError;
705
936
  exports.computeWebSocketAccept = computeWebSocketAccept;
706
937
  exports.createNodeWebSocket = createNodeWebSocket;
707
938
  exports.encodeWebSocketFrame = encodeWebSocketFrame;
708
939
  exports.isCloseCode = isCloseCode;
709
- exports.isWebSocketFrameCanonical = isWebSocketFrameCanonical;
940
+ exports.isWebSocketError = isWebSocketError;
710
941
  exports.isWebSocketKey = isWebSocketKey;
711
942
  exports.isWebSocketProtocol = isWebSocketProtocol;
943
+ exports.matchesWebSocketCanonical = matchesWebSocketCanonical;
712
944
  exports.measureWebSocketFrame = measureWebSocketFrame;
713
945
  exports.parseUTF8 = parseUTF8;
714
946
  exports.parseWebSocketFrame = parseWebSocketFrame;