ventiws 1.0.0-alpha.13 → 1.0.0-alpha.15

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
@@ -12,7 +12,7 @@ observable behaviour rather than its source. ventiws is not affiliated with the
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-alpha.13`.** The surface may change between alpha
15
+ **ventiws is at `1.0.0-alpha.15`.** The surface may change between alpha
16
16
  releases. What "alpha" 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
@@ -434,6 +434,41 @@ function closeWebSocketServer(state, callback) {
434
434
  emitClose(state);
435
435
  });
436
436
  }
437
+ function normalizeParameters(parameters, isServer) {
438
+ const out = {};
439
+ for (const [key, values] of Object.entries(parameters)) {
440
+ if (values.length > 1) return null;
441
+ const parsed = parseParameter(key, values[0] ?? "", isServer);
442
+ if (parsed === null) return null;
443
+ out[key] = parsed;
444
+ }
445
+ return {
446
+ server_no_context_takeover: out.server_no_context_takeover === true,
447
+ client_no_context_takeover: out.client_no_context_takeover === true,
448
+ server_max_window_bits: numberOf(out.server_max_window_bits),
449
+ client_max_window_bits: windowOf(out.client_max_window_bits)
450
+ };
451
+ }
452
+ function parseParameter(key, value, isServer) {
453
+ if (key === "server_no_context_takeover" || key === "client_no_context_takeover") return value === "" ? true : null;
454
+ if (key === "client_max_window_bits") {
455
+ if (value === "") return isServer ? true : null;
456
+ return windowSize(value);
457
+ }
458
+ if (key === "server_max_window_bits") return value === "" ? null : windowSize(value);
459
+ return null;
460
+ }
461
+ function windowSize(value) {
462
+ const parsed = Number(value);
463
+ if (!Number.isInteger(parsed) || parsed < 8 || parsed > 15) return null;
464
+ return parsed;
465
+ }
466
+ function windowOf(value) {
467
+ return typeof value === "number" || value === true ? value : void 0;
468
+ }
469
+ function numberOf(value) {
470
+ return typeof value === "number" ? value : void 0;
471
+ }
437
472
  //#endregion
438
473
  //#region src/compat/options/shared.ts
439
474
  const DEFAULT_MAX_PAYLOAD = 104857600;
@@ -463,6 +498,11 @@ function normalizeProtocols$1(protocols) {
463
498
  function parseProtocolHeader(header) {
464
499
  return normalizeProtocols$1(header.split(",").map((protocol) => protocol.trim()));
465
500
  }
501
+ function windowBitsOf(name, raw) {
502
+ if (raw === void 0 || raw === false) return raw;
503
+ if (typeof raw === "number" && Number.isInteger(raw) && raw >= 8 && raw <= 15) return raw;
504
+ return invalidOption(`The ${name} option must be an integer in [8, 15] (received ${String(raw)})`, RangeError);
505
+ }
466
506
  function normalizePerMessageDeflate(value, fallback) {
467
507
  if (value !== void 0 && !value) return false;
468
508
  const resolved = value ?? fallback;
@@ -471,8 +511,8 @@ function normalizePerMessageDeflate(value, fallback) {
471
511
  return {
472
512
  serverNoContextTakeover: options.serverNoContextTakeover,
473
513
  clientNoContextTakeover: options.clientNoContextTakeover,
474
- serverMaxWindowBits: options.serverMaxWindowBits,
475
- clientMaxWindowBits: options.clientMaxWindowBits,
514
+ serverMaxWindowBits: windowBitsOf("serverMaxWindowBits", options.serverMaxWindowBits),
515
+ clientMaxWindowBits: windowBitsOf("clientMaxWindowBits", options.clientMaxWindowBits),
476
516
  threshold: options.threshold ?? 1024,
477
517
  concurrencyLimit: options.concurrencyLimit ?? 10,
478
518
  zlibDeflateOptions: options.zlibDeflateOptions,
@@ -850,12 +890,6 @@ function socketBufferedAmount(server, connection) {
850
890
  return callNative(() => addon.socketBufferedAmount(server, connection));
851
891
  }
852
892
  //#endregion
853
- //#region src/compat/socket/queued.ts
854
- function queuedBytes(state) {
855
- if (state.transport === null) return 0;
856
- return state.transport.writableLength;
857
- }
858
- //#endregion
859
893
  //#region src/compat/socket/payload.ts
860
894
  const READY_STATE_NAMES = [
861
895
  "CONNECTING",
@@ -895,6 +929,10 @@ function notOpenError(readyState) {
895
929
  function statusError(status) {
896
930
  return createStatusError(status, `ventiws: socket operation failed with status "${status}"`);
897
931
  }
932
+ function queuedBytes(state) {
933
+ if (state.transport === null) return 0;
934
+ return state.transport.writableLength;
935
+ }
898
936
  function bufferedAmountOf(state) {
899
937
  if (state.attachment === null) return queuedBytes(state);
900
938
  return socketBufferedAmount(state.attachment.server, state.attachment.connection);
@@ -1005,8 +1043,8 @@ function closeFailure(status) {
1005
1043
  case "payload-too-large":
1006
1044
  case "invalid-close-code":
1007
1045
  case "invalid-close-reason":
1008
- case "protocol-error":
1009
- case "policy-violation": return statusError(status);
1046
+ case "protocol-error": return statusError(status);
1047
+ case "policy-violation": return createError("ERR_POLICY_VIOLATION", "ventiws: app-initiated close is not implemented on the engine route");
1010
1048
  }
1011
1049
  }
1012
1050
  //#endregion
@@ -1082,6 +1120,7 @@ function finishConnection(state, code, reason) {
1082
1120
  state.readyState = 3;
1083
1121
  state.closeCode = code;
1084
1122
  state.closeReason = reason;
1123
+ state.pendingInput = [];
1085
1124
  emitEvent(state, "close", code, reason);
1086
1125
  }
1087
1126
  function failConnection(state, error) {
@@ -1132,6 +1171,11 @@ function closeConnection(state, code, reason) {
1132
1171
  finishConnection(state, CLOSE_ABNORMAL$1, EMPTY$1);
1133
1172
  return;
1134
1173
  }
1174
+ if (status === "policy-violation") {
1175
+ state.readyState = 1;
1176
+ reportWithoutClosing(state, closeFailure(status));
1177
+ return;
1178
+ }
1135
1179
  failConnection(state, closeFailure(status));
1136
1180
  }
1137
1181
  function closeUnattached(state) {
@@ -1532,59 +1576,16 @@ const NO_CONTEXT_TAKEOVER = {
1532
1576
  client_no_context_takeover: ""
1533
1577
  };
1534
1578
  //#endregion
1535
- //#region src/compat/extensions/params.ts
1536
- const MIN_WINDOW_BITS = 8;
1537
- const MAX_WINDOW_BITS = 15;
1538
- function normalizeParameters(parameters, isServer) {
1539
- const out = {};
1540
- for (const [key, values] of Object.entries(parameters)) {
1541
- if (values.length > 1) return null;
1542
- const parsed = parseParameter(key, values[0] ?? "", isServer);
1543
- if (parsed === null) return null;
1544
- out[key] = parsed;
1545
- }
1546
- return {
1547
- server_no_context_takeover: out.server_no_context_takeover === true,
1548
- client_no_context_takeover: out.client_no_context_takeover === true,
1549
- server_max_window_bits: numberOf(out.server_max_window_bits),
1550
- client_max_window_bits: windowOf(out.client_max_window_bits)
1551
- };
1552
- }
1553
- function parseParameter(key, value, isServer) {
1554
- if (key === "server_no_context_takeover" || key === "client_no_context_takeover") return value === "" ? true : null;
1555
- if (key === "client_max_window_bits") {
1556
- if (value === "") return isServer ? true : null;
1557
- return windowSize(value);
1558
- }
1559
- if (key === "server_max_window_bits") return value === "" ? null : windowSize(value);
1560
- return null;
1561
- }
1562
- function windowSize(value) {
1563
- const parsed = Number(value);
1564
- if (!Number.isInteger(parsed) || parsed < MIN_WINDOW_BITS || parsed > MAX_WINDOW_BITS) return null;
1565
- return parsed;
1566
- }
1567
- function windowOf(value) {
1568
- return typeof value === "number" || value === true ? value : void 0;
1569
- }
1570
- function numberOf(value) {
1571
- return typeof value === "number" ? value : void 0;
1572
- }
1573
- //#endregion
1574
- //#region src/compat/extensions/window.ts
1575
- function narrowed(parameters, name, option) {
1576
- if (typeof option !== "number" || option >= 15) return;
1577
- parameters[name] = String(option);
1578
- }
1579
- //#endregion
1580
1579
  //#region src/compat/extensions/offer-window.ts
1581
1580
  function serverWindowUsable(options, offer) {
1581
+ const wanted = options.serverMaxWindowBits;
1582
+ if (typeof wanted === "number" && wanted < 15) return false;
1582
1583
  const asked = offer.server_max_window_bits;
1583
1584
  if (asked === void 0) return true;
1584
- if (options.serverMaxWindowBits === false) return false;
1585
- if (typeof options.serverMaxWindowBits === "number") {
1586
- if (options.serverMaxWindowBits > asked) return false;
1587
- return options.serverMaxWindowBits >= 15;
1585
+ if (wanted === false) return false;
1586
+ if (typeof wanted === "number") {
1587
+ if (wanted > asked) return false;
1588
+ return wanted >= 15;
1588
1589
  }
1589
1590
  return asked >= 15;
1590
1591
  }
@@ -1592,7 +1593,7 @@ function serverWindowUsable(options, offer) {
1592
1593
  //#region src/compat/extensions/decline.ts
1593
1594
  function declines(options, offer) {
1594
1595
  if (options.serverNoContextTakeover === false && offer.server_no_context_takeover) return true;
1595
- if (offer.server_max_window_bits !== void 0 && !serverWindowUsable(options, offer)) return true;
1596
+ if (!serverWindowUsable(options, offer)) return true;
1596
1597
  if (offer.client_max_window_bits === true && options.clientMaxWindowBits === false) return true;
1597
1598
  if (typeof options.clientMaxWindowBits === "number") {
1598
1599
  if (offer.client_max_window_bits === void 0) return true;
@@ -1628,11 +1629,14 @@ function acceptAsClient(response, options) {
1628
1629
  if (typeof asked === "number" && asked < 15) return { refusal: "Server requested a window this implementation cannot use" };
1629
1630
  return { accepted: answer(options, normalized) };
1630
1631
  }
1632
+ function narrowClientWindow(parameters, option) {
1633
+ if (typeof option !== "number" || option >= 15) return;
1634
+ parameters.client_max_window_bits = String(option);
1635
+ }
1631
1636
  function answer(options, offer) {
1632
1637
  const parameters = { ...NO_CONTEXT_TAKEOVER };
1633
1638
  if (offer.server_no_context_takeover) parameters.server_no_context_takeover = "";
1634
- narrowed(parameters, "server_max_window_bits", options.serverMaxWindowBits);
1635
- narrowed(parameters, "client_max_window_bits", options.clientMaxWindowBits);
1639
+ narrowClientWindow(parameters, options.clientMaxWindowBits);
1636
1640
  return {
1637
1641
  name: PERMESSAGE_DEFLATE,
1638
1642
  parameters,
@@ -1843,7 +1847,7 @@ function completeUpgrade(state, request, socket, head, key, protocols, callback)
1843
1847
  if (extensions) acceptedState.extensions = extensions.header;
1844
1848
  acceptedState.threshold = thresholdOf(state.normalizedOptions.perMessageDeflate);
1845
1849
  }
1846
- attachSocket(accepted, socket);
1850
+ attachSocket(accepted, socket, void 0, head);
1847
1851
  if (state.normalizedOptions.clientTracking) trackClient(state, accepted);
1848
1852
  callback(accepted, request);
1849
1853
  }
@@ -2731,6 +2735,69 @@ function acceptExtension(response, options) {
2731
2735
  return acceptAsClient(offered[0], options) ?? { accepted: null };
2732
2736
  }
2733
2737
  //#endregion
2738
+ //#region src/compat/client/hop.ts
2739
+ function finish(attempt, request) {
2740
+ const callback = attempt.options.finishRequest;
2741
+ if (callback === void 0) {
2742
+ request.end();
2743
+ return;
2744
+ }
2745
+ const target = attempt.state.target;
2746
+ if (target === void 0) {
2747
+ request.end();
2748
+ return;
2749
+ }
2750
+ callback(request, target);
2751
+ }
2752
+ function create(attempt, handlers) {
2753
+ const { options, handshake } = attempt;
2754
+ if (attempt.state.readyState === 3) return null;
2755
+ const request = issue(attempt, handshake.request);
2756
+ attempt.request = request;
2757
+ attempt.state.cancelHandshake = () => {
2758
+ request.abort();
2759
+ };
2760
+ request.on("error", (error) => {
2761
+ if (attempt.request !== request) return;
2762
+ attempt.request = null;
2763
+ failConnection(attempt.state, error);
2764
+ });
2765
+ if (options.handshakeTimeout) request.setTimeout(options.handshakeTimeout, () => {
2766
+ refuse(attempt, request, "Opening handshake has timed out");
2767
+ });
2768
+ request.on("upgrade", (response, socket, head) => {
2769
+ if (attempt.request !== request) return;
2770
+ attempt.request = null;
2771
+ handlers.upgrade(attempt, response, socket, head);
2772
+ });
2773
+ request.on("response", (response) => {
2774
+ if (attempt.request !== request) return;
2775
+ attempt.request = null;
2776
+ handlers.response(attempt, request, response);
2777
+ });
2778
+ return request;
2779
+ }
2780
+ function issue(attempt, options) {
2781
+ return attempt.address.secure ? (0, node_https.request)(options) : (0, node_http.request)(options);
2782
+ }
2783
+ function refuse(attempt, request, message) {
2784
+ request?.abort();
2785
+ abort(attempt, createError("ERR_PROTOCOL", message));
2786
+ }
2787
+ function abort(attempt, error) {
2788
+ const { state } = attempt;
2789
+ if (state.readyState === 3) return;
2790
+ state.cancelHandshake = null;
2791
+ attempt.request?.destroy();
2792
+ attempt.request = null;
2793
+ if (attempt.transport !== null) discard(attempt.transport);
2794
+ failConnection(state, error);
2795
+ }
2796
+ function discard(socket) {
2797
+ socket.on("error", () => void 0);
2798
+ socket.destroy();
2799
+ }
2800
+ //#endregion
2734
2801
  //#region src/compat/client/open.ts
2735
2802
  function onUpgrade(attempt, response, socket, head) {
2736
2803
  const { state } = attempt;
@@ -2777,16 +2844,6 @@ function reportUnexpected(attempt, request, response) {
2777
2844
  abort(attempt, createError("ERR_PROTOCOL", `Unexpected server response: ${response.statusCode ?? 0}`));
2778
2845
  }
2779
2846
  //#endregion
2780
- //#region src/compat/client/credentials.ts
2781
- function stripCredentials(headers) {
2782
- if (typeof headers !== "object" || headers === null) return;
2783
- const map = headers;
2784
- for (const name of Object.keys(map)) {
2785
- const lower = name.toLowerCase();
2786
- if (lower === "authorization" || lower === "cookie") delete map[name];
2787
- }
2788
- }
2789
- //#endregion
2790
2847
  //#region src/compat/client/redirect.ts
2791
2848
  function isRedirect(status, location) {
2792
2849
  return location !== void 0 && status >= 300 && status < 400;
@@ -2799,6 +2856,14 @@ function onResponse(attempt, request, response) {
2799
2856
  }
2800
2857
  reportUnexpected(attempt, request, response);
2801
2858
  }
2859
+ function stripCredentials(headers) {
2860
+ if (typeof headers !== "object" || headers === null) return;
2861
+ const map = headers;
2862
+ for (const name of Object.keys(map)) {
2863
+ const lower = name.toLowerCase();
2864
+ if (lower === "authorization" || lower === "cookie") delete map[name];
2865
+ }
2866
+ }
2802
2867
  function follow(attempt, request, response, location) {
2803
2868
  if (attempt.redirects + 1 > attempt.options.maxRedirects) {
2804
2869
  request.abort();
@@ -2828,7 +2893,10 @@ function follow(attempt, request, response, location) {
2828
2893
  attempt.address = next;
2829
2894
  attempt.redirects += 1;
2830
2895
  attempt.handshake = buildRequest(next, attempt.options, attempt.requested, newKey(), attempt.auth);
2831
- const hop = create(attempt);
2896
+ const hop = create(attempt, {
2897
+ upgrade: onUpgrade,
2898
+ response: onResponse
2899
+ });
2832
2900
  if (hop === null) return;
2833
2901
  emitEvent(attempt.state, "redirect", next.url, hop);
2834
2902
  finish(attempt, hop);
@@ -2846,72 +2914,14 @@ function listenerCount(attempt) {
2846
2914
  //#endregion
2847
2915
  //#region src/compat/client/dial.ts
2848
2916
  function dial(attempt) {
2849
- const request = create(attempt);
2917
+ const request = create(attempt, {
2918
+ upgrade: onUpgrade,
2919
+ response: onResponse
2920
+ });
2850
2921
  if (request === null) return null;
2851
2922
  finish(attempt, request);
2852
2923
  return request;
2853
2924
  }
2854
- function finish(attempt, request) {
2855
- const callback = attempt.options.finishRequest;
2856
- if (callback === void 0) {
2857
- request.end();
2858
- return;
2859
- }
2860
- const target = attempt.state.target;
2861
- if (target === void 0) {
2862
- request.end();
2863
- return;
2864
- }
2865
- callback(request, target);
2866
- }
2867
- function create(attempt) {
2868
- const { options, handshake } = attempt;
2869
- if (attempt.state.readyState === 3) return null;
2870
- const request = issue(attempt, handshake.request);
2871
- attempt.request = request;
2872
- attempt.state.cancelHandshake = () => {
2873
- request.abort();
2874
- };
2875
- request.on("error", (error) => {
2876
- if (attempt.request !== request) return;
2877
- attempt.request = null;
2878
- failConnection(attempt.state, error);
2879
- });
2880
- if (options.handshakeTimeout) request.setTimeout(options.handshakeTimeout, () => {
2881
- refuse(attempt, request, "Opening handshake has timed out");
2882
- });
2883
- request.on("upgrade", (response, socket, head) => {
2884
- if (attempt.request !== request) return;
2885
- attempt.request = null;
2886
- onUpgrade(attempt, response, socket, head);
2887
- });
2888
- request.on("response", (response) => {
2889
- if (attempt.request !== request) return;
2890
- attempt.request = null;
2891
- onResponse(attempt, request, response);
2892
- });
2893
- return request;
2894
- }
2895
- function issue(attempt, options) {
2896
- return attempt.address.secure ? (0, node_https.request)(options) : (0, node_http.request)(options);
2897
- }
2898
- function refuse(attempt, request, message) {
2899
- request?.abort();
2900
- abort(attempt, createError("ERR_PROTOCOL", message));
2901
- }
2902
- function abort(attempt, error) {
2903
- const { state } = attempt;
2904
- if (state.readyState === 3) return;
2905
- state.cancelHandshake = null;
2906
- attempt.request?.destroy();
2907
- attempt.request = null;
2908
- if (attempt.transport !== null) discard(attempt.transport);
2909
- failConnection(state, error);
2910
- }
2911
- function discard(socket) {
2912
- socket.on("error", () => void 0);
2913
- socket.destroy();
2914
- }
2915
2925
  //#endregion
2916
2926
  //#region src/compat/client/connect.ts
2917
2927
  function connectSocket(address, protocols, options) {
package/dist/index.mjs CHANGED
@@ -430,6 +430,41 @@ function closeWebSocketServer(state, callback) {
430
430
  emitClose(state);
431
431
  });
432
432
  }
433
+ function normalizeParameters(parameters, isServer) {
434
+ const out = {};
435
+ for (const [key, values] of Object.entries(parameters)) {
436
+ if (values.length > 1) return null;
437
+ const parsed = parseParameter(key, values[0] ?? "", isServer);
438
+ if (parsed === null) return null;
439
+ out[key] = parsed;
440
+ }
441
+ return {
442
+ server_no_context_takeover: out.server_no_context_takeover === true,
443
+ client_no_context_takeover: out.client_no_context_takeover === true,
444
+ server_max_window_bits: numberOf(out.server_max_window_bits),
445
+ client_max_window_bits: windowOf(out.client_max_window_bits)
446
+ };
447
+ }
448
+ function parseParameter(key, value, isServer) {
449
+ if (key === "server_no_context_takeover" || key === "client_no_context_takeover") return value === "" ? true : null;
450
+ if (key === "client_max_window_bits") {
451
+ if (value === "") return isServer ? true : null;
452
+ return windowSize(value);
453
+ }
454
+ if (key === "server_max_window_bits") return value === "" ? null : windowSize(value);
455
+ return null;
456
+ }
457
+ function windowSize(value) {
458
+ const parsed = Number(value);
459
+ if (!Number.isInteger(parsed) || parsed < 8 || parsed > 15) return null;
460
+ return parsed;
461
+ }
462
+ function windowOf(value) {
463
+ return typeof value === "number" || value === true ? value : void 0;
464
+ }
465
+ function numberOf(value) {
466
+ return typeof value === "number" ? value : void 0;
467
+ }
433
468
  //#endregion
434
469
  //#region src/compat/options/shared.ts
435
470
  const DEFAULT_MAX_PAYLOAD = 104857600;
@@ -459,6 +494,11 @@ function normalizeProtocols$1(protocols) {
459
494
  function parseProtocolHeader(header) {
460
495
  return normalizeProtocols$1(header.split(",").map((protocol) => protocol.trim()));
461
496
  }
497
+ function windowBitsOf(name, raw) {
498
+ if (raw === void 0 || raw === false) return raw;
499
+ if (typeof raw === "number" && Number.isInteger(raw) && raw >= 8 && raw <= 15) return raw;
500
+ return invalidOption(`The ${name} option must be an integer in [8, 15] (received ${String(raw)})`, RangeError);
501
+ }
462
502
  function normalizePerMessageDeflate(value, fallback) {
463
503
  if (value !== void 0 && !value) return false;
464
504
  const resolved = value ?? fallback;
@@ -467,8 +507,8 @@ function normalizePerMessageDeflate(value, fallback) {
467
507
  return {
468
508
  serverNoContextTakeover: options.serverNoContextTakeover,
469
509
  clientNoContextTakeover: options.clientNoContextTakeover,
470
- serverMaxWindowBits: options.serverMaxWindowBits,
471
- clientMaxWindowBits: options.clientMaxWindowBits,
510
+ serverMaxWindowBits: windowBitsOf("serverMaxWindowBits", options.serverMaxWindowBits),
511
+ clientMaxWindowBits: windowBitsOf("clientMaxWindowBits", options.clientMaxWindowBits),
472
512
  threshold: options.threshold ?? 1024,
473
513
  concurrencyLimit: options.concurrencyLimit ?? 10,
474
514
  zlibDeflateOptions: options.zlibDeflateOptions,
@@ -846,12 +886,6 @@ function socketBufferedAmount(server, connection) {
846
886
  return callNative(() => addon.socketBufferedAmount(server, connection));
847
887
  }
848
888
  //#endregion
849
- //#region src/compat/socket/queued.ts
850
- function queuedBytes(state) {
851
- if (state.transport === null) return 0;
852
- return state.transport.writableLength;
853
- }
854
- //#endregion
855
889
  //#region src/compat/socket/payload.ts
856
890
  const READY_STATE_NAMES = [
857
891
  "CONNECTING",
@@ -891,6 +925,10 @@ function notOpenError(readyState) {
891
925
  function statusError(status) {
892
926
  return createStatusError(status, `ventiws: socket operation failed with status "${status}"`);
893
927
  }
928
+ function queuedBytes(state) {
929
+ if (state.transport === null) return 0;
930
+ return state.transport.writableLength;
931
+ }
894
932
  function bufferedAmountOf(state) {
895
933
  if (state.attachment === null) return queuedBytes(state);
896
934
  return socketBufferedAmount(state.attachment.server, state.attachment.connection);
@@ -1001,8 +1039,8 @@ function closeFailure(status) {
1001
1039
  case "payload-too-large":
1002
1040
  case "invalid-close-code":
1003
1041
  case "invalid-close-reason":
1004
- case "protocol-error":
1005
- case "policy-violation": return statusError(status);
1042
+ case "protocol-error": return statusError(status);
1043
+ case "policy-violation": return createError("ERR_POLICY_VIOLATION", "ventiws: app-initiated close is not implemented on the engine route");
1006
1044
  }
1007
1045
  }
1008
1046
  //#endregion
@@ -1078,6 +1116,7 @@ function finishConnection(state, code, reason) {
1078
1116
  state.readyState = 3;
1079
1117
  state.closeCode = code;
1080
1118
  state.closeReason = reason;
1119
+ state.pendingInput = [];
1081
1120
  emitEvent(state, "close", code, reason);
1082
1121
  }
1083
1122
  function failConnection(state, error) {
@@ -1128,6 +1167,11 @@ function closeConnection(state, code, reason) {
1128
1167
  finishConnection(state, CLOSE_ABNORMAL$1, EMPTY$1);
1129
1168
  return;
1130
1169
  }
1170
+ if (status === "policy-violation") {
1171
+ state.readyState = 1;
1172
+ reportWithoutClosing(state, closeFailure(status));
1173
+ return;
1174
+ }
1131
1175
  failConnection(state, closeFailure(status));
1132
1176
  }
1133
1177
  function closeUnattached(state) {
@@ -1528,59 +1572,16 @@ const NO_CONTEXT_TAKEOVER = {
1528
1572
  client_no_context_takeover: ""
1529
1573
  };
1530
1574
  //#endregion
1531
- //#region src/compat/extensions/params.ts
1532
- const MIN_WINDOW_BITS = 8;
1533
- const MAX_WINDOW_BITS = 15;
1534
- function normalizeParameters(parameters, isServer) {
1535
- const out = {};
1536
- for (const [key, values] of Object.entries(parameters)) {
1537
- if (values.length > 1) return null;
1538
- const parsed = parseParameter(key, values[0] ?? "", isServer);
1539
- if (parsed === null) return null;
1540
- out[key] = parsed;
1541
- }
1542
- return {
1543
- server_no_context_takeover: out.server_no_context_takeover === true,
1544
- client_no_context_takeover: out.client_no_context_takeover === true,
1545
- server_max_window_bits: numberOf(out.server_max_window_bits),
1546
- client_max_window_bits: windowOf(out.client_max_window_bits)
1547
- };
1548
- }
1549
- function parseParameter(key, value, isServer) {
1550
- if (key === "server_no_context_takeover" || key === "client_no_context_takeover") return value === "" ? true : null;
1551
- if (key === "client_max_window_bits") {
1552
- if (value === "") return isServer ? true : null;
1553
- return windowSize(value);
1554
- }
1555
- if (key === "server_max_window_bits") return value === "" ? null : windowSize(value);
1556
- return null;
1557
- }
1558
- function windowSize(value) {
1559
- const parsed = Number(value);
1560
- if (!Number.isInteger(parsed) || parsed < MIN_WINDOW_BITS || parsed > MAX_WINDOW_BITS) return null;
1561
- return parsed;
1562
- }
1563
- function windowOf(value) {
1564
- return typeof value === "number" || value === true ? value : void 0;
1565
- }
1566
- function numberOf(value) {
1567
- return typeof value === "number" ? value : void 0;
1568
- }
1569
- //#endregion
1570
- //#region src/compat/extensions/window.ts
1571
- function narrowed(parameters, name, option) {
1572
- if (typeof option !== "number" || option >= 15) return;
1573
- parameters[name] = String(option);
1574
- }
1575
- //#endregion
1576
1575
  //#region src/compat/extensions/offer-window.ts
1577
1576
  function serverWindowUsable(options, offer) {
1577
+ const wanted = options.serverMaxWindowBits;
1578
+ if (typeof wanted === "number" && wanted < 15) return false;
1578
1579
  const asked = offer.server_max_window_bits;
1579
1580
  if (asked === void 0) return true;
1580
- if (options.serverMaxWindowBits === false) return false;
1581
- if (typeof options.serverMaxWindowBits === "number") {
1582
- if (options.serverMaxWindowBits > asked) return false;
1583
- return options.serverMaxWindowBits >= 15;
1581
+ if (wanted === false) return false;
1582
+ if (typeof wanted === "number") {
1583
+ if (wanted > asked) return false;
1584
+ return wanted >= 15;
1584
1585
  }
1585
1586
  return asked >= 15;
1586
1587
  }
@@ -1588,7 +1589,7 @@ function serverWindowUsable(options, offer) {
1588
1589
  //#region src/compat/extensions/decline.ts
1589
1590
  function declines(options, offer) {
1590
1591
  if (options.serverNoContextTakeover === false && offer.server_no_context_takeover) return true;
1591
- if (offer.server_max_window_bits !== void 0 && !serverWindowUsable(options, offer)) return true;
1592
+ if (!serverWindowUsable(options, offer)) return true;
1592
1593
  if (offer.client_max_window_bits === true && options.clientMaxWindowBits === false) return true;
1593
1594
  if (typeof options.clientMaxWindowBits === "number") {
1594
1595
  if (offer.client_max_window_bits === void 0) return true;
@@ -1624,11 +1625,14 @@ function acceptAsClient(response, options) {
1624
1625
  if (typeof asked === "number" && asked < 15) return { refusal: "Server requested a window this implementation cannot use" };
1625
1626
  return { accepted: answer(options, normalized) };
1626
1627
  }
1628
+ function narrowClientWindow(parameters, option) {
1629
+ if (typeof option !== "number" || option >= 15) return;
1630
+ parameters.client_max_window_bits = String(option);
1631
+ }
1627
1632
  function answer(options, offer) {
1628
1633
  const parameters = { ...NO_CONTEXT_TAKEOVER };
1629
1634
  if (offer.server_no_context_takeover) parameters.server_no_context_takeover = "";
1630
- narrowed(parameters, "server_max_window_bits", options.serverMaxWindowBits);
1631
- narrowed(parameters, "client_max_window_bits", options.clientMaxWindowBits);
1635
+ narrowClientWindow(parameters, options.clientMaxWindowBits);
1632
1636
  return {
1633
1637
  name: PERMESSAGE_DEFLATE,
1634
1638
  parameters,
@@ -1839,7 +1843,7 @@ function completeUpgrade(state, request, socket, head, key, protocols, callback)
1839
1843
  if (extensions) acceptedState.extensions = extensions.header;
1840
1844
  acceptedState.threshold = thresholdOf(state.normalizedOptions.perMessageDeflate);
1841
1845
  }
1842
- attachSocket(accepted, socket);
1846
+ attachSocket(accepted, socket, void 0, head);
1843
1847
  if (state.normalizedOptions.clientTracking) trackClient(state, accepted);
1844
1848
  callback(accepted, request);
1845
1849
  }
@@ -2727,6 +2731,69 @@ function acceptExtension(response, options) {
2727
2731
  return acceptAsClient(offered[0], options) ?? { accepted: null };
2728
2732
  }
2729
2733
  //#endregion
2734
+ //#region src/compat/client/hop.ts
2735
+ function finish(attempt, request) {
2736
+ const callback = attempt.options.finishRequest;
2737
+ if (callback === void 0) {
2738
+ request.end();
2739
+ return;
2740
+ }
2741
+ const target = attempt.state.target;
2742
+ if (target === void 0) {
2743
+ request.end();
2744
+ return;
2745
+ }
2746
+ callback(request, target);
2747
+ }
2748
+ function create(attempt, handlers) {
2749
+ const { options, handshake } = attempt;
2750
+ if (attempt.state.readyState === 3) return null;
2751
+ const request = issue(attempt, handshake.request);
2752
+ attempt.request = request;
2753
+ attempt.state.cancelHandshake = () => {
2754
+ request.abort();
2755
+ };
2756
+ request.on("error", (error) => {
2757
+ if (attempt.request !== request) return;
2758
+ attempt.request = null;
2759
+ failConnection(attempt.state, error);
2760
+ });
2761
+ if (options.handshakeTimeout) request.setTimeout(options.handshakeTimeout, () => {
2762
+ refuse(attempt, request, "Opening handshake has timed out");
2763
+ });
2764
+ request.on("upgrade", (response, socket, head) => {
2765
+ if (attempt.request !== request) return;
2766
+ attempt.request = null;
2767
+ handlers.upgrade(attempt, response, socket, head);
2768
+ });
2769
+ request.on("response", (response) => {
2770
+ if (attempt.request !== request) return;
2771
+ attempt.request = null;
2772
+ handlers.response(attempt, request, response);
2773
+ });
2774
+ return request;
2775
+ }
2776
+ function issue(attempt, options) {
2777
+ return attempt.address.secure ? request$1(options) : request(options);
2778
+ }
2779
+ function refuse(attempt, request, message) {
2780
+ request?.abort();
2781
+ abort(attempt, createError("ERR_PROTOCOL", message));
2782
+ }
2783
+ function abort(attempt, error) {
2784
+ const { state } = attempt;
2785
+ if (state.readyState === 3) return;
2786
+ state.cancelHandshake = null;
2787
+ attempt.request?.destroy();
2788
+ attempt.request = null;
2789
+ if (attempt.transport !== null) discard(attempt.transport);
2790
+ failConnection(state, error);
2791
+ }
2792
+ function discard(socket) {
2793
+ socket.on("error", () => void 0);
2794
+ socket.destroy();
2795
+ }
2796
+ //#endregion
2730
2797
  //#region src/compat/client/open.ts
2731
2798
  function onUpgrade(attempt, response, socket, head) {
2732
2799
  const { state } = attempt;
@@ -2773,16 +2840,6 @@ function reportUnexpected(attempt, request, response) {
2773
2840
  abort(attempt, createError("ERR_PROTOCOL", `Unexpected server response: ${response.statusCode ?? 0}`));
2774
2841
  }
2775
2842
  //#endregion
2776
- //#region src/compat/client/credentials.ts
2777
- function stripCredentials(headers) {
2778
- if (typeof headers !== "object" || headers === null) return;
2779
- const map = headers;
2780
- for (const name of Object.keys(map)) {
2781
- const lower = name.toLowerCase();
2782
- if (lower === "authorization" || lower === "cookie") delete map[name];
2783
- }
2784
- }
2785
- //#endregion
2786
2843
  //#region src/compat/client/redirect.ts
2787
2844
  function isRedirect(status, location) {
2788
2845
  return location !== void 0 && status >= 300 && status < 400;
@@ -2795,6 +2852,14 @@ function onResponse(attempt, request, response) {
2795
2852
  }
2796
2853
  reportUnexpected(attempt, request, response);
2797
2854
  }
2855
+ function stripCredentials(headers) {
2856
+ if (typeof headers !== "object" || headers === null) return;
2857
+ const map = headers;
2858
+ for (const name of Object.keys(map)) {
2859
+ const lower = name.toLowerCase();
2860
+ if (lower === "authorization" || lower === "cookie") delete map[name];
2861
+ }
2862
+ }
2798
2863
  function follow(attempt, request, response, location) {
2799
2864
  if (attempt.redirects + 1 > attempt.options.maxRedirects) {
2800
2865
  request.abort();
@@ -2824,7 +2889,10 @@ function follow(attempt, request, response, location) {
2824
2889
  attempt.address = next;
2825
2890
  attempt.redirects += 1;
2826
2891
  attempt.handshake = buildRequest(next, attempt.options, attempt.requested, newKey(), attempt.auth);
2827
- const hop = create(attempt);
2892
+ const hop = create(attempt, {
2893
+ upgrade: onUpgrade,
2894
+ response: onResponse
2895
+ });
2828
2896
  if (hop === null) return;
2829
2897
  emitEvent(attempt.state, "redirect", next.url, hop);
2830
2898
  finish(attempt, hop);
@@ -2842,72 +2910,14 @@ function listenerCount(attempt) {
2842
2910
  //#endregion
2843
2911
  //#region src/compat/client/dial.ts
2844
2912
  function dial(attempt) {
2845
- const request = create(attempt);
2913
+ const request = create(attempt, {
2914
+ upgrade: onUpgrade,
2915
+ response: onResponse
2916
+ });
2846
2917
  if (request === null) return null;
2847
2918
  finish(attempt, request);
2848
2919
  return request;
2849
2920
  }
2850
- function finish(attempt, request) {
2851
- const callback = attempt.options.finishRequest;
2852
- if (callback === void 0) {
2853
- request.end();
2854
- return;
2855
- }
2856
- const target = attempt.state.target;
2857
- if (target === void 0) {
2858
- request.end();
2859
- return;
2860
- }
2861
- callback(request, target);
2862
- }
2863
- function create(attempt) {
2864
- const { options, handshake } = attempt;
2865
- if (attempt.state.readyState === 3) return null;
2866
- const request = issue(attempt, handshake.request);
2867
- attempt.request = request;
2868
- attempt.state.cancelHandshake = () => {
2869
- request.abort();
2870
- };
2871
- request.on("error", (error) => {
2872
- if (attempt.request !== request) return;
2873
- attempt.request = null;
2874
- failConnection(attempt.state, error);
2875
- });
2876
- if (options.handshakeTimeout) request.setTimeout(options.handshakeTimeout, () => {
2877
- refuse(attempt, request, "Opening handshake has timed out");
2878
- });
2879
- request.on("upgrade", (response, socket, head) => {
2880
- if (attempt.request !== request) return;
2881
- attempt.request = null;
2882
- onUpgrade(attempt, response, socket, head);
2883
- });
2884
- request.on("response", (response) => {
2885
- if (attempt.request !== request) return;
2886
- attempt.request = null;
2887
- onResponse(attempt, request, response);
2888
- });
2889
- return request;
2890
- }
2891
- function issue(attempt, options) {
2892
- return attempt.address.secure ? request$1(options) : request(options);
2893
- }
2894
- function refuse(attempt, request, message) {
2895
- request?.abort();
2896
- abort(attempt, createError("ERR_PROTOCOL", message));
2897
- }
2898
- function abort(attempt, error) {
2899
- const { state } = attempt;
2900
- if (state.readyState === 3) return;
2901
- state.cancelHandshake = null;
2902
- attempt.request?.destroy();
2903
- attempt.request = null;
2904
- if (attempt.transport !== null) discard(attempt.transport);
2905
- failConnection(state, error);
2906
- }
2907
- function discard(socket) {
2908
- socket.on("error", () => void 0);
2909
- socket.destroy();
2910
- }
2911
2921
  //#endregion
2912
2922
  //#region src/compat/client/connect.ts
2913
2923
  function connectSocket(address, protocols, options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ventiws",
3
- "version": "1.0.0-alpha.13",
3
+ "version": "1.0.0-alpha.15",
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": {
@@ -30,10 +30,10 @@
30
30
  "node": ">=22.12.0"
31
31
  },
32
32
  "optionalDependencies": {
33
- "@ventiws/binding-darwin-arm64": "1.0.0-alpha.13",
34
- "@ventiws/binding-darwin-x64": "1.0.0-alpha.13",
35
- "@ventiws/binding-linux-arm64-gnu": "1.0.0-alpha.13",
36
- "@ventiws/binding-linux-x64-gnu": "1.0.0-alpha.13",
37
- "@ventiws/binding-linux-x64-musl": "1.0.0-alpha.13"
33
+ "@ventiws/binding-darwin-arm64": "1.0.0-alpha.15",
34
+ "@ventiws/binding-darwin-x64": "1.0.0-alpha.15",
35
+ "@ventiws/binding-linux-arm64-gnu": "1.0.0-alpha.15",
36
+ "@ventiws/binding-linux-x64-gnu": "1.0.0-alpha.15",
37
+ "@ventiws/binding-linux-x64-musl": "1.0.0-alpha.15"
38
38
  }
39
39
  }