@orkestrel/websocket 0.0.10 → 0.0.11
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.
- package/README.md +8 -3
- package/dist/src/server/index.cjs +317 -205
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +202 -109
- package/dist/src/server/index.d.ts +202 -109
- package/dist/src/server/index.js +312 -202
- package/dist/src/server/index.js.map +1 -1
- package/package.json +19 -15
|
@@ -3,7 +3,7 @@ let node_crypto = require("node:crypto");
|
|
|
3
3
|
let _orkestrel_emitter = require("@orkestrel/emitter");
|
|
4
4
|
//#region src/server/constants.ts
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Names the RFC 6455 GUID concatenated to a client's `Sec-WebSocket-Key` before the SHA-1
|
|
7
7
|
* hash that yields the `Sec-WebSocket-Accept` response value.
|
|
8
8
|
*
|
|
9
9
|
* @remarks
|
|
@@ -11,52 +11,113 @@ let _orkestrel_emitter = require("@orkestrel/emitter");
|
|
|
11
11
|
* {@link computeWebSocketAccept}.
|
|
12
12
|
*/
|
|
13
13
|
var WEBSOCKET_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
14
|
-
/**
|
|
14
|
+
/** Names the WebSocket protocol version this wrapper speaks (`Sec-WebSocket-Version: 13`). */
|
|
15
15
|
var WEBSOCKET_VERSION = "13";
|
|
16
|
-
/**
|
|
16
|
+
/** Names the text frame opcode — a UTF-8 payload (RFC 6455 §5.6). */
|
|
17
17
|
var WEBSOCKET_OPCODE_TEXT = 1;
|
|
18
|
-
/**
|
|
18
|
+
/** Names the binary frame opcode — a raw byte payload (RFC 6455 §5.6). */
|
|
19
19
|
var WEBSOCKET_OPCODE_BINARY = 2;
|
|
20
|
-
/**
|
|
20
|
+
/** Names the continuation frame opcode — the next fragment of an open data message (RFC 6455 §5.4). */
|
|
21
21
|
var WEBSOCKET_OPCODE_CONTINUATION = 0;
|
|
22
|
-
/**
|
|
22
|
+
/** Names the close frame opcode — a control frame ending the connection (RFC 6455 §5.5.1). */
|
|
23
23
|
var WEBSOCKET_OPCODE_CLOSE = 8;
|
|
24
|
-
/**
|
|
24
|
+
/** Names the ping frame opcode — a control frame the peer must answer with a pong (RFC 6455 §5.5.2). */
|
|
25
25
|
var WEBSOCKET_OPCODE_PING = 9;
|
|
26
|
-
/**
|
|
26
|
+
/** Names the pong frame opcode — a control frame answering a ping (RFC 6455 §5.5.3). */
|
|
27
27
|
var WEBSOCKET_OPCODE_PONG = 10;
|
|
28
|
-
/**
|
|
28
|
+
/** Names the ready state for a connecting WebSocket (before the handshake completes). */
|
|
29
29
|
var WEBSOCKET_READY_CONNECTING = 0;
|
|
30
|
-
/**
|
|
30
|
+
/** Names the ready state for an open WebSocket (the handshake completed; frames flow). */
|
|
31
31
|
var WEBSOCKET_READY_OPEN = 1;
|
|
32
|
-
/**
|
|
32
|
+
/** Names the ready state for a closing WebSocket (a close frame was sent or received). */
|
|
33
33
|
var WEBSOCKET_READY_CLOSING = 2;
|
|
34
|
-
/**
|
|
34
|
+
/** Names the ready state for a closed WebSocket (the socket ended). */
|
|
35
35
|
var WEBSOCKET_READY_CLOSED = 3;
|
|
36
|
-
/**
|
|
36
|
+
/** Names the normal-closure status code (RFC 6455 §7.4.1) — the default `close` code. */
|
|
37
37
|
var WEBSOCKET_CLOSE_NORMAL = 1e3;
|
|
38
|
-
/**
|
|
38
|
+
/** Names the protocol-error status code (RFC 6455 §7.4.1) — a framing/state rule was violated. */
|
|
39
39
|
var WEBSOCKET_CLOSE_PROTOCOL = 1002;
|
|
40
|
-
/**
|
|
40
|
+
/** Names the unsupported-data status code (RFC 6455 §7.4.1) — the endpoint received a data type it cannot accept, for example binary on a text-only endpoint. */
|
|
41
41
|
var WEBSOCKET_CLOSE_UNSUPPORTED = 1003;
|
|
42
|
-
/**
|
|
42
|
+
/** Names the invalid-frame-payload-data status code (RFC 6455 §7.4.1) — for example non-UTF-8 text or an unparseable close reason. */
|
|
43
43
|
var WEBSOCKET_CLOSE_INVALID = 1007;
|
|
44
|
-
/**
|
|
45
|
-
var
|
|
46
|
-
/**
|
|
44
|
+
/** Names the message-too-big status code (RFC 6455 §7.4.1) — a reassembled message exceeded the payload cap. */
|
|
45
|
+
var WEBSOCKET_CLOSE_TOO_BIG = 1009;
|
|
46
|
+
/** Names the default maximum inbound single-frame length AND reassembled-message total byte count (100 MiB — the `ws` package default). */
|
|
47
47
|
var WEBSOCKET_MAX_PAYLOAD = 104857600;
|
|
48
|
-
/**
|
|
48
|
+
/** Names the default close-handshake timeout in milliseconds — how long `close()` waits for the peer's echo before tearing the socket down. */
|
|
49
49
|
var WEBSOCKET_CLOSE_TIMEOUT_MS = 3e4;
|
|
50
|
-
/**
|
|
50
|
+
/** Names 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
51
|
var WEBSOCKET_FAIL_TIMEOUT_MS = 1e3;
|
|
52
|
-
/**
|
|
53
|
-
var
|
|
54
|
-
/**
|
|
55
|
-
var
|
|
52
|
+
/** Names the maximum control-frame payload length in bytes (RFC 6455 §5.5). */
|
|
53
|
+
var WEBSOCKET_CONTROL_MAX_LENGTH = 125;
|
|
54
|
+
/** Names the maximum UTF-8 close-reason length after the two-byte status code. */
|
|
55
|
+
var WEBSOCKET_CLOSE_REASON_MAX_LENGTH = 123;
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/server/errors.ts
|
|
58
|
+
/**
|
|
59
|
+
* Represents an error thrown by the WebSocket wrapper for a refused caller-supplied value.
|
|
60
|
+
*
|
|
61
|
+
* @remarks
|
|
62
|
+
* Carries a {@link WebSocketErrorCode} and an optional `context` record holding the
|
|
63
|
+
* refused value under a key naming it: an `'OPTION'` carries the offending option
|
|
64
|
+
* (`payload`, `timeout`, `key`, or `protocol`), a `'LIMIT'` carries `size` and the
|
|
65
|
+
* `limit` it exceeded, a `'CLOSE'` carries the refused close `code`, and a `'FRAME'`
|
|
66
|
+
* carries `opcode` or the mask's `size`. Narrow a caught value with
|
|
67
|
+
* {@link isWebSocketError}.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* import { createNodeWebSocket, isWebSocketError } from '@src/server'
|
|
72
|
+
*
|
|
73
|
+
* try {
|
|
74
|
+
* createNodeWebSocket({ socket, key: 'not-base64' })
|
|
75
|
+
* } catch (error) {
|
|
76
|
+
* if (isWebSocketError(error) && error.code === 'OPTION') socket.destroy()
|
|
77
|
+
* }
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
var WebSocketError = class extends Error {
|
|
81
|
+
code;
|
|
82
|
+
context;
|
|
83
|
+
/**
|
|
84
|
+
* Creates a WebSocket error carrying a machine-readable code.
|
|
85
|
+
*
|
|
86
|
+
* @param code - The machine-readable {@link WebSocketErrorCode} a `catch` branches on
|
|
87
|
+
* @param message - The human-readable description, carried as the `Error` message
|
|
88
|
+
* @param context - The refused value keyed by name; omitted leaves `context` `undefined`
|
|
89
|
+
*/
|
|
90
|
+
constructor(code, message, context) {
|
|
91
|
+
super(message);
|
|
92
|
+
this.name = "WebSocketError";
|
|
93
|
+
this.code = code;
|
|
94
|
+
if (context !== void 0) this.context = context;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Checks whether a value is a {@link WebSocketError}.
|
|
99
|
+
*
|
|
100
|
+
* @param value - The value to test (typically a `catch` binding)
|
|
101
|
+
* @returns True if `value` is a `WebSocketError`; false otherwise
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* import { isWebSocketError } from '@src/server'
|
|
106
|
+
*
|
|
107
|
+
* try {
|
|
108
|
+
* ws.close(1000.5)
|
|
109
|
+
* } catch (error) {
|
|
110
|
+
* if (isWebSocketError(error) && error.code === 'CLOSE') ws.close()
|
|
111
|
+
* }
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
function isWebSocketError(value) {
|
|
115
|
+
return value instanceof WebSocketError;
|
|
116
|
+
}
|
|
56
117
|
//#endregion
|
|
57
118
|
//#region src/server/helpers.ts
|
|
58
119
|
/**
|
|
59
|
-
*
|
|
120
|
+
* Computes the `Sec-WebSocket-Accept` response value for an RFC 6455 upgrade.
|
|
60
121
|
*
|
|
61
122
|
* @remarks
|
|
62
123
|
* The base64-encoded SHA-1 of the client's `Sec-WebSocket-Key` concatenated with the
|
|
@@ -70,7 +131,121 @@ function computeWebSocketAccept(key) {
|
|
|
70
131
|
return (0, node_crypto.createHash)("sha1").update(key + WEBSOCKET_GUID).digest("base64");
|
|
71
132
|
}
|
|
72
133
|
/**
|
|
73
|
-
*
|
|
134
|
+
* Reads the declared payload length off the front of a buffer, without buffering or
|
|
135
|
+
* reading the payload itself.
|
|
136
|
+
*
|
|
137
|
+
* @remarks
|
|
138
|
+
* Decodes only byte 1's 7-bit length field, extended by the 16-bit (`126`) or 64-bit
|
|
139
|
+
* (`127`) form exactly like `parseWebSocketFrame` — but stops there, so a caller
|
|
140
|
+
* can reject an over-cap frame the moment its length is known, before the payload
|
|
141
|
+
* bytes have even arrived. Returns `undefined` until the length field itself is fully
|
|
142
|
+
* buffered (mirrors the parser's incomplete-buffer contract). Pure; never throws.
|
|
143
|
+
*
|
|
144
|
+
* @param buffer - The accumulation buffer to read the next frame's length from
|
|
145
|
+
* @returns The declared payload length, or `undefined` when the buffer is too short to know it yet
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```ts
|
|
149
|
+
* const declared = measureWebSocketFrame(buffer)
|
|
150
|
+
* if (declared !== undefined && declared > limit) fail(WEBSOCKET_CLOSE_TOO_BIG)
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
function measureWebSocketFrame(buffer) {
|
|
154
|
+
if (buffer.length < 2) return void 0;
|
|
155
|
+
let length = buffer.readUInt8(1) & 127;
|
|
156
|
+
const offset = 2;
|
|
157
|
+
if (length === 126) {
|
|
158
|
+
if (buffer.length < 4) return void 0;
|
|
159
|
+
length = buffer.readUInt16BE(offset);
|
|
160
|
+
} else if (length === 127) {
|
|
161
|
+
if (buffer.length < 10) return void 0;
|
|
162
|
+
const high = buffer.readUInt32BE(offset);
|
|
163
|
+
const low = buffer.readUInt32BE(6);
|
|
164
|
+
length = high * 4294967296 + low;
|
|
165
|
+
}
|
|
166
|
+
return length;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Checks whether the next frame uses the shortest valid RFC 6455 payload-length encoding.
|
|
170
|
+
*
|
|
171
|
+
* @remarks
|
|
172
|
+
* Returns `undefined` until the complete length prefix is buffered. The 16-bit form
|
|
173
|
+
* is canonical only for lengths at least 126; the 64-bit form only for lengths at
|
|
174
|
+
* least 65,536 and with its most-significant bit clear (RFC 6455 §5.2). Reads the same
|
|
175
|
+
* length prefix as {@link measureWebSocketFrame}, under the same incomplete-buffer
|
|
176
|
+
* contract. Pure; never throws.
|
|
177
|
+
*
|
|
178
|
+
* @param buffer - The accumulation buffer containing the next frame header
|
|
179
|
+
* @returns Its canonicality, or `undefined` while the length prefix is incomplete
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```ts
|
|
183
|
+
* if (matchesWebSocketCanonical(buffer) === false) fail(WEBSOCKET_CLOSE_PROTOCOL)
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
function matchesWebSocketCanonical(buffer) {
|
|
187
|
+
if (buffer.length < 2) return void 0;
|
|
188
|
+
const lengthCode = buffer.readUInt8(1) & 127;
|
|
189
|
+
if (lengthCode < 126) return true;
|
|
190
|
+
if (lengthCode === 126) {
|
|
191
|
+
if (buffer.length < 4) return void 0;
|
|
192
|
+
return buffer.readUInt16BE(2) >= 126;
|
|
193
|
+
}
|
|
194
|
+
if (buffer.length < 10) return void 0;
|
|
195
|
+
const high = buffer.readUInt32BE(2);
|
|
196
|
+
const low = buffer.readUInt32BE(6);
|
|
197
|
+
if ((high & 2147483648) !== 0) return false;
|
|
198
|
+
return high > 0 || low >= 65536;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Encodes a single RFC 6455 frame to its wire bytes — the inverse of
|
|
202
|
+
* `parseWebSocketFrame`.
|
|
203
|
+
*
|
|
204
|
+
* @remarks
|
|
205
|
+
* Builds a final (FIN-set) frame: byte 0 is `0x80 | opcode`; the payload length uses
|
|
206
|
+
* the 7-bit form below 126, the `126` + 16-bit form below 65 536, or the `127` +
|
|
207
|
+
* 64-bit form beyond; when `masked` is set the mask bit is set, a 4-byte key (supplied
|
|
208
|
+
* through `options.mask`, else random) is written, and the payload is XOR-masked. Server→
|
|
209
|
+
* client frames are unmasked (the default); pass `masked: true` to encode a CLIENT
|
|
210
|
+
* frame (for example to feed the parser in a test). A `string` payload is encoded as
|
|
211
|
+
* UTF-8. Returns one contiguous `Buffer` (header + payload), so the wrapper writes it
|
|
212
|
+
* with a single `socket.write`. Pure.
|
|
213
|
+
*
|
|
214
|
+
* @param opcode - The frame opcode (a `WEBSOCKET_OPCODE_*` value)
|
|
215
|
+
* @param payload - The payload, a `Buffer` or a UTF-8 `string`
|
|
216
|
+
* @param options - Masking control ({@link WebSocketEncodeOptions}); defaults to unmasked
|
|
217
|
+
* @returns The complete frame as wire bytes
|
|
218
|
+
* @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`
|
|
219
|
+
*/
|
|
220
|
+
function encodeWebSocketFrame(opcode, payload, options) {
|
|
221
|
+
if (!Number.isInteger(opcode) || opcode < 0 || opcode > 15) throw new WebSocketError("FRAME", "opcode must be an integer between 0 and 15", { opcode });
|
|
222
|
+
if (options?.mask !== void 0 && options.mask.length !== 4) throw new WebSocketError("FRAME", "mask must contain exactly 4 bytes", { size: options.mask.length });
|
|
223
|
+
if (options?.mask !== void 0 && options.masked !== true) throw new WebSocketError("FRAME", "mask requires masked: true");
|
|
224
|
+
const body = typeof payload === "string" ? Buffer.from(payload, "utf-8") : payload;
|
|
225
|
+
const length = body.length;
|
|
226
|
+
const masked = options?.masked === true;
|
|
227
|
+
const mask = masked ? options?.mask ?? (0, node_crypto.randomBytes)(4) : void 0;
|
|
228
|
+
const maskBit = masked ? 128 : 0;
|
|
229
|
+
const extended = length < 126 ? 0 : length < 65536 ? 2 : 8;
|
|
230
|
+
const header = Buffer.alloc(2 + extended + (mask !== void 0 ? 4 : 0));
|
|
231
|
+
header[0] = 128 | opcode;
|
|
232
|
+
if (length < 126) header[1] = maskBit | length;
|
|
233
|
+
else if (length < 65536) {
|
|
234
|
+
header[1] = maskBit | 126;
|
|
235
|
+
header.writeUInt16BE(length, 2);
|
|
236
|
+
} else {
|
|
237
|
+
header[1] = maskBit | 127;
|
|
238
|
+
header.writeUInt32BE(Math.floor(length / 4294967296), 2);
|
|
239
|
+
header.writeUInt32BE(length % 4294967296, 6);
|
|
240
|
+
}
|
|
241
|
+
if (mask === void 0) return Buffer.concat([header, body]);
|
|
242
|
+
mask.copy(header, header.length - 4);
|
|
243
|
+
const maskedBody = Buffer.alloc(length);
|
|
244
|
+
for (let index = 0; index < length; index += 1) maskedBody[index] = body.readUInt8(index) ^ mask.readUInt8(index % 4);
|
|
245
|
+
return Buffer.concat([header, maskedBody]);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Checks whether a value is a canonical RFC 6455 `Sec-WebSocket-Key`.
|
|
74
249
|
*
|
|
75
250
|
* @remarks
|
|
76
251
|
* A valid key is exactly 16 random bytes encoded as 24 characters of base64, ending
|
|
@@ -78,7 +253,7 @@ function computeWebSocketAccept(key) {
|
|
|
78
253
|
* malformed or non-canonical encodings return `false`; nothing is thrown.
|
|
79
254
|
*
|
|
80
255
|
* @param key - The proposed `Sec-WebSocket-Key` header value
|
|
81
|
-
* @returns
|
|
256
|
+
* @returns True if `key` is the canonical base64 encoding of 16 bytes; false otherwise
|
|
82
257
|
*
|
|
83
258
|
* @example
|
|
84
259
|
* ```ts
|
|
@@ -91,7 +266,7 @@ function isWebSocketKey(key) {
|
|
|
91
266
|
return Buffer.from(key, "base64").length === 16;
|
|
92
267
|
}
|
|
93
268
|
/**
|
|
94
|
-
*
|
|
269
|
+
* Checks whether a value is one valid WebSocket subprotocol token.
|
|
95
270
|
*
|
|
96
271
|
* @remarks
|
|
97
272
|
* Subprotocols use the HTTP `token` grammar. Whitespace, separators, commas, and
|
|
@@ -99,18 +274,48 @@ function isWebSocketKey(key) {
|
|
|
99
274
|
* second handshake header.
|
|
100
275
|
*
|
|
101
276
|
* @param protocol - The negotiated subprotocol to validate
|
|
102
|
-
* @returns
|
|
277
|
+
* @returns True if `protocol` is one non-empty HTTP token; false otherwise
|
|
103
278
|
*
|
|
104
279
|
* @example
|
|
105
280
|
* ```ts
|
|
106
|
-
* if (!isWebSocketProtocol(protocol))
|
|
281
|
+
* if (!isWebSocketProtocol(protocol)) socket.destroy()
|
|
107
282
|
* ```
|
|
108
283
|
*/
|
|
109
284
|
function isWebSocketProtocol(protocol) {
|
|
110
285
|
return /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(protocol);
|
|
111
286
|
}
|
|
112
287
|
/**
|
|
113
|
-
*
|
|
288
|
+
* Checks whether a numeric value is a valid RFC 6455 close status code to RECEIVE (§7.4.1).
|
|
289
|
+
*
|
|
290
|
+
* @remarks
|
|
291
|
+
* True for `1000`–`1003`, `1007`–`1014`, and the application range `3000`–`4999`; false
|
|
292
|
+
* for anything below `1000`, the reserved-for-local-use-only codes `1004`–`1006` and
|
|
293
|
+
* `1015`, and the unassigned `1016`–`2999` range. The `1012`–`1014` extension of the
|
|
294
|
+
* strict RFC 6455 receivable set is a deliberate IANA-interop choice: those three codes
|
|
295
|
+
* (Service Restart, Try Again Later, Bad Gateway) are IANA-registered in the WebSocket
|
|
296
|
+
* Close Code Number Registry and accepted by the `ws` ecosystem and modern conformance
|
|
297
|
+
* suites, so a peer sending one is not treated as a protocol violation. Pure predicate,
|
|
298
|
+
* never throws.
|
|
299
|
+
*
|
|
300
|
+
* @param code - The close status code to validate
|
|
301
|
+
* @returns True if `code` is a valid RFC 6455 close code; false otherwise
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* ```ts
|
|
305
|
+
* if (!isCloseCode(code)) fail(WEBSOCKET_CLOSE_PROTOCOL)
|
|
306
|
+
* ```
|
|
307
|
+
*/
|
|
308
|
+
function isCloseCode(code) {
|
|
309
|
+
if (!Number.isInteger(code)) return false;
|
|
310
|
+
if (code >= 1e3 && code <= 1003) return true;
|
|
311
|
+
if (code >= 1007 && code <= 1014) return true;
|
|
312
|
+
if (code >= 3e3 && code <= 4999) return true;
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
//#endregion
|
|
316
|
+
//#region src/server/parsers.ts
|
|
317
|
+
/**
|
|
318
|
+
* Decodes a single RFC 6455 frame from the front of a buffer.
|
|
114
319
|
*
|
|
115
320
|
* @remarks
|
|
116
321
|
* Reads the FIN bit and opcode (byte 0), the mask bit and 7-bit payload length (byte
|
|
@@ -120,12 +325,17 @@ function isWebSocketProtocol(protocol) {
|
|
|
120
325
|
* 6455 §5.3; an unmasked frame still decodes, leaving the payload as-is, so the caller
|
|
121
326
|
* can enforce policy). Returns `undefined` the moment the buffer is too short for the
|
|
122
327
|
* 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
|
|
124
|
-
*
|
|
125
|
-
* remainder. Pure; never throws on a short buffer.
|
|
328
|
+
* the caller to read more bytes and retry. `consumed` is the total bytes the frame
|
|
329
|
+
* occupied, so the caller slices the remainder. Pure; never throws on a short buffer.
|
|
126
330
|
*
|
|
127
331
|
* @param buffer - The accumulation buffer to decode the next frame from
|
|
128
332
|
* @returns The parsed {@link WebSocketFrame}, or `undefined` when the buffer is incomplete
|
|
333
|
+
*
|
|
334
|
+
* @example
|
|
335
|
+
* ```ts
|
|
336
|
+
* const frame = parseWebSocketFrame(buffer)
|
|
337
|
+
* if (frame === undefined) return // incomplete — wait for more bytes
|
|
338
|
+
* ```
|
|
129
339
|
*/
|
|
130
340
|
function parseWebSocketFrame(buffer) {
|
|
131
341
|
if (buffer.length < 2) return void 0;
|
|
@@ -168,77 +378,12 @@ function parseWebSocketFrame(buffer) {
|
|
|
168
378
|
};
|
|
169
379
|
}
|
|
170
380
|
/**
|
|
171
|
-
*
|
|
172
|
-
* reading the payload itself.
|
|
173
|
-
*
|
|
174
|
-
* @remarks
|
|
175
|
-
* 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
|
|
177
|
-
* 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.
|
|
180
|
-
*
|
|
181
|
-
* @param buffer - The accumulation buffer to read the next frame's length from
|
|
182
|
-
* @returns The declared payload length, or `undefined` when the buffer is too short to know it yet
|
|
183
|
-
*
|
|
184
|
-
* @example
|
|
185
|
-
* ```ts
|
|
186
|
-
* const declared = measureWebSocketFrame(buffer)
|
|
187
|
-
* if (declared !== undefined && declared > limit) fail(WEBSOCKET_CLOSE_TOOBIG)
|
|
188
|
-
* ```
|
|
189
|
-
*/
|
|
190
|
-
function measureWebSocketFrame(buffer) {
|
|
191
|
-
if (buffer.length < 2) return void 0;
|
|
192
|
-
let length = buffer.readUInt8(1) & 127;
|
|
193
|
-
const offset = 2;
|
|
194
|
-
if (length === 126) {
|
|
195
|
-
if (buffer.length < 4) return void 0;
|
|
196
|
-
length = buffer.readUInt16BE(offset);
|
|
197
|
-
} else if (length === 127) {
|
|
198
|
-
if (buffer.length < 10) return void 0;
|
|
199
|
-
const high = buffer.readUInt32BE(offset);
|
|
200
|
-
const low = buffer.readUInt32BE(6);
|
|
201
|
-
length = high * 4294967296 + low;
|
|
202
|
-
}
|
|
203
|
-
return length;
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* Whether the next frame uses the shortest valid RFC 6455 payload-length encoding.
|
|
207
|
-
*
|
|
208
|
-
* @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).
|
|
212
|
-
*
|
|
213
|
-
* @param buffer - The accumulation buffer containing the next frame header
|
|
214
|
-
* @returns Its canonicality, or `undefined` while the length prefix is incomplete
|
|
215
|
-
*
|
|
216
|
-
* @example
|
|
217
|
-
* ```ts
|
|
218
|
-
* if (isWebSocketFrameCanonical(buffer) === false) fail(WEBSOCKET_CLOSE_PROTOCOL)
|
|
219
|
-
* ```
|
|
220
|
-
*/
|
|
221
|
-
function isWebSocketFrameCanonical(buffer) {
|
|
222
|
-
if (buffer.length < 2) return void 0;
|
|
223
|
-
const lengthCode = buffer.readUInt8(1) & 127;
|
|
224
|
-
if (lengthCode < 126) return true;
|
|
225
|
-
if (lengthCode === 126) {
|
|
226
|
-
if (buffer.length < 4) return void 0;
|
|
227
|
-
return buffer.readUInt16BE(2) >= 126;
|
|
228
|
-
}
|
|
229
|
-
if (buffer.length < 10) return void 0;
|
|
230
|
-
const high = buffer.readUInt32BE(2);
|
|
231
|
-
const low = buffer.readUInt32BE(6);
|
|
232
|
-
if ((high & 2147483648) !== 0) return false;
|
|
233
|
-
return high > 0 || low >= 65536;
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* Decode a byte sequence as strict UTF-8, or signal it is malformed.
|
|
381
|
+
* Decodes a byte sequence as strict UTF-8, or signals it is malformed.
|
|
237
382
|
*
|
|
238
383
|
* @remarks
|
|
239
384
|
* Wraps `TextDecoder('utf-8', { fatal: true })` in a try/catch so a malformed sequence
|
|
240
|
-
* returns `undefined` instead of throwing
|
|
241
|
-
*
|
|
385
|
+
* returns `undefined` instead of throwing — a guard-adjacent coercer never throws on bad
|
|
386
|
+
* input. Pure.
|
|
242
387
|
*
|
|
243
388
|
* @param bytes - The raw bytes to decode
|
|
244
389
|
* @returns The decoded string, or `undefined` when `bytes` is not valid UTF-8
|
|
@@ -256,84 +401,10 @@ function parseUTF8(bytes) {
|
|
|
256
401
|
return;
|
|
257
402
|
}
|
|
258
403
|
}
|
|
259
|
-
/**
|
|
260
|
-
* Whether a numeric value is a valid RFC 6455 close status code to RECEIVE (§7.4.1).
|
|
261
|
-
*
|
|
262
|
-
* @remarks
|
|
263
|
-
* True for `1000`–`1003`, `1007`–`1014`, and the application range `3000`–`4999`; false
|
|
264
|
-
* for anything below `1000`, the reserved-for-local-use-only codes `1004`–`1006` and
|
|
265
|
-
* `1015`, and the unassigned `1016`–`2999` range. The `1012`–`1014` extension of the
|
|
266
|
-
* strict RFC 6455 receivable set is a deliberate IANA-interop choice: those three codes
|
|
267
|
-
* (Service Restart, Try Again Later, Bad Gateway) are IANA-registered in the WebSocket
|
|
268
|
-
* Close Code Number Registry and accepted by the `ws` ecosystem and modern conformance
|
|
269
|
-
* suites, so a peer sending one is not treated as a protocol violation. Pure predicate,
|
|
270
|
-
* never throws.
|
|
271
|
-
*
|
|
272
|
-
* @param code - The close status code to validate
|
|
273
|
-
* @returns `true` when `code` is a valid RFC 6455 close code
|
|
274
|
-
*
|
|
275
|
-
* @example
|
|
276
|
-
* ```ts
|
|
277
|
-
* if (!isCloseCode(code)) fail(WEBSOCKET_CLOSE_PROTOCOL)
|
|
278
|
-
* ```
|
|
279
|
-
*/
|
|
280
|
-
function isCloseCode(code) {
|
|
281
|
-
if (!Number.isInteger(code)) return false;
|
|
282
|
-
if (code >= 1e3 && code <= 1003) return true;
|
|
283
|
-
if (code >= 1007 && code <= 1014) return true;
|
|
284
|
-
if (code >= 3e3 && code <= 4999) return true;
|
|
285
|
-
return false;
|
|
286
|
-
}
|
|
287
|
-
/**
|
|
288
|
-
* Encode a single RFC 6455 frame to its wire bytes — the inverse of
|
|
289
|
-
* {@link parseWebSocketFrame}.
|
|
290
|
-
*
|
|
291
|
-
* @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.
|
|
300
|
-
*
|
|
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
|
|
305
|
-
*/
|
|
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);
|
|
326
|
-
}
|
|
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
|
-
}
|
|
333
404
|
//#endregion
|
|
334
405
|
//#region src/server/NodeWebSocket.ts
|
|
335
406
|
/**
|
|
336
|
-
*
|
|
407
|
+
* Represents a server-native WebSocket over a raw upgraded `node:stream` Duplex — the lean
|
|
337
408
|
* wrapper around the RFC 6455 wire protocol.
|
|
338
409
|
*
|
|
339
410
|
* @remarks
|
|
@@ -346,11 +417,25 @@ function encodeWebSocketFrame(opcode, payload, options) {
|
|
|
346
417
|
* `fin: false` frames — decodes to UTF-8 and emits `message`; a PING is auto-answered
|
|
347
418
|
* with a PONG and emits `ping`; a PONG emits `pong`; a CLOSE is echoed and ends the
|
|
348
419
|
* 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`
|
|
350
|
-
* isolates a throwing listener and routes the error to its own `error` handler
|
|
351
|
-
* option) — the socket never crashes. An underlying socket error emits the
|
|
352
|
-
* `error` event and terminates the wrapper. The untyped socket `data` is narrowed
|
|
353
|
-
* `Buffer` with a guard, never an assertion
|
|
420
|
+
* frame; `destroy` tears down immediately. It owns a typed `#emitter` by composition, and
|
|
421
|
+
* the emitter isolates a throwing listener and routes the error to its own `error` handler
|
|
422
|
+
* (the `error` option) — the socket never crashes. An underlying socket error emits the
|
|
423
|
+
* domain `error` event and terminates the wrapper. The untyped socket `data` is narrowed
|
|
424
|
+
* to a `Buffer` with a guard, never an assertion.
|
|
425
|
+
*
|
|
426
|
+
* @example
|
|
427
|
+
* ```ts
|
|
428
|
+
* import { NodeWebSocket } from '@src/server'
|
|
429
|
+
*
|
|
430
|
+
* // In a node:http 'upgrade' handler, over the socket the server already handed over:
|
|
431
|
+
* const key = request.headers['sec-websocket-key']
|
|
432
|
+
* if (typeof key !== 'string') {
|
|
433
|
+
* socket.destroy()
|
|
434
|
+
* return
|
|
435
|
+
* }
|
|
436
|
+
* const ws = new NodeWebSocket({ socket, key, head })
|
|
437
|
+
* ws.emitter.on('message', (text) => ws.send(`echo: ${text}`))
|
|
438
|
+
* ```
|
|
354
439
|
*/
|
|
355
440
|
var NodeWebSocket = class {
|
|
356
441
|
#emitter;
|
|
@@ -373,14 +458,25 @@ var NodeWebSocket = class {
|
|
|
373
458
|
#closeTimer;
|
|
374
459
|
#destroyed = false;
|
|
375
460
|
#detached = false;
|
|
461
|
+
/**
|
|
462
|
+
* Creates a WebSocket wrapper over an already-upgraded Duplex socket.
|
|
463
|
+
*
|
|
464
|
+
* @remarks
|
|
465
|
+
* `key` selects the mode: present runs SERVER mode and writes the `101 Switching
|
|
466
|
+
* Protocols` handshake, omitted runs CLIENT mode and masks every outgoing frame.
|
|
467
|
+
* {@link NodeWebSocketOptions} describes every member.
|
|
468
|
+
*
|
|
469
|
+
* @param options - The {@link NodeWebSocketOptions} the wrapper is built from
|
|
470
|
+
* @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`
|
|
471
|
+
*/
|
|
376
472
|
constructor(options) {
|
|
377
473
|
const payload = options.payload ?? 104857600;
|
|
378
|
-
if (!Number.isSafeInteger(payload) || payload < 0) throw new
|
|
474
|
+
if (!Number.isSafeInteger(payload) || payload < 0) throw new WebSocketError("OPTION", "payload must be a non-negative safe integer", { payload });
|
|
379
475
|
const timeout = options.timeout ?? 3e4;
|
|
380
|
-
if (!Number.isSafeInteger(timeout) || timeout < 0) throw new
|
|
381
|
-
if (options.key !== void 0 && !isWebSocketKey(options.key)) throw new
|
|
382
|
-
if (options.protocol !== void 0 && !isWebSocketProtocol(options.protocol)) throw new
|
|
383
|
-
if (options.protocol !== void 0 && options.key === void 0) throw new
|
|
476
|
+
if (!Number.isSafeInteger(timeout) || timeout < 0) throw new WebSocketError("OPTION", "timeout must be a non-negative safe integer", { timeout });
|
|
477
|
+
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 });
|
|
478
|
+
if (options.protocol !== void 0 && !isWebSocketProtocol(options.protocol)) throw new WebSocketError("OPTION", "protocol must be a valid WebSocket subprotocol token", { protocol: options.protocol });
|
|
479
|
+
if (options.protocol !== void 0 && options.key === void 0) throw new WebSocketError("OPTION", "protocol requires a server key", { protocol: options.protocol });
|
|
384
480
|
this.#emitter = new _orkestrel_emitter.Emitter({
|
|
385
481
|
...options.on === void 0 ? {} : { on: options.on },
|
|
386
482
|
...options.error === void 0 ? {} : { error: options.error }
|
|
@@ -422,19 +518,27 @@ var NodeWebSocket = class {
|
|
|
422
518
|
get readyState() {
|
|
423
519
|
return this.#readyState;
|
|
424
520
|
}
|
|
425
|
-
send(
|
|
521
|
+
send(message) {
|
|
426
522
|
if (this.#readyState !== 1) return;
|
|
427
|
-
this.#write(1, Buffer.from(
|
|
523
|
+
this.#write(1, Buffer.from(message, "utf-8"));
|
|
428
524
|
}
|
|
429
|
-
ping(
|
|
525
|
+
ping(payload) {
|
|
430
526
|
if (this.#readyState !== 1) return;
|
|
431
|
-
|
|
432
|
-
|
|
527
|
+
const size = payload === void 0 ? 0 : Buffer.byteLength(payload, "utf-8");
|
|
528
|
+
if (size > 125) throw new WebSocketError("LIMIT", `ping payload exceeds 125 bytes`, {
|
|
529
|
+
size,
|
|
530
|
+
limit: 125
|
|
531
|
+
});
|
|
532
|
+
this.#write(9, payload === void 0 ? Buffer.alloc(0) : Buffer.from(payload, "utf-8"));
|
|
433
533
|
}
|
|
434
534
|
close(code, reason) {
|
|
435
535
|
if (this.#readyState === 2 || this.#readyState === 3) return;
|
|
436
|
-
if (code !== void 0 && !isCloseCode(code)) throw new
|
|
437
|
-
|
|
536
|
+
if (code !== void 0 && !isCloseCode(code)) throw new WebSocketError("CLOSE", "invalid close code", { code });
|
|
537
|
+
const size = reason === void 0 ? 0 : Buffer.byteLength(reason, "utf-8");
|
|
538
|
+
if (size > 123) throw new WebSocketError("LIMIT", `close reason exceeds 123 bytes`, {
|
|
539
|
+
size,
|
|
540
|
+
limit: 123
|
|
541
|
+
});
|
|
438
542
|
this.#readyState = 2;
|
|
439
543
|
this.#code = code ?? 1e3;
|
|
440
544
|
this.#reason = reason === void 0 || reason.length === 0 ? void 0 : reason;
|
|
@@ -456,13 +560,13 @@ var NodeWebSocket = class {
|
|
|
456
560
|
}
|
|
457
561
|
#drain() {
|
|
458
562
|
for (;;) {
|
|
459
|
-
if (
|
|
563
|
+
if (matchesWebSocketCanonical(this.#buffer) === false) {
|
|
460
564
|
this.#fail(WEBSOCKET_CLOSE_PROTOCOL);
|
|
461
565
|
return;
|
|
462
566
|
}
|
|
463
567
|
const declared = measureWebSocketFrame(this.#buffer);
|
|
464
568
|
if (declared !== void 0 && declared > this.#payload) {
|
|
465
|
-
this.#fail(
|
|
569
|
+
this.#fail(WEBSOCKET_CLOSE_TOO_BIG);
|
|
466
570
|
return;
|
|
467
571
|
}
|
|
468
572
|
const frame = parseWebSocketFrame(this.#buffer);
|
|
@@ -516,7 +620,7 @@ var NodeWebSocket = class {
|
|
|
516
620
|
this.#fragments.push(payload);
|
|
517
621
|
this.#fragmentBytes += payload.length;
|
|
518
622
|
if (this.#fragmentBytes > this.#payload) {
|
|
519
|
-
this.#fail(
|
|
623
|
+
this.#fail(WEBSOCKET_CLOSE_TOO_BIG);
|
|
520
624
|
return;
|
|
521
625
|
}
|
|
522
626
|
if (!fin) return;
|
|
@@ -645,10 +749,10 @@ var NodeWebSocket = class {
|
|
|
645
749
|
//#endregion
|
|
646
750
|
//#region src/server/factories.ts
|
|
647
751
|
/**
|
|
648
|
-
*
|
|
752
|
+
* Creates a server-native WebSocket over a raw upgraded `node:stream` Duplex socket.
|
|
649
753
|
*
|
|
650
754
|
* @remarks
|
|
651
|
-
* The construction entry point for the {@link NodeWebSocketInterface}
|
|
755
|
+
* The construction entry point for the {@link NodeWebSocketInterface}. Pass
|
|
652
756
|
* the upgraded `socket` plus the client's `Sec-WebSocket-Key` as `key` to run in SERVER
|
|
653
757
|
* mode — the wrapper writes the `101 Switching Protocols` handshake and sends unmasked
|
|
654
758
|
* frames; omit `key` for CLIENT mode (no handshake, masked frames). This is the
|
|
@@ -659,6 +763,7 @@ var NodeWebSocket = class {
|
|
|
659
763
|
* @param options - The {@link NodeWebSocketOptions} (`socket`, optional `key` / `head` /
|
|
660
764
|
* `protocol` / `on`)
|
|
661
765
|
* @returns A typed {@link NodeWebSocketInterface}
|
|
766
|
+
* @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
767
|
*
|
|
663
768
|
* @example
|
|
664
769
|
* ```ts
|
|
@@ -666,9 +771,14 @@ var NodeWebSocket = class {
|
|
|
666
771
|
*
|
|
667
772
|
* // In a node:http 'upgrade' handler — server mode, identified by the client key:
|
|
668
773
|
* server.on('upgrade', (request, socket, head) => {
|
|
774
|
+
* const key = request.headers['sec-websocket-key']
|
|
775
|
+
* if (typeof key !== 'string') {
|
|
776
|
+
* socket.destroy()
|
|
777
|
+
* return
|
|
778
|
+
* }
|
|
669
779
|
* const ws = createNodeWebSocket({
|
|
670
780
|
* socket,
|
|
671
|
-
* key
|
|
781
|
+
* key, // present => server mode + 101 handshake
|
|
672
782
|
* head,
|
|
673
783
|
* on: { message: (text) => ws.send(`echo: ${text}`) },
|
|
674
784
|
* })
|
|
@@ -683,11 +793,11 @@ exports.NodeWebSocket = NodeWebSocket;
|
|
|
683
793
|
exports.WEBSOCKET_CLOSE_INVALID = WEBSOCKET_CLOSE_INVALID;
|
|
684
794
|
exports.WEBSOCKET_CLOSE_NORMAL = WEBSOCKET_CLOSE_NORMAL;
|
|
685
795
|
exports.WEBSOCKET_CLOSE_PROTOCOL = WEBSOCKET_CLOSE_PROTOCOL;
|
|
686
|
-
exports.
|
|
796
|
+
exports.WEBSOCKET_CLOSE_REASON_MAX_LENGTH = WEBSOCKET_CLOSE_REASON_MAX_LENGTH;
|
|
687
797
|
exports.WEBSOCKET_CLOSE_TIMEOUT_MS = WEBSOCKET_CLOSE_TIMEOUT_MS;
|
|
688
|
-
exports.
|
|
798
|
+
exports.WEBSOCKET_CLOSE_TOO_BIG = WEBSOCKET_CLOSE_TOO_BIG;
|
|
689
799
|
exports.WEBSOCKET_CLOSE_UNSUPPORTED = WEBSOCKET_CLOSE_UNSUPPORTED;
|
|
690
|
-
exports.
|
|
800
|
+
exports.WEBSOCKET_CONTROL_MAX_LENGTH = WEBSOCKET_CONTROL_MAX_LENGTH;
|
|
691
801
|
exports.WEBSOCKET_FAIL_TIMEOUT_MS = WEBSOCKET_FAIL_TIMEOUT_MS;
|
|
692
802
|
exports.WEBSOCKET_GUID = WEBSOCKET_GUID;
|
|
693
803
|
exports.WEBSOCKET_MAX_PAYLOAD = WEBSOCKET_MAX_PAYLOAD;
|
|
@@ -702,13 +812,15 @@ exports.WEBSOCKET_READY_CLOSING = WEBSOCKET_READY_CLOSING;
|
|
|
702
812
|
exports.WEBSOCKET_READY_CONNECTING = WEBSOCKET_READY_CONNECTING;
|
|
703
813
|
exports.WEBSOCKET_READY_OPEN = WEBSOCKET_READY_OPEN;
|
|
704
814
|
exports.WEBSOCKET_VERSION = WEBSOCKET_VERSION;
|
|
815
|
+
exports.WebSocketError = WebSocketError;
|
|
705
816
|
exports.computeWebSocketAccept = computeWebSocketAccept;
|
|
706
817
|
exports.createNodeWebSocket = createNodeWebSocket;
|
|
707
818
|
exports.encodeWebSocketFrame = encodeWebSocketFrame;
|
|
708
819
|
exports.isCloseCode = isCloseCode;
|
|
709
|
-
exports.
|
|
820
|
+
exports.isWebSocketError = isWebSocketError;
|
|
710
821
|
exports.isWebSocketKey = isWebSocketKey;
|
|
711
822
|
exports.isWebSocketProtocol = isWebSocketProtocol;
|
|
823
|
+
exports.matchesWebSocketCanonical = matchesWebSocketCanonical;
|
|
712
824
|
exports.measureWebSocketFrame = measureWebSocketFrame;
|
|
713
825
|
exports.parseUTF8 = parseUTF8;
|
|
714
826
|
exports.parseWebSocketFrame = parseWebSocketFrame;
|