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 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.21.3 and reproduces that release's
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.0`.** The surface may change between beta
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.0";
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 createError("ERR_INVALID_OPTION", "An invalid or duplicated subprotocol was specified", SyntaxError);
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 createError("ERR_INVALID_OPTION", "An invalid or duplicated subprotocol was specified", SyntaxError);
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
- const payload = toPayload(data);
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.0";
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 createError("ERR_INVALID_OPTION", "An invalid or duplicated subprotocol was specified", SyntaxError);
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 createError("ERR_INVALID_OPTION", "An invalid or duplicated subprotocol was specified", SyntaxError);
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
- const payload = toPayload(data);
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.0",
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.0",
38
- "@ventiws/binding-darwin-x64": "1.0.0-rc.0",
39
- "@ventiws/binding-linux-arm64-gnu": "1.0.0-rc.0",
40
- "@ventiws/binding-linux-x64-gnu": "1.0.0-rc.0",
41
- "@ventiws/binding-linux-x64-musl": "1.0.0-rc.0"
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
  }