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