ventiws 1.0.0-alpha
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/LICENSE +21 -0
- package/README.md +138 -0
- package/dist/index.cjs +2986 -0
- package/dist/index.d.cts +400 -0
- package/dist/index.d.mts +401 -0
- package/dist/index.mjs +2977 -0
- package/dist/ventiws.node +0 -0
- package/package.json +68 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2986 @@
|
|
|
1
|
+
Object.defineProperties(exports, {
|
|
2
|
+
__esModule: { value: true },
|
|
3
|
+
[Symbol.toStringTag]: { value: "Module" }
|
|
4
|
+
});
|
|
5
|
+
let node_http = require("node:http");
|
|
6
|
+
let node_fs = require("node:fs");
|
|
7
|
+
let node_module = require("node:module");
|
|
8
|
+
let node_process = require("node:process");
|
|
9
|
+
let node_path = require("node:path");
|
|
10
|
+
let node_url = require("node:url");
|
|
11
|
+
let node_util = require("node:util");
|
|
12
|
+
let node_crypto = require("node:crypto");
|
|
13
|
+
let node_https = require("node:https");
|
|
14
|
+
let node_stream = require("node:stream");
|
|
15
|
+
//#region src/binding/addon-error.ts
|
|
16
|
+
function unloadableAddonMessage(path, platform, arch) {
|
|
17
|
+
return `ventiws: the native addon at ${path} was found but could not be loaded, so it was built for a different ${platform}-${arch} or against a different libc. In a checkout the fix is "pnpm build:binding"; in an install, a package built for this platform. The loader's own error is the cause.`;
|
|
18
|
+
}
|
|
19
|
+
function missingAddonMessage(root, platform, arch) {
|
|
20
|
+
const target = `${platform}-${arch}`;
|
|
21
|
+
if (root === void 0) return `ventiws: the package layout is broken -- there is no package.json above the native addon, so the artifact for ${target} cannot be located. Reinstalling ventiws should restore it.`;
|
|
22
|
+
return `ventiws: no native addon for ${target} was found under ${root}. An install ships the addon for one platform, so a ${target} machine needs ventiws built for ${target}, which needs the Zig toolchain (zig 0.16.0). If you are working in a checkout, the command is "pnpm build:binding".`;
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/binding/load.ts
|
|
26
|
+
const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
27
|
+
const candidatePaths = [[
|
|
28
|
+
"zig-out",
|
|
29
|
+
"lib",
|
|
30
|
+
"ventiws.node"
|
|
31
|
+
], ["dist", "ventiws.node"]];
|
|
32
|
+
function findPackageRoot(start) {
|
|
33
|
+
let current = start;
|
|
34
|
+
while (current !== void 0) {
|
|
35
|
+
if ((0, node_fs.existsSync)((0, node_path.join)(current, "package.json"))) return current;
|
|
36
|
+
const parent = (0, node_path.dirname)(current);
|
|
37
|
+
current = parent === current ? void 0 : parent;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function resolveAddonPath() {
|
|
41
|
+
const root = findPackageRoot((0, node_path.dirname)((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href)));
|
|
42
|
+
for (const parts of candidatePaths) {
|
|
43
|
+
if (root === void 0) continue;
|
|
44
|
+
const candidate = (0, node_path.join)(root, ...parts);
|
|
45
|
+
if ((0, node_fs.existsSync)(candidate)) return candidate;
|
|
46
|
+
}
|
|
47
|
+
throw new Error(missingAddonMessage(root, node_process.platform, node_process.arch));
|
|
48
|
+
}
|
|
49
|
+
let addon;
|
|
50
|
+
function loadAddon() {
|
|
51
|
+
if (addon !== void 0) return addon;
|
|
52
|
+
const path = resolveAddonPath();
|
|
53
|
+
try {
|
|
54
|
+
addon = require$1(path);
|
|
55
|
+
} catch (cause) {
|
|
56
|
+
throw Object.assign(new Error(unloadableAddonMessage(path, node_process.platform, node_process.arch)), { cause });
|
|
57
|
+
}
|
|
58
|
+
return addon;
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/compat/events/registry.ts
|
|
62
|
+
function createRegistry() {
|
|
63
|
+
return {};
|
|
64
|
+
}
|
|
65
|
+
function listenerCount$1(registry, event) {
|
|
66
|
+
return registry[event]?.length ?? 0;
|
|
67
|
+
}
|
|
68
|
+
function subscribe(registry, event, handler) {
|
|
69
|
+
const bucket = registry[event] ?? [];
|
|
70
|
+
return {
|
|
71
|
+
...registry,
|
|
72
|
+
[event]: [...bucket, handler]
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function prepend(registry, event, handler) {
|
|
76
|
+
const bucket = registry[event] ?? [];
|
|
77
|
+
return {
|
|
78
|
+
...registry,
|
|
79
|
+
[event]: [handler, ...bucket]
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function unsubscribeMatching(registry, event, matches) {
|
|
83
|
+
const bucket = registry[event];
|
|
84
|
+
if (bucket === void 0) return registry;
|
|
85
|
+
let index = bucket.length - 1;
|
|
86
|
+
while (index >= 0) {
|
|
87
|
+
if (matches(bucket[index])) return {
|
|
88
|
+
...registry,
|
|
89
|
+
[event]: bucket.toSpliced(index, 1)
|
|
90
|
+
};
|
|
91
|
+
index -= 1;
|
|
92
|
+
}
|
|
93
|
+
return registry;
|
|
94
|
+
}
|
|
95
|
+
function removeAll(registry, event) {
|
|
96
|
+
if (event === void 0) return {};
|
|
97
|
+
return {
|
|
98
|
+
...registry,
|
|
99
|
+
[event]: []
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function eventNames(registry) {
|
|
103
|
+
const names = [];
|
|
104
|
+
for (const key of Object.keys(registry)) if ((registry[key]?.length ?? 0) > 0) names.push(key);
|
|
105
|
+
return names;
|
|
106
|
+
}
|
|
107
|
+
function dispatchWith(registry, target, event, ...args) {
|
|
108
|
+
const bucket = registry[event];
|
|
109
|
+
if (bucket === void 0) return 0;
|
|
110
|
+
for (let index = 0; index < bucket.length; index += 1) Reflect.apply(bucket[index], target, args);
|
|
111
|
+
return bucket.length;
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/compat/errors.ts
|
|
115
|
+
const STATUS_ERROR_CODES = {
|
|
116
|
+
closed: "ERR_SOCKET_CLOSED",
|
|
117
|
+
"invalid-handle": "ERR_INVALID_HANDLE",
|
|
118
|
+
"payload-too-large": "ERR_MAX_PAYLOAD",
|
|
119
|
+
"invalid-close-code": "ERR_INVALID_CLOSE_CODE",
|
|
120
|
+
"invalid-close-reason": "ERR_INVALID_CLOSE_REASON",
|
|
121
|
+
"protocol-error": "ERR_PROTOCOL",
|
|
122
|
+
"policy-violation": "ERR_POLICY_VIOLATION"
|
|
123
|
+
};
|
|
124
|
+
function createError(code, message, constructor = Error) {
|
|
125
|
+
return Object.assign(new constructor(message), { code });
|
|
126
|
+
}
|
|
127
|
+
function createStatusError(status, message) {
|
|
128
|
+
return createError(STATUS_ERROR_CODES[status], message);
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region src/compat/events/limits.ts
|
|
132
|
+
function warnOnOverflow(state, event) {
|
|
133
|
+
if (state.maxListeners <= 0) return;
|
|
134
|
+
const count = listenerCount$1(state.listeners, event);
|
|
135
|
+
if (count <= state.maxListeners) return;
|
|
136
|
+
const name = String(event);
|
|
137
|
+
if (state.warned.has(name)) return;
|
|
138
|
+
state.warned.add(name);
|
|
139
|
+
process.emitWarning(createOverflowWarning(count, name, state.maxListeners));
|
|
140
|
+
}
|
|
141
|
+
function forgetWarning(state, event) {
|
|
142
|
+
if (event === void 0) {
|
|
143
|
+
state.warned.clear();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
state.warned.delete(String(event));
|
|
147
|
+
}
|
|
148
|
+
function createOverflowWarning(count, event, limit) {
|
|
149
|
+
const warning = /* @__PURE__ */ new Error(`Possible EventEmitter memory leak detected. ${count} ${event} listeners added to the emitter. MaxListeners is ${limit}. Use emitter.setMaxListeners() to increase limit`);
|
|
150
|
+
warning.name = "MaxListenersExceededWarning";
|
|
151
|
+
return warning;
|
|
152
|
+
}
|
|
153
|
+
function assertListenerLimit(count) {
|
|
154
|
+
if (typeof count !== "number") throw createError("ERR_INVALID_OPTION", "The \"setMaxListeners\" argument must be of type number. Received " + String(count), TypeError);
|
|
155
|
+
if (count < 0 || Number.isNaN(count)) throw createError("ERR_INVALID_OPTION", `The value of "setMaxListeners" is out of range. It must be >= 0. Received ${count}`, RangeError);
|
|
156
|
+
return count;
|
|
157
|
+
}
|
|
158
|
+
//#endregion
|
|
159
|
+
//#region src/compat/events/tags.ts
|
|
160
|
+
const MAX_WRAPPER_DEPTH = 3;
|
|
161
|
+
const DOM_WRAPPER = Symbol("ventiws.domWrapper");
|
|
162
|
+
function asTagged(entry) {
|
|
163
|
+
return entry;
|
|
164
|
+
}
|
|
165
|
+
function originalOf(entry) {
|
|
166
|
+
let current = entry;
|
|
167
|
+
for (let depth = 0; depth < MAX_WRAPPER_DEPTH; depth += 1) {
|
|
168
|
+
if (typeof current !== "function") return current;
|
|
169
|
+
const next = asTagged(current).listener;
|
|
170
|
+
if (next === void 0) return current;
|
|
171
|
+
current = next;
|
|
172
|
+
}
|
|
173
|
+
return current;
|
|
174
|
+
}
|
|
175
|
+
function isDomEntry(entry) {
|
|
176
|
+
const tagged = asTagged(entry);
|
|
177
|
+
if (tagged[DOM_WRAPPER] === true) return true;
|
|
178
|
+
const inner = tagged.listener;
|
|
179
|
+
if (inner === void 0) return false;
|
|
180
|
+
return asTagged(inner)[DOM_WRAPPER] === true;
|
|
181
|
+
}
|
|
182
|
+
function listenerView(entry) {
|
|
183
|
+
const inner = asTagged(entry).listener;
|
|
184
|
+
if (inner === void 0) return entry;
|
|
185
|
+
if (isDomEntry(entry)) return asTagged(inner)[DOM_WRAPPER] === true ? inner : entry;
|
|
186
|
+
return inner;
|
|
187
|
+
}
|
|
188
|
+
function removeTagged(state, event, handler) {
|
|
189
|
+
state.listeners = unsubscribeMatching(state.listeners, event, (entry) => entry === handler || !isDomEntry(entry) && originalOf(entry) === handler);
|
|
190
|
+
}
|
|
191
|
+
function onceWrapper(state, event, raw) {
|
|
192
|
+
const wrapper = (...args) => {
|
|
193
|
+
state.listeners = unsubscribeMatching(state.listeners, event, (entry) => entry === wrapper);
|
|
194
|
+
Reflect.apply(raw, state.target, args);
|
|
195
|
+
};
|
|
196
|
+
return Object.assign(wrapper, { listener: raw });
|
|
197
|
+
}
|
|
198
|
+
function onceEvent(state, event, handler) {
|
|
199
|
+
state.listeners = subscribe(state.listeners, event, onceWrapper(state, event, handler));
|
|
200
|
+
}
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region src/compat/events/emitter.ts
|
|
203
|
+
const ERROR_EVENT = "error";
|
|
204
|
+
function emitEvent(state, event, ...args) {
|
|
205
|
+
if (event === ERROR_EVENT && listenerCount$1(state.listeners, event) === 0) {
|
|
206
|
+
const failure = args[0];
|
|
207
|
+
if (failure instanceof Error) throw failure;
|
|
208
|
+
const wrapped = /* @__PURE__ */ new Error(`Unhandled error. (${(0, node_util.inspect)(failure)})`);
|
|
209
|
+
Object.assign(wrapped, { context: failure });
|
|
210
|
+
throw wrapped;
|
|
211
|
+
}
|
|
212
|
+
return dispatchWith(state.listeners, state.target, event, ...args) > 0;
|
|
213
|
+
}
|
|
214
|
+
function createEmitter(state) {
|
|
215
|
+
const addListener = (event, handler) => {
|
|
216
|
+
state.listeners = subscribe(state.listeners, event, handler);
|
|
217
|
+
warnOnOverflow(state, event);
|
|
218
|
+
return state.target;
|
|
219
|
+
};
|
|
220
|
+
const once = (event, handler) => {
|
|
221
|
+
state.listeners = subscribe(state.listeners, event, onceWrapper(state, event, handler));
|
|
222
|
+
warnOnOverflow(state, event);
|
|
223
|
+
return state.target;
|
|
224
|
+
};
|
|
225
|
+
const prependListener = (event, handler) => {
|
|
226
|
+
state.listeners = prepend(state.listeners, event, handler);
|
|
227
|
+
warnOnOverflow(state, event);
|
|
228
|
+
return state.target;
|
|
229
|
+
};
|
|
230
|
+
const prependOnceListener = (event, handler) => {
|
|
231
|
+
state.listeners = prepend(state.listeners, event, onceWrapper(state, event, handler));
|
|
232
|
+
warnOnOverflow(state, event);
|
|
233
|
+
return state.target;
|
|
234
|
+
};
|
|
235
|
+
const removeListener = (event, handler) => {
|
|
236
|
+
removeTagged(state, event, handler);
|
|
237
|
+
return state.target;
|
|
238
|
+
};
|
|
239
|
+
const emit = (event, ...args) => emitEvent(state, event, ...args);
|
|
240
|
+
return {
|
|
241
|
+
on: addListener,
|
|
242
|
+
addListener,
|
|
243
|
+
once,
|
|
244
|
+
prependListener,
|
|
245
|
+
prependOnceListener,
|
|
246
|
+
off: removeListener,
|
|
247
|
+
removeListener,
|
|
248
|
+
removeAllListeners: (event) => {
|
|
249
|
+
state.listeners = removeAll(state.listeners, event);
|
|
250
|
+
forgetWarning(state, event);
|
|
251
|
+
return state.target;
|
|
252
|
+
},
|
|
253
|
+
emit,
|
|
254
|
+
listeners: (event) => {
|
|
255
|
+
return (state.listeners[event] ?? []).map((entry) => listenerView(entry));
|
|
256
|
+
},
|
|
257
|
+
rawListeners: (event) => [...state.listeners[event] ?? []],
|
|
258
|
+
eventNames: () => eventNames(state.listeners),
|
|
259
|
+
listenerCount: (event) => listenerCount$1(state.listeners, event),
|
|
260
|
+
getMaxListeners: () => state.maxListeners,
|
|
261
|
+
setMaxListeners: (count) => {
|
|
262
|
+
state.maxListeners = assertListenerLimit(count);
|
|
263
|
+
state.warned.clear();
|
|
264
|
+
return state.target;
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
//#endregion
|
|
269
|
+
//#region src/compat/server/clients.ts
|
|
270
|
+
function clientCount(state) {
|
|
271
|
+
return state.clients?.size ?? 0;
|
|
272
|
+
}
|
|
273
|
+
function trackClient(state, socket) {
|
|
274
|
+
const clients = state.clients;
|
|
275
|
+
if (clients === void 0) return;
|
|
276
|
+
clients.add(socket);
|
|
277
|
+
socket.once("close", () => {
|
|
278
|
+
clients.delete(socket);
|
|
279
|
+
if (state.shouldEmitClose && clients.size === 0) process.nextTick(() => emitClose(state));
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
//#endregion
|
|
283
|
+
//#region src/compat/server/close.ts
|
|
284
|
+
function emitClose(state) {
|
|
285
|
+
if (state.lifecycle === "closed") return;
|
|
286
|
+
state.lifecycle = "closed";
|
|
287
|
+
emitEvent(state, "close");
|
|
288
|
+
}
|
|
289
|
+
function emitCloseAgain(state) {
|
|
290
|
+
emitEvent(state, "close");
|
|
291
|
+
}
|
|
292
|
+
function detachServer(state) {
|
|
293
|
+
if (state.removeListeners !== null) state.removeListeners();
|
|
294
|
+
state.removeListeners = null;
|
|
295
|
+
state.server = null;
|
|
296
|
+
}
|
|
297
|
+
function addressOf(state) {
|
|
298
|
+
if (state.normalizedOptions.noServer) throw createError("ERR_INVALID_STATE", "The server is operating in \"noServer\" mode");
|
|
299
|
+
if (state.server === null) return null;
|
|
300
|
+
return state.server.address();
|
|
301
|
+
}
|
|
302
|
+
function closeWebSocketServer(state, callback) {
|
|
303
|
+
if (state.lifecycle === "closed") {
|
|
304
|
+
if (callback) onceEvent(state, "close", () => {
|
|
305
|
+
callback(createError("ERR_SOCKET_CLOSED", "The server is not running"));
|
|
306
|
+
});
|
|
307
|
+
process.nextTick(() => {
|
|
308
|
+
emitCloseAgain(state);
|
|
309
|
+
});
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (callback) onceEvent(state, "close", () => {
|
|
313
|
+
callback();
|
|
314
|
+
});
|
|
315
|
+
if (state.lifecycle === "closing") return;
|
|
316
|
+
state.lifecycle = "closing";
|
|
317
|
+
if (state.normalizedOptions.noServer || state.normalizedOptions.server !== null) {
|
|
318
|
+
detachServer(state);
|
|
319
|
+
if (!state.normalizedOptions.clientTracking || clientCount(state) === 0) {
|
|
320
|
+
process.nextTick(() => {
|
|
321
|
+
emitClose(state);
|
|
322
|
+
});
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
state.shouldEmitClose = true;
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const httpServer = state.server;
|
|
329
|
+
detachServer(state);
|
|
330
|
+
httpServer?.close(() => {
|
|
331
|
+
emitClose(state);
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
//#endregion
|
|
335
|
+
//#region src/compat/options/shared.ts
|
|
336
|
+
const DEFAULT_MAX_PAYLOAD = 104857600;
|
|
337
|
+
const DEFAULT_MAX_FRAGMENTS = 16384;
|
|
338
|
+
const DEFAULT_MAX_BUFFERED_CHUNKS = 262144;
|
|
339
|
+
const DEFAULT_CLOSE_TIMEOUT = 3e4;
|
|
340
|
+
const SUBPROTOCOL_PATTERN = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;
|
|
341
|
+
function isProtocolToken(value) {
|
|
342
|
+
return typeof value === "string" && SUBPROTOCOL_PATTERN.test(value);
|
|
343
|
+
}
|
|
344
|
+
function invalidOption(message, constructor = TypeError) {
|
|
345
|
+
throw createError("ERR_INVALID_OPTION", message, constructor);
|
|
346
|
+
}
|
|
347
|
+
function normalizeProtocols$1(protocols) {
|
|
348
|
+
if (protocols === void 0) return [];
|
|
349
|
+
const list = typeof protocols === "string" ? [protocols] : protocols;
|
|
350
|
+
const candidates = Array.isArray(list) ? list : [list];
|
|
351
|
+
const seen = /* @__PURE__ */ new Set();
|
|
352
|
+
const result = [];
|
|
353
|
+
for (const protocol of candidates) {
|
|
354
|
+
if (typeof protocol !== "string" || !SUBPROTOCOL_PATTERN.test(protocol) || seen.has(protocol)) invalidOption("An invalid or duplicated subprotocol was specified", SyntaxError);
|
|
355
|
+
seen.add(protocol);
|
|
356
|
+
result.push(protocol);
|
|
357
|
+
}
|
|
358
|
+
return result;
|
|
359
|
+
}
|
|
360
|
+
function parseProtocolHeader(header) {
|
|
361
|
+
return normalizeProtocols$1(header.split(",").map((protocol) => protocol.trim()));
|
|
362
|
+
}
|
|
363
|
+
function normalizePerMessageDeflate(value, fallback) {
|
|
364
|
+
if (value !== void 0 && !value) return false;
|
|
365
|
+
const resolved = value ?? fallback;
|
|
366
|
+
if (!resolved) return false;
|
|
367
|
+
const options = resolved === true ? {} : resolved;
|
|
368
|
+
return {
|
|
369
|
+
serverNoContextTakeover: options.serverNoContextTakeover,
|
|
370
|
+
clientNoContextTakeover: options.clientNoContextTakeover,
|
|
371
|
+
serverMaxWindowBits: options.serverMaxWindowBits,
|
|
372
|
+
clientMaxWindowBits: options.clientMaxWindowBits,
|
|
373
|
+
threshold: options.threshold ?? 1024,
|
|
374
|
+
concurrencyLimit: options.concurrencyLimit ?? 10,
|
|
375
|
+
zlibDeflateOptions: options.zlibDeflateOptions,
|
|
376
|
+
zlibInflateOptions: options.zlibInflateOptions
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
function closeTimeoutOf(source) {
|
|
380
|
+
const raw = source.closeTimeout;
|
|
381
|
+
if (raw === void 0) return DEFAULT_CLOSE_TIMEOUT;
|
|
382
|
+
if (typeof raw !== "number" || !Number.isFinite(raw) || raw < 0) invalidOption(`The closeTimeout option must be a non-negative number (received ${String(raw)})`, RangeError);
|
|
383
|
+
return raw;
|
|
384
|
+
}
|
|
385
|
+
//#endregion
|
|
386
|
+
//#region src/compat/server/handshake.ts
|
|
387
|
+
const KEY_PATTERN = /^[+/0-9A-Za-z]{22}==$/;
|
|
388
|
+
const GUID$1 = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
389
|
+
const STATUS_MIN = 100;
|
|
390
|
+
const STATUS_MAX = 599;
|
|
391
|
+
function hasControlCharacters(value) {
|
|
392
|
+
return value.includes("\0") || value.includes("\r") || value.includes("\n");
|
|
393
|
+
}
|
|
394
|
+
function socketAccept(key) {
|
|
395
|
+
return (0, node_crypto.createHash)("sha1").update(key + GUID$1).digest("base64");
|
|
396
|
+
}
|
|
397
|
+
function requestHeader(request, name) {
|
|
398
|
+
const value = request.headers[name];
|
|
399
|
+
return Array.isArray(value) ? value[0] : value;
|
|
400
|
+
}
|
|
401
|
+
function isSecure(request) {
|
|
402
|
+
const socket = request.socket;
|
|
403
|
+
return Boolean(socket.authorized || socket.encrypted);
|
|
404
|
+
}
|
|
405
|
+
function parseProtocols(state, request, socket) {
|
|
406
|
+
const header = request.headers["sec-websocket-protocol"];
|
|
407
|
+
if (header === void 0) return [];
|
|
408
|
+
try {
|
|
409
|
+
return parseProtocolHeader(Array.isArray(header) ? header.join(",") : header);
|
|
410
|
+
} catch {
|
|
411
|
+
abortOrEmit(state, request, socket, 400, "Invalid Sec-WebSocket-Protocol header");
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
function selectProtocol(state, protocols, request) {
|
|
416
|
+
if (protocols.length === 0) return false;
|
|
417
|
+
const offered = new Set(protocols);
|
|
418
|
+
const handleProtocols = state.options.handleProtocols;
|
|
419
|
+
const selected = handleProtocols ? handleProtocols(offered, request) : protocols[0] ?? false;
|
|
420
|
+
if (!isProtocolToken(selected)) return false;
|
|
421
|
+
return selected;
|
|
422
|
+
}
|
|
423
|
+
function abortOrEmit(state, request, socket, code, message, headers) {
|
|
424
|
+
if (listenerCount$1(state.listeners, "wsClientError") > 0) {
|
|
425
|
+
emitEvent(state, "wsClientError", createError("ERR_PROTOCOL", message), socket, request);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
abortHandshake(socket, code, message, headers);
|
|
429
|
+
}
|
|
430
|
+
function abortHandshake(socket, code, message, headers) {
|
|
431
|
+
const status = Number.isInteger(code) && code >= STATUS_MIN && code <= STATUS_MAX ? code : 500;
|
|
432
|
+
const body = message || node_http.STATUS_CODES[status] || "";
|
|
433
|
+
const merged = {
|
|
434
|
+
Connection: "close",
|
|
435
|
+
"Content-Type": "text/html",
|
|
436
|
+
"Content-Length": Buffer.byteLength(body),
|
|
437
|
+
...sanitizeHeaders(headers)
|
|
438
|
+
};
|
|
439
|
+
const lines = Object.keys(merged).map((name) => `${name}: ${String(merged[name])}`).join("\r\n");
|
|
440
|
+
socket.once("finish", () => {
|
|
441
|
+
socket.destroy();
|
|
442
|
+
});
|
|
443
|
+
socket.end(`HTTP/1.1 ${status} ${node_http.STATUS_CODES[status] ?? ""}\r\n${lines}\r\n\r\n${body}`);
|
|
444
|
+
}
|
|
445
|
+
function sanitizeHeaders(headers) {
|
|
446
|
+
const safe = {};
|
|
447
|
+
for (const [name, value] of Object.entries(headers ?? {})) {
|
|
448
|
+
if (hasControlCharacters(name)) continue;
|
|
449
|
+
if ((Array.isArray(value) ? value : [value]).some((entry) => entry !== void 0 && hasControlCharacters(String(entry)))) continue;
|
|
450
|
+
safe[name] = value;
|
|
451
|
+
}
|
|
452
|
+
return safe;
|
|
453
|
+
}
|
|
454
|
+
//#endregion
|
|
455
|
+
//#region src/binding/codec-status.ts
|
|
456
|
+
const CODEC_ROLE = {
|
|
457
|
+
client: 0,
|
|
458
|
+
server: 1
|
|
459
|
+
};
|
|
460
|
+
const CODEC_OUTCOME = {
|
|
461
|
+
backpressure: 1,
|
|
462
|
+
failed: 2,
|
|
463
|
+
staleHandle: 3
|
|
464
|
+
};
|
|
465
|
+
const CODEC_KINDS = [
|
|
466
|
+
"text",
|
|
467
|
+
"binary",
|
|
468
|
+
"ping",
|
|
469
|
+
"pong",
|
|
470
|
+
"close",
|
|
471
|
+
"rejected",
|
|
472
|
+
"continuation"
|
|
473
|
+
];
|
|
474
|
+
const CODEC_FAILURES = [
|
|
475
|
+
"protocolError",
|
|
476
|
+
"expectedFin",
|
|
477
|
+
"expectedMask",
|
|
478
|
+
"invalidCloseCode",
|
|
479
|
+
"invalidControlPayloadLength",
|
|
480
|
+
"invalidOpcode",
|
|
481
|
+
"invalidUtf8",
|
|
482
|
+
"unexpectedMask",
|
|
483
|
+
"unexpectedRsv1",
|
|
484
|
+
"unexpectedRsv2or3",
|
|
485
|
+
"tooManyBufferedParts",
|
|
486
|
+
"unsupportedDataPayloadLength",
|
|
487
|
+
"unsupportedMessageLength",
|
|
488
|
+
"invalidCompressedData",
|
|
489
|
+
"invalidMask"
|
|
490
|
+
];
|
|
491
|
+
function decodeOutcome(value) {
|
|
492
|
+
if (value >= 0) return {
|
|
493
|
+
kind: "consumed",
|
|
494
|
+
bytes: value
|
|
495
|
+
};
|
|
496
|
+
const ordinal = -value;
|
|
497
|
+
if (ordinal === CODEC_OUTCOME.backpressure) return { kind: "backpressure" };
|
|
498
|
+
if (ordinal === CODEC_OUTCOME.failed) return { kind: "failed" };
|
|
499
|
+
return { kind: "stale-handle" };
|
|
500
|
+
}
|
|
501
|
+
//#endregion
|
|
502
|
+
//#region src/binding/codec-limits.ts
|
|
503
|
+
function codecLimits() {
|
|
504
|
+
return loadAddon().engineLimits();
|
|
505
|
+
}
|
|
506
|
+
//#endregion
|
|
507
|
+
//#region src/binding/errors.ts
|
|
508
|
+
const NATIVE_ERROR_CODES = {
|
|
509
|
+
UnknownServer: "ERR_INVALID_HANDLE",
|
|
510
|
+
InvalidServerState: "ERR_INVALID_STATE",
|
|
511
|
+
ServerNotClosed: "ERR_INVALID_STATE",
|
|
512
|
+
EventsPending: "ERR_INVALID_STATE",
|
|
513
|
+
EnvCleanupUnavailable: "ERR_INVALID_STATE",
|
|
514
|
+
InvalidHost: "ERR_INVALID_OPTION",
|
|
515
|
+
InvalidPort: "ERR_INVALID_OPTION",
|
|
516
|
+
InvalidBacklog: "ERR_INVALID_OPTION",
|
|
517
|
+
InvalidPath: "ERR_INVALID_OPTION",
|
|
518
|
+
InvalidConnectionCapacity: "ERR_INVALID_OPTION",
|
|
519
|
+
InvalidMessageCapacity: "ERR_INVALID_OPTION",
|
|
520
|
+
InvalidFrameCapacity: "ERR_INVALID_OPTION",
|
|
521
|
+
ThreadsafeFunctionUnavailable: "ERR_PROTOCOL",
|
|
522
|
+
CapacityExhausted: "ERR_INVALID_STATE",
|
|
523
|
+
ServerCapacityExhausted: "ERR_INVALID_STATE",
|
|
524
|
+
EngineWorkerMissing: "ERR_INVALID_STATE",
|
|
525
|
+
AddressInUse: "ERR_INVALID_STATE",
|
|
526
|
+
SystemResources: "ERR_INVALID_STATE",
|
|
527
|
+
ThreadQuotaExceeded: "ERR_INVALID_STATE",
|
|
528
|
+
OutOfMemory: "ERR_INVALID_STATE"
|
|
529
|
+
};
|
|
530
|
+
function nativeError(error) {
|
|
531
|
+
const name = error instanceof Error ? error.message : String(error);
|
|
532
|
+
const code = NATIVE_ERROR_CODES[name] ?? "ERR_PROTOCOL";
|
|
533
|
+
return Object.assign(new Error(name), { code });
|
|
534
|
+
}
|
|
535
|
+
function guardError(message, code, constructor = RangeError) {
|
|
536
|
+
return Object.assign(new constructor(message), { code });
|
|
537
|
+
}
|
|
538
|
+
function callNative(call) {
|
|
539
|
+
try {
|
|
540
|
+
return call();
|
|
541
|
+
} catch (error) {
|
|
542
|
+
throw nativeError(error);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
//#endregion
|
|
546
|
+
//#region src/binding/codec-create.ts
|
|
547
|
+
function createCodec(role, options) {
|
|
548
|
+
const limits = codecLimits();
|
|
549
|
+
assertCeiling("maxPayload", options.maxPayload, limits.maxPayloadBytes);
|
|
550
|
+
assertCeiling("maxFragments", options.maxFragments, limits.maxFragments);
|
|
551
|
+
const addon = loadAddon();
|
|
552
|
+
return callNative(() => addon.codecCreate(role, options.validateUtf8 ? 1 : 0, options.maxPayload, options.maxFragments, options.permessageDeflate ? 1 : 0));
|
|
553
|
+
}
|
|
554
|
+
function assertCeiling(name, value, ceiling) {
|
|
555
|
+
if (Number.isSafeInteger(value) && value >= 0 && value <= ceiling) return;
|
|
556
|
+
throw createError("ERR_INVALID_OPTION", `ventiws: ${name} must be an integer in [0, ${ceiling}] (received ${String(value)})`, RangeError);
|
|
557
|
+
}
|
|
558
|
+
//#endregion
|
|
559
|
+
//#region src/binding/codec-encode.ts
|
|
560
|
+
function encodeCodecFrame(handle, kind, fin, payload, compress, maskFrame = true, mask = EMPTY$3) {
|
|
561
|
+
const addon = loadAddon();
|
|
562
|
+
return callNative(() => addon.codecEncode(handle, kind, fin ? 1 : 0, payload, compress ? 1 : 0, maskFrame ? 1 : 0, mask));
|
|
563
|
+
}
|
|
564
|
+
const EMPTY$3 = /* @__PURE__ */ new Uint8Array(0);
|
|
565
|
+
function codecOutbound(handle) {
|
|
566
|
+
const addon = loadAddon();
|
|
567
|
+
return callNative(() => addon.codecOutbound(handle));
|
|
568
|
+
}
|
|
569
|
+
//#endregion
|
|
570
|
+
//#region src/binding/codec-state.ts
|
|
571
|
+
function codecFailureCode(handle) {
|
|
572
|
+
const addon = loadAddon();
|
|
573
|
+
return callNative(() => addon.codecFailureCode(handle));
|
|
574
|
+
}
|
|
575
|
+
function codecFailure(handle) {
|
|
576
|
+
const addon = loadAddon();
|
|
577
|
+
const ordinal = callNative(() => addon.codecFailure(handle));
|
|
578
|
+
return CODEC_FAILURES[ordinal - 1] ?? null;
|
|
579
|
+
}
|
|
580
|
+
//#endregion
|
|
581
|
+
//#region src/binding/codec.ts
|
|
582
|
+
function destroyCodec(handle) {
|
|
583
|
+
const addon = loadAddon();
|
|
584
|
+
callNative(() => addon.codecDestroy(handle));
|
|
585
|
+
}
|
|
586
|
+
function feedCodec(handle, bytes) {
|
|
587
|
+
const addon = loadAddon();
|
|
588
|
+
return decodeOutcome(callNative(() => addon.codecFeed(handle, bytes)));
|
|
589
|
+
}
|
|
590
|
+
function codecFeedResume(handle) {
|
|
591
|
+
const addon = loadAddon();
|
|
592
|
+
return callNative(() => addon.codecResume(handle));
|
|
593
|
+
}
|
|
594
|
+
function pendingCodecEvents(handle) {
|
|
595
|
+
const addon = loadAddon();
|
|
596
|
+
return callNative(() => addon.codecPending(handle));
|
|
597
|
+
}
|
|
598
|
+
function selectCodecEvent(handle) {
|
|
599
|
+
const addon = loadAddon();
|
|
600
|
+
return callNative(() => addon.codecSelect(handle));
|
|
601
|
+
}
|
|
602
|
+
function selectedCodecEvent(handle) {
|
|
603
|
+
const addon = loadAddon();
|
|
604
|
+
const event = callNative(() => addon.codecEvent(handle));
|
|
605
|
+
if (event === null) return null;
|
|
606
|
+
return {
|
|
607
|
+
kind: CODEC_KINDS[event[0]] ?? "rejected",
|
|
608
|
+
code: event[1],
|
|
609
|
+
payload: event[2]
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
function takeCodecEvent(handle) {
|
|
613
|
+
const addon = loadAddon();
|
|
614
|
+
callNative(() => addon.codecTake(handle));
|
|
615
|
+
}
|
|
616
|
+
function codecFragmentEnds(handle) {
|
|
617
|
+
const addon = loadAddon();
|
|
618
|
+
return callNative(() => addon.codecFragments(handle));
|
|
619
|
+
}
|
|
620
|
+
//#endregion
|
|
621
|
+
//#region src/compat/socket/codec-handle.ts
|
|
622
|
+
function codecOf(state) {
|
|
623
|
+
return state.codec;
|
|
624
|
+
}
|
|
625
|
+
function openCodec(state, role) {
|
|
626
|
+
if (state.codec !== null) return state.codec;
|
|
627
|
+
const handle = createCodec(role, {
|
|
628
|
+
maxPayload: state.maxPayload,
|
|
629
|
+
maxFragments: state.maxFragments,
|
|
630
|
+
validateUtf8: state.validateUtf8,
|
|
631
|
+
permessageDeflate: state.compressible
|
|
632
|
+
});
|
|
633
|
+
state.codec = handle;
|
|
634
|
+
return handle;
|
|
635
|
+
}
|
|
636
|
+
function closeCodec(state) {
|
|
637
|
+
if (state.codec === null) return;
|
|
638
|
+
destroyCodec(state.codec);
|
|
639
|
+
state.codec = null;
|
|
640
|
+
}
|
|
641
|
+
function isWritable(state) {
|
|
642
|
+
return state.codec !== null && state.transport !== null && !state.transport.writableEnded;
|
|
643
|
+
}
|
|
644
|
+
function noTransportError() {
|
|
645
|
+
return createError("ERR_INVALID_STATE", "ventiws: the socket has no transport to write to");
|
|
646
|
+
}
|
|
647
|
+
//#endregion
|
|
648
|
+
//#region src/compat/socket/codec-deferral.ts
|
|
649
|
+
function isDeliveryPaused(state) {
|
|
650
|
+
return state.deliveryPaused;
|
|
651
|
+
}
|
|
652
|
+
function pauseUntilNextTick(state, resume) {
|
|
653
|
+
if (state.deliveryPaused) return true;
|
|
654
|
+
state.deliveryPaused = true;
|
|
655
|
+
setImmediate(() => {
|
|
656
|
+
state.deliveryPaused = false;
|
|
657
|
+
resume();
|
|
658
|
+
});
|
|
659
|
+
return true;
|
|
660
|
+
}
|
|
661
|
+
//#endregion
|
|
662
|
+
//#region src/binding/native.ts
|
|
663
|
+
const NATIVE_SOCKET_STATUSES = [
|
|
664
|
+
"ok",
|
|
665
|
+
"closing",
|
|
666
|
+
"closed",
|
|
667
|
+
"backpressure",
|
|
668
|
+
"invalidHandle",
|
|
669
|
+
"payloadTooLarge",
|
|
670
|
+
"invalidCloseCode",
|
|
671
|
+
"invalidCloseReason",
|
|
672
|
+
"protocolError",
|
|
673
|
+
"policyViolation"
|
|
674
|
+
];
|
|
675
|
+
//#endregion
|
|
676
|
+
//#region src/binding/handle.ts
|
|
677
|
+
const MAX_HANDLE_BIG = 18446744073709551615n;
|
|
678
|
+
function assertConnectionHandle(handle) {
|
|
679
|
+
if (typeof handle !== "bigint" || handle < 0n || handle > MAX_HANDLE_BIG) throw guardError(`ventiws: connection handle out of range: ${String(handle)}`, "ERR_INVALID_HANDLE");
|
|
680
|
+
}
|
|
681
|
+
//#endregion
|
|
682
|
+
//#region src/binding/server.ts
|
|
683
|
+
const MAX_SERVER_HANDLE = 2 ** 40 - 1;
|
|
684
|
+
function assertServerHandle(handle) {
|
|
685
|
+
if (!Number.isSafeInteger(handle) || handle < 0 || handle > MAX_SERVER_HANDLE) throw guardError(`ventiws: server handle must be a uint40, got ${handle}`, "ERR_INVALID_HANDLE");
|
|
686
|
+
}
|
|
687
|
+
function engineLimits() {
|
|
688
|
+
return loadAddon().engineLimits();
|
|
689
|
+
}
|
|
690
|
+
//#endregion
|
|
691
|
+
//#region src/binding/socket.ts
|
|
692
|
+
const MAX_UINT16 = 65535;
|
|
693
|
+
const ENGINE_STATUS_BY_NATIVE = {
|
|
694
|
+
ok: "ok",
|
|
695
|
+
closing: "closing",
|
|
696
|
+
closed: "closed",
|
|
697
|
+
backpressure: "backpressure",
|
|
698
|
+
invalidHandle: "invalid-handle",
|
|
699
|
+
payloadTooLarge: "payload-too-large",
|
|
700
|
+
invalidCloseCode: "invalid-close-code",
|
|
701
|
+
invalidCloseReason: "invalid-close-reason",
|
|
702
|
+
protocolError: "protocol-error",
|
|
703
|
+
policyViolation: "policy-violation"
|
|
704
|
+
};
|
|
705
|
+
function assertPayload(data) {
|
|
706
|
+
if (!(data instanceof Uint8Array)) throw guardError("ventiws: socket payload must be a Uint8Array", "ERR_INVALID_OPTION", TypeError);
|
|
707
|
+
}
|
|
708
|
+
function statusFromOrdinal(ordinal) {
|
|
709
|
+
const status = NATIVE_SOCKET_STATUSES[ordinal];
|
|
710
|
+
if (status === void 0) throw guardError(`ventiws: unknown native socket status ${ordinal}`, "ERR_PROTOCOL", RangeError);
|
|
711
|
+
return ENGINE_STATUS_BY_NATIVE[status];
|
|
712
|
+
}
|
|
713
|
+
function assertCloseCode$1(code) {
|
|
714
|
+
if (!Number.isInteger(code) || code < 0 || code > MAX_UINT16) throw guardError(`ventiws: close code must be a uint16, got ${code}`, "ERR_INVALID_CLOSE_CODE");
|
|
715
|
+
}
|
|
716
|
+
function sendSocket(server, connection, data, binary = false) {
|
|
717
|
+
assertServerHandle(server);
|
|
718
|
+
assertConnectionHandle(connection);
|
|
719
|
+
assertPayload(data);
|
|
720
|
+
const addon = loadAddon();
|
|
721
|
+
return statusFromOrdinal(callNative(() => addon.sendSocket(server, connection, data, binary)));
|
|
722
|
+
}
|
|
723
|
+
function closeSocket(server, connection, code, reason) {
|
|
724
|
+
assertServerHandle(server);
|
|
725
|
+
assertConnectionHandle(connection);
|
|
726
|
+
assertCloseCode$1(code);
|
|
727
|
+
assertPayload(reason);
|
|
728
|
+
const addon = loadAddon();
|
|
729
|
+
return statusFromOrdinal(callNative(() => addon.closeSocket(server, connection, code, reason)));
|
|
730
|
+
}
|
|
731
|
+
function pauseSocket(server, connection) {
|
|
732
|
+
assertServerHandle(server);
|
|
733
|
+
assertConnectionHandle(connection);
|
|
734
|
+
const addon = loadAddon();
|
|
735
|
+
return statusFromOrdinal(callNative(() => addon.pauseSocket(server, connection)));
|
|
736
|
+
}
|
|
737
|
+
function resumeSocket(server, connection) {
|
|
738
|
+
assertServerHandle(server);
|
|
739
|
+
assertConnectionHandle(connection);
|
|
740
|
+
const addon = loadAddon();
|
|
741
|
+
return statusFromOrdinal(callNative(() => addon.resumeSocket(server, connection)));
|
|
742
|
+
}
|
|
743
|
+
function socketBufferedAmount(server, connection) {
|
|
744
|
+
assertServerHandle(server);
|
|
745
|
+
assertConnectionHandle(connection);
|
|
746
|
+
const addon = loadAddon();
|
|
747
|
+
return callNative(() => addon.socketBufferedAmount(server, connection));
|
|
748
|
+
}
|
|
749
|
+
//#endregion
|
|
750
|
+
//#region src/compat/socket/queued.ts
|
|
751
|
+
function queuedBytes(state) {
|
|
752
|
+
if (state.transport === null) return 0;
|
|
753
|
+
return state.transport.writableLength;
|
|
754
|
+
}
|
|
755
|
+
//#endregion
|
|
756
|
+
//#region src/compat/socket/payload.ts
|
|
757
|
+
const READY_STATE_NAMES = [
|
|
758
|
+
"CONNECTING",
|
|
759
|
+
"OPEN",
|
|
760
|
+
"CLOSING",
|
|
761
|
+
"CLOSED"
|
|
762
|
+
];
|
|
763
|
+
function toPayload(data) {
|
|
764
|
+
if (typeof data === "number") return {
|
|
765
|
+
bytes: Buffer.from(String(data), "utf8"),
|
|
766
|
+
binary: false
|
|
767
|
+
};
|
|
768
|
+
if (typeof data === "string") return data.length === 0 ? {
|
|
769
|
+
bytes: Buffer.alloc(0),
|
|
770
|
+
binary: false
|
|
771
|
+
} : {
|
|
772
|
+
bytes: Buffer.from(data, "utf8"),
|
|
773
|
+
binary: false
|
|
774
|
+
};
|
|
775
|
+
if (!data) return {
|
|
776
|
+
bytes: Buffer.alloc(0),
|
|
777
|
+
binary: true
|
|
778
|
+
};
|
|
779
|
+
return {
|
|
780
|
+
bytes: toBytes(data),
|
|
781
|
+
binary: true
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
function toBytes(data) {
|
|
785
|
+
if (ArrayBuffer.isView(data)) return Buffer.from(data.buffer, data.byteOffset, data.byteLength);
|
|
786
|
+
if (data instanceof ArrayBuffer) return Buffer.from(data);
|
|
787
|
+
return Buffer.from(data);
|
|
788
|
+
}
|
|
789
|
+
function notOpenError(readyState) {
|
|
790
|
+
return createError("ERR_SOCKET_NOT_OPEN", `WebSocket is not open: readyState ${readyState} (${READY_STATE_NAMES[readyState] ?? "UNKNOWN"})`);
|
|
791
|
+
}
|
|
792
|
+
function statusError(status) {
|
|
793
|
+
return createStatusError(status, `ventiws: socket operation failed with status "${status}"`);
|
|
794
|
+
}
|
|
795
|
+
function bufferedAmountOf(state) {
|
|
796
|
+
if (state.attachment === null) return queuedBytes(state);
|
|
797
|
+
return socketBufferedAmount(state.attachment.server, state.attachment.connection);
|
|
798
|
+
}
|
|
799
|
+
function defer(callback, error) {
|
|
800
|
+
if (typeof callback !== "function") return;
|
|
801
|
+
process.nextTick(callback, error);
|
|
802
|
+
}
|
|
803
|
+
//#endregion
|
|
804
|
+
//#region src/compat/socket/codec-outbound.ts
|
|
805
|
+
function ordinalOf(kind) {
|
|
806
|
+
return CODEC_KINDS.indexOf(kind);
|
|
807
|
+
}
|
|
808
|
+
function maskFor(state, mask) {
|
|
809
|
+
if (state.isServer || !mask || state.generateMask === null) return NO_MASK;
|
|
810
|
+
state.generateMask(state.maskScratch);
|
|
811
|
+
return state.maskScratch;
|
|
812
|
+
}
|
|
813
|
+
const NO_MASK = /* @__PURE__ */ new Uint8Array(0);
|
|
814
|
+
function writeFrame(state, kind, payload, fin = true, compress = false, mask = true) {
|
|
815
|
+
const handle = state.codec;
|
|
816
|
+
if (handle === null) return "closed";
|
|
817
|
+
if (!isWritable(state)) return state.transport?.writableEnded === true ? "closed" : "closing";
|
|
818
|
+
const length = encodeCodecFrame(handle, ordinalOf(kind), fin, payload, compress, mask, maskFor(state, mask));
|
|
819
|
+
if (length < 0) return encodeFailure(-length);
|
|
820
|
+
const framed = codecOutbound(handle);
|
|
821
|
+
if (state.transport === null) return "closed";
|
|
822
|
+
state.transport.write(framed.subarray(0, length));
|
|
823
|
+
return "ok";
|
|
824
|
+
}
|
|
825
|
+
function writePong(state, payload) {
|
|
826
|
+
writeFrame(state, "pong", payload);
|
|
827
|
+
}
|
|
828
|
+
function writeCloseFrame(state, code, reason) {
|
|
829
|
+
if (state.closeFrameSent) return;
|
|
830
|
+
if (writeFrame(state, "close", closePayload(code, reason)) !== "ok") return;
|
|
831
|
+
state.closeFrameSent = true;
|
|
832
|
+
}
|
|
833
|
+
function closePayload(code, reason) {
|
|
834
|
+
if (code === void 0) return Buffer.alloc(0);
|
|
835
|
+
const payload = Buffer.alloc(2 + reason.length);
|
|
836
|
+
payload.writeUInt16BE(code, 0);
|
|
837
|
+
reason.copy(payload, 2);
|
|
838
|
+
return payload;
|
|
839
|
+
}
|
|
840
|
+
function encodeFailure(ordinal) {
|
|
841
|
+
switch (ordinal) {
|
|
842
|
+
case 1: return "protocol-error";
|
|
843
|
+
case 2: return "payload-too-large";
|
|
844
|
+
case 3: return "protocol-error";
|
|
845
|
+
case 4: return "invalid-handle";
|
|
846
|
+
default: return "protocol-error";
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
function frameError(status) {
|
|
850
|
+
if (status === "closed" || status === "closing") return noTransportError();
|
|
851
|
+
if (status === "backpressure") return createError("ERR_BACKPRESSURE", "ventiws: the codec's event queue is full");
|
|
852
|
+
return statusError(status);
|
|
853
|
+
}
|
|
854
|
+
const CLOSE_NO_STATUS = 1005;
|
|
855
|
+
const CLOSE_ABNORMAL$1 = 1006;
|
|
856
|
+
function isValidControlPayload(payload) {
|
|
857
|
+
return Buffer.byteLength(payload) <= 125;
|
|
858
|
+
}
|
|
859
|
+
function isValidStatusCode(code) {
|
|
860
|
+
if (code >= 3e3 && code <= 4999) return true;
|
|
861
|
+
return code >= 1e3 && code <= 1014 && code !== 1004 && code !== 1005 && code !== 1006;
|
|
862
|
+
}
|
|
863
|
+
//#endregion
|
|
864
|
+
//#region src/compat/socket/close-code.ts
|
|
865
|
+
function closeCodeOf(code) {
|
|
866
|
+
if (code === void 0) return void 0;
|
|
867
|
+
return Math.trunc(assertCloseCode(code));
|
|
868
|
+
}
|
|
869
|
+
function assertCloseCode(code) {
|
|
870
|
+
if (typeof code !== "number" || !isValidStatusCode(code)) throw createError("ERR_INVALID_CLOSE_CODE", "First argument must be a valid error code number", TypeError);
|
|
871
|
+
return code;
|
|
872
|
+
}
|
|
873
|
+
//#endregion
|
|
874
|
+
//#region src/compat/socket/close-reason.ts
|
|
875
|
+
const EMPTY$2 = Buffer.alloc(0);
|
|
876
|
+
function toCloseReason(reason) {
|
|
877
|
+
if (reason === void 0) return EMPTY$2;
|
|
878
|
+
if (reason === null) return EMPTY$2;
|
|
879
|
+
if (!hasReasonData(reason)) return EMPTY$2;
|
|
880
|
+
assertReasonLength(Buffer.byteLength(reason));
|
|
881
|
+
if (typeof reason === "string") return Buffer.from(reason, "utf8");
|
|
882
|
+
if (reason instanceof Uint8Array) return Buffer.from(reason);
|
|
883
|
+
throw createError("ERR_INVALID_OPTION", "Second argument must be a string or a Uint8Array", TypeError);
|
|
884
|
+
}
|
|
885
|
+
function hasReasonData(reason) {
|
|
886
|
+
const length = reason.length;
|
|
887
|
+
return typeof length === "number" && length > 0;
|
|
888
|
+
}
|
|
889
|
+
function assertReasonLength(length) {
|
|
890
|
+
if (length <= 123) return;
|
|
891
|
+
throw createError("ERR_INVALID_CLOSE_REASON", "The message must not be greater than 123 bytes", RangeError);
|
|
892
|
+
}
|
|
893
|
+
//#endregion
|
|
894
|
+
//#region src/compat/socket/close-failure.ts
|
|
895
|
+
function closeFailure(status) {
|
|
896
|
+
switch (status) {
|
|
897
|
+
case "backpressure": return createError("ERR_BACKPRESSURE", "ventiws: the outbound staging ring is full");
|
|
898
|
+
case "invalid-handle": return createError("ERR_INVALID_HANDLE", "ventiws: the connection handle is stale");
|
|
899
|
+
case "ok":
|
|
900
|
+
case "closing":
|
|
901
|
+
case "closed": return createError("ERR_INVALID_STATE", "ventiws: the connection is already closing");
|
|
902
|
+
case "payload-too-large":
|
|
903
|
+
case "invalid-close-code":
|
|
904
|
+
case "invalid-close-reason":
|
|
905
|
+
case "protocol-error":
|
|
906
|
+
case "policy-violation": return statusError(status);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
//#endregion
|
|
910
|
+
//#region src/compat/socket/state.ts
|
|
911
|
+
const SOCKET_BRAND = Symbol("ventiws.socket");
|
|
912
|
+
const SOCKET_STATE = Symbol("ventiws.socket.state");
|
|
913
|
+
function brandSocket(socket, state) {
|
|
914
|
+
Object.defineProperties(socket, {
|
|
915
|
+
[SOCKET_BRAND]: { value: true },
|
|
916
|
+
[SOCKET_STATE]: { value: state }
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
function isSocket(value) {
|
|
920
|
+
if (typeof value !== "object" || value === null) return false;
|
|
921
|
+
return value[SOCKET_BRAND] === true;
|
|
922
|
+
}
|
|
923
|
+
function socketStateOf(socket) {
|
|
924
|
+
if (typeof socket !== "object" || socket === null) return void 0;
|
|
925
|
+
return socket[SOCKET_STATE];
|
|
926
|
+
}
|
|
927
|
+
function createSocketState() {
|
|
928
|
+
return {
|
|
929
|
+
url: "",
|
|
930
|
+
protocol: "",
|
|
931
|
+
extensions: "",
|
|
932
|
+
binaryType: "nodebuffer",
|
|
933
|
+
readyState: 0,
|
|
934
|
+
isPaused: false,
|
|
935
|
+
isServer: true,
|
|
936
|
+
closeCode: 1006,
|
|
937
|
+
closeReason: Buffer.alloc(0),
|
|
938
|
+
closeFrameSent: false,
|
|
939
|
+
closeFrameReceived: false,
|
|
940
|
+
fragmentsOpen: false,
|
|
941
|
+
errorEmitted: false,
|
|
942
|
+
attachment: null,
|
|
943
|
+
transport: null,
|
|
944
|
+
codec: null,
|
|
945
|
+
closeTimer: null,
|
|
946
|
+
closeTimeout: DEFAULT_CLOSE_TIMEOUT,
|
|
947
|
+
allowSynchronousEvents: true,
|
|
948
|
+
deliveryPaused: false,
|
|
949
|
+
pendingInput: [],
|
|
950
|
+
maxBufferedChunks: DEFAULT_MAX_BUFFERED_CHUNKS,
|
|
951
|
+
validateUtf8: true,
|
|
952
|
+
maxPayload: DEFAULT_MAX_PAYLOAD,
|
|
953
|
+
maxFragments: DEFAULT_MAX_FRAGMENTS,
|
|
954
|
+
cancelHandshake: null,
|
|
955
|
+
pendingSend: null,
|
|
956
|
+
compressible: false,
|
|
957
|
+
threshold: 0,
|
|
958
|
+
autoPong: true,
|
|
959
|
+
generateMask: null,
|
|
960
|
+
maskScratch: Buffer.alloc(4),
|
|
961
|
+
listeners: createRegistry(),
|
|
962
|
+
maxListeners: 10,
|
|
963
|
+
warned: /* @__PURE__ */ new Set(),
|
|
964
|
+
target: void 0
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
//#endregion
|
|
968
|
+
//#region src/compat/socket/lifecycle.ts
|
|
969
|
+
const EMPTY$1 = Buffer.alloc(0);
|
|
970
|
+
function closeFrameWritten(socket) {
|
|
971
|
+
return socketStateOf(socket)?.closeFrameSent === true;
|
|
972
|
+
}
|
|
973
|
+
function finishConnection(state, code, reason) {
|
|
974
|
+
if (state.readyState === 3) return;
|
|
975
|
+
if (state.closeTimer !== null) {
|
|
976
|
+
clearTimeout(state.closeTimer);
|
|
977
|
+
state.closeTimer = null;
|
|
978
|
+
}
|
|
979
|
+
state.readyState = 3;
|
|
980
|
+
state.closeCode = code;
|
|
981
|
+
state.closeReason = reason;
|
|
982
|
+
emitEvent(state, "close", code, reason);
|
|
983
|
+
}
|
|
984
|
+
function failConnection(state, error) {
|
|
985
|
+
if (state.readyState === 3) return;
|
|
986
|
+
if (!state.errorEmitted) {
|
|
987
|
+
state.errorEmitted = true;
|
|
988
|
+
state.readyState = 2;
|
|
989
|
+
try {
|
|
990
|
+
emitEvent(state, "error", error);
|
|
991
|
+
} finally {
|
|
992
|
+
finishConnection(state, CLOSE_ABNORMAL$1, EMPTY$1);
|
|
993
|
+
}
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
finishConnection(state, CLOSE_ABNORMAL$1, EMPTY$1);
|
|
997
|
+
}
|
|
998
|
+
function reportWithoutClosing(state, error) {
|
|
999
|
+
if (state.errorEmitted) return;
|
|
1000
|
+
if (state.readyState === 3) return;
|
|
1001
|
+
state.errorEmitted = true;
|
|
1002
|
+
emitEvent(state, "error", error);
|
|
1003
|
+
}
|
|
1004
|
+
function closeConnection(state, code, reason) {
|
|
1005
|
+
if (state.readyState === 3) return;
|
|
1006
|
+
if (state.readyState === 0) {
|
|
1007
|
+
failConnection(state, createError("ERR_INVALID_STATE", "WebSocket was closed before the connection was established"));
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
if (state.readyState === 2) return;
|
|
1011
|
+
state.readyState = 2;
|
|
1012
|
+
const closeCode = closeCodeOf(code);
|
|
1013
|
+
const closeReason = toCloseReason(reason);
|
|
1014
|
+
if (state.codec !== null) {
|
|
1015
|
+
closeFramed(state, closeCode, closeReason);
|
|
1016
|
+
armCloseTimeout(state, state.closeTimeout);
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
if (state.attachment === null) {
|
|
1020
|
+
closeUnattached(state);
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
const status = closeSocket(state.attachment.server, state.attachment.connection, closeCode ?? 1e3, closeReason);
|
|
1024
|
+
if (status === "ok") {
|
|
1025
|
+
state.closeFrameSent = true;
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
if (status === "closing" || status === "closed") {
|
|
1029
|
+
finishConnection(state, CLOSE_ABNORMAL$1, EMPTY$1);
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
failConnection(state, closeFailure(status));
|
|
1033
|
+
}
|
|
1034
|
+
function closeUnattached(state) {
|
|
1035
|
+
state.cancelHandshake?.();
|
|
1036
|
+
if (state.transport === null) {
|
|
1037
|
+
finishConnection(state, CLOSE_ABNORMAL$1, EMPTY$1);
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
state.transport.destroy();
|
|
1041
|
+
}
|
|
1042
|
+
//#endregion
|
|
1043
|
+
//#region src/compat/socket/codec-close.ts
|
|
1044
|
+
const CLOSE_ABNORMAL = 1006;
|
|
1045
|
+
function closeFramed(state, code, reason) {
|
|
1046
|
+
writeCloseFrame(state, code, reason);
|
|
1047
|
+
}
|
|
1048
|
+
function refuseFramed(state, refusal) {
|
|
1049
|
+
if (state.readyState === 3) return;
|
|
1050
|
+
state.readyState = 2;
|
|
1051
|
+
writeCloseFrame(state, refusal.closeCode, Buffer.from(refusal.reason, "utf8"));
|
|
1052
|
+
try {
|
|
1053
|
+
if (!state.errorEmitted) {
|
|
1054
|
+
state.errorEmitted = true;
|
|
1055
|
+
emitEvent(state, "error", createError(refusal.code, refusal.message, refusal.ctor));
|
|
1056
|
+
}
|
|
1057
|
+
} finally {
|
|
1058
|
+
finishConnection(state, refusal.closeCode, Buffer.from(refusal.reason, "utf8"));
|
|
1059
|
+
closeCodec(state);
|
|
1060
|
+
state.transport?.end();
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
function armCloseTimeout(state, milliseconds) {
|
|
1064
|
+
clearCloseTimeout(state);
|
|
1065
|
+
state.closeTimer = setTimeout(() => {
|
|
1066
|
+
state.closeTimer = null;
|
|
1067
|
+
if (state.readyState !== 2) return;
|
|
1068
|
+
state.transport?.destroy();
|
|
1069
|
+
finishConnection(state, CLOSE_ABNORMAL, Buffer.alloc(0));
|
|
1070
|
+
}, Math.max(milliseconds, 0));
|
|
1071
|
+
state.closeTimer.unref?.();
|
|
1072
|
+
}
|
|
1073
|
+
function clearCloseTimeout(state) {
|
|
1074
|
+
if (state.closeTimer === null) return;
|
|
1075
|
+
clearTimeout(state.closeTimer);
|
|
1076
|
+
state.closeTimer = null;
|
|
1077
|
+
}
|
|
1078
|
+
//#endregion
|
|
1079
|
+
//#region src/compat/socket/refusal-table.ts
|
|
1080
|
+
const REFUSALS = {
|
|
1081
|
+
expectedFin: refused(1002, "WS_ERR_EXPECTED_FIN", "protocol error", "FIN must be set"),
|
|
1082
|
+
expectedMask: refused(1002, "WS_ERR_EXPECTED_MASK", "protocol error", "MASK must be set"),
|
|
1083
|
+
invalidCloseCode: refused(1002, "WS_ERR_INVALID_CLOSE_CODE", "protocol error", "invalid status code"),
|
|
1084
|
+
invalidControlPayloadLength: refused(1002, "WS_ERR_INVALID_CONTROL_PAYLOAD_LENGTH", "protocol error", "invalid payload length"),
|
|
1085
|
+
invalidOpcode: refused(1002, "WS_ERR_INVALID_OPCODE", "protocol error", "invalid opcode"),
|
|
1086
|
+
unexpectedMask: refused(1002, "WS_ERR_UNEXPECTED_MASK", "protocol error", "MASK must be clear"),
|
|
1087
|
+
unexpectedRsv1: refused(1002, "WS_ERR_UNEXPECTED_RSV_1", "protocol error", "RSV1 must be clear"),
|
|
1088
|
+
unexpectedRsv2or3: refused(1002, "WS_ERR_UNEXPECTED_RSV_2_3", "protocol error", "RSV2 and RSV3 must be clear"),
|
|
1089
|
+
unsupportedDataPayloadLength: {
|
|
1090
|
+
closeCode: 1009,
|
|
1091
|
+
code: "WS_ERR_UNSUPPORTED_DATA_PAYLOAD_LENGTH",
|
|
1092
|
+
reason: "message too big",
|
|
1093
|
+
message: "Unsupported WebSocket frame: payload length > 2^53 - 1",
|
|
1094
|
+
ctor: RangeError
|
|
1095
|
+
},
|
|
1096
|
+
invalidUtf8: {
|
|
1097
|
+
closeCode: 1007,
|
|
1098
|
+
code: "WS_ERR_INVALID_UTF8",
|
|
1099
|
+
reason: "invalid payload",
|
|
1100
|
+
message: "Invalid WebSocket frame: invalid UTF-8 sequence",
|
|
1101
|
+
ctor: Error
|
|
1102
|
+
},
|
|
1103
|
+
invalidCompressedData: {
|
|
1104
|
+
closeCode: 1007,
|
|
1105
|
+
code: "ERR_INVALID_COMPRESSED_DATA",
|
|
1106
|
+
reason: "invalid payload",
|
|
1107
|
+
message: "Invalid WebSocket frame: invalid compressed data",
|
|
1108
|
+
ctor: Error
|
|
1109
|
+
},
|
|
1110
|
+
tooManyBufferedParts: {
|
|
1111
|
+
closeCode: 1008,
|
|
1112
|
+
code: "WS_ERR_TOO_MANY_BUFFERED_PARTS",
|
|
1113
|
+
reason: "too many message fragments",
|
|
1114
|
+
message: "Too many message fragments",
|
|
1115
|
+
ctor: RangeError
|
|
1116
|
+
},
|
|
1117
|
+
unsupportedMessageLength: {
|
|
1118
|
+
closeCode: 1009,
|
|
1119
|
+
code: "WS_ERR_UNSUPPORTED_MESSAGE_LENGTH",
|
|
1120
|
+
reason: "message too big",
|
|
1121
|
+
message: "Max payload size exceeded",
|
|
1122
|
+
ctor: RangeError
|
|
1123
|
+
},
|
|
1124
|
+
invalidMask: {
|
|
1125
|
+
closeCode: 1002,
|
|
1126
|
+
code: "ERR_INVALID_OPTION",
|
|
1127
|
+
reason: "protocol error",
|
|
1128
|
+
message: "ventiws: generateMask must fill all four bytes of the masking key",
|
|
1129
|
+
ctor: RangeError
|
|
1130
|
+
},
|
|
1131
|
+
protocolError: {
|
|
1132
|
+
closeCode: 1002,
|
|
1133
|
+
code: "ERR_PROTOCOL",
|
|
1134
|
+
reason: "protocol error",
|
|
1135
|
+
message: "Invalid WebSocket frame",
|
|
1136
|
+
ctor: RangeError
|
|
1137
|
+
}
|
|
1138
|
+
};
|
|
1139
|
+
function refused(closeCode, code, reason, detail) {
|
|
1140
|
+
return {
|
|
1141
|
+
closeCode,
|
|
1142
|
+
code,
|
|
1143
|
+
reason,
|
|
1144
|
+
message: `Invalid WebSocket frame: ${detail}`,
|
|
1145
|
+
ctor: RangeError
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
//#endregion
|
|
1149
|
+
//#region src/compat/socket/codec-refusal.ts
|
|
1150
|
+
function refusalOf(failure) {
|
|
1151
|
+
return REFUSALS[failure] ?? REFUSALS.protocolError;
|
|
1152
|
+
}
|
|
1153
|
+
function failureByCode(code) {
|
|
1154
|
+
switch (code) {
|
|
1155
|
+
case 1007: return "invalidUtf8";
|
|
1156
|
+
case 1008: return "tooManyBufferedParts";
|
|
1157
|
+
case 1009: return "unsupportedMessageLength";
|
|
1158
|
+
default: return "protocolError";
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
function refuseByCodec(state, failure) {
|
|
1162
|
+
refuseFramed(state, refusalOf(failure));
|
|
1163
|
+
}
|
|
1164
|
+
//#endregion
|
|
1165
|
+
//#region src/compat/socket/payload-shape.ts
|
|
1166
|
+
function shapeBinary(binaryType, payload, ends) {
|
|
1167
|
+
switch (binaryType) {
|
|
1168
|
+
case "arraybuffer": return slicedArrayBuffer(payload);
|
|
1169
|
+
case "blob": return new Blob([new Uint8Array(payload)]);
|
|
1170
|
+
case "fragments": return sliceFragments(payload, ends);
|
|
1171
|
+
case "nodebuffer": return payload;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
function slicedArrayBuffer(payload) {
|
|
1175
|
+
return payload.buffer.slice(payload.byteOffset, payload.byteOffset + payload.byteLength);
|
|
1176
|
+
}
|
|
1177
|
+
function sliceFragments(payload, ends) {
|
|
1178
|
+
if (ends === null || ends.length === 0) return [payload];
|
|
1179
|
+
const pieces = [];
|
|
1180
|
+
let start = 0;
|
|
1181
|
+
for (const end of ends) {
|
|
1182
|
+
const at = Math.min(end, payload.length);
|
|
1183
|
+
if (at > start) pieces.push(payload.subarray(start, at));
|
|
1184
|
+
start = at;
|
|
1185
|
+
}
|
|
1186
|
+
if (start < payload.length) pieces.push(payload.subarray(start));
|
|
1187
|
+
return pieces.length > 0 ? pieces : [payload];
|
|
1188
|
+
}
|
|
1189
|
+
//#endregion
|
|
1190
|
+
//#region src/compat/socket/codec-peer-close.ts
|
|
1191
|
+
function closeFromPeer(state, event) {
|
|
1192
|
+
if (state.readyState !== 2) state.readyState = 2;
|
|
1193
|
+
state.closeFrameReceived = true;
|
|
1194
|
+
if (!state.closeFrameSent) writeCloseFrame(state, event.code === 0 ? void 0 : event.code, event.payload);
|
|
1195
|
+
finishConnection(state, event.code === 0 ? CLOSE_NO_STATUS : event.code, event.payload);
|
|
1196
|
+
closeCodec(state);
|
|
1197
|
+
state.transport?.end();
|
|
1198
|
+
}
|
|
1199
|
+
//#endregion
|
|
1200
|
+
//#region src/compat/socket/codec-events.ts
|
|
1201
|
+
function deliver(state, resume) {
|
|
1202
|
+
drain(state, false, resume);
|
|
1203
|
+
}
|
|
1204
|
+
function drainReleased(state, resume) {
|
|
1205
|
+
drain(state, true, resume);
|
|
1206
|
+
}
|
|
1207
|
+
function drain(state, released, resume) {
|
|
1208
|
+
const handle = codecOf(state);
|
|
1209
|
+
if (handle === null) return;
|
|
1210
|
+
if (isDeliveryPaused(state)) return;
|
|
1211
|
+
let honoured = released;
|
|
1212
|
+
while (pendingCodecEvents(handle) > 0 && selectCodecEvent(handle)) {
|
|
1213
|
+
const event = selectedCodecEvent(handle);
|
|
1214
|
+
if (!honoured && state.allowSynchronousEvents === false && isDeferrable(event)) {
|
|
1215
|
+
pauseUntilNextTick(state, resume);
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
honoured = false;
|
|
1219
|
+
const ends = codecFragmentEnds(handle);
|
|
1220
|
+
takeCodecEvent(handle);
|
|
1221
|
+
if (event === null) continue;
|
|
1222
|
+
dispatch(state, handle, event, ends);
|
|
1223
|
+
if (state.readyState === 3) return;
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
function isDeferrable(event) {
|
|
1227
|
+
if (event === null) return false;
|
|
1228
|
+
return event.kind === "text" || event.kind === "binary" || event.kind === "ping" || event.kind === "pong";
|
|
1229
|
+
}
|
|
1230
|
+
function dispatch(state, handle, event, ends) {
|
|
1231
|
+
switch (event.kind) {
|
|
1232
|
+
case "text":
|
|
1233
|
+
emitEvent(state, "message", event.payload, false);
|
|
1234
|
+
return;
|
|
1235
|
+
case "binary":
|
|
1236
|
+
emitBinary(state, event, ends);
|
|
1237
|
+
return;
|
|
1238
|
+
case "ping":
|
|
1239
|
+
if (state.autoPong) writePong(state, event.payload);
|
|
1240
|
+
emitEvent(state, "ping", event.payload);
|
|
1241
|
+
return;
|
|
1242
|
+
case "pong":
|
|
1243
|
+
emitEvent(state, "pong", event.payload);
|
|
1244
|
+
return;
|
|
1245
|
+
case "close":
|
|
1246
|
+
closeFromPeer(state, event);
|
|
1247
|
+
return;
|
|
1248
|
+
case "rejected":
|
|
1249
|
+
refuseByCodec(state, codecFailure(handle) ?? failureByCode(codecFailureCode(handle)));
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
function emitBinary(state, event, ends) {
|
|
1254
|
+
emitEvent(state, "message", shapeBinary(state.binaryType, event.payload, ends), true);
|
|
1255
|
+
}
|
|
1256
|
+
//#endregion
|
|
1257
|
+
//#region src/compat/socket/codec-inbound.ts
|
|
1258
|
+
function driveInbound(state, transport, pending) {
|
|
1259
|
+
if (pending !== void 0 && pending.length > 0) ingest(state, pending);
|
|
1260
|
+
transport.on("data", (chunk) => {
|
|
1261
|
+
ingest(state, chunk);
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
function ingest(state, chunk) {
|
|
1265
|
+
const handle = codecOf(state);
|
|
1266
|
+
if (handle === null) return;
|
|
1267
|
+
if (state.pendingInput.length > 0) {
|
|
1268
|
+
enqueue(state, chunk);
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
consume(state, handle, chunk);
|
|
1272
|
+
}
|
|
1273
|
+
function enqueue(state, chunk) {
|
|
1274
|
+
if (state.maxBufferedChunks > 0 && state.pendingInput.length >= state.maxBufferedChunks) refuseByCodec(state, "tooManyBufferedParts");
|
|
1275
|
+
else state.pendingInput.push(chunk);
|
|
1276
|
+
}
|
|
1277
|
+
function consume(state, handle, chunk) {
|
|
1278
|
+
let rest = chunk;
|
|
1279
|
+
while (rest.length > 0) {
|
|
1280
|
+
const outcome = feedCodec(handle, rest);
|
|
1281
|
+
try {
|
|
1282
|
+
deliver(state, () => {
|
|
1283
|
+
guardedResume(state);
|
|
1284
|
+
});
|
|
1285
|
+
} catch (error) {
|
|
1286
|
+
failOnThrow(state, error);
|
|
1287
|
+
throw error;
|
|
1288
|
+
}
|
|
1289
|
+
if (outcome.kind === "failed") {
|
|
1290
|
+
refuseByCodec(state, codecFailure(handle) ?? failureByCode(codecFailureCode(handle)));
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
if (outcome.kind === "stale-handle") return;
|
|
1294
|
+
const consumed = outcome.kind === "consumed" ? outcome.bytes : codecFeedResume(handle);
|
|
1295
|
+
if (consumed <= 0) return;
|
|
1296
|
+
rest = rest.subarray(consumed);
|
|
1297
|
+
if (outcome.kind === "consumed") return;
|
|
1298
|
+
if (isDeliveryPaused(state)) {
|
|
1299
|
+
enqueue(state, rest);
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
function failOnThrow(state, error) {
|
|
1305
|
+
if (state.readyState === 3) return;
|
|
1306
|
+
try {
|
|
1307
|
+
failConnection(state, error instanceof Error ? error : createError("ERR_PROTOCOL", String(error)));
|
|
1308
|
+
} finally {
|
|
1309
|
+
closeCodec(state);
|
|
1310
|
+
state.transport?.destroy();
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
function guardedResume(state) {
|
|
1314
|
+
try {
|
|
1315
|
+
resume(state);
|
|
1316
|
+
} catch (error) {
|
|
1317
|
+
failOnThrow(state, error);
|
|
1318
|
+
throw error;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
function resume(state) {
|
|
1322
|
+
drainReleased(state, () => {
|
|
1323
|
+
guardedResume(state);
|
|
1324
|
+
});
|
|
1325
|
+
const queued = state.pendingInput;
|
|
1326
|
+
const handle = codecOf(state);
|
|
1327
|
+
state.pendingInput = [];
|
|
1328
|
+
if (handle === null) return;
|
|
1329
|
+
for (const [index, chunk] of queued.entries()) {
|
|
1330
|
+
if (codecOf(state) === null) return;
|
|
1331
|
+
consume(state, handle, chunk);
|
|
1332
|
+
if (!isDeliveryPaused(state)) continue;
|
|
1333
|
+
state.pendingInput = state.pendingInput.concat(queued.slice(index + 1));
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
//#endregion
|
|
1338
|
+
//#region src/compat/socket/transport.ts
|
|
1339
|
+
const EMPTY = Buffer.alloc(0);
|
|
1340
|
+
function failTransport(state, error) {
|
|
1341
|
+
if (state.readyState === 3) return;
|
|
1342
|
+
state.readyState = 2;
|
|
1343
|
+
emitEvent(state, "error", error);
|
|
1344
|
+
}
|
|
1345
|
+
function terminateConnection(state) {
|
|
1346
|
+
if (state.readyState === 3) return;
|
|
1347
|
+
if (state.readyState === 0) {
|
|
1348
|
+
failConnection(state, createError("ERR_INVALID_STATE", "WebSocket was closed before the connection was established"));
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
state.readyState = 2;
|
|
1352
|
+
if (state.transport !== null) {
|
|
1353
|
+
state.transport.destroy();
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
finishConnection(state, CLOSE_ABNORMAL$1, EMPTY);
|
|
1357
|
+
}
|
|
1358
|
+
//#endregion
|
|
1359
|
+
//#region src/compat/socket/attach.ts
|
|
1360
|
+
function attachSocket(socket, transport, role, pending) {
|
|
1361
|
+
const state = socketStateOf(socket);
|
|
1362
|
+
if (state === void 0) throw createError("ERR_INVALID_HANDLE", "ventiws: attachSocket requires a ventiws socket record");
|
|
1363
|
+
if (!transport.readable || !transport.writable) {
|
|
1364
|
+
const gone = createError("ERR_INVALID_STATE", "ventiws: the transport was closed before the connection was established");
|
|
1365
|
+
process.nextTick(() => {
|
|
1366
|
+
failConnection(state, gone);
|
|
1367
|
+
});
|
|
1368
|
+
transport.destroy();
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
state.transport = transport;
|
|
1372
|
+
openCodec(state, role ?? CODEC_ROLE.server);
|
|
1373
|
+
transport.on("end", () => {
|
|
1374
|
+
if (state.readyState !== 3) state.readyState = 2;
|
|
1375
|
+
transport.end();
|
|
1376
|
+
});
|
|
1377
|
+
transport.on("error", (error) => {
|
|
1378
|
+
try {
|
|
1379
|
+
failTransport(state, asCodedError(error));
|
|
1380
|
+
} finally {
|
|
1381
|
+
transport.destroy();
|
|
1382
|
+
}
|
|
1383
|
+
});
|
|
1384
|
+
transport.on("close", () => {
|
|
1385
|
+
closeCodec(state);
|
|
1386
|
+
finishConnection(state, state.closeCode, state.closeReason);
|
|
1387
|
+
});
|
|
1388
|
+
openSocket(state);
|
|
1389
|
+
driveInbound(state, transport, pending);
|
|
1390
|
+
}
|
|
1391
|
+
function openSocket(state) {
|
|
1392
|
+
if (state.readyState === 1) return;
|
|
1393
|
+
state.readyState = 1;
|
|
1394
|
+
emitEvent(state, "open");
|
|
1395
|
+
}
|
|
1396
|
+
function asCodedError(error) {
|
|
1397
|
+
if (error instanceof Error) return error;
|
|
1398
|
+
return createError("ERR_PROTOCOL", `ventiws: the transport failed: ${String(error)}`);
|
|
1399
|
+
}
|
|
1400
|
+
//#endregion
|
|
1401
|
+
//#region src/compat/server/handshake-error.ts
|
|
1402
|
+
const HANDSHAKE_ERROR = Symbol("ventiws.handshakeError");
|
|
1403
|
+
function attachHandshakeError(socket) {
|
|
1404
|
+
const handler = () => {
|
|
1405
|
+
socket.destroy();
|
|
1406
|
+
};
|
|
1407
|
+
socket[HANDSHAKE_ERROR] = handler;
|
|
1408
|
+
socket.on("error", handler);
|
|
1409
|
+
}
|
|
1410
|
+
function detachHandshakeError(socket) {
|
|
1411
|
+
const record = socket;
|
|
1412
|
+
const handler = record[HANDSHAKE_ERROR];
|
|
1413
|
+
if (handler === void 0) return;
|
|
1414
|
+
socket.removeListener("error", handler);
|
|
1415
|
+
delete record[HANDSHAKE_ERROR];
|
|
1416
|
+
}
|
|
1417
|
+
//#endregion
|
|
1418
|
+
//#region src/compat/extensions/format.ts
|
|
1419
|
+
function formatExtension(name, parameters) {
|
|
1420
|
+
const parts = [name];
|
|
1421
|
+
for (const [key, value] of Object.entries(parameters)) parts.push(value === "" ? key : `${key}=${value}`);
|
|
1422
|
+
return parts.join("; ");
|
|
1423
|
+
}
|
|
1424
|
+
//#endregion
|
|
1425
|
+
//#region src/compat/extensions/negotiated.ts
|
|
1426
|
+
const PERMESSAGE_DEFLATE = "permessage-deflate";
|
|
1427
|
+
const NO_CONTEXT_TAKEOVER = {
|
|
1428
|
+
server_no_context_takeover: "",
|
|
1429
|
+
client_no_context_takeover: ""
|
|
1430
|
+
};
|
|
1431
|
+
//#endregion
|
|
1432
|
+
//#region src/compat/extensions/params.ts
|
|
1433
|
+
const MIN_WINDOW_BITS = 8;
|
|
1434
|
+
const MAX_WINDOW_BITS = 15;
|
|
1435
|
+
function normalizeParameters(parameters, isServer) {
|
|
1436
|
+
const out = {};
|
|
1437
|
+
for (const [key, values] of Object.entries(parameters)) {
|
|
1438
|
+
if (values.length > 1) return null;
|
|
1439
|
+
const parsed = parseParameter(key, values[0] ?? "", isServer);
|
|
1440
|
+
if (parsed === null) return null;
|
|
1441
|
+
out[key] = parsed;
|
|
1442
|
+
}
|
|
1443
|
+
return {
|
|
1444
|
+
server_no_context_takeover: out.server_no_context_takeover === true,
|
|
1445
|
+
client_no_context_takeover: out.client_no_context_takeover === true,
|
|
1446
|
+
server_max_window_bits: numberOf(out.server_max_window_bits),
|
|
1447
|
+
client_max_window_bits: windowOf(out.client_max_window_bits)
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
function parseParameter(key, value, isServer) {
|
|
1451
|
+
if (key === "server_no_context_takeover" || key === "client_no_context_takeover") return value === "" ? true : null;
|
|
1452
|
+
if (key === "client_max_window_bits") {
|
|
1453
|
+
if (value === "") return isServer ? true : null;
|
|
1454
|
+
return windowSize(value);
|
|
1455
|
+
}
|
|
1456
|
+
if (key === "server_max_window_bits") return value === "" ? null : windowSize(value);
|
|
1457
|
+
return null;
|
|
1458
|
+
}
|
|
1459
|
+
function windowSize(value) {
|
|
1460
|
+
const parsed = Number(value);
|
|
1461
|
+
if (!Number.isInteger(parsed) || parsed < MIN_WINDOW_BITS || parsed > MAX_WINDOW_BITS) return null;
|
|
1462
|
+
return parsed;
|
|
1463
|
+
}
|
|
1464
|
+
function windowOf(value) {
|
|
1465
|
+
return typeof value === "number" || value === true ? value : void 0;
|
|
1466
|
+
}
|
|
1467
|
+
function numberOf(value) {
|
|
1468
|
+
return typeof value === "number" ? value : void 0;
|
|
1469
|
+
}
|
|
1470
|
+
//#endregion
|
|
1471
|
+
//#region src/compat/extensions/window.ts
|
|
1472
|
+
function narrowed(parameters, name, option) {
|
|
1473
|
+
if (typeof option !== "number" || option >= 15) return;
|
|
1474
|
+
parameters[name] = String(option);
|
|
1475
|
+
}
|
|
1476
|
+
//#endregion
|
|
1477
|
+
//#region src/compat/extensions/offer-window.ts
|
|
1478
|
+
function serverWindowUsable(options, offer) {
|
|
1479
|
+
const asked = offer.server_max_window_bits;
|
|
1480
|
+
if (asked === void 0) return true;
|
|
1481
|
+
if (options.serverMaxWindowBits === false) return false;
|
|
1482
|
+
if (typeof options.serverMaxWindowBits === "number") {
|
|
1483
|
+
if (options.serverMaxWindowBits > asked) return false;
|
|
1484
|
+
return options.serverMaxWindowBits >= 15;
|
|
1485
|
+
}
|
|
1486
|
+
return asked >= 15;
|
|
1487
|
+
}
|
|
1488
|
+
//#endregion
|
|
1489
|
+
//#region src/compat/extensions/decline.ts
|
|
1490
|
+
function declines(options, offer) {
|
|
1491
|
+
if (options.serverNoContextTakeover === false && offer.server_no_context_takeover) return true;
|
|
1492
|
+
if (offer.server_max_window_bits !== void 0 && !serverWindowUsable(options, offer)) return true;
|
|
1493
|
+
if (offer.client_max_window_bits === true && options.clientMaxWindowBits === false) return true;
|
|
1494
|
+
if (typeof options.clientMaxWindowBits === "number") {
|
|
1495
|
+
if (offer.client_max_window_bits === void 0) return true;
|
|
1496
|
+
const asked = offer.client_max_window_bits;
|
|
1497
|
+
if (typeof asked === "number" && options.clientMaxWindowBits > asked) return true;
|
|
1498
|
+
}
|
|
1499
|
+
return false;
|
|
1500
|
+
}
|
|
1501
|
+
//#endregion
|
|
1502
|
+
//#region src/compat/extensions/deflate.ts
|
|
1503
|
+
function acceptAsServer(offers, options) {
|
|
1504
|
+
if (options === false) return null;
|
|
1505
|
+
if (offers.length === 0) return null;
|
|
1506
|
+
for (const offer of offers) {
|
|
1507
|
+
const normalized = normalizeParameters(offer.parameters, true);
|
|
1508
|
+
if (normalized === null) return { refusal: "Invalid or unacceptable Sec-WebSocket-Extensions header" };
|
|
1509
|
+
if (declines(options, normalized)) continue;
|
|
1510
|
+
return { accepted: answer(options, normalized) };
|
|
1511
|
+
}
|
|
1512
|
+
return { refusal: "Invalid or unacceptable Sec-WebSocket-Extensions header" };
|
|
1513
|
+
}
|
|
1514
|
+
function acceptAsClient(response, options) {
|
|
1515
|
+
if (options === false) return null;
|
|
1516
|
+
const normalized = normalizeParameters(response.parameters, false);
|
|
1517
|
+
if (normalized === null) return { refusal: "Invalid Sec-WebSocket-Extensions header" };
|
|
1518
|
+
if (normalized.client_no_context_takeover && options.clientNoContextTakeover === false) return { refusal: "Unexpected parameter \"client_no_context_takeover\"" };
|
|
1519
|
+
const asked = normalized.client_max_window_bits;
|
|
1520
|
+
if (asked === void 0) return { accepted: answer(options, normalized) };
|
|
1521
|
+
if (asked === true) return { refusal: "Value must be specified for \"client_max_window_bits\"" };
|
|
1522
|
+
const wanted = options.clientMaxWindowBits;
|
|
1523
|
+
if (wanted === false) return { refusal: "Unexpected or invalid parameter \"client_max_window_bits\"" };
|
|
1524
|
+
if (typeof wanted === "number" && asked > wanted) return { refusal: "Unexpected or invalid parameter \"client_max_window_bits\"" };
|
|
1525
|
+
if (typeof asked === "number" && asked < 15) return { refusal: "Server requested a window this implementation cannot use" };
|
|
1526
|
+
return { accepted: answer(options, normalized) };
|
|
1527
|
+
}
|
|
1528
|
+
function answer(options, offer) {
|
|
1529
|
+
const parameters = { ...NO_CONTEXT_TAKEOVER };
|
|
1530
|
+
if (offer.server_no_context_takeover) parameters.server_no_context_takeover = "";
|
|
1531
|
+
narrowed(parameters, "server_max_window_bits", options.serverMaxWindowBits);
|
|
1532
|
+
narrowed(parameters, "client_max_window_bits", options.clientMaxWindowBits);
|
|
1533
|
+
return {
|
|
1534
|
+
name: PERMESSAGE_DEFLATE,
|
|
1535
|
+
parameters,
|
|
1536
|
+
header: formatExtension(PERMESSAGE_DEFLATE, parameters)
|
|
1537
|
+
};
|
|
1538
|
+
}
|
|
1539
|
+
//#endregion
|
|
1540
|
+
//#region src/compat/extensions/scan.ts
|
|
1541
|
+
const TOKEN$1 = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;
|
|
1542
|
+
const COMMA$1 = 44;
|
|
1543
|
+
const SEMICOLON = 59;
|
|
1544
|
+
const EQUALS = 61;
|
|
1545
|
+
const QUOTE = 34;
|
|
1546
|
+
const isWhitespace = (code) => code === 32 || code === 9;
|
|
1547
|
+
function scanConfiguration(header, start) {
|
|
1548
|
+
const name = readToken(header, start, "an extension name");
|
|
1549
|
+
if (name === null) throw unexpectedEnd(header, start);
|
|
1550
|
+
const parameters = {};
|
|
1551
|
+
let index = start + name.length;
|
|
1552
|
+
for (;;) {
|
|
1553
|
+
index = skipWhitespace(header, index);
|
|
1554
|
+
if (index >= header.length || header.charCodeAt(index) === COMMA$1) break;
|
|
1555
|
+
if (header.charCodeAt(index) !== SEMICOLON) throw syntax(header, index, "expected ';' or ','");
|
|
1556
|
+
const afterSemicolon = skipWhitespace(header, index + 1);
|
|
1557
|
+
if (afterSemicolon >= header.length) {
|
|
1558
|
+
index = afterSemicolon;
|
|
1559
|
+
break;
|
|
1560
|
+
}
|
|
1561
|
+
index = readParameter(header, index, parameters);
|
|
1562
|
+
}
|
|
1563
|
+
return {
|
|
1564
|
+
name,
|
|
1565
|
+
parameters,
|
|
1566
|
+
rest: index
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
function readParameter(header, start, into) {
|
|
1570
|
+
const nameStart = skipWhitespace(header, start + 1);
|
|
1571
|
+
const name = readToken(header, nameStart, "a parameter name");
|
|
1572
|
+
if (name === null) throw unexpectedEnd(header, nameStart);
|
|
1573
|
+
const afterEquals = skipWhitespace(header, nameStart + name.length);
|
|
1574
|
+
if (header.charCodeAt(afterEquals) !== EQUALS) {
|
|
1575
|
+
push(into, name, "");
|
|
1576
|
+
return afterEquals;
|
|
1577
|
+
}
|
|
1578
|
+
const value = readValue(header, skipWhitespace(header, afterEquals + 1));
|
|
1579
|
+
push(into, name, value.value);
|
|
1580
|
+
return value.end;
|
|
1581
|
+
}
|
|
1582
|
+
function push(into, name, value) {
|
|
1583
|
+
const existing = into[name];
|
|
1584
|
+
if (existing === void 0) into[name] = [value];
|
|
1585
|
+
else existing.push(value);
|
|
1586
|
+
}
|
|
1587
|
+
function readValue(header, start) {
|
|
1588
|
+
if (header.charCodeAt(start) !== QUOTE) {
|
|
1589
|
+
const token = readToken(header, start, "a parameter value");
|
|
1590
|
+
if (token === null) throw unexpectedEnd(header, start);
|
|
1591
|
+
if (!TOKEN$1.test(token)) throw syntax(header, start, "invalid parameter value");
|
|
1592
|
+
return {
|
|
1593
|
+
value: token,
|
|
1594
|
+
end: start + token.length
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
let index = start + 1;
|
|
1598
|
+
let value = "";
|
|
1599
|
+
for (;;) {
|
|
1600
|
+
if (index >= header.length) throw unexpectedEnd(header, start);
|
|
1601
|
+
const char = header[index];
|
|
1602
|
+
if (char === "\\") {
|
|
1603
|
+
const escaped = header[index + 1];
|
|
1604
|
+
if (escaped === void 0) throw unexpectedEnd(header, index);
|
|
1605
|
+
value += escaped;
|
|
1606
|
+
index += 2;
|
|
1607
|
+
continue;
|
|
1608
|
+
}
|
|
1609
|
+
if (char === "\"") return {
|
|
1610
|
+
value,
|
|
1611
|
+
end: index + 1
|
|
1612
|
+
};
|
|
1613
|
+
value += char;
|
|
1614
|
+
index += 1;
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
function readToken(header, start, what) {
|
|
1618
|
+
let index = start;
|
|
1619
|
+
while (index < header.length) {
|
|
1620
|
+
const code = header.charCodeAt(index);
|
|
1621
|
+
if (isWhitespace(code) || code === SEMICOLON || code === COMMA$1 || code === EQUALS) break;
|
|
1622
|
+
if (code === QUOTE) throw syntax(header, index, `expected ${what}`);
|
|
1623
|
+
index += 1;
|
|
1624
|
+
}
|
|
1625
|
+
if (index === start) return null;
|
|
1626
|
+
return header.slice(start, index);
|
|
1627
|
+
}
|
|
1628
|
+
function skipWhitespace(header, start) {
|
|
1629
|
+
let index = start;
|
|
1630
|
+
while (index < header.length && isWhitespace(header.charCodeAt(index))) index += 1;
|
|
1631
|
+
return index;
|
|
1632
|
+
}
|
|
1633
|
+
function syntax(header, at, detail) {
|
|
1634
|
+
return /* @__PURE__ */ new SyntaxError(`Invalid Sec-WebSocket-Extensions header at ${at}: ${detail}`);
|
|
1635
|
+
}
|
|
1636
|
+
function unexpectedEnd(header, at) {
|
|
1637
|
+
return /* @__PURE__ */ new SyntaxError(`Unexpected end of Sec-WebSocket-Extensions header at ${at}`);
|
|
1638
|
+
}
|
|
1639
|
+
//#endregion
|
|
1640
|
+
//#region src/compat/extensions/grammar.ts
|
|
1641
|
+
function parseExtensions(header) {
|
|
1642
|
+
const out = /* @__PURE__ */ new Map();
|
|
1643
|
+
let index = 0;
|
|
1644
|
+
while (index < header.length) {
|
|
1645
|
+
const scanned = scanConfiguration(header, index);
|
|
1646
|
+
const extension = {
|
|
1647
|
+
name: scanned.name,
|
|
1648
|
+
parameters: scanned.parameters
|
|
1649
|
+
};
|
|
1650
|
+
index = skipWhitespace(header, scanned.rest);
|
|
1651
|
+
if (index < header.length) {
|
|
1652
|
+
if (header.charCodeAt(index) !== COMMA) throw syntax(header, index, "expected ',' between extensions");
|
|
1653
|
+
index = skipWhitespace(header, index + 1);
|
|
1654
|
+
}
|
|
1655
|
+
const existing = out.get(extension.name);
|
|
1656
|
+
if (existing === void 0) out.set(extension.name, [extension]);
|
|
1657
|
+
else existing.push(extension);
|
|
1658
|
+
}
|
|
1659
|
+
if (out.size === 0) throw unexpectedEnd(header, 0);
|
|
1660
|
+
return out;
|
|
1661
|
+
}
|
|
1662
|
+
const COMMA = 44;
|
|
1663
|
+
//#endregion
|
|
1664
|
+
//#region src/compat/server/negotiate.ts
|
|
1665
|
+
function negotiateExtensions(state, request, socket) {
|
|
1666
|
+
const options = state.normalizedOptions.perMessageDeflate;
|
|
1667
|
+
if (options === false) return { outcome: "none" };
|
|
1668
|
+
const header = request.headers["sec-websocket-extensions"];
|
|
1669
|
+
if (header === void 0) return { outcome: "none" };
|
|
1670
|
+
let offers;
|
|
1671
|
+
try {
|
|
1672
|
+
offers = parseExtensions(Array.isArray(header) ? header.join(", ") : header).get("permessage-deflate") ?? [];
|
|
1673
|
+
} catch {
|
|
1674
|
+
abortOrEmit(state, request, socket, 400, "Invalid Sec-WebSocket-Extensions header");
|
|
1675
|
+
return { outcome: "refused" };
|
|
1676
|
+
}
|
|
1677
|
+
const outcome = acceptAsServer(offers, options);
|
|
1678
|
+
if (outcome === null) return { outcome: "none" };
|
|
1679
|
+
if ("refusal" in outcome) {
|
|
1680
|
+
abortOrEmit(state, request, socket, 400, outcome.refusal);
|
|
1681
|
+
return { outcome: "refused" };
|
|
1682
|
+
}
|
|
1683
|
+
return {
|
|
1684
|
+
outcome: "accepted",
|
|
1685
|
+
accepted: outcome.accepted
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1688
|
+
//#endregion
|
|
1689
|
+
//#region src/compat/extensions/threshold.ts
|
|
1690
|
+
function thresholdOf(options) {
|
|
1691
|
+
if (options === false) return 0;
|
|
1692
|
+
return options.threshold;
|
|
1693
|
+
}
|
|
1694
|
+
//#endregion
|
|
1695
|
+
//#region src/compat/server/accept.ts
|
|
1696
|
+
const UPGRADED = Symbol("ventiws.upgraded");
|
|
1697
|
+
function completeUpgrade(state, request, socket, head, key, protocols, callback) {
|
|
1698
|
+
if (!socket.readable || !socket.writable) {
|
|
1699
|
+
socket.destroy();
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1702
|
+
if (socket[UPGRADED] === true) throw createError("ERR_INVALID_STATE", "server.handleUpgrade() was called more than once with the same socket, possibly due to a misconfiguration");
|
|
1703
|
+
if (state.lifecycle !== "running") {
|
|
1704
|
+
abortHandshake(socket, 503);
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1707
|
+
const headers = [
|
|
1708
|
+
"HTTP/1.1 101 Switching Protocols",
|
|
1709
|
+
"Upgrade: websocket",
|
|
1710
|
+
"Connection: Upgrade",
|
|
1711
|
+
`Sec-WebSocket-Accept: ${socketAccept(key)}`
|
|
1712
|
+
];
|
|
1713
|
+
const negotiation = negotiateExtensions(state, request, socket);
|
|
1714
|
+
if (negotiation.outcome === "refused") return;
|
|
1715
|
+
const extensions = negotiation.outcome === "accepted" ? negotiation.accepted : null;
|
|
1716
|
+
const SocketClass = state.options.WebSocket ?? state.webSocket;
|
|
1717
|
+
const accepted = Reflect.construct(SocketClass, [
|
|
1718
|
+
null,
|
|
1719
|
+
void 0,
|
|
1720
|
+
state.options
|
|
1721
|
+
]);
|
|
1722
|
+
const protocol = selectProtocol(state, protocols, request);
|
|
1723
|
+
if (protocol) headers.push(`Sec-WebSocket-Protocol: ${protocol}`);
|
|
1724
|
+
if (extensions) headers.push(`Sec-WebSocket-Extensions: ${extensions.header}`);
|
|
1725
|
+
emitEvent(state, "headers", headers, request);
|
|
1726
|
+
Object.defineProperty(socket, UPGRADED, { value: true });
|
|
1727
|
+
detachHandshakeError(socket);
|
|
1728
|
+
socket.write(headers.concat("\r\n").join("\r\n"));
|
|
1729
|
+
const acceptedState = socketStateOf(accepted);
|
|
1730
|
+
if (acceptedState !== void 0 && protocol) acceptedState.protocol = protocol;
|
|
1731
|
+
if (acceptedState !== void 0) {
|
|
1732
|
+
acceptedState.closeTimeout = state.normalizedOptions.closeTimeout;
|
|
1733
|
+
acceptedState.autoPong = state.normalizedOptions.autoPong;
|
|
1734
|
+
acceptedState.allowSynchronousEvents = state.normalizedOptions.allowSynchronousEvents;
|
|
1735
|
+
acceptedState.validateUtf8 = !state.normalizedOptions.skipUTF8Validation;
|
|
1736
|
+
acceptedState.maxPayload = state.normalizedOptions.maxPayload;
|
|
1737
|
+
acceptedState.maxFragments = state.normalizedOptions.maxFragments;
|
|
1738
|
+
acceptedState.maxBufferedChunks = state.normalizedOptions.maxBufferedChunks;
|
|
1739
|
+
acceptedState.compressible = extensions !== null;
|
|
1740
|
+
if (extensions) acceptedState.extensions = extensions.header;
|
|
1741
|
+
acceptedState.threshold = thresholdOf(state.normalizedOptions.perMessageDeflate);
|
|
1742
|
+
}
|
|
1743
|
+
attachSocket(accepted, socket);
|
|
1744
|
+
if (state.normalizedOptions.clientTracking) trackClient(state, accepted);
|
|
1745
|
+
callback(accepted, request);
|
|
1746
|
+
}
|
|
1747
|
+
//#endregion
|
|
1748
|
+
//#region src/compat/server/upgrade.ts
|
|
1749
|
+
function defaultShouldHandle(state, request) {
|
|
1750
|
+
const path = state.options.path;
|
|
1751
|
+
if (!path) return true;
|
|
1752
|
+
const url = request.url ?? "";
|
|
1753
|
+
const index = url.indexOf("?");
|
|
1754
|
+
return (index !== -1 ? url.slice(0, index) : url) === path;
|
|
1755
|
+
}
|
|
1756
|
+
function shouldHandle(state, request) {
|
|
1757
|
+
const assigned = state.record?.shouldHandle;
|
|
1758
|
+
if (typeof assigned === "function") return assigned(request);
|
|
1759
|
+
return defaultShouldHandle(state, request);
|
|
1760
|
+
}
|
|
1761
|
+
function handleUpgrade(state, request, socket, head, callback) {
|
|
1762
|
+
attachHandshakeError(socket);
|
|
1763
|
+
const key = request.headers["sec-websocket-key"];
|
|
1764
|
+
const upgrade = request.headers.upgrade;
|
|
1765
|
+
const version = Number(request.headers["sec-websocket-version"]);
|
|
1766
|
+
if (request.method !== "GET") {
|
|
1767
|
+
abortOrEmit(state, request, socket, 405, "Invalid HTTP method");
|
|
1768
|
+
return;
|
|
1769
|
+
}
|
|
1770
|
+
if (typeof upgrade !== "string" || upgrade.toLowerCase() !== "websocket") {
|
|
1771
|
+
abortOrEmit(state, request, socket, 400, "Invalid Upgrade header");
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1774
|
+
if (typeof key !== "string" || !KEY_PATTERN.test(key)) {
|
|
1775
|
+
abortOrEmit(state, request, socket, 400, "Missing or invalid Sec-WebSocket-Key header");
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1778
|
+
if (version !== 13 && version !== 8) {
|
|
1779
|
+
abortOrEmit(state, request, socket, 400, "Missing or invalid Sec-WebSocket-Version header", { "Sec-WebSocket-Version": "13, 8" });
|
|
1780
|
+
return;
|
|
1781
|
+
}
|
|
1782
|
+
if (!shouldHandle(state, request)) {
|
|
1783
|
+
abortHandshake(socket, 400);
|
|
1784
|
+
return;
|
|
1785
|
+
}
|
|
1786
|
+
const protocols = parseProtocols(state, request, socket);
|
|
1787
|
+
if (protocols === void 0) return;
|
|
1788
|
+
const verify = state.options.verifyClient ?? null;
|
|
1789
|
+
if (verify === null) {
|
|
1790
|
+
completeUpgrade(state, request, socket, head, key, protocols, callback);
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1793
|
+
const info = {
|
|
1794
|
+
origin: requestHeader(request, version === 8 ? "sec-websocket-origin" : "origin"),
|
|
1795
|
+
secure: isSecure(request),
|
|
1796
|
+
req: request
|
|
1797
|
+
};
|
|
1798
|
+
if (verify.length === 2) {
|
|
1799
|
+
verify(info, (verified, code, message, headers) => {
|
|
1800
|
+
if (!verified) {
|
|
1801
|
+
abortHandshake(socket, code || 401, message, headers);
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
completeUpgrade(state, request, socket, head, key, protocols, callback);
|
|
1805
|
+
});
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
if (!verify(info)) {
|
|
1809
|
+
abortHandshake(socket, 401);
|
|
1810
|
+
return;
|
|
1811
|
+
}
|
|
1812
|
+
completeUpgrade(state, request, socket, head, key, protocols, callback);
|
|
1813
|
+
}
|
|
1814
|
+
//#endregion
|
|
1815
|
+
//#region src/compat/server/listeners.ts
|
|
1816
|
+
function wireServer(state) {
|
|
1817
|
+
const httpServer = state.server;
|
|
1818
|
+
if (httpServer === null) return;
|
|
1819
|
+
const handlers = {
|
|
1820
|
+
onListening: () => {
|
|
1821
|
+
emitEvent(state, "listening");
|
|
1822
|
+
},
|
|
1823
|
+
onError: (error) => {
|
|
1824
|
+
emitEvent(state, "error", error);
|
|
1825
|
+
},
|
|
1826
|
+
onUpgrade: (request, socket, head) => {
|
|
1827
|
+
handleUpgrade(state, request, socket, head, (accepted, incoming) => {
|
|
1828
|
+
emitEvent(state, "connection", accepted, incoming);
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1831
|
+
};
|
|
1832
|
+
httpServer.on("listening", handlers.onListening);
|
|
1833
|
+
httpServer.on("error", handlers.onError);
|
|
1834
|
+
httpServer.on("upgrade", handlers.onUpgrade);
|
|
1835
|
+
state.removeListeners = () => {
|
|
1836
|
+
httpServer.removeListener("listening", handlers.onListening);
|
|
1837
|
+
httpServer.removeListener("error", handlers.onError);
|
|
1838
|
+
httpServer.removeListener("upgrade", handlers.onUpgrade);
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
//#endregion
|
|
1842
|
+
//#region src/compat/options/bounded.ts
|
|
1843
|
+
function maxPayloadOf(source, ceiling) {
|
|
1844
|
+
return boundedOption("maxPayload", source, DEFAULT_MAX_PAYLOAD, ceiling);
|
|
1845
|
+
}
|
|
1846
|
+
function maxFragmentsOf(source, ceiling) {
|
|
1847
|
+
return boundedOption("maxFragments", source, DEFAULT_MAX_FRAGMENTS, ceiling);
|
|
1848
|
+
}
|
|
1849
|
+
function maxBufferedChunksOf(source) {
|
|
1850
|
+
const raw = source.maxBufferedChunks;
|
|
1851
|
+
if (raw === void 0) return DEFAULT_MAX_BUFFERED_CHUNKS;
|
|
1852
|
+
if (typeof raw !== "number" || !Number.isSafeInteger(raw) || raw < 0) invalidOption(`The maxBufferedChunks option must be an integer in [0, ${Number.MAX_SAFE_INTEGER}] (received ${String(raw)})`, RangeError);
|
|
1853
|
+
return raw;
|
|
1854
|
+
}
|
|
1855
|
+
function boundedOption(name, source, fallback, readCeiling) {
|
|
1856
|
+
const raw = source[name];
|
|
1857
|
+
if (raw === void 0) return fallback;
|
|
1858
|
+
if (typeof raw === "number" && Number.isSafeInteger(raw) && raw >= 0 && raw === 0) return readCeiling();
|
|
1859
|
+
const ceiling = readCeiling();
|
|
1860
|
+
if (typeof raw !== "number" || !Number.isSafeInteger(raw) || raw < 0 || raw > ceiling) invalidOption(`The ${name} option must be an integer in [0, ${ceiling}] (received ${String(raw)})`, RangeError);
|
|
1861
|
+
return raw;
|
|
1862
|
+
}
|
|
1863
|
+
//#endregion
|
|
1864
|
+
//#region src/compat/options/server.ts
|
|
1865
|
+
function normalizeServerOptions(options) {
|
|
1866
|
+
const source = { ...options };
|
|
1867
|
+
const port = source.port ?? null;
|
|
1868
|
+
const server = source.server ?? null;
|
|
1869
|
+
const noServer = source.noServer ?? false;
|
|
1870
|
+
if (isAmbiguousListenTarget(port, server, noServer)) invalidOption("One and only one of the \"port\", \"server\", or \"noServer\" options must be specified");
|
|
1871
|
+
return {
|
|
1872
|
+
host: source.host ?? null,
|
|
1873
|
+
port,
|
|
1874
|
+
backlog: source.backlog ?? null,
|
|
1875
|
+
path: source.path ?? null,
|
|
1876
|
+
server,
|
|
1877
|
+
noServer,
|
|
1878
|
+
clientTracking: source.clientTracking === void 0 ? true : Boolean(source.clientTracking),
|
|
1879
|
+
allowSynchronousEvents: source.allowSynchronousEvents ?? true,
|
|
1880
|
+
autoPong: source.autoPong ?? true,
|
|
1881
|
+
maxPayload: maxPayloadOf(source, () => codecLimits().maxPayloadBytes),
|
|
1882
|
+
maxFragments: maxFragmentsOf(source, () => codecLimits().maxFragments),
|
|
1883
|
+
maxBufferedChunks: maxBufferedChunksOf(source),
|
|
1884
|
+
skipUTF8Validation: source.skipUTF8Validation ?? false,
|
|
1885
|
+
perMessageDeflate: normalizePerMessageDeflate(source.perMessageDeflate, false),
|
|
1886
|
+
closeTimeout: closeTimeoutOf(source),
|
|
1887
|
+
verifyClient: source.verifyClient ?? null,
|
|
1888
|
+
handleProtocols: source.handleProtocols ?? null,
|
|
1889
|
+
WebSocket: source.WebSocket
|
|
1890
|
+
};
|
|
1891
|
+
}
|
|
1892
|
+
function isAmbiguousListenTarget(port, server, noServer) {
|
|
1893
|
+
const hasPort = port !== null;
|
|
1894
|
+
const hasServer = Boolean(server);
|
|
1895
|
+
if (hasPort && (hasServer || noServer)) return true;
|
|
1896
|
+
if (hasServer && noServer) return true;
|
|
1897
|
+
return !hasPort && !hasServer && !noServer;
|
|
1898
|
+
}
|
|
1899
|
+
//#endregion
|
|
1900
|
+
//#region src/compat/server/server.ts
|
|
1901
|
+
const SERVER_BRAND = Symbol("ventiws.server");
|
|
1902
|
+
function requireAddon() {
|
|
1903
|
+
loadAddon();
|
|
1904
|
+
}
|
|
1905
|
+
function isServer(value) {
|
|
1906
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1907
|
+
return value[SERVER_BRAND] === true;
|
|
1908
|
+
}
|
|
1909
|
+
function createWebSocketServer(socketClass, options, callback) {
|
|
1910
|
+
requireAddon();
|
|
1911
|
+
const resolved = {
|
|
1912
|
+
allowSynchronousEvents: true,
|
|
1913
|
+
autoPong: true,
|
|
1914
|
+
maxBufferedChunks: 262144,
|
|
1915
|
+
maxFragments: 16384,
|
|
1916
|
+
closeTimeout: 3e4,
|
|
1917
|
+
maxPayload: 104857600,
|
|
1918
|
+
skipUTF8Validation: false,
|
|
1919
|
+
perMessageDeflate: false,
|
|
1920
|
+
handleProtocols: null,
|
|
1921
|
+
clientTracking: true,
|
|
1922
|
+
verifyClient: null,
|
|
1923
|
+
noServer: false,
|
|
1924
|
+
backlog: null,
|
|
1925
|
+
server: null,
|
|
1926
|
+
host: null,
|
|
1927
|
+
path: null,
|
|
1928
|
+
port: null,
|
|
1929
|
+
WebSocket: socketClass,
|
|
1930
|
+
...options
|
|
1931
|
+
};
|
|
1932
|
+
if (resolved.perMessageDeflate === true) resolved.perMessageDeflate = {};
|
|
1933
|
+
const normalized = normalizeServerOptions(resolved);
|
|
1934
|
+
const state = {
|
|
1935
|
+
options: resolved,
|
|
1936
|
+
normalizedOptions: normalized,
|
|
1937
|
+
path: resolved.path ?? "",
|
|
1938
|
+
clients: normalized.clientTracking ? /* @__PURE__ */ new Set() : void 0,
|
|
1939
|
+
webSocket: resolved.WebSocket ?? socketClass,
|
|
1940
|
+
server: null,
|
|
1941
|
+
lifecycle: "running",
|
|
1942
|
+
record: null,
|
|
1943
|
+
shouldEmitClose: false,
|
|
1944
|
+
removeListeners: null,
|
|
1945
|
+
listeners: createRegistry(),
|
|
1946
|
+
maxListeners: 10,
|
|
1947
|
+
warned: /* @__PURE__ */ new Set(),
|
|
1948
|
+
target: void 0
|
|
1949
|
+
};
|
|
1950
|
+
if (resolved.port !== null && resolved.port !== void 0) {
|
|
1951
|
+
const httpServer = (0, node_http.createServer)((_request, response) => {
|
|
1952
|
+
const body = node_http.STATUS_CODES[426] ?? "Upgrade Required";
|
|
1953
|
+
response.writeHead(426, {
|
|
1954
|
+
"Content-Length": Buffer.byteLength(body),
|
|
1955
|
+
"Content-Type": "text/plain"
|
|
1956
|
+
});
|
|
1957
|
+
response.end(body);
|
|
1958
|
+
});
|
|
1959
|
+
state.server = httpServer;
|
|
1960
|
+
httpServer.listen(resolved.port, resolved.host ?? void 0, resolved.backlog ?? void 0, callback);
|
|
1961
|
+
} else if (resolved.server) state.server = resolved.server;
|
|
1962
|
+
if (state.server !== null) wireServer(state);
|
|
1963
|
+
const server = {
|
|
1964
|
+
...createEmitter(state),
|
|
1965
|
+
options: resolved,
|
|
1966
|
+
path: state.path,
|
|
1967
|
+
...state.clients === void 0 ? {} : { clients: state.clients },
|
|
1968
|
+
address: () => addressOf(state),
|
|
1969
|
+
close: (closeCallback) => {
|
|
1970
|
+
closeWebSocketServer(state, closeCallback);
|
|
1971
|
+
},
|
|
1972
|
+
handleUpgrade: (request, socket, head, upgradeCallback) => {
|
|
1973
|
+
handleUpgrade(state, request, socket, head, upgradeCallback);
|
|
1974
|
+
},
|
|
1975
|
+
shouldHandle: (request) => defaultShouldHandle(state, request)
|
|
1976
|
+
};
|
|
1977
|
+
state.target = server;
|
|
1978
|
+
state.record = server;
|
|
1979
|
+
Object.defineProperty(server, SERVER_BRAND, { value: true });
|
|
1980
|
+
return server;
|
|
1981
|
+
}
|
|
1982
|
+
//#endregion
|
|
1983
|
+
//#region src/compat/client/protocols.ts
|
|
1984
|
+
const TOKEN = /^[!#$%&'*+\-.^_`|~\dA-Za-z]+$/;
|
|
1985
|
+
function normalizeProtocols(protocols) {
|
|
1986
|
+
if (protocols === void 0) return [];
|
|
1987
|
+
const list = typeof protocols === "string" ? [protocols] : protocols;
|
|
1988
|
+
if (!Array.isArray(list)) throw createError("ERR_INVALID_OPTION", "An invalid or duplicated subprotocol was specified", SyntaxError);
|
|
1989
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1990
|
+
for (const protocol of list) {
|
|
1991
|
+
if (typeof protocol !== "string" || !TOKEN.test(protocol) || seen.has(protocol)) throw createError("ERR_INVALID_OPTION", "An invalid or duplicated subprotocol was specified", SyntaxError);
|
|
1992
|
+
seen.add(protocol);
|
|
1993
|
+
}
|
|
1994
|
+
return [...list];
|
|
1995
|
+
}
|
|
1996
|
+
function protocolSet(protocols) {
|
|
1997
|
+
return new Set(protocols);
|
|
1998
|
+
}
|
|
1999
|
+
function promoteOptions(protocols, options) {
|
|
2000
|
+
if (typeof protocols !== "object" || protocols === null || Array.isArray(protocols)) return {
|
|
2001
|
+
protocols,
|
|
2002
|
+
options
|
|
2003
|
+
};
|
|
2004
|
+
return {
|
|
2005
|
+
protocols: void 0,
|
|
2006
|
+
options: protocols
|
|
2007
|
+
};
|
|
2008
|
+
}
|
|
2009
|
+
function protocolRejection(chosen, offered) {
|
|
2010
|
+
if (chosen === void 0) return offered.size > 0 ? "Server sent no subprotocol" : null;
|
|
2011
|
+
if (offered.size === 0) return "Server sent a subprotocol but none was requested";
|
|
2012
|
+
if (!offered.has(chosen)) return "Server sent an invalid subprotocol";
|
|
2013
|
+
return null;
|
|
2014
|
+
}
|
|
2015
|
+
//#endregion
|
|
2016
|
+
//#region src/compat/events/dom-events.ts
|
|
2017
|
+
function createOpenEvent(target) {
|
|
2018
|
+
return {
|
|
2019
|
+
type: "open",
|
|
2020
|
+
target
|
|
2021
|
+
};
|
|
2022
|
+
}
|
|
2023
|
+
function createMessageEvent(target, data) {
|
|
2024
|
+
return {
|
|
2025
|
+
type: "message",
|
|
2026
|
+
target,
|
|
2027
|
+
data
|
|
2028
|
+
};
|
|
2029
|
+
}
|
|
2030
|
+
function createCloseEvent(target, code, reason, wasClean) {
|
|
2031
|
+
return {
|
|
2032
|
+
type: "close",
|
|
2033
|
+
target,
|
|
2034
|
+
code,
|
|
2035
|
+
reason: reason.toString(),
|
|
2036
|
+
wasClean
|
|
2037
|
+
};
|
|
2038
|
+
}
|
|
2039
|
+
function createErrorEvent(target, error) {
|
|
2040
|
+
return {
|
|
2041
|
+
type: "error",
|
|
2042
|
+
target,
|
|
2043
|
+
error,
|
|
2044
|
+
message: error.message
|
|
2045
|
+
};
|
|
2046
|
+
}
|
|
2047
|
+
function callListener(handler, target, event) {
|
|
2048
|
+
if (typeof handler === "function") {
|
|
2049
|
+
Reflect.apply(handler, target, [event]);
|
|
2050
|
+
return;
|
|
2051
|
+
}
|
|
2052
|
+
if (typeof handler === "object" && handler !== null) {
|
|
2053
|
+
const handleEvent = handler.handleEvent;
|
|
2054
|
+
if (typeof handleEvent === "function") {
|
|
2055
|
+
Reflect.apply(handleEvent, handler, [event]);
|
|
2056
|
+
return;
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
throw new TypeError("EventListener must be a function or an object with a handleEvent method");
|
|
2060
|
+
}
|
|
2061
|
+
//#endregion
|
|
2062
|
+
//#region src/compat/events/dom-listeners.ts
|
|
2063
|
+
const DOM_TYPES = [
|
|
2064
|
+
"open",
|
|
2065
|
+
"error",
|
|
2066
|
+
"close",
|
|
2067
|
+
"message"
|
|
2068
|
+
];
|
|
2069
|
+
const ATTRIBUTES = [
|
|
2070
|
+
["onopen", "open"],
|
|
2071
|
+
["onerror", "error"],
|
|
2072
|
+
["onclose", "close"],
|
|
2073
|
+
["onmessage", "message"]
|
|
2074
|
+
];
|
|
2075
|
+
function isDomType(type) {
|
|
2076
|
+
return DOM_TYPES.includes(type);
|
|
2077
|
+
}
|
|
2078
|
+
function isAttribute(entry) {
|
|
2079
|
+
const tagged = asTagged(entry);
|
|
2080
|
+
return tagged[DOM_WRAPPER] === true && tagged.attribute === true;
|
|
2081
|
+
}
|
|
2082
|
+
function wrapperFor(state, type, handler) {
|
|
2083
|
+
const socket = state.target;
|
|
2084
|
+
switch (type) {
|
|
2085
|
+
case "open": return (() => {
|
|
2086
|
+
callListener(handler, socket, createOpenEvent(socket));
|
|
2087
|
+
});
|
|
2088
|
+
case "message": return ((data, isBinary) => {
|
|
2089
|
+
callListener(handler, socket, createMessageEvent(socket, isBinary ? data : data.toString()));
|
|
2090
|
+
});
|
|
2091
|
+
case "close": return ((code, reason) => {
|
|
2092
|
+
const clean = state.closeFrameReceived && state.closeFrameSent;
|
|
2093
|
+
callListener(handler, socket, createCloseEvent(socket, code, reason, clean));
|
|
2094
|
+
});
|
|
2095
|
+
case "error": return ((error) => {
|
|
2096
|
+
callListener(handler, socket, createErrorEvent(socket, error));
|
|
2097
|
+
});
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
function addEventListener(state, type, handler, options = {}) {
|
|
2101
|
+
if (!isDomType(type)) return;
|
|
2102
|
+
const attributed = options.attribute === true;
|
|
2103
|
+
const duplicate = (state.listeners[type] ?? []).some((entry) => isDomEntry(entry) && originalOf(entry) === handler && !isAttribute(entry));
|
|
2104
|
+
if (!attributed && duplicate) return;
|
|
2105
|
+
const wrapper = wrapperFor(state, type, handler);
|
|
2106
|
+
wrapper.attribute = attributed;
|
|
2107
|
+
wrapper.listener = handler;
|
|
2108
|
+
wrapper[DOM_WRAPPER] = true;
|
|
2109
|
+
const entry = options.once ? onceWrapper(state, type, wrapper) : wrapper;
|
|
2110
|
+
state.listeners = subscribe(state.listeners, type, entry);
|
|
2111
|
+
}
|
|
2112
|
+
function removeEventListener(state, type, handler) {
|
|
2113
|
+
if (!isDomType(type)) return;
|
|
2114
|
+
state.listeners = unsubscribeMatching(state.listeners, type, (entry) => isDomEntry(entry) && originalOf(entry) === handler && !isAttribute(entry));
|
|
2115
|
+
}
|
|
2116
|
+
function findAttributeListener(state, type) {
|
|
2117
|
+
for (const entry of state.listeners[type] ?? []) if (isAttribute(entry)) return originalOf(entry);
|
|
2118
|
+
return null;
|
|
2119
|
+
}
|
|
2120
|
+
function defineDomAttributes(socket, state) {
|
|
2121
|
+
for (const [attribute, type] of ATTRIBUTES) Object.defineProperty(socket, attribute, {
|
|
2122
|
+
enumerable: true,
|
|
2123
|
+
configurable: true,
|
|
2124
|
+
get: () => findAttributeListener(state, type),
|
|
2125
|
+
set: (handler) => {
|
|
2126
|
+
state.listeners = unsubscribeMatching(state.listeners, type, (entry) => isAttribute(entry));
|
|
2127
|
+
if (typeof handler !== "function") return;
|
|
2128
|
+
addEventListener(state, type, handler, { attribute: true });
|
|
2129
|
+
}
|
|
2130
|
+
});
|
|
2131
|
+
}
|
|
2132
|
+
//#endregion
|
|
2133
|
+
//#region src/compat/socket/gating.ts
|
|
2134
|
+
function pauseConnection(state) {
|
|
2135
|
+
if (state.readyState === 0 || state.readyState === 3) return;
|
|
2136
|
+
state.isPaused = true;
|
|
2137
|
+
if (state.attachment === null) {
|
|
2138
|
+
state.transport?.pause();
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
2141
|
+
pauseSocket(state.attachment.server, state.attachment.connection);
|
|
2142
|
+
}
|
|
2143
|
+
function resumeConnection(state) {
|
|
2144
|
+
if (state.readyState === 0 || state.readyState === 3) return;
|
|
2145
|
+
state.isPaused = false;
|
|
2146
|
+
if (state.attachment === null) {
|
|
2147
|
+
state.transport?.resume();
|
|
2148
|
+
return;
|
|
2149
|
+
}
|
|
2150
|
+
resumeSocket(state.attachment.server, state.attachment.connection);
|
|
2151
|
+
}
|
|
2152
|
+
//#endregion
|
|
2153
|
+
//#region src/compat/socket/control.ts
|
|
2154
|
+
function controlFrame(state, kind, data, mask, callback) {
|
|
2155
|
+
if (state.readyState === 0) throw notOpenError(0);
|
|
2156
|
+
let payload = data;
|
|
2157
|
+
let failure = callback;
|
|
2158
|
+
if (typeof payload === "function") {
|
|
2159
|
+
failure = payload;
|
|
2160
|
+
payload = void 0;
|
|
2161
|
+
} else if (typeof mask === "function") {
|
|
2162
|
+
failure = mask;
|
|
2163
|
+
mask = void 0;
|
|
2164
|
+
}
|
|
2165
|
+
if (typeof payload === "number") payload = String(payload);
|
|
2166
|
+
if (state.readyState !== 1) {
|
|
2167
|
+
defer(failure, notOpenError(state.readyState));
|
|
2168
|
+
return;
|
|
2169
|
+
}
|
|
2170
|
+
const bytes = toPayload(payload).bytes;
|
|
2171
|
+
if (!isValidControlPayload(bytes)) throw createError("ERR_INVALID_OPTION", "The data size must not be greater than 125 bytes", RangeError);
|
|
2172
|
+
const status = writeFrame(state, kind, bytes, true, false, mask !== false);
|
|
2173
|
+
if (status === "ok") {
|
|
2174
|
+
defer(failure);
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2177
|
+
defer(failure, frameError(status));
|
|
2178
|
+
}
|
|
2179
|
+
//#endregion
|
|
2180
|
+
//#region src/compat/socket/send-failure.ts
|
|
2181
|
+
const NOT_ATTACHED = "ventiws: the socket has no native transport attached; engine socket adoption is not implemented yet";
|
|
2182
|
+
function notAttachedError() {
|
|
2183
|
+
return createError("ERR_INVALID_STATE", NOT_ATTACHED);
|
|
2184
|
+
}
|
|
2185
|
+
function reportFailure(state, callback, error) {
|
|
2186
|
+
if (typeof callback === "function") {
|
|
2187
|
+
defer(callback, error);
|
|
2188
|
+
return;
|
|
2189
|
+
}
|
|
2190
|
+
reportWithoutClosing(state, error);
|
|
2191
|
+
}
|
|
2192
|
+
//#endregion
|
|
2193
|
+
//#region src/compat/socket/codec-send.ts
|
|
2194
|
+
function sendFramed(state, payload, options, callback) {
|
|
2195
|
+
const framing = frameOptions(options, payload.binary, state.isServer);
|
|
2196
|
+
const status = writeFrame(state, state.fragmentsOpen ? "continuation" : framing.binary ? "binary" : "text", payload.bytes, framing.fin, mayCompress(state, framing, payload.bytes.length), framing.mask);
|
|
2197
|
+
if (status === "ok") state.fragmentsOpen = !framing.fin;
|
|
2198
|
+
switch (status) {
|
|
2199
|
+
case "ok":
|
|
2200
|
+
defer(callback);
|
|
2201
|
+
return;
|
|
2202
|
+
case "backpressure":
|
|
2203
|
+
case "closing":
|
|
2204
|
+
case "closed":
|
|
2205
|
+
defer(callback, notOpenError(state.readyState));
|
|
2206
|
+
return;
|
|
2207
|
+
case "invalid-handle":
|
|
2208
|
+
case "payload-too-large":
|
|
2209
|
+
case "protocol-error":
|
|
2210
|
+
reportFailure(state, callback, frameError(status));
|
|
2211
|
+
return;
|
|
2212
|
+
}
|
|
2213
|
+
throw unhandledFrameStatus(status);
|
|
2214
|
+
}
|
|
2215
|
+
function mayCompress(state, framing, length) {
|
|
2216
|
+
if (!framing.compress || !state.compressible || !framing.fin || state.fragmentsOpen) return false;
|
|
2217
|
+
return length >= state.threshold;
|
|
2218
|
+
}
|
|
2219
|
+
function frameOptions(options, autodetected, isServer) {
|
|
2220
|
+
const source = typeof options === "object" && options !== null ? options : {};
|
|
2221
|
+
return {
|
|
2222
|
+
binary: booleanOr$1(source.binary, autodetected),
|
|
2223
|
+
fin: booleanOr$1(source.fin, true),
|
|
2224
|
+
compress: booleanOr$1(source.compress, true),
|
|
2225
|
+
mask: booleanOr$1(source.mask, !isServer)
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2228
|
+
function booleanOr$1(value, fallback) {
|
|
2229
|
+
return typeof value === "boolean" ? value : fallback;
|
|
2230
|
+
}
|
|
2231
|
+
function unhandledFrameStatus(status) {
|
|
2232
|
+
return createError("ERR_INVALID_STATE", `ventiws: the codec reported an unknown frame status "${String(status)}"`);
|
|
2233
|
+
}
|
|
2234
|
+
//#endregion
|
|
2235
|
+
//#region src/compat/socket/send-blob.ts
|
|
2236
|
+
function isBlob(value) {
|
|
2237
|
+
return typeof value === "object" && value !== null && typeof value.arrayBuffer === "function" && typeof value.size === "number";
|
|
2238
|
+
}
|
|
2239
|
+
function sendBlob(state, blob, options, callback) {
|
|
2240
|
+
if (state.readyState !== 1) {
|
|
2241
|
+
defer(callback, notOpenError(state.readyState));
|
|
2242
|
+
return;
|
|
2243
|
+
}
|
|
2244
|
+
const reading = blob.arrayBuffer().then((buffer) => {
|
|
2245
|
+
if (state.readyState !== 1) {
|
|
2246
|
+
defer(callback, notOpenError(state.readyState));
|
|
2247
|
+
return;
|
|
2248
|
+
}
|
|
2249
|
+
framePayload(state, Buffer.from(buffer), {
|
|
2250
|
+
...asOptions(options),
|
|
2251
|
+
binary: true
|
|
2252
|
+
}, callback);
|
|
2253
|
+
}).catch((error) => {
|
|
2254
|
+
defer(callback, error instanceof Error ? error : createError("ERR_INVALID_OPTION", String(error)));
|
|
2255
|
+
}).finally(() => {
|
|
2256
|
+
if (state.pendingSend === reading) state.pendingSend = null;
|
|
2257
|
+
});
|
|
2258
|
+
state.pendingSend = reading;
|
|
2259
|
+
}
|
|
2260
|
+
function afterPendingSend(state, work) {
|
|
2261
|
+
const pending = state.pendingSend;
|
|
2262
|
+
if (pending === null) {
|
|
2263
|
+
work();
|
|
2264
|
+
return;
|
|
2265
|
+
}
|
|
2266
|
+
state.pendingSend = pending.then(work, work);
|
|
2267
|
+
}
|
|
2268
|
+
function asOptions(options) {
|
|
2269
|
+
return typeof options === "object" && options !== null ? { ...options } : {};
|
|
2270
|
+
}
|
|
2271
|
+
//#endregion
|
|
2272
|
+
//#region src/compat/socket/send.ts
|
|
2273
|
+
function sendData(state, data, options, callback) {
|
|
2274
|
+
if (state.readyState === 0) throw notOpenError(0);
|
|
2275
|
+
afterPendingSend(state, () => framePayload(state, data, options, callback));
|
|
2276
|
+
}
|
|
2277
|
+
function framePayload(state, data, options, callback) {
|
|
2278
|
+
const failure = resolveCallback(options, callback);
|
|
2279
|
+
if (isBlob(data)) {
|
|
2280
|
+
sendBlob(state, data, options, failure);
|
|
2281
|
+
return;
|
|
2282
|
+
}
|
|
2283
|
+
const payload = toPayload(data);
|
|
2284
|
+
if (state.readyState !== 1) {
|
|
2285
|
+
defer(failure, notOpenError(state.readyState));
|
|
2286
|
+
return;
|
|
2287
|
+
}
|
|
2288
|
+
if (state.codec !== null) {
|
|
2289
|
+
sendFramed(state, payload, options, failure);
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2292
|
+
if (state.attachment === null) {
|
|
2293
|
+
if (typeof failure === "function") defer(failure, notAttachedError());
|
|
2294
|
+
else reportWithoutClosing(state, notAttachedError());
|
|
2295
|
+
return;
|
|
2296
|
+
}
|
|
2297
|
+
const binary = sendBinary(options, payload.binary);
|
|
2298
|
+
applySendStatus(state, sendSocket(state.attachment.server, state.attachment.connection, payload.bytes, binary), payload.bytes.length, failure);
|
|
2299
|
+
}
|
|
2300
|
+
function resolveCallback(options, callback) {
|
|
2301
|
+
if (typeof options === "function") return options;
|
|
2302
|
+
return callback;
|
|
2303
|
+
}
|
|
2304
|
+
function sendBinary(options, fallback) {
|
|
2305
|
+
if (typeof options !== "object" || options === null) return fallback;
|
|
2306
|
+
const binary = options.binary;
|
|
2307
|
+
return typeof binary === "boolean" ? binary : fallback;
|
|
2308
|
+
}
|
|
2309
|
+
function applySendStatus(state, status, length, callback) {
|
|
2310
|
+
switch (status) {
|
|
2311
|
+
case "ok":
|
|
2312
|
+
defer(callback);
|
|
2313
|
+
return;
|
|
2314
|
+
case "backpressure":
|
|
2315
|
+
reportFailure(state, callback, createError("ERR_BACKPRESSURE", "ventiws: the outbound staging ring is full"));
|
|
2316
|
+
return;
|
|
2317
|
+
case "closing":
|
|
2318
|
+
case "closed":
|
|
2319
|
+
defer(callback, notOpenError(state.readyState));
|
|
2320
|
+
return;
|
|
2321
|
+
case "invalid-handle":
|
|
2322
|
+
reportFailure(state, callback, createError("ERR_INVALID_HANDLE", "ventiws: the connection handle is stale"));
|
|
2323
|
+
return;
|
|
2324
|
+
case "payload-too-large":
|
|
2325
|
+
case "invalid-close-code":
|
|
2326
|
+
case "invalid-close-reason":
|
|
2327
|
+
case "protocol-error":
|
|
2328
|
+
case "policy-violation":
|
|
2329
|
+
reportFailure(state, callback, statusError(status));
|
|
2330
|
+
return;
|
|
2331
|
+
}
|
|
2332
|
+
throw unhandledStatus(status);
|
|
2333
|
+
}
|
|
2334
|
+
function unhandledStatus(status) {
|
|
2335
|
+
return createError("ERR_INVALID_STATE", `ventiws: the engine reported an unknown socket status "${String(status)}"`);
|
|
2336
|
+
}
|
|
2337
|
+
//#endregion
|
|
2338
|
+
//#region src/compat/socket/record.ts
|
|
2339
|
+
const BINARY_TYPE_VALUES = [
|
|
2340
|
+
"nodebuffer",
|
|
2341
|
+
"arraybuffer",
|
|
2342
|
+
"fragments",
|
|
2343
|
+
"blob"
|
|
2344
|
+
];
|
|
2345
|
+
function buildSocketRecord(state) {
|
|
2346
|
+
const socket = {
|
|
2347
|
+
CONNECTING: 0,
|
|
2348
|
+
OPEN: 1,
|
|
2349
|
+
CLOSING: 2,
|
|
2350
|
+
CLOSED: 3,
|
|
2351
|
+
get binaryType() {
|
|
2352
|
+
return state.binaryType;
|
|
2353
|
+
},
|
|
2354
|
+
set binaryType(value) {
|
|
2355
|
+
if (!BINARY_TYPE_VALUES.includes(value)) return;
|
|
2356
|
+
state.binaryType = value;
|
|
2357
|
+
},
|
|
2358
|
+
get bufferedAmount() {
|
|
2359
|
+
return bufferedAmountOf(state);
|
|
2360
|
+
},
|
|
2361
|
+
get extensions() {
|
|
2362
|
+
return state.extensions;
|
|
2363
|
+
},
|
|
2364
|
+
get isPaused() {
|
|
2365
|
+
return state.isPaused;
|
|
2366
|
+
},
|
|
2367
|
+
get protocol() {
|
|
2368
|
+
return state.protocol;
|
|
2369
|
+
},
|
|
2370
|
+
get readyState() {
|
|
2371
|
+
return state.readyState;
|
|
2372
|
+
},
|
|
2373
|
+
get url() {
|
|
2374
|
+
return state.url;
|
|
2375
|
+
},
|
|
2376
|
+
...createEmitter(state),
|
|
2377
|
+
send: (data, sendOptions, callback) => {
|
|
2378
|
+
sendData(state, data, sendOptions, callback);
|
|
2379
|
+
},
|
|
2380
|
+
ping: (data, mask, callback) => {
|
|
2381
|
+
controlFrame(state, "ping", data, mask, callback);
|
|
2382
|
+
},
|
|
2383
|
+
pong: (data, mask, callback) => {
|
|
2384
|
+
controlFrame(state, "pong", data, mask, callback);
|
|
2385
|
+
},
|
|
2386
|
+
close: (code, reason) => {
|
|
2387
|
+
closeConnection(state, code, reason);
|
|
2388
|
+
},
|
|
2389
|
+
terminate: () => {
|
|
2390
|
+
terminateConnection(state);
|
|
2391
|
+
},
|
|
2392
|
+
pause: () => {
|
|
2393
|
+
pauseConnection(state);
|
|
2394
|
+
},
|
|
2395
|
+
resume: () => {
|
|
2396
|
+
resumeConnection(state);
|
|
2397
|
+
},
|
|
2398
|
+
addEventListener: (type, handler, listenerOptions) => {
|
|
2399
|
+
addEventListener(state, type, handler, listenerOptions);
|
|
2400
|
+
},
|
|
2401
|
+
removeEventListener: (type, handler) => {
|
|
2402
|
+
removeEventListener(state, type, handler);
|
|
2403
|
+
}
|
|
2404
|
+
};
|
|
2405
|
+
state.target = socket;
|
|
2406
|
+
defineDomAttributes(socket, state);
|
|
2407
|
+
brandSocket(socket, state);
|
|
2408
|
+
return socket;
|
|
2409
|
+
}
|
|
2410
|
+
//#endregion
|
|
2411
|
+
//#region src/compat/options/client.ts
|
|
2412
|
+
function normalizeClientOptions(options) {
|
|
2413
|
+
const source = { ...options };
|
|
2414
|
+
const protocolVersion = source.protocolVersion ?? 13;
|
|
2415
|
+
if (protocolVersion !== 8 && protocolVersion !== 13) invalidOption(`Unsupported protocol version: ${protocolVersion} (supported versions: 8, 13)`, RangeError);
|
|
2416
|
+
return {
|
|
2417
|
+
protocolVersion,
|
|
2418
|
+
followRedirects: source.followRedirects ?? false,
|
|
2419
|
+
maxRedirects: source.maxRedirects ?? 10,
|
|
2420
|
+
handshakeTimeout: source.handshakeTimeout,
|
|
2421
|
+
maxPayload: maxPayloadOf(source, () => codecLimits().maxPayloadBytes),
|
|
2422
|
+
maxFragments: maxFragmentsOf(source, () => codecLimits().maxFragments),
|
|
2423
|
+
maxBufferedChunks: maxBufferedChunksOf(source),
|
|
2424
|
+
skipUTF8Validation: source.skipUTF8Validation ?? false,
|
|
2425
|
+
allowSynchronousEvents: source.allowSynchronousEvents ?? true,
|
|
2426
|
+
autoPong: source.autoPong ?? true,
|
|
2427
|
+
closeTimeout: closeTimeoutOf(source),
|
|
2428
|
+
perMessageDeflate: normalizePerMessageDeflate(source.perMessageDeflate, true),
|
|
2429
|
+
origin: source.origin,
|
|
2430
|
+
headers: source.headers === void 0 ? void 0 : { ...source.headers },
|
|
2431
|
+
requestOptions: { ...source },
|
|
2432
|
+
finishRequest: source.finishRequest,
|
|
2433
|
+
generateMask: source.generateMask
|
|
2434
|
+
};
|
|
2435
|
+
}
|
|
2436
|
+
//#endregion
|
|
2437
|
+
//#region src/compat/client/address.ts
|
|
2438
|
+
const DEFAULT_PORTS = {
|
|
2439
|
+
"ws:": 80,
|
|
2440
|
+
"wss:": 443
|
|
2441
|
+
};
|
|
2442
|
+
const WS_SCHEMES = {
|
|
2443
|
+
"http:": "ws:",
|
|
2444
|
+
"https:": "wss:",
|
|
2445
|
+
"ws:": "ws:",
|
|
2446
|
+
"wss:": "wss:",
|
|
2447
|
+
"ws+unix:": "ws:"
|
|
2448
|
+
};
|
|
2449
|
+
const IPC_SCHEME = "ws+unix:";
|
|
2450
|
+
function parseAddress(address) {
|
|
2451
|
+
const parsed = toUrl(address);
|
|
2452
|
+
const scheme = parsed.protocol;
|
|
2453
|
+
if (scheme === IPC_SCHEME) return buildIpc(parsed);
|
|
2454
|
+
const resolved = WS_SCHEMES[scheme];
|
|
2455
|
+
if (resolved === void 0) throw createError("ERR_INVALID_OPTION", "The URL's protocol must be one of \"ws:\", \"wss:\", \"http:\", \"https:\", or \"ws+unix:\"", SyntaxError);
|
|
2456
|
+
if (parsed.hash) throw createError("ERR_INVALID_OPTION", "The URL contains a fragment identifier", SyntaxError);
|
|
2457
|
+
return build(parsed, resolved);
|
|
2458
|
+
}
|
|
2459
|
+
function toUrl(address) {
|
|
2460
|
+
if (address instanceof URL) return address;
|
|
2461
|
+
try {
|
|
2462
|
+
return new URL(address);
|
|
2463
|
+
} catch {
|
|
2464
|
+
throw createError("ERR_INVALID_OPTION", `Invalid URL: ${address}`, SyntaxError);
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
function build(parsed, scheme) {
|
|
2468
|
+
parsed.protocol = scheme;
|
|
2469
|
+
const hasCredentials = parsed.username !== "" || parsed.password !== "";
|
|
2470
|
+
const host = parsed.hostname.startsWith("[") ? parsed.hostname.slice(1, -1) : parsed.hostname;
|
|
2471
|
+
const port = parsed.port === "" ? DEFAULT_PORTS[scheme] : Number(parsed.port);
|
|
2472
|
+
return {
|
|
2473
|
+
url: parsed.href,
|
|
2474
|
+
secure: scheme === "wss:",
|
|
2475
|
+
host,
|
|
2476
|
+
authority: parsed.host,
|
|
2477
|
+
port,
|
|
2478
|
+
path: `${parsed.pathname}${parsed.search}`,
|
|
2479
|
+
auth: hasCredentials ? `${parsed.username}:${parsed.password}` : void 0,
|
|
2480
|
+
socketPath: void 0
|
|
2481
|
+
};
|
|
2482
|
+
}
|
|
2483
|
+
function buildIpc(parsed) {
|
|
2484
|
+
if (parsed.pathname === "") throw createError("ERR_INVALID_OPTION", "The URL's pathname is empty", SyntaxError);
|
|
2485
|
+
if (parsed.hash) throw createError("ERR_INVALID_OPTION", "The URL contains a fragment identifier", SyntaxError);
|
|
2486
|
+
const parts = parsed.pathname.split(":");
|
|
2487
|
+
const socketPath = parts[0] ?? "";
|
|
2488
|
+
const target = parts.length > 1 ? parts.slice(1).join(":") : "";
|
|
2489
|
+
return {
|
|
2490
|
+
url: parsed.href,
|
|
2491
|
+
secure: false,
|
|
2492
|
+
host: "",
|
|
2493
|
+
authority: socketPath,
|
|
2494
|
+
port: DEFAULT_PORTS["ws:"],
|
|
2495
|
+
path: target === "" ? "/" : `/${target.replace(/^\/+/, "")}${parsed.search}`,
|
|
2496
|
+
auth: void 0,
|
|
2497
|
+
socketPath
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2500
|
+
//#endregion
|
|
2501
|
+
//#region src/compat/extensions/offer.ts
|
|
2502
|
+
function offer(options) {
|
|
2503
|
+
if (options === false) return void 0;
|
|
2504
|
+
return formatExtension(PERMESSAGE_DEFLATE, NO_CONTEXT_TAKEOVER);
|
|
2505
|
+
}
|
|
2506
|
+
//#endregion
|
|
2507
|
+
//#region src/compat/client/handshake-headers.ts
|
|
2508
|
+
function buildHeaders(options, protocols, key, credentials) {
|
|
2509
|
+
const headers = {
|
|
2510
|
+
"Sec-WebSocket-Version": String(options.protocolVersion),
|
|
2511
|
+
"Sec-WebSocket-Key": key,
|
|
2512
|
+
Connection: "Upgrade",
|
|
2513
|
+
Upgrade: "websocket"
|
|
2514
|
+
};
|
|
2515
|
+
if (protocols.length > 0) headers["Sec-WebSocket-Protocol"] = protocols.join(",");
|
|
2516
|
+
const extension = offer(options.perMessageDeflate);
|
|
2517
|
+
if (extension !== void 0) headers["Sec-WebSocket-Extensions"] = extension;
|
|
2518
|
+
if (options.origin) {
|
|
2519
|
+
if (options.protocolVersion < 13) headers["Sec-WebSocket-Origin"] = options.origin;
|
|
2520
|
+
else headers.Origin = options.origin;
|
|
2521
|
+
}
|
|
2522
|
+
const merged = {
|
|
2523
|
+
...lowerCased(options.headers),
|
|
2524
|
+
...headers
|
|
2525
|
+
};
|
|
2526
|
+
if (credentials !== void 0 && merged.authorization === void 0) merged.authorization = `Basic ${Buffer.from(credentials, "utf8").toString("base64")}`;
|
|
2527
|
+
return {
|
|
2528
|
+
key,
|
|
2529
|
+
headers: merged
|
|
2530
|
+
};
|
|
2531
|
+
}
|
|
2532
|
+
function lowerCased(headers) {
|
|
2533
|
+
const out = {};
|
|
2534
|
+
for (const [name, value] of Object.entries(headers ?? {})) out[name.toLowerCase()] = value;
|
|
2535
|
+
return out;
|
|
2536
|
+
}
|
|
2537
|
+
//#endregion
|
|
2538
|
+
//#region src/compat/client/transport-options.ts
|
|
2539
|
+
const FORWARDED = [
|
|
2540
|
+
"ca",
|
|
2541
|
+
"cert",
|
|
2542
|
+
"key",
|
|
2543
|
+
"pfx",
|
|
2544
|
+
"passphrase",
|
|
2545
|
+
"secureContext",
|
|
2546
|
+
"rejectUnauthorized",
|
|
2547
|
+
"checkServerIdentity",
|
|
2548
|
+
"servername",
|
|
2549
|
+
"agent",
|
|
2550
|
+
"createConnection",
|
|
2551
|
+
"localAddress",
|
|
2552
|
+
"family",
|
|
2553
|
+
"lookup",
|
|
2554
|
+
"insecureHTTPParser",
|
|
2555
|
+
"maxHeaderSize",
|
|
2556
|
+
"joinDuplicateHeaders",
|
|
2557
|
+
"signal"
|
|
2558
|
+
];
|
|
2559
|
+
function transportOptions(address, options) {
|
|
2560
|
+
const carried = {};
|
|
2561
|
+
for (const key of FORWARDED) {
|
|
2562
|
+
const value = options.requestOptions[key];
|
|
2563
|
+
if (value !== void 0) carried[key] = value;
|
|
2564
|
+
}
|
|
2565
|
+
if (address.socketPath !== void 0) return {
|
|
2566
|
+
...carried,
|
|
2567
|
+
socketPath: address.socketPath
|
|
2568
|
+
};
|
|
2569
|
+
if (!address.secure) return carried;
|
|
2570
|
+
return {
|
|
2571
|
+
...carried,
|
|
2572
|
+
servername: stringOr(carried.servername, address.host),
|
|
2573
|
+
rejectUnauthorized: booleanOr(carried.rejectUnauthorized, true)
|
|
2574
|
+
};
|
|
2575
|
+
}
|
|
2576
|
+
function stringOr(value, fallback) {
|
|
2577
|
+
return typeof value === "string" ? value : fallback;
|
|
2578
|
+
}
|
|
2579
|
+
function booleanOr(value, fallback) {
|
|
2580
|
+
return typeof value === "boolean" ? value : fallback;
|
|
2581
|
+
}
|
|
2582
|
+
//#endregion
|
|
2583
|
+
//#region src/compat/client/request.ts
|
|
2584
|
+
const GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
2585
|
+
function expectedAccept(key) {
|
|
2586
|
+
return (0, node_crypto.createHash)("sha1").update(key + GUID).digest("base64");
|
|
2587
|
+
}
|
|
2588
|
+
function newKey() {
|
|
2589
|
+
return (0, node_crypto.randomBytes)(16).toString("base64");
|
|
2590
|
+
}
|
|
2591
|
+
function buildRequest(address, options, protocols, key, carriedAuth) {
|
|
2592
|
+
const built = buildHeaders(options, protocols, key, address.auth ?? carriedAuth);
|
|
2593
|
+
return {
|
|
2594
|
+
key: built.key,
|
|
2595
|
+
headers: built.headers,
|
|
2596
|
+
request: {
|
|
2597
|
+
hostname: address.host,
|
|
2598
|
+
port: address.port,
|
|
2599
|
+
path: address.path,
|
|
2600
|
+
method: "GET",
|
|
2601
|
+
headers: {
|
|
2602
|
+
host: hostHeader(address),
|
|
2603
|
+
...built.headers
|
|
2604
|
+
},
|
|
2605
|
+
setHost: false,
|
|
2606
|
+
...transportOptions(address, options)
|
|
2607
|
+
}
|
|
2608
|
+
};
|
|
2609
|
+
}
|
|
2610
|
+
function hostHeader(address) {
|
|
2611
|
+
if (address.socketPath !== void 0) return address.socketPath;
|
|
2612
|
+
return `${address.host.includes(":") ? `[${address.host}]` : address.host}:${address.port}`;
|
|
2613
|
+
}
|
|
2614
|
+
//#endregion
|
|
2615
|
+
//#region src/compat/client/extension.ts
|
|
2616
|
+
function acceptExtension(response, options) {
|
|
2617
|
+
const claimed = response.headers["sec-websocket-extensions"];
|
|
2618
|
+
if (claimed === void 0) return { accepted: null };
|
|
2619
|
+
const value = Array.isArray(claimed) ? claimed.join(", ") : claimed;
|
|
2620
|
+
if (options === false) return { refusal: "Server sent an extension but none was requested" };
|
|
2621
|
+
let offered;
|
|
2622
|
+
try {
|
|
2623
|
+
offered = parseExtensions(value).get("permessage-deflate") ?? [];
|
|
2624
|
+
} catch {
|
|
2625
|
+
return { refusal: "Invalid Sec-WebSocket-Extensions header" };
|
|
2626
|
+
}
|
|
2627
|
+
if (offered.length === 0) return { refusal: "Server sent an extension but none was requested" };
|
|
2628
|
+
return acceptAsClient(offered[0], options) ?? { accepted: null };
|
|
2629
|
+
}
|
|
2630
|
+
//#endregion
|
|
2631
|
+
//#region src/compat/client/open.ts
|
|
2632
|
+
function onUpgrade(attempt, response, socket, head) {
|
|
2633
|
+
const { state } = attempt;
|
|
2634
|
+
socket.on("error", () => void 0);
|
|
2635
|
+
emitEvent(state, "upgrade", response);
|
|
2636
|
+
if (state.readyState === 3) {
|
|
2637
|
+
discard(socket);
|
|
2638
|
+
return;
|
|
2639
|
+
}
|
|
2640
|
+
const refused = reject(response, attempt);
|
|
2641
|
+
if (refused !== null) {
|
|
2642
|
+
abort(attempt, createError("ERR_PROTOCOL", refused));
|
|
2643
|
+
return;
|
|
2644
|
+
}
|
|
2645
|
+
const extension = acceptExtension(response, attempt.options.perMessageDeflate);
|
|
2646
|
+
if ("refusal" in extension) {
|
|
2647
|
+
abort(attempt, createError("ERR_PROTOCOL", extension.refusal));
|
|
2648
|
+
return;
|
|
2649
|
+
}
|
|
2650
|
+
state.protocol = response.headers["sec-websocket-protocol"] ?? "";
|
|
2651
|
+
state.extensions = response.headers["sec-websocket-extensions"] ?? "";
|
|
2652
|
+
state.compressible = extension.accepted !== null;
|
|
2653
|
+
socket.setTimeout(0);
|
|
2654
|
+
const target = state.target ?? null;
|
|
2655
|
+
if (target === null) {
|
|
2656
|
+
abort(attempt, createError("ERR_INVALID_STATE", "ventiws: the socket record is missing"));
|
|
2657
|
+
return;
|
|
2658
|
+
}
|
|
2659
|
+
attempt.transport = socket;
|
|
2660
|
+
state.cancelHandshake = null;
|
|
2661
|
+
attachSocket(target, socket, CODEC_ROLE.client, head);
|
|
2662
|
+
}
|
|
2663
|
+
function reject(response, attempt) {
|
|
2664
|
+
const upgrade = response.headers.upgrade;
|
|
2665
|
+
if (upgrade === void 0 || upgrade.toLowerCase() !== "websocket") return "Invalid Upgrade header";
|
|
2666
|
+
if (response.headers["sec-websocket-accept"] !== expectedAccept(attempt.handshake.key)) return "Invalid Sec-WebSocket-Accept header";
|
|
2667
|
+
return protocolRejection(response.headers["sec-websocket-protocol"], attempt.offered);
|
|
2668
|
+
}
|
|
2669
|
+
//#endregion
|
|
2670
|
+
//#region src/compat/client/unexpected.ts
|
|
2671
|
+
function reportUnexpected(attempt, request, response) {
|
|
2672
|
+
if (emitEvent(attempt.state, "unexpected-response", request, response)) return;
|
|
2673
|
+
request.abort();
|
|
2674
|
+
abort(attempt, createError("ERR_PROTOCOL", `Unexpected server response: ${response.statusCode ?? 0}`));
|
|
2675
|
+
}
|
|
2676
|
+
//#endregion
|
|
2677
|
+
//#region src/compat/client/credentials.ts
|
|
2678
|
+
function stripCredentials(headers) {
|
|
2679
|
+
if (typeof headers !== "object" || headers === null) return;
|
|
2680
|
+
const map = headers;
|
|
2681
|
+
for (const name of Object.keys(map)) {
|
|
2682
|
+
const lower = name.toLowerCase();
|
|
2683
|
+
if (lower === "authorization" || lower === "cookie") delete map[name];
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
//#endregion
|
|
2687
|
+
//#region src/compat/client/redirect.ts
|
|
2688
|
+
function isRedirect(status, location) {
|
|
2689
|
+
return location !== void 0 && status >= 300 && status < 400;
|
|
2690
|
+
}
|
|
2691
|
+
function onResponse(attempt, request, response) {
|
|
2692
|
+
const location = response.headers.location;
|
|
2693
|
+
if (isRedirect(response.statusCode ?? 0, location) && attempt.options.followRedirects) {
|
|
2694
|
+
follow(attempt, request, response, location ?? "");
|
|
2695
|
+
return;
|
|
2696
|
+
}
|
|
2697
|
+
reportUnexpected(attempt, request, response);
|
|
2698
|
+
}
|
|
2699
|
+
function follow(attempt, request, response, location) {
|
|
2700
|
+
if (attempt.redirects + 1 > attempt.options.maxRedirects) {
|
|
2701
|
+
request.abort();
|
|
2702
|
+
abort(attempt, createError("ERR_PROTOCOL", "Maximum redirects exceeded"));
|
|
2703
|
+
return;
|
|
2704
|
+
}
|
|
2705
|
+
const target = resolve(location, attempt.state.url);
|
|
2706
|
+
let next;
|
|
2707
|
+
try {
|
|
2708
|
+
next = parseAddress(target);
|
|
2709
|
+
} catch (error) {
|
|
2710
|
+
request.abort();
|
|
2711
|
+
abort(attempt, createError("ERR_PROTOCOL", error instanceof Error ? error.message : `Invalid URL: ${target}`));
|
|
2712
|
+
return;
|
|
2713
|
+
}
|
|
2714
|
+
if (attempt.address.secure && !next.secure) {
|
|
2715
|
+
request.abort();
|
|
2716
|
+
abort(attempt, createError("ERR_PROTOCOL", "Cannot follow a redirect from wss: to ws:"));
|
|
2717
|
+
return;
|
|
2718
|
+
}
|
|
2719
|
+
if (next.authority !== attempt.address.authority && listenerCount(attempt) === 0) {
|
|
2720
|
+
stripCredentials(attempt.handshake.request.headers);
|
|
2721
|
+
attempt.auth = void 0;
|
|
2722
|
+
}
|
|
2723
|
+
request.abort();
|
|
2724
|
+
attempt.state.url = next.url;
|
|
2725
|
+
attempt.address = next;
|
|
2726
|
+
attempt.redirects += 1;
|
|
2727
|
+
attempt.handshake = buildRequest(next, attempt.options, attempt.requested, newKey(), attempt.auth);
|
|
2728
|
+
const hop = create(attempt);
|
|
2729
|
+
if (hop === null) return;
|
|
2730
|
+
emitEvent(attempt.state, "redirect", next.url, hop);
|
|
2731
|
+
finish(attempt, hop);
|
|
2732
|
+
}
|
|
2733
|
+
function resolve(location, base) {
|
|
2734
|
+
try {
|
|
2735
|
+
return new URL(location, base).href;
|
|
2736
|
+
} catch {
|
|
2737
|
+
return location;
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
function listenerCount(attempt) {
|
|
2741
|
+
return listenerCount$1(attempt.state.listeners, "redirect");
|
|
2742
|
+
}
|
|
2743
|
+
//#endregion
|
|
2744
|
+
//#region src/compat/client/dial.ts
|
|
2745
|
+
function dial(attempt) {
|
|
2746
|
+
const request = create(attempt);
|
|
2747
|
+
if (request === null) return null;
|
|
2748
|
+
finish(attempt, request);
|
|
2749
|
+
return request;
|
|
2750
|
+
}
|
|
2751
|
+
function finish(attempt, request) {
|
|
2752
|
+
const callback = attempt.options.finishRequest;
|
|
2753
|
+
if (callback === void 0) {
|
|
2754
|
+
request.end();
|
|
2755
|
+
return;
|
|
2756
|
+
}
|
|
2757
|
+
const target = attempt.state.target;
|
|
2758
|
+
if (target === void 0) {
|
|
2759
|
+
request.end();
|
|
2760
|
+
return;
|
|
2761
|
+
}
|
|
2762
|
+
callback(request, target);
|
|
2763
|
+
}
|
|
2764
|
+
function create(attempt) {
|
|
2765
|
+
const { options, handshake } = attempt;
|
|
2766
|
+
if (attempt.state.readyState === 3) return null;
|
|
2767
|
+
const request = issue(attempt, handshake.request);
|
|
2768
|
+
attempt.request = request;
|
|
2769
|
+
attempt.state.cancelHandshake = () => {
|
|
2770
|
+
request.abort();
|
|
2771
|
+
};
|
|
2772
|
+
request.on("error", (error) => {
|
|
2773
|
+
if (attempt.request !== request) return;
|
|
2774
|
+
attempt.request = null;
|
|
2775
|
+
failConnection(attempt.state, error);
|
|
2776
|
+
});
|
|
2777
|
+
if (options.handshakeTimeout) request.setTimeout(options.handshakeTimeout, () => {
|
|
2778
|
+
refuse(attempt, request, "Opening handshake has timed out");
|
|
2779
|
+
});
|
|
2780
|
+
request.on("upgrade", (response, socket, head) => {
|
|
2781
|
+
if (attempt.request !== request) return;
|
|
2782
|
+
attempt.request = null;
|
|
2783
|
+
onUpgrade(attempt, response, socket, head);
|
|
2784
|
+
});
|
|
2785
|
+
request.on("response", (response) => {
|
|
2786
|
+
if (attempt.request !== request) return;
|
|
2787
|
+
attempt.request = null;
|
|
2788
|
+
onResponse(attempt, request, response);
|
|
2789
|
+
});
|
|
2790
|
+
return request;
|
|
2791
|
+
}
|
|
2792
|
+
function issue(attempt, options) {
|
|
2793
|
+
return attempt.address.secure ? (0, node_https.request)(options) : (0, node_http.request)(options);
|
|
2794
|
+
}
|
|
2795
|
+
function refuse(attempt, request, message) {
|
|
2796
|
+
request?.abort();
|
|
2797
|
+
abort(attempt, createError("ERR_PROTOCOL", message));
|
|
2798
|
+
}
|
|
2799
|
+
function abort(attempt, error) {
|
|
2800
|
+
const { state } = attempt;
|
|
2801
|
+
if (state.readyState === 3) return;
|
|
2802
|
+
state.cancelHandshake = null;
|
|
2803
|
+
attempt.request?.destroy();
|
|
2804
|
+
attempt.request = null;
|
|
2805
|
+
if (attempt.transport !== null) discard(attempt.transport);
|
|
2806
|
+
failConnection(state, error);
|
|
2807
|
+
}
|
|
2808
|
+
function discard(socket) {
|
|
2809
|
+
socket.on("error", () => void 0);
|
|
2810
|
+
socket.destroy();
|
|
2811
|
+
}
|
|
2812
|
+
//#endregion
|
|
2813
|
+
//#region src/compat/client/connect.ts
|
|
2814
|
+
function connectSocket(address, protocols, options) {
|
|
2815
|
+
const normalized = normalizeClientOptions(options);
|
|
2816
|
+
const parsed = parseAddress(address);
|
|
2817
|
+
const requested = normalizeProtocols(protocols);
|
|
2818
|
+
const state = createSocketState();
|
|
2819
|
+
state.isServer = false;
|
|
2820
|
+
state.url = parsed.url;
|
|
2821
|
+
state.closeTimeout = normalized.closeTimeout;
|
|
2822
|
+
state.autoPong = normalized.autoPong;
|
|
2823
|
+
state.generateMask = normalized.generateMask ?? null;
|
|
2824
|
+
state.allowSynchronousEvents = normalized.allowSynchronousEvents;
|
|
2825
|
+
state.validateUtf8 = !normalized.skipUTF8Validation;
|
|
2826
|
+
state.maxPayload = normalized.maxPayload;
|
|
2827
|
+
state.maxFragments = normalized.maxFragments;
|
|
2828
|
+
state.maxBufferedChunks = normalized.maxBufferedChunks;
|
|
2829
|
+
state.threshold = thresholdOf(normalized.perMessageDeflate);
|
|
2830
|
+
const socket = buildSocketRecord(state);
|
|
2831
|
+
dial({
|
|
2832
|
+
state,
|
|
2833
|
+
options: normalized,
|
|
2834
|
+
requested,
|
|
2835
|
+
offered: protocolSet(requested),
|
|
2836
|
+
request: null,
|
|
2837
|
+
transport: null,
|
|
2838
|
+
handshake: buildRequest(parsed, normalized, requested, newKey()),
|
|
2839
|
+
address: parsed,
|
|
2840
|
+
auth: parsed.auth,
|
|
2841
|
+
redirects: 0
|
|
2842
|
+
});
|
|
2843
|
+
return socket;
|
|
2844
|
+
}
|
|
2845
|
+
//#endregion
|
|
2846
|
+
//#region src/compat/socket/socket.ts
|
|
2847
|
+
function createSocket(address, protocols, options) {
|
|
2848
|
+
if (address === null) return buildSocketRecord(createSocketState());
|
|
2849
|
+
const promoted = promoteOptions(protocols, options);
|
|
2850
|
+
return connectSocket(address, promoted.protocols, promoted.options);
|
|
2851
|
+
}
|
|
2852
|
+
//#endregion
|
|
2853
|
+
//#region src/compat/stream.ts
|
|
2854
|
+
function createWebSocketStream(ws, options) {
|
|
2855
|
+
let terminateOnDestroy = true;
|
|
2856
|
+
const duplex = new node_stream.Duplex({
|
|
2857
|
+
...options,
|
|
2858
|
+
autoDestroy: false,
|
|
2859
|
+
emitClose: false,
|
|
2860
|
+
objectMode: false,
|
|
2861
|
+
writableObjectMode: false
|
|
2862
|
+
});
|
|
2863
|
+
const onEnd = () => {
|
|
2864
|
+
if (!duplex.destroyed && duplex.writableFinished) duplex.destroy();
|
|
2865
|
+
};
|
|
2866
|
+
const onError = (error) => {
|
|
2867
|
+
duplex.removeListener("error", onError);
|
|
2868
|
+
duplex.destroy();
|
|
2869
|
+
if (duplex.listenerCount("error") === 0) duplex.emit("error", error);
|
|
2870
|
+
};
|
|
2871
|
+
ws.on("message", (message, isBinary) => {
|
|
2872
|
+
const data = !isBinary && duplex.readableObjectMode ? message.toString() : message;
|
|
2873
|
+
if (!duplex.push(data)) ws.pause();
|
|
2874
|
+
});
|
|
2875
|
+
ws.once("error", (error) => {
|
|
2876
|
+
if (duplex.destroyed) return;
|
|
2877
|
+
terminateOnDestroy = false;
|
|
2878
|
+
duplex.destroy(error);
|
|
2879
|
+
});
|
|
2880
|
+
ws.once("close", () => {
|
|
2881
|
+
if (!duplex.destroyed) duplex.push(null);
|
|
2882
|
+
});
|
|
2883
|
+
duplex._destroy = (error, callback) => {
|
|
2884
|
+
if (ws.readyState === ws.CLOSED) {
|
|
2885
|
+
callback(error);
|
|
2886
|
+
process.nextTick(() => {
|
|
2887
|
+
duplex.emit("close");
|
|
2888
|
+
});
|
|
2889
|
+
return;
|
|
2890
|
+
}
|
|
2891
|
+
let called = false;
|
|
2892
|
+
ws.once("error", (failure) => {
|
|
2893
|
+
called = true;
|
|
2894
|
+
callback(failure);
|
|
2895
|
+
});
|
|
2896
|
+
ws.once("close", () => {
|
|
2897
|
+
if (!called) callback(error);
|
|
2898
|
+
process.nextTick(() => {
|
|
2899
|
+
duplex.emit("close");
|
|
2900
|
+
});
|
|
2901
|
+
});
|
|
2902
|
+
if (terminateOnDestroy) ws.terminate();
|
|
2903
|
+
};
|
|
2904
|
+
duplex._final = (callback) => {
|
|
2905
|
+
if (ws.readyState === ws.CONNECTING) {
|
|
2906
|
+
ws.once("open", () => {
|
|
2907
|
+
duplex._final?.(callback);
|
|
2908
|
+
});
|
|
2909
|
+
return;
|
|
2910
|
+
}
|
|
2911
|
+
if (ws.readyState === ws.CLOSED) {
|
|
2912
|
+
callback();
|
|
2913
|
+
return;
|
|
2914
|
+
}
|
|
2915
|
+
ws.close();
|
|
2916
|
+
if (!closeFrameWritten(ws)) {
|
|
2917
|
+
ws.once("close", () => {
|
|
2918
|
+
callback();
|
|
2919
|
+
});
|
|
2920
|
+
return;
|
|
2921
|
+
}
|
|
2922
|
+
callback();
|
|
2923
|
+
if (duplex.readableEnded) duplex.destroy();
|
|
2924
|
+
};
|
|
2925
|
+
duplex._read = () => {
|
|
2926
|
+
if (ws.isPaused) ws.resume();
|
|
2927
|
+
};
|
|
2928
|
+
duplex._write = (chunk, encoding, callback) => {
|
|
2929
|
+
if (ws.readyState === ws.CONNECTING) {
|
|
2930
|
+
ws.once("open", () => {
|
|
2931
|
+
duplex._write?.(chunk, encoding, callback);
|
|
2932
|
+
});
|
|
2933
|
+
return;
|
|
2934
|
+
}
|
|
2935
|
+
ws.send(chunk, callback);
|
|
2936
|
+
};
|
|
2937
|
+
duplex.on("end", onEnd);
|
|
2938
|
+
duplex.on("error", onError);
|
|
2939
|
+
return duplex;
|
|
2940
|
+
}
|
|
2941
|
+
//#endregion
|
|
2942
|
+
//#region src/compat/constructors.ts
|
|
2943
|
+
function buildSocket(address, protocols, options) {
|
|
2944
|
+
if (new.target === void 0) throw new TypeError("Class constructor WebSocket cannot be invoked without 'new'");
|
|
2945
|
+
return createSocket(address, protocols, options);
|
|
2946
|
+
}
|
|
2947
|
+
function buildServer(options, callback) {
|
|
2948
|
+
if (new.target === void 0) throw new TypeError("Class constructor WebSocketServer cannot be invoked without 'new'");
|
|
2949
|
+
return createWebSocketServer(WebSocket, options, callback);
|
|
2950
|
+
}
|
|
2951
|
+
const WebSocket = buildSocket;
|
|
2952
|
+
const WebSocketServer = buildServer;
|
|
2953
|
+
Object.defineProperty(WebSocket, Symbol.hasInstance, { value: isSocket });
|
|
2954
|
+
Object.defineProperty(WebSocketServer, Symbol.hasInstance, { value: isServer });
|
|
2955
|
+
Object.defineProperties(WebSocket, {
|
|
2956
|
+
CONNECTING: {
|
|
2957
|
+
value: 0,
|
|
2958
|
+
enumerable: true
|
|
2959
|
+
},
|
|
2960
|
+
OPEN: {
|
|
2961
|
+
value: 1,
|
|
2962
|
+
enumerable: true
|
|
2963
|
+
},
|
|
2964
|
+
CLOSING: {
|
|
2965
|
+
value: 2,
|
|
2966
|
+
enumerable: true
|
|
2967
|
+
},
|
|
2968
|
+
CLOSED: {
|
|
2969
|
+
value: 3,
|
|
2970
|
+
enumerable: true
|
|
2971
|
+
}
|
|
2972
|
+
});
|
|
2973
|
+
Object.assign(WebSocket, {
|
|
2974
|
+
WebSocket,
|
|
2975
|
+
WebSocketServer,
|
|
2976
|
+
Server: WebSocketServer,
|
|
2977
|
+
createWebSocketStream
|
|
2978
|
+
});
|
|
2979
|
+
//#endregion
|
|
2980
|
+
exports.WebSocket = WebSocket;
|
|
2981
|
+
exports.WebSocketServer = WebSocketServer;
|
|
2982
|
+
exports.createWebSocketStream = createWebSocketStream;
|
|
2983
|
+
exports.default = WebSocket;
|
|
2984
|
+
exports.engineLimits = engineLimits;
|
|
2985
|
+
|
|
2986
|
+
module.exports = Object.assign(module.exports.default ?? module.exports, module.exports);
|