ventiws 1.0.0-rc.0 → 1.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.cjs +60 -23
- package/dist/index.d.cts +4 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.mjs +60 -23
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
A WebSocket implementation for Node.js with the API of
|
|
8
8
|
[`ws`](https://github.com/websockets/ws), delivered as a native addon whose framing
|
|
9
|
-
engine is written in Zig. It targets `ws` 8.
|
|
9
|
+
engine is written in Zig. It targets `ws` 8.22.0 and reproduces that release's
|
|
10
10
|
observable behaviour rather than its source. ventiws is not affiliated with the
|
|
11
11
|
`ws` project and vendors none of its code; the upstream API reference is vendored
|
|
12
12
|
as the pinned contract, credited in
|
|
13
13
|
[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
|
|
14
14
|
|
|
15
|
-
**ventiws is at `1.0.0-rc.
|
|
15
|
+
**ventiws is at `1.0.0-rc.2`.** The surface may change between beta
|
|
16
16
|
releases. What "beta" means here is bounded: the `ws` compatibility
|
|
17
17
|
contract is tracked row by row in
|
|
18
18
|
[COMPATIBILITY.md](COMPATIBILITY.md), and the four places ventiws knowingly
|
package/dist/index.cjs
CHANGED
|
@@ -475,7 +475,7 @@ function trackClient(state, socket) {
|
|
|
475
475
|
}
|
|
476
476
|
//#endregion
|
|
477
477
|
//#region package.json
|
|
478
|
-
var version = "1.0.0-rc.
|
|
478
|
+
var version = "1.0.0-rc.2";
|
|
479
479
|
//#endregion
|
|
480
480
|
//#region src/logging/lifecycle.ts
|
|
481
481
|
const CONTROL = /\p{Cc}/gu;
|
|
@@ -1091,8 +1091,8 @@ function queuedBytes(state) {
|
|
|
1091
1091
|
return state.transport.writableLength;
|
|
1092
1092
|
}
|
|
1093
1093
|
function bufferedAmountOf(state) {
|
|
1094
|
-
if (state.attachment === null) return queuedBytes(state);
|
|
1095
|
-
return socketBufferedAmount(state.attachment.server, state.attachment.connection);
|
|
1094
|
+
if (state.attachment === null) return queuedBytes(state) + state.bufferedExtra;
|
|
1095
|
+
return socketBufferedAmount(state.attachment.server, state.attachment.connection) + state.bufferedExtra;
|
|
1096
1096
|
}
|
|
1097
1097
|
function defer(callback, error) {
|
|
1098
1098
|
if (typeof callback !== "function") return;
|
|
@@ -1327,6 +1327,7 @@ function createSocketState() {
|
|
|
1327
1327
|
maxFragments: DEFAULT_MAX_FRAGMENTS,
|
|
1328
1328
|
cancelHandshake: null,
|
|
1329
1329
|
pendingSend: null,
|
|
1330
|
+
bufferedExtra: 0,
|
|
1330
1331
|
compressible: false,
|
|
1331
1332
|
threshold: 0,
|
|
1332
1333
|
autoPong: true,
|
|
@@ -1354,6 +1355,7 @@ function finishConnection(state, code, reason) {
|
|
|
1354
1355
|
state.closeCode = code;
|
|
1355
1356
|
state.closeReason = reason;
|
|
1356
1357
|
state.pendingInput = [];
|
|
1358
|
+
state.cancelHandshake = null;
|
|
1357
1359
|
logSocketClose(state, code, reason);
|
|
1358
1360
|
emitEvent(state, "close", code, reason);
|
|
1359
1361
|
}
|
|
@@ -1382,13 +1384,14 @@ function reportWithoutClosing(state, error) {
|
|
|
1382
1384
|
function closeConnection(state, code, reason) {
|
|
1383
1385
|
if (state.readyState === 3) return;
|
|
1384
1386
|
if (state.readyState === 0) {
|
|
1387
|
+
state.cancelHandshake?.();
|
|
1385
1388
|
failConnection(state, createError("ERR_INVALID_STATE", "WebSocket was closed before the connection was established"));
|
|
1386
1389
|
return;
|
|
1387
1390
|
}
|
|
1388
1391
|
if (state.readyState === 2) return;
|
|
1389
|
-
state.readyState = 2;
|
|
1390
1392
|
const closeCode = closeCodeOf(code);
|
|
1391
1393
|
const closeReason = toCloseReason(reason);
|
|
1394
|
+
state.readyState = 2;
|
|
1392
1395
|
if (state.codec !== null) {
|
|
1393
1396
|
closeFramed(state, closeCode, closeReason);
|
|
1394
1397
|
armCloseTimeout(state, state.closeTimeout);
|
|
@@ -1529,6 +1532,7 @@ function shapeBinary(binaryType, payload, ends) {
|
|
|
1529
1532
|
}
|
|
1530
1533
|
}
|
|
1531
1534
|
function slicedArrayBuffer(payload) {
|
|
1535
|
+
if (payload.length === payload.buffer.byteLength) return payload.buffer;
|
|
1532
1536
|
return payload.buffer.slice(payload.byteOffset, payload.byteOffset + payload.byteLength);
|
|
1533
1537
|
}
|
|
1534
1538
|
function sliceFragments(payload, ends) {
|
|
@@ -1536,7 +1540,7 @@ function sliceFragments(payload, ends) {
|
|
|
1536
1540
|
const pieces = [];
|
|
1537
1541
|
let start = 0;
|
|
1538
1542
|
for (const end of ends) {
|
|
1539
|
-
const at = Math.min(end, payload.length);
|
|
1543
|
+
const at = Math.max(start, Math.min(end, payload.length));
|
|
1540
1544
|
if (at > start) pieces.push(payload.subarray(start, at));
|
|
1541
1545
|
start = at;
|
|
1542
1546
|
}
|
|
@@ -1554,7 +1558,7 @@ function dispatch(state, handle, kind, code, payload, ends) {
|
|
|
1554
1558
|
emitBinary(state, payload, ends);
|
|
1555
1559
|
return;
|
|
1556
1560
|
case "ping":
|
|
1557
|
-
if (state.autoPong) writePong(state, payload);
|
|
1561
|
+
if (state.autoPong && state.readyState === 1) writePong(state, payload);
|
|
1558
1562
|
emitEvent(state, "ping", payload);
|
|
1559
1563
|
return;
|
|
1560
1564
|
case "pong":
|
|
@@ -1615,7 +1619,7 @@ function drainDeferred(state, handle, released, resume) {
|
|
|
1615
1619
|
return;
|
|
1616
1620
|
}
|
|
1617
1621
|
honoured = false;
|
|
1618
|
-
const ends = codecFragmentEnds(handle);
|
|
1622
|
+
const ends = state.binaryType === "fragments" ? codecFragmentEnds(handle) : null;
|
|
1619
1623
|
takeCodecEvent(handle);
|
|
1620
1624
|
if (event === null) continue;
|
|
1621
1625
|
dispatch(state, handle, event.kind, event.code, event.payload, ends);
|
|
@@ -1744,6 +1748,7 @@ function failTransport(state, error) {
|
|
|
1744
1748
|
function terminateConnection(state) {
|
|
1745
1749
|
if (state.readyState === 3) return;
|
|
1746
1750
|
if (state.readyState === 0) {
|
|
1751
|
+
state.cancelHandshake?.();
|
|
1747
1752
|
failConnection(state, createError("ERR_INVALID_STATE", "WebSocket was closed before the connection was established"));
|
|
1748
1753
|
return;
|
|
1749
1754
|
}
|
|
@@ -1847,7 +1852,6 @@ function serverWindowUsable(options, offer) {
|
|
|
1847
1852
|
function declines(options, offer) {
|
|
1848
1853
|
if (options.serverNoContextTakeover === false && offer.server_no_context_takeover) return true;
|
|
1849
1854
|
if (!serverWindowUsable(options, offer)) return true;
|
|
1850
|
-
if (offer.client_max_window_bits === true && options.clientMaxWindowBits === false) return true;
|
|
1851
1855
|
if (typeof options.clientMaxWindowBits === "number") {
|
|
1852
1856
|
if (offer.client_max_window_bits === void 0) return true;
|
|
1853
1857
|
const asked = offer.client_max_window_bits;
|
|
@@ -1872,6 +1876,7 @@ function acceptAsClient(response, options) {
|
|
|
1872
1876
|
if (options === false) return null;
|
|
1873
1877
|
const normalized = normalizeParameters(response.parameters, false);
|
|
1874
1878
|
if (normalized === null) return { refusal: "Invalid Sec-WebSocket-Extensions header" };
|
|
1879
|
+
if (normalized.server_no_context_takeover !== true) return { refusal: "Missing parameter \"server_no_context_takeover\"" };
|
|
1875
1880
|
if (normalized.client_no_context_takeover && options.clientNoContextTakeover === false) return { refusal: "Unexpected parameter \"client_no_context_takeover\"" };
|
|
1876
1881
|
const asked = normalized.client_max_window_bits;
|
|
1877
1882
|
if (asked === void 0) return { accepted: answer(options, normalized) };
|
|
@@ -2346,13 +2351,16 @@ function createWebSocketServer(socketClass, options, callback) {
|
|
|
2346
2351
|
//#endregion
|
|
2347
2352
|
//#region src/compat/client/protocols.ts
|
|
2348
2353
|
const TOKEN = /^[!#$%&'*+\-.^_`|~\dA-Za-z]+$/;
|
|
2354
|
+
function invalidProtocols() {
|
|
2355
|
+
return createError("ERR_INVALID_OPTION", "An invalid or duplicated subprotocol was specified", SyntaxError);
|
|
2356
|
+
}
|
|
2349
2357
|
function normalizeProtocols(protocols) {
|
|
2350
2358
|
if (protocols === void 0) return [];
|
|
2351
2359
|
const list = typeof protocols === "string" ? [protocols] : protocols;
|
|
2352
|
-
if (!Array.isArray(list)) throw
|
|
2360
|
+
if (!Array.isArray(list)) throw invalidProtocols();
|
|
2353
2361
|
const seen = /* @__PURE__ */ new Set();
|
|
2354
2362
|
for (const protocol of list) {
|
|
2355
|
-
if (typeof protocol !== "string" || !TOKEN.test(protocol) || seen.has(protocol)) throw
|
|
2363
|
+
if (typeof protocol !== "string" || !TOKEN.test(protocol) || seen.has(protocol)) throw invalidProtocols();
|
|
2356
2364
|
seen.add(protocol);
|
|
2357
2365
|
}
|
|
2358
2366
|
return [...list];
|
|
@@ -2598,11 +2606,13 @@ function isBlob(value) {
|
|
|
2598
2606
|
}
|
|
2599
2607
|
function sendBlob(state, blob, options, callback) {
|
|
2600
2608
|
if (state.readyState !== 1) {
|
|
2609
|
+
state.bufferedExtra += blob.size;
|
|
2601
2610
|
defer(callback, notOpenError(state.readyState));
|
|
2602
2611
|
return;
|
|
2603
2612
|
}
|
|
2604
2613
|
const reading = blob.arrayBuffer().then((buffer) => {
|
|
2605
2614
|
if (state.readyState !== 1) {
|
|
2615
|
+
state.bufferedExtra += buffer.byteLength;
|
|
2606
2616
|
defer(callback, notOpenError(state.readyState));
|
|
2607
2617
|
return;
|
|
2608
2618
|
}
|
|
@@ -2621,25 +2631,18 @@ function asOptions(options) {
|
|
|
2621
2631
|
return typeof options === "object" && options !== null ? { ...options } : {};
|
|
2622
2632
|
}
|
|
2623
2633
|
//#endregion
|
|
2624
|
-
//#region src/compat/socket/send.ts
|
|
2625
|
-
function sendData(state, data, options, callback) {
|
|
2626
|
-
if (state.readyState === 0) throw notOpenError(0);
|
|
2627
|
-
const pending = state.pendingSend;
|
|
2628
|
-
if (pending === null) {
|
|
2629
|
-
framePayload(state, data, options, callback);
|
|
2630
|
-
return;
|
|
2631
|
-
}
|
|
2632
|
-
const work = () => framePayload(state, data, options, callback);
|
|
2633
|
-
pending.then(work, work);
|
|
2634
|
-
}
|
|
2634
|
+
//#region src/compat/socket/send-frame.ts
|
|
2635
2635
|
function framePayload(state, data, options, callback) {
|
|
2636
2636
|
const failure = resolveCallback(options, callback);
|
|
2637
2637
|
if (isBlob(data)) {
|
|
2638
2638
|
sendBlob(state, data, options, failure);
|
|
2639
2639
|
return;
|
|
2640
2640
|
}
|
|
2641
|
-
|
|
2641
|
+
frameResolved(state, toPayload(data), options, failure);
|
|
2642
|
+
}
|
|
2643
|
+
function frameResolved(state, payload, options, failure) {
|
|
2642
2644
|
if (state.readyState !== 1) {
|
|
2645
|
+
state.bufferedExtra += payload.bytes.length;
|
|
2643
2646
|
defer(failure, notOpenError(state.readyState));
|
|
2644
2647
|
return;
|
|
2645
2648
|
}
|
|
@@ -2693,6 +2696,38 @@ function unhandledStatus(status) {
|
|
|
2693
2696
|
return createError("ERR_INVALID_STATE", `ventiws: the engine reported an unknown socket status "${String(status)}"`);
|
|
2694
2697
|
}
|
|
2695
2698
|
//#endregion
|
|
2699
|
+
//#region src/compat/socket/send.ts
|
|
2700
|
+
function sendData(state, data, options, callback) {
|
|
2701
|
+
if (state.readyState === 0) throw notOpenError(0);
|
|
2702
|
+
const pending = state.pendingSend;
|
|
2703
|
+
if (pending === null) {
|
|
2704
|
+
framePayload(state, data, options, callback);
|
|
2705
|
+
return;
|
|
2706
|
+
}
|
|
2707
|
+
queueSend(state, pending, data, options, callback);
|
|
2708
|
+
}
|
|
2709
|
+
function queueSend(state, pending, data, options, callback) {
|
|
2710
|
+
const failure = resolveCallback(options, callback);
|
|
2711
|
+
if (isBlob(data)) {
|
|
2712
|
+
settle(state, pending, failure, () => framePayload(state, data, options, callback));
|
|
2713
|
+
return;
|
|
2714
|
+
}
|
|
2715
|
+
const payload = toPayload(data);
|
|
2716
|
+
settle(state, pending, failure, () => frameResolved(state, payload, options, failure));
|
|
2717
|
+
}
|
|
2718
|
+
function settle(state, pending, failure, work) {
|
|
2719
|
+
const run = () => {
|
|
2720
|
+
try {
|
|
2721
|
+
work();
|
|
2722
|
+
} catch (error) {
|
|
2723
|
+
const reported = error instanceof Error ? error : createError("ERR_INVALID_STATE", String(error));
|
|
2724
|
+
if (typeof failure === "function") defer(failure, reported);
|
|
2725
|
+
else reportWithoutClosing(state, reported);
|
|
2726
|
+
}
|
|
2727
|
+
};
|
|
2728
|
+
pending.then(run, run);
|
|
2729
|
+
}
|
|
2730
|
+
//#endregion
|
|
2696
2731
|
//#region src/compat/socket/record.ts
|
|
2697
2732
|
const BINARY_TYPE_VALUES = [
|
|
2698
2733
|
"nodebuffer",
|
|
@@ -2782,6 +2817,7 @@ function normalizeClientOptions(options) {
|
|
|
2782
2817
|
skipUTF8Validation: source.skipUTF8Validation ?? false,
|
|
2783
2818
|
allowSynchronousEvents: source.allowSynchronousEvents ?? true,
|
|
2784
2819
|
autoPong: source.autoPong ?? true,
|
|
2820
|
+
protocols: source.protocols,
|
|
2785
2821
|
closeTimeout: closeTimeoutOf(source),
|
|
2786
2822
|
perMessageDeflate: normalizePerMessageDeflate(source.perMessageDeflate, true),
|
|
2787
2823
|
origin: source.origin,
|
|
@@ -3137,6 +3173,7 @@ function follow(attempt, request, response, location) {
|
|
|
3137
3173
|
}
|
|
3138
3174
|
if (next.authority !== attempt.address.authority && listenerCount(attempt) === 0) {
|
|
3139
3175
|
stripCredentials(attempt.handshake.request.headers);
|
|
3176
|
+
stripCredentials(attempt.options.headers);
|
|
3140
3177
|
attempt.auth = void 0;
|
|
3141
3178
|
}
|
|
3142
3179
|
request.abort();
|
|
@@ -3178,7 +3215,7 @@ function dial(attempt) {
|
|
|
3178
3215
|
function connectSocket(address, protocols, options) {
|
|
3179
3216
|
const normalized = normalizeClientOptions(options);
|
|
3180
3217
|
const parsed = parseAddress(address);
|
|
3181
|
-
const requested = normalizeProtocols(protocols);
|
|
3218
|
+
const requested = normalizeProtocols(protocols === void 0 ? normalized.protocols : protocols);
|
|
3182
3219
|
const state = createSocketState();
|
|
3183
3220
|
state.isServer = false;
|
|
3184
3221
|
state.url = parsed.url;
|
package/dist/index.d.cts
CHANGED
|
@@ -202,6 +202,8 @@ declare namespace WebSocket$1 {
|
|
|
202
202
|
allowSynchronousEvents?: boolean | undefined;
|
|
203
203
|
autoPong?: boolean | undefined;
|
|
204
204
|
maxPayload?: number | undefined;
|
|
205
|
+
maxBufferedChunks?: number | undefined;
|
|
206
|
+
maxFragments?: number | undefined;
|
|
205
207
|
skipUTF8Validation?: boolean | undefined;
|
|
206
208
|
createConnection?: typeof createConnection | undefined;
|
|
207
209
|
finishRequest?: FinishRequestCallback | undefined;
|
|
@@ -271,6 +273,8 @@ declare namespace WebSocket$1 {
|
|
|
271
273
|
clientTracking?: boolean | undefined;
|
|
272
274
|
perMessageDeflate?: boolean | PerMessageDeflateOptions | undefined;
|
|
273
275
|
maxPayload?: number | undefined;
|
|
276
|
+
maxBufferedChunks?: number | undefined;
|
|
277
|
+
maxFragments?: number | undefined;
|
|
274
278
|
skipUTF8Validation?: boolean | undefined;
|
|
275
279
|
WebSocket?: U | undefined;
|
|
276
280
|
}
|
package/dist/index.d.mts
CHANGED
|
@@ -203,6 +203,8 @@ declare namespace WebSocket$1 {
|
|
|
203
203
|
allowSynchronousEvents?: boolean | undefined;
|
|
204
204
|
autoPong?: boolean | undefined;
|
|
205
205
|
maxPayload?: number | undefined;
|
|
206
|
+
maxBufferedChunks?: number | undefined;
|
|
207
|
+
maxFragments?: number | undefined;
|
|
206
208
|
skipUTF8Validation?: boolean | undefined;
|
|
207
209
|
createConnection?: typeof createConnection | undefined;
|
|
208
210
|
finishRequest?: FinishRequestCallback | undefined;
|
|
@@ -272,6 +274,8 @@ declare namespace WebSocket$1 {
|
|
|
272
274
|
clientTracking?: boolean | undefined;
|
|
273
275
|
perMessageDeflate?: boolean | PerMessageDeflateOptions | undefined;
|
|
274
276
|
maxPayload?: number | undefined;
|
|
277
|
+
maxBufferedChunks?: number | undefined;
|
|
278
|
+
maxFragments?: number | undefined;
|
|
275
279
|
skipUTF8Validation?: boolean | undefined;
|
|
276
280
|
WebSocket?: U | undefined;
|
|
277
281
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -448,7 +448,7 @@ function trackClient(state, socket) {
|
|
|
448
448
|
}
|
|
449
449
|
//#endregion
|
|
450
450
|
//#region package.json
|
|
451
|
-
var version = "1.0.0-rc.
|
|
451
|
+
var version = "1.0.0-rc.2";
|
|
452
452
|
//#endregion
|
|
453
453
|
//#region src/logging/lifecycle.ts
|
|
454
454
|
const CONTROL = /\p{Cc}/gu;
|
|
@@ -1064,8 +1064,8 @@ function queuedBytes(state) {
|
|
|
1064
1064
|
return state.transport.writableLength;
|
|
1065
1065
|
}
|
|
1066
1066
|
function bufferedAmountOf(state) {
|
|
1067
|
-
if (state.attachment === null) return queuedBytes(state);
|
|
1068
|
-
return socketBufferedAmount(state.attachment.server, state.attachment.connection);
|
|
1067
|
+
if (state.attachment === null) return queuedBytes(state) + state.bufferedExtra;
|
|
1068
|
+
return socketBufferedAmount(state.attachment.server, state.attachment.connection) + state.bufferedExtra;
|
|
1069
1069
|
}
|
|
1070
1070
|
function defer(callback, error) {
|
|
1071
1071
|
if (typeof callback !== "function") return;
|
|
@@ -1300,6 +1300,7 @@ function createSocketState() {
|
|
|
1300
1300
|
maxFragments: DEFAULT_MAX_FRAGMENTS,
|
|
1301
1301
|
cancelHandshake: null,
|
|
1302
1302
|
pendingSend: null,
|
|
1303
|
+
bufferedExtra: 0,
|
|
1303
1304
|
compressible: false,
|
|
1304
1305
|
threshold: 0,
|
|
1305
1306
|
autoPong: true,
|
|
@@ -1327,6 +1328,7 @@ function finishConnection(state, code, reason) {
|
|
|
1327
1328
|
state.closeCode = code;
|
|
1328
1329
|
state.closeReason = reason;
|
|
1329
1330
|
state.pendingInput = [];
|
|
1331
|
+
state.cancelHandshake = null;
|
|
1330
1332
|
logSocketClose(state, code, reason);
|
|
1331
1333
|
emitEvent(state, "close", code, reason);
|
|
1332
1334
|
}
|
|
@@ -1355,13 +1357,14 @@ function reportWithoutClosing(state, error) {
|
|
|
1355
1357
|
function closeConnection(state, code, reason) {
|
|
1356
1358
|
if (state.readyState === 3) return;
|
|
1357
1359
|
if (state.readyState === 0) {
|
|
1360
|
+
state.cancelHandshake?.();
|
|
1358
1361
|
failConnection(state, createError("ERR_INVALID_STATE", "WebSocket was closed before the connection was established"));
|
|
1359
1362
|
return;
|
|
1360
1363
|
}
|
|
1361
1364
|
if (state.readyState === 2) return;
|
|
1362
|
-
state.readyState = 2;
|
|
1363
1365
|
const closeCode = closeCodeOf(code);
|
|
1364
1366
|
const closeReason = toCloseReason(reason);
|
|
1367
|
+
state.readyState = 2;
|
|
1365
1368
|
if (state.codec !== null) {
|
|
1366
1369
|
closeFramed(state, closeCode, closeReason);
|
|
1367
1370
|
armCloseTimeout(state, state.closeTimeout);
|
|
@@ -1502,6 +1505,7 @@ function shapeBinary(binaryType, payload, ends) {
|
|
|
1502
1505
|
}
|
|
1503
1506
|
}
|
|
1504
1507
|
function slicedArrayBuffer(payload) {
|
|
1508
|
+
if (payload.length === payload.buffer.byteLength) return payload.buffer;
|
|
1505
1509
|
return payload.buffer.slice(payload.byteOffset, payload.byteOffset + payload.byteLength);
|
|
1506
1510
|
}
|
|
1507
1511
|
function sliceFragments(payload, ends) {
|
|
@@ -1509,7 +1513,7 @@ function sliceFragments(payload, ends) {
|
|
|
1509
1513
|
const pieces = [];
|
|
1510
1514
|
let start = 0;
|
|
1511
1515
|
for (const end of ends) {
|
|
1512
|
-
const at = Math.min(end, payload.length);
|
|
1516
|
+
const at = Math.max(start, Math.min(end, payload.length));
|
|
1513
1517
|
if (at > start) pieces.push(payload.subarray(start, at));
|
|
1514
1518
|
start = at;
|
|
1515
1519
|
}
|
|
@@ -1527,7 +1531,7 @@ function dispatch(state, handle, kind, code, payload, ends) {
|
|
|
1527
1531
|
emitBinary(state, payload, ends);
|
|
1528
1532
|
return;
|
|
1529
1533
|
case "ping":
|
|
1530
|
-
if (state.autoPong) writePong(state, payload);
|
|
1534
|
+
if (state.autoPong && state.readyState === 1) writePong(state, payload);
|
|
1531
1535
|
emitEvent(state, "ping", payload);
|
|
1532
1536
|
return;
|
|
1533
1537
|
case "pong":
|
|
@@ -1588,7 +1592,7 @@ function drainDeferred(state, handle, released, resume) {
|
|
|
1588
1592
|
return;
|
|
1589
1593
|
}
|
|
1590
1594
|
honoured = false;
|
|
1591
|
-
const ends = codecFragmentEnds(handle);
|
|
1595
|
+
const ends = state.binaryType === "fragments" ? codecFragmentEnds(handle) : null;
|
|
1592
1596
|
takeCodecEvent(handle);
|
|
1593
1597
|
if (event === null) continue;
|
|
1594
1598
|
dispatch(state, handle, event.kind, event.code, event.payload, ends);
|
|
@@ -1717,6 +1721,7 @@ function failTransport(state, error) {
|
|
|
1717
1721
|
function terminateConnection(state) {
|
|
1718
1722
|
if (state.readyState === 3) return;
|
|
1719
1723
|
if (state.readyState === 0) {
|
|
1724
|
+
state.cancelHandshake?.();
|
|
1720
1725
|
failConnection(state, createError("ERR_INVALID_STATE", "WebSocket was closed before the connection was established"));
|
|
1721
1726
|
return;
|
|
1722
1727
|
}
|
|
@@ -1820,7 +1825,6 @@ function serverWindowUsable(options, offer) {
|
|
|
1820
1825
|
function declines(options, offer) {
|
|
1821
1826
|
if (options.serverNoContextTakeover === false && offer.server_no_context_takeover) return true;
|
|
1822
1827
|
if (!serverWindowUsable(options, offer)) return true;
|
|
1823
|
-
if (offer.client_max_window_bits === true && options.clientMaxWindowBits === false) return true;
|
|
1824
1828
|
if (typeof options.clientMaxWindowBits === "number") {
|
|
1825
1829
|
if (offer.client_max_window_bits === void 0) return true;
|
|
1826
1830
|
const asked = offer.client_max_window_bits;
|
|
@@ -1845,6 +1849,7 @@ function acceptAsClient(response, options) {
|
|
|
1845
1849
|
if (options === false) return null;
|
|
1846
1850
|
const normalized = normalizeParameters(response.parameters, false);
|
|
1847
1851
|
if (normalized === null) return { refusal: "Invalid Sec-WebSocket-Extensions header" };
|
|
1852
|
+
if (normalized.server_no_context_takeover !== true) return { refusal: "Missing parameter \"server_no_context_takeover\"" };
|
|
1848
1853
|
if (normalized.client_no_context_takeover && options.clientNoContextTakeover === false) return { refusal: "Unexpected parameter \"client_no_context_takeover\"" };
|
|
1849
1854
|
const asked = normalized.client_max_window_bits;
|
|
1850
1855
|
if (asked === void 0) return { accepted: answer(options, normalized) };
|
|
@@ -2319,13 +2324,16 @@ function createWebSocketServer(socketClass, options, callback) {
|
|
|
2319
2324
|
//#endregion
|
|
2320
2325
|
//#region src/compat/client/protocols.ts
|
|
2321
2326
|
const TOKEN = /^[!#$%&'*+\-.^_`|~\dA-Za-z]+$/;
|
|
2327
|
+
function invalidProtocols() {
|
|
2328
|
+
return createError("ERR_INVALID_OPTION", "An invalid or duplicated subprotocol was specified", SyntaxError);
|
|
2329
|
+
}
|
|
2322
2330
|
function normalizeProtocols(protocols) {
|
|
2323
2331
|
if (protocols === void 0) return [];
|
|
2324
2332
|
const list = typeof protocols === "string" ? [protocols] : protocols;
|
|
2325
|
-
if (!Array.isArray(list)) throw
|
|
2333
|
+
if (!Array.isArray(list)) throw invalidProtocols();
|
|
2326
2334
|
const seen = /* @__PURE__ */ new Set();
|
|
2327
2335
|
for (const protocol of list) {
|
|
2328
|
-
if (typeof protocol !== "string" || !TOKEN.test(protocol) || seen.has(protocol)) throw
|
|
2336
|
+
if (typeof protocol !== "string" || !TOKEN.test(protocol) || seen.has(protocol)) throw invalidProtocols();
|
|
2329
2337
|
seen.add(protocol);
|
|
2330
2338
|
}
|
|
2331
2339
|
return [...list];
|
|
@@ -2571,11 +2579,13 @@ function isBlob(value) {
|
|
|
2571
2579
|
}
|
|
2572
2580
|
function sendBlob(state, blob, options, callback) {
|
|
2573
2581
|
if (state.readyState !== 1) {
|
|
2582
|
+
state.bufferedExtra += blob.size;
|
|
2574
2583
|
defer(callback, notOpenError(state.readyState));
|
|
2575
2584
|
return;
|
|
2576
2585
|
}
|
|
2577
2586
|
const reading = blob.arrayBuffer().then((buffer) => {
|
|
2578
2587
|
if (state.readyState !== 1) {
|
|
2588
|
+
state.bufferedExtra += buffer.byteLength;
|
|
2579
2589
|
defer(callback, notOpenError(state.readyState));
|
|
2580
2590
|
return;
|
|
2581
2591
|
}
|
|
@@ -2594,25 +2604,18 @@ function asOptions(options) {
|
|
|
2594
2604
|
return typeof options === "object" && options !== null ? { ...options } : {};
|
|
2595
2605
|
}
|
|
2596
2606
|
//#endregion
|
|
2597
|
-
//#region src/compat/socket/send.ts
|
|
2598
|
-
function sendData(state, data, options, callback) {
|
|
2599
|
-
if (state.readyState === 0) throw notOpenError(0);
|
|
2600
|
-
const pending = state.pendingSend;
|
|
2601
|
-
if (pending === null) {
|
|
2602
|
-
framePayload(state, data, options, callback);
|
|
2603
|
-
return;
|
|
2604
|
-
}
|
|
2605
|
-
const work = () => framePayload(state, data, options, callback);
|
|
2606
|
-
pending.then(work, work);
|
|
2607
|
-
}
|
|
2607
|
+
//#region src/compat/socket/send-frame.ts
|
|
2608
2608
|
function framePayload(state, data, options, callback) {
|
|
2609
2609
|
const failure = resolveCallback(options, callback);
|
|
2610
2610
|
if (isBlob(data)) {
|
|
2611
2611
|
sendBlob(state, data, options, failure);
|
|
2612
2612
|
return;
|
|
2613
2613
|
}
|
|
2614
|
-
|
|
2614
|
+
frameResolved(state, toPayload(data), options, failure);
|
|
2615
|
+
}
|
|
2616
|
+
function frameResolved(state, payload, options, failure) {
|
|
2615
2617
|
if (state.readyState !== 1) {
|
|
2618
|
+
state.bufferedExtra += payload.bytes.length;
|
|
2616
2619
|
defer(failure, notOpenError(state.readyState));
|
|
2617
2620
|
return;
|
|
2618
2621
|
}
|
|
@@ -2666,6 +2669,38 @@ function unhandledStatus(status) {
|
|
|
2666
2669
|
return createError("ERR_INVALID_STATE", `ventiws: the engine reported an unknown socket status "${String(status)}"`);
|
|
2667
2670
|
}
|
|
2668
2671
|
//#endregion
|
|
2672
|
+
//#region src/compat/socket/send.ts
|
|
2673
|
+
function sendData(state, data, options, callback) {
|
|
2674
|
+
if (state.readyState === 0) throw notOpenError(0);
|
|
2675
|
+
const pending = state.pendingSend;
|
|
2676
|
+
if (pending === null) {
|
|
2677
|
+
framePayload(state, data, options, callback);
|
|
2678
|
+
return;
|
|
2679
|
+
}
|
|
2680
|
+
queueSend(state, pending, data, options, callback);
|
|
2681
|
+
}
|
|
2682
|
+
function queueSend(state, pending, data, options, callback) {
|
|
2683
|
+
const failure = resolveCallback(options, callback);
|
|
2684
|
+
if (isBlob(data)) {
|
|
2685
|
+
settle(state, pending, failure, () => framePayload(state, data, options, callback));
|
|
2686
|
+
return;
|
|
2687
|
+
}
|
|
2688
|
+
const payload = toPayload(data);
|
|
2689
|
+
settle(state, pending, failure, () => frameResolved(state, payload, options, failure));
|
|
2690
|
+
}
|
|
2691
|
+
function settle(state, pending, failure, work) {
|
|
2692
|
+
const run = () => {
|
|
2693
|
+
try {
|
|
2694
|
+
work();
|
|
2695
|
+
} catch (error) {
|
|
2696
|
+
const reported = error instanceof Error ? error : createError("ERR_INVALID_STATE", String(error));
|
|
2697
|
+
if (typeof failure === "function") defer(failure, reported);
|
|
2698
|
+
else reportWithoutClosing(state, reported);
|
|
2699
|
+
}
|
|
2700
|
+
};
|
|
2701
|
+
pending.then(run, run);
|
|
2702
|
+
}
|
|
2703
|
+
//#endregion
|
|
2669
2704
|
//#region src/compat/socket/record.ts
|
|
2670
2705
|
const BINARY_TYPE_VALUES = [
|
|
2671
2706
|
"nodebuffer",
|
|
@@ -2755,6 +2790,7 @@ function normalizeClientOptions(options) {
|
|
|
2755
2790
|
skipUTF8Validation: source.skipUTF8Validation ?? false,
|
|
2756
2791
|
allowSynchronousEvents: source.allowSynchronousEvents ?? true,
|
|
2757
2792
|
autoPong: source.autoPong ?? true,
|
|
2793
|
+
protocols: source.protocols,
|
|
2758
2794
|
closeTimeout: closeTimeoutOf(source),
|
|
2759
2795
|
perMessageDeflate: normalizePerMessageDeflate(source.perMessageDeflate, true),
|
|
2760
2796
|
origin: source.origin,
|
|
@@ -3110,6 +3146,7 @@ function follow(attempt, request, response, location) {
|
|
|
3110
3146
|
}
|
|
3111
3147
|
if (next.authority !== attempt.address.authority && listenerCount(attempt) === 0) {
|
|
3112
3148
|
stripCredentials(attempt.handshake.request.headers);
|
|
3149
|
+
stripCredentials(attempt.options.headers);
|
|
3113
3150
|
attempt.auth = void 0;
|
|
3114
3151
|
}
|
|
3115
3152
|
request.abort();
|
|
@@ -3151,7 +3188,7 @@ function dial(attempt) {
|
|
|
3151
3188
|
function connectSocket(address, protocols, options) {
|
|
3152
3189
|
const normalized = normalizeClientOptions(options);
|
|
3153
3190
|
const parsed = parseAddress(address);
|
|
3154
|
-
const requested = normalizeProtocols(protocols);
|
|
3191
|
+
const requested = normalizeProtocols(protocols === void 0 ? normalized.protocols : protocols);
|
|
3155
3192
|
const state = createSocketState();
|
|
3156
3193
|
state.isServer = false;
|
|
3157
3194
|
state.url = parsed.url;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ventiws",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.2",
|
|
4
4
|
"description": "A drop-in replacement for ws for Node.js, built on the first-party µWebZockets Zig engine via napi-zig.",
|
|
5
5
|
"homepage": "https://github.com/farbenbuilds/ventiws#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -34,10 +34,10 @@
|
|
|
34
34
|
"node": ">=22.12.0"
|
|
35
35
|
},
|
|
36
36
|
"optionalDependencies": {
|
|
37
|
-
"@ventiws/binding-darwin-arm64": "1.0.0-rc.
|
|
38
|
-
"@ventiws/binding-darwin-x64": "1.0.0-rc.
|
|
39
|
-
"@ventiws/binding-linux-arm64-gnu": "1.0.0-rc.
|
|
40
|
-
"@ventiws/binding-linux-x64-gnu": "1.0.0-rc.
|
|
41
|
-
"@ventiws/binding-linux-x64-musl": "1.0.0-rc.
|
|
37
|
+
"@ventiws/binding-darwin-arm64": "1.0.0-rc.2",
|
|
38
|
+
"@ventiws/binding-darwin-x64": "1.0.0-rc.2",
|
|
39
|
+
"@ventiws/binding-linux-arm64-gnu": "1.0.0-rc.2",
|
|
40
|
+
"@ventiws/binding-linux-x64-gnu": "1.0.0-rc.2",
|
|
41
|
+
"@ventiws/binding-linux-x64-musl": "1.0.0-rc.2"
|
|
42
42
|
}
|
|
43
43
|
}
|