kinetex 1.1.0 → 1.3.0

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.
Files changed (95) hide show
  1. package/README.md +919 -445
  2. package/dist/browser/kinetex.esm.js +18 -18
  3. package/dist/browser/kinetex.js +749 -295
  4. package/dist/browser/kinetex.min.js +18 -18
  5. package/dist/cjs/aws-sigv4.js +4 -1
  6. package/dist/cjs/cache.js +52 -14
  7. package/dist/cjs/circuit-breaker.js +24 -4
  8. package/dist/cjs/client.js +495 -131
  9. package/dist/cjs/cookie-parser.js +7 -4
  10. package/dist/cjs/cookie-store.js +16 -8
  11. package/dist/cjs/core.js +83 -35
  12. package/dist/cjs/dedup.js +9 -7
  13. package/dist/cjs/digest.js +26 -0
  14. package/dist/cjs/graphql.js +19 -4
  15. package/dist/cjs/headers.js +64 -8
  16. package/dist/cjs/interceptors.js +86 -33
  17. package/dist/cjs/logging.js +1 -1
  18. package/dist/cjs/pagination.js +14 -6
  19. package/dist/cjs/progress.js +129 -42
  20. package/dist/cjs/socks5.js +36 -21
  21. package/dist/cjs/sse.js +48 -11
  22. package/dist/cjs/utils.js +24 -12
  23. package/dist/cjs/worker.js +6 -6
  24. package/dist/cjs/ws.js +23 -7
  25. package/dist/esm/aws-sigv4.js +4 -1
  26. package/dist/esm/aws-sigv4.js.map +1 -1
  27. package/dist/esm/cache.js +52 -14
  28. package/dist/esm/cache.js.map +1 -1
  29. package/dist/esm/circuit-breaker.js +24 -4
  30. package/dist/esm/circuit-breaker.js.map +1 -1
  31. package/dist/esm/client.js +495 -131
  32. package/dist/esm/client.js.map +1 -1
  33. package/dist/esm/cookie-parser.js +7 -4
  34. package/dist/esm/cookie-parser.js.map +1 -1
  35. package/dist/esm/cookie-store.js +16 -8
  36. package/dist/esm/cookie-store.js.map +1 -1
  37. package/dist/esm/core.js +83 -35
  38. package/dist/esm/core.js.map +1 -1
  39. package/dist/esm/dedup.js +9 -7
  40. package/dist/esm/dedup.js.map +1 -1
  41. package/dist/esm/digest.js +26 -0
  42. package/dist/esm/digest.js.map +1 -1
  43. package/dist/esm/graphql.js +19 -4
  44. package/dist/esm/graphql.js.map +1 -1
  45. package/dist/esm/headers.js +64 -8
  46. package/dist/esm/headers.js.map +1 -1
  47. package/dist/esm/interceptors.js +86 -33
  48. package/dist/esm/interceptors.js.map +1 -1
  49. package/dist/esm/logging.js +1 -1
  50. package/dist/esm/pagination.js +14 -6
  51. package/dist/esm/pagination.js.map +1 -1
  52. package/dist/esm/progress.js +129 -42
  53. package/dist/esm/progress.js.map +1 -1
  54. package/dist/esm/socks5.js +36 -21
  55. package/dist/esm/socks5.js.map +1 -1
  56. package/dist/esm/sse.js +48 -11
  57. package/dist/esm/sse.js.map +1 -1
  58. package/dist/esm/types.js.map +1 -1
  59. package/dist/esm/utils.js +24 -12
  60. package/dist/esm/utils.js.map +1 -1
  61. package/dist/esm/worker.js +6 -6
  62. package/dist/esm/worker.js.map +1 -1
  63. package/dist/esm/ws.js +23 -7
  64. package/dist/esm/ws.js.map +1 -1
  65. package/dist/types/aws-sigv4.d.ts.map +1 -1
  66. package/dist/types/cache.d.ts +8 -1
  67. package/dist/types/cache.d.ts.map +1 -1
  68. package/dist/types/circuit-breaker.d.ts.map +1 -1
  69. package/dist/types/client.d.ts +29 -12
  70. package/dist/types/client.d.ts.map +1 -1
  71. package/dist/types/cookie-parser.d.ts.map +1 -1
  72. package/dist/types/cookie-store.d.ts.map +1 -1
  73. package/dist/types/core.d.ts +10 -0
  74. package/dist/types/core.d.ts.map +1 -1
  75. package/dist/types/dedup.d.ts +0 -7
  76. package/dist/types/dedup.d.ts.map +1 -1
  77. package/dist/types/digest.d.ts +14 -0
  78. package/dist/types/digest.d.ts.map +1 -1
  79. package/dist/types/graphql.d.ts.map +1 -1
  80. package/dist/types/headers.d.ts +25 -10
  81. package/dist/types/headers.d.ts.map +1 -1
  82. package/dist/types/interceptors.d.ts.map +1 -1
  83. package/dist/types/logging.d.ts +1 -1
  84. package/dist/types/pagination.d.ts.map +1 -1
  85. package/dist/types/progress.d.ts.map +1 -1
  86. package/dist/types/socks5.d.ts.map +1 -1
  87. package/dist/types/sse.d.ts.map +1 -1
  88. package/dist/types/types.d.ts +25 -2
  89. package/dist/types/types.d.ts.map +1 -1
  90. package/dist/types/utils.d.ts.map +1 -1
  91. package/dist/types/worker.d.ts +6 -6
  92. package/dist/types/worker.d.ts.map +1 -1
  93. package/dist/types/ws.d.ts +4 -0
  94. package/dist/types/ws.d.ts.map +1 -1
  95. package/package.json +4 -4
@@ -1,4 +1,4 @@
1
- /* kinetex v1.1.0 | MIT | https://github.com/kinetexjs/kinetex */
1
+ /* kinetex v1.3.0 | MIT | https://github.com/kinetexjs/kinetex */
2
2
  "use strict";
3
3
  var kinetex = (() => {
4
4
  var __create = Object.create;
@@ -283,8 +283,11 @@ var kinetex = (() => {
283
283
  function parseIPv4Host(host) {
284
284
  if (host.includes(":") || !/^[0-9a-fA-FxX.]+$/.test(host))
285
285
  return null;
286
- const parts = host.split(".");
287
- if (parts.length > 4)
286
+ const bare = host.endsWith(".") ? host.slice(0, -1) : host;
287
+ if (bare === "")
288
+ return null;
289
+ const parts = bare.split(".");
290
+ if (parts.length > 4 || parts.length < 1)
288
291
  return null;
289
292
  if (parts.length === 1) {
290
293
  const v = parseIPv4Component(parts[0]);
@@ -292,17 +295,21 @@ var kinetex = (() => {
292
295
  return null;
293
296
  return v >>> 0;
294
297
  }
295
- const nums = [];
296
- for (const p of parts) {
297
- const v = parseIPv4Component(p);
298
- const isLast = p === parts[parts.length - 1];
299
- if (v === null || v > (isLast && nums.length === 3 ? 255 : 255))
298
+ const octets = [];
299
+ for (let i = 0; i < parts.length - 1; i++) {
300
+ const v = parseIPv4Component(parts[i]);
301
+ if (v === null || v > 255)
300
302
  return null;
301
- nums.push(v);
303
+ octets.push(v);
304
+ }
305
+ const last = parseIPv4Component(parts[parts.length - 1]);
306
+ const lastCap = 2 ** (8 * (5 - parts.length));
307
+ if (last === null || last >= lastCap)
308
+ return null;
309
+ for (let shift = 8 * (4 - parts.length); shift >= 0; shift -= 8) {
310
+ octets.push(last >>> shift & 255);
302
311
  }
303
- while (nums.length < 4)
304
- nums.push(0);
305
- return (nums[0] << 24 | nums[1] << 16 | nums[2] << 8 | nums[3]) >>> 0;
312
+ return (octets[0] << 24 | octets[1] << 16 | octets[2] << 8 | octets[3]) >>> 0;
306
313
  }
307
314
  function parseIPv6Host(host) {
308
315
  if (!host.includes(":"))
@@ -895,6 +902,7 @@ var kinetex = (() => {
895
902
  // dist/esm/cache.js
896
903
  var cache_exports = {};
897
904
  __export(cache_exports, {
905
+ CREDENTIAL_HEADERS: () => CREDENTIAL_HEADERS,
898
906
  CloudflareKVAdapter: () => CloudflareKVAdapter,
899
907
  HTTPCache: () => HTTPCache,
900
908
  MemoryStorageAdapter: () => MemoryStorageAdapter,
@@ -986,18 +994,16 @@ var kinetex = (() => {
986
994
  return parts.join(";");
987
995
  }
988
996
  function normalizeCacheKeyPart(part) {
989
- const controlRanges = [
990
- String.fromCharCode(0) + "-" + String.fromCharCode(31),
991
- String.fromCharCode(127) + "-" + String.fromCharCode(159)
992
- ].join("");
993
- const controlRegex = new RegExp(`[${controlRanges}]`, "g");
994
- return part.replaceAll(controlRegex, "").replaceAll(/\s+/g, " ").trim();
997
+ return part.replaceAll(CONTROL_CHARS, "").replaceAll(/\s+/g, " ").trim();
995
998
  }
996
999
  async function getAuthFingerprint(headers) {
997
- const authHeaders = ["authorization", "cookie", "x-api-key", "x-auth-token"];
1000
+ const lowered = /* @__PURE__ */ new Map();
1001
+ for (const [name, value] of Object.entries(headers)) {
1002
+ lowered.set(name.toLowerCase(), value);
1003
+ }
998
1004
  const parts = [];
999
- for (const header of authHeaders) {
1000
- const value = headers[header.toLowerCase()];
1005
+ for (const header of CREDENTIAL_HEADERS) {
1006
+ const value = lowered.get(header);
1001
1007
  if (value)
1002
1008
  parts.push(`${header}=${value}`);
1003
1009
  }
@@ -1111,7 +1117,7 @@ var kinetex = (() => {
1111
1117
  function createTwoTierCache(l2, config) {
1112
1118
  return new HTTPCache({ ...config, storage: new TwoTierStorageAdapter(l2) });
1113
1119
  }
1114
- var MemoryStorageAdapter, WebStorageAdapter, CloudflareKVAdapter, TwoTierStorageAdapter, LRUNode, LRUTracker, HTTPCache;
1120
+ var MemoryStorageAdapter, WebStorageAdapter, CloudflareKVAdapter, TwoTierStorageAdapter, CONTROL_CHARS, CREDENTIAL_HEADERS, LRUNode, LRUTracker, HTTPCache;
1115
1121
  var init_cache = __esm({
1116
1122
  "dist/esm/cache.js"() {
1117
1123
  "use strict";
@@ -1279,6 +1285,24 @@ var kinetex = (() => {
1279
1285
  await Promise.all([this.l1.clear(), this.l2.clear()]);
1280
1286
  }
1281
1287
  };
1288
+ CONTROL_CHARS = /[\u0000-\u001f\u007f-\u009f]/g;
1289
+ CREDENTIAL_HEADERS = [
1290
+ "authorization",
1291
+ "proxy-authorization",
1292
+ "cookie",
1293
+ "x-api-key",
1294
+ "apikey",
1295
+ "api-key",
1296
+ "x-auth-token",
1297
+ "x-access-token",
1298
+ "x-refresh-token",
1299
+ "x-session-id",
1300
+ "x-session-token",
1301
+ "x-secret",
1302
+ "x-secret-key",
1303
+ "x-private-key",
1304
+ "x-csrf-token"
1305
+ ];
1282
1306
  LRUNode = class {
1283
1307
  constructor(key, prev = null, next = null) {
1284
1308
  __publicField(this, "key");
@@ -1393,6 +1417,8 @@ var kinetex = (() => {
1393
1417
  // 7 days
1394
1418
  honorCacheControl: config.honorCacheControl ?? true,
1395
1419
  cacheMethods: config.cacheMethods ?? ["GET", "HEAD"],
1420
+ // 304 is intentionally absent: the client revalidates rather than
1421
+ // storing a 304, so listing it only implied a guarantee that never held.
1396
1422
  cacheStatuses: config.cacheStatuses ?? [
1397
1423
  200,
1398
1424
  203,
@@ -1400,7 +1426,6 @@ var kinetex = (() => {
1400
1426
  206,
1401
1427
  300,
1402
1428
  301,
1403
- 304,
1404
1429
  404,
1405
1430
  405,
1406
1431
  410,
@@ -1767,6 +1792,9 @@ var kinetex = (() => {
1767
1792
  for (const tag of entry.tags) {
1768
1793
  this.tagIndex.get(tag)?.delete(key);
1769
1794
  }
1795
+ } else {
1796
+ for (const set of this.tagIndex.values())
1797
+ set.delete(key);
1770
1798
  }
1771
1799
  }
1772
1800
  async _ensureCapacity(newEntrySize) {
@@ -1959,6 +1987,23 @@ var kinetex = (() => {
1959
1987
  }
1960
1988
  return out;
1961
1989
  }
1990
+ function splitCacheControlDirectives(value) {
1991
+ const parts = [];
1992
+ let current = "";
1993
+ let inQuotes = false;
1994
+ for (const ch of value) {
1995
+ if (ch === '"')
1996
+ inQuotes = !inQuotes;
1997
+ if (ch === "," && !inQuotes) {
1998
+ parts.push(current);
1999
+ current = "";
2000
+ continue;
2001
+ }
2002
+ current += ch;
2003
+ }
2004
+ parts.push(current);
2005
+ return parts;
2006
+ }
1962
2007
  function parseCacheControl2(value) {
1963
2008
  const d = {
1964
2009
  noCache: false,
@@ -1979,7 +2024,7 @@ var kinetex = (() => {
1979
2024
  staleWhileRevalidate: null,
1980
2025
  unknown: /* @__PURE__ */ new Map()
1981
2026
  };
1982
- for (const part of value.split(",")) {
2027
+ for (const part of splitCacheControlDirectives(value)) {
1983
2028
  const t = part.trim();
1984
2029
  const eq = t.indexOf("=");
1985
2030
  const k = (eq === -1 ? t : t.slice(0, eq)).trim().toLowerCase();
@@ -2303,11 +2348,30 @@ var kinetex = (() => {
2303
2348
  proto: xproto ?? null
2304
2349
  };
2305
2350
  }
2306
- function getClientIP(headers) {
2351
+ function getClientIP(headers, options = {}) {
2352
+ const trustedHops = Math.max(0, Math.trunc(options.trustedHops ?? 0));
2307
2353
  const fwd = normalizeForwardedHeaders(headers);
2308
- if (fwd.for.length > 0)
2309
- return fwd.for[0];
2310
- return headers.get(HeaderName.XRealIP);
2354
+ if (fwd.for.length > 0) {
2355
+ const list = fwd.for;
2356
+ const index = trustedHops === 0 ? 0 : list.length - trustedHops;
2357
+ const value = list[index] ?? list[0];
2358
+ return stripIPPortAndBrackets(value);
2359
+ }
2360
+ return stripIPPortAndBrackets(headers.get(HeaderName.XRealIP) ?? "") || null;
2361
+ }
2362
+ function stripIPPortAndBrackets(value) {
2363
+ let v = value.trim();
2364
+ if (v.startsWith('"') && v.endsWith('"') && v.length >= 2)
2365
+ v = v.slice(1, -1);
2366
+ if (v.startsWith("[")) {
2367
+ const end = v.indexOf("]");
2368
+ if (end !== -1)
2369
+ return v.slice(1, end);
2370
+ }
2371
+ const m = /^(.*):\d+$/.exec(v);
2372
+ if (m && (m[1] ?? "").includes("."))
2373
+ return m[1];
2374
+ return v;
2311
2375
  }
2312
2376
  function parseRetryAfter(value) {
2313
2377
  const t = value.trim();
@@ -4449,6 +4513,7 @@ var kinetex = (() => {
4449
4513
  // dist/esm/core.js
4450
4514
  var core_exports = {};
4451
4515
  __export(core_exports, {
4516
+ DEFAULT_ACCEPT_ENCODING: () => DEFAULT_ACCEPT_ENCODING,
4452
4517
  FetchTransport: () => FetchTransport,
4453
4518
  HAS_NATIVE_FETCH: () => HAS_NATIVE_FETCH,
4454
4519
  HTTP_STATUS_TEXTS: () => HTTP_STATUS_TEXTS,
@@ -4502,6 +4567,11 @@ var kinetex = (() => {
4502
4567
  }
4503
4568
  }
4504
4569
  function createTransport(fetchFn, preferHTTP2 = true, sessionOptions, transportOptions) {
4570
+ if (IS_NODE && preferHTTP2 && fetchFn) {
4571
+ if (!isProductionEnvironment()) {
4572
+ console.warn('[kinetex] httpVersion: "HTTP/2" is ignored when a custom `fetch` is configured \u2014 NodeHTTP2Transport cannot use a custom fetch, so the request goes through FetchTransport (HTTP/1.1 semantics). Drop the `fetch` option to use HTTP/2.');
4573
+ }
4574
+ }
4505
4575
  if (IS_NODE && preferHTTP2 && !fetchFn) {
4506
4576
  return new NodeHTTP2Transport({
4507
4577
  ...sessionOptions?.sessionTTLMs !== void 0 ? { sessionTTLMs: sessionOptions.sessionTTLMs } : {},
@@ -4743,7 +4813,8 @@ var kinetex = (() => {
4743
4813
  } else if (typeof body === "string") {
4744
4814
  stream.end(body);
4745
4815
  } else {
4746
- stream.end();
4816
+ const bytes = await serializeRawBody(body);
4817
+ stream.end(bytes);
4747
4818
  }
4748
4819
  }
4749
4820
  async function pipeBodyToNodeReq(req, body) {
@@ -4762,8 +4833,18 @@ var kinetex = (() => {
4762
4833
  } else if (typeof body === "string") {
4763
4834
  req.end(body);
4764
4835
  } else {
4765
- req.end();
4836
+ const bytes = await serializeRawBody(body);
4837
+ req.end(bytes);
4838
+ }
4839
+ }
4840
+ async function serializeRawBody(body) {
4841
+ if (typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams) {
4842
+ return new TextEncoder().encode(body.toString());
4766
4843
  }
4844
+ if (typeof Blob !== "undefined" && body instanceof Blob) {
4845
+ return new Uint8Array(await body.arrayBuffer());
4846
+ }
4847
+ return new Uint8Array(0);
4767
4848
  }
4768
4849
  async function decompressBodyStream(body, headers) {
4769
4850
  if (!body)
@@ -4788,7 +4869,7 @@ var kinetex = (() => {
4788
4869
  const { applyDecompression: applyDecompression2 } = await Promise.resolve().then(() => (init_response(), response_exports));
4789
4870
  return applyDecompression2(body, headers);
4790
4871
  }
4791
- var g, RUNTIME, _runtimeOverride, IS_NODE, HAS_NATIVE_FETCH, FetchTransport, NodeHTTP2Transport, nodeReadableToWebStream, HTTP_STATUS_TEXTS;
4872
+ var g, RUNTIME, _runtimeOverride, IS_NODE, HAS_NATIVE_FETCH, DEFAULT_ACCEPT_ENCODING, FetchTransport, NodeHTTP2Transport, nodeReadableToWebStream, HTTP_STATUS_TEXTS;
4792
4873
  var init_core = __esm({
4793
4874
  "dist/esm/core.js"() {
4794
4875
  "use strict";
@@ -4801,6 +4882,7 @@ var kinetex = (() => {
4801
4882
  _runtimeOverride = null;
4802
4883
  IS_NODE = RUNTIME === "node";
4803
4884
  HAS_NATIVE_FETCH = RUNTIME !== "node" || typeof globalThis.fetch === "function";
4885
+ DEFAULT_ACCEPT_ENCODING = "gzip, deflate, br";
4804
4886
  FetchTransport = class {
4805
4887
  /**
4806
4888
  * @param fetchFnOrOptions - Custom fetch function or options object
@@ -4848,7 +4930,7 @@ var kinetex = (() => {
4848
4930
  sanitizedHeaders[name] = value;
4849
4931
  }
4850
4932
  const ae = sanitizedHeaders["accept-encoding"];
4851
- if (ae && ae.toLowerCase().includes("gzip") && ae.toLowerCase().includes("deflate") && ae.toLowerCase().includes("br")) {
4933
+ if (ae && ae.toLowerCase().replace(/\s+/g, " ") === DEFAULT_ACCEPT_ENCODING) {
4852
4934
  delete sanitizedHeaders["accept-encoding"];
4853
4935
  }
4854
4936
  const init = {
@@ -4913,6 +4995,8 @@ var kinetex = (() => {
4913
4995
  __publicField(this, "sessionUsage", /* @__PURE__ */ new Map());
4914
4996
  __publicField(this, "_strict");
4915
4997
  __publicField(this, "_onDroppedHeader");
4998
+ /** Optional CA bundle for origins with self-signed / private-PKI certificates. */
4999
+ __publicField(this, "_ca");
4916
5000
  /** FIX 11: Configurable connect timeout (replaces hardcoded 30 000 ms) */
4917
5001
  __publicField(this, "_connectTimeoutMs");
4918
5002
  /** FIX 11: Configurable per-request stream timeout (replaces hardcoded 30 000 ms) */
@@ -4924,6 +5008,7 @@ var kinetex = (() => {
4924
5008
  this.maxSessions = options.maxSessions ?? 100;
4925
5009
  this._strict = options.strict ?? false;
4926
5010
  this._onDroppedHeader = options.onDroppedHeader;
5011
+ this._ca = options.ca;
4927
5012
  this._connectTimeoutMs = options.connectTimeoutMs ?? 3e4;
4928
5013
  this._requestTimeoutMs = options.requestTimeoutMs ?? 3e4;
4929
5014
  }
@@ -4973,7 +5058,10 @@ var kinetex = (() => {
4973
5058
  }
4974
5059
  }
4975
5060
  const session = await new Promise((resolve, reject) => {
4976
- const s = http2.connect(origin, { rejectUnauthorized: true });
5061
+ const s = http2.connect(origin, {
5062
+ rejectUnauthorized: true,
5063
+ ...this._ca !== void 0 ? { ca: this._ca } : {}
5064
+ });
4977
5065
  const connectTimeout = setTimeout(() => {
4978
5066
  s.destroy();
4979
5067
  reject(new KinetexError(`HTTP/2 connection to ${origin} timed out`, "ETIMEOUT", {
@@ -5086,30 +5174,28 @@ var kinetex = (() => {
5086
5174
  ":authority": currentUrl.host,
5087
5175
  ...currentReq.headers
5088
5176
  };
5089
- if (this._strict) {
5090
- for (const [hName, hValue] of Object.entries(h2ReqHeaders)) {
5091
- if (hName.startsWith(":"))
5092
- continue;
5093
- const hStr = Array.isArray(hValue) ? hValue.join(", ") : String(hValue);
5094
- let hasForbidden = false;
5095
- for (let ci = 0; ci < hStr.length; ci++) {
5096
- const code = hStr.charCodeAt(ci);
5097
- if (code >= 0 && code <= 8 || code >= 10 && code <= 31 || code === 127) {
5098
- hasForbidden = true;
5099
- break;
5100
- }
5177
+ for (const [hName, hValue] of Object.entries(h2ReqHeaders)) {
5178
+ if (hName.startsWith(":"))
5179
+ continue;
5180
+ const hStr = Array.isArray(hValue) ? hValue.join(", ") : String(hValue);
5181
+ let hasForbidden = false;
5182
+ for (let ci = 0; ci < hStr.length; ci++) {
5183
+ const code = hStr.charCodeAt(ci);
5184
+ if (code >= 0 && code <= 8 || code >= 10 && code <= 31 || code === 127) {
5185
+ hasForbidden = true;
5186
+ break;
5101
5187
  }
5102
- if (hasForbidden) {
5103
- if (this._strict) {
5104
- throw new KinetexError(`Strict mode: header "${hName}" contains forbidden control characters`, "EVALIDATION", { request: currentReq });
5105
- }
5106
- if (this._onDroppedHeader) {
5107
- this._onDroppedHeader(hName, hStr);
5108
- } else if (typeof console !== "undefined") {
5109
- console.warn(`[kinetex] Invalid header dropped (HTTP/2): "${hName}" \u2014 value contains illegal control characters. Pass strictHeaders: true to throw instead.`);
5110
- }
5111
- delete h2ReqHeaders[hName];
5188
+ }
5189
+ if (hasForbidden) {
5190
+ if (this._strict) {
5191
+ throw new KinetexError(`Strict mode: header "${hName}" contains forbidden control characters`, "EVALIDATION", { request: currentReq });
5192
+ }
5193
+ if (this._onDroppedHeader) {
5194
+ this._onDroppedHeader(hName, hStr);
5195
+ } else if (typeof console !== "undefined") {
5196
+ console.warn(`[kinetex] Invalid header dropped (HTTP/2): "${hName}" \u2014 value contains illegal control characters. Pass strictHeaders: true to throw instead.`);
5112
5197
  }
5198
+ delete h2ReqHeaders[hName];
5113
5199
  }
5114
5200
  }
5115
5201
  const endStream = !currentReq.body || currentReq.method === "GET" || currentReq.method === "HEAD";
@@ -5648,8 +5734,7 @@ var kinetex = (() => {
5648
5734
  return false;
5649
5735
  if (isIPAddress(cd))
5650
5736
  return false;
5651
- const psl = getPublicSuffix(rh);
5652
- if (psl === rh)
5737
+ if (isPublicSuffix(cd))
5653
5738
  return false;
5654
5739
  return true;
5655
5740
  }
@@ -7358,7 +7443,7 @@ var kinetex = (() => {
7358
7443
  let cookieDomain;
7359
7444
  let hostOnly;
7360
7445
  if (parsed.domain !== null && parsed.domain !== "") {
7361
- const cd = parsed.domain;
7446
+ const cd = parsed.domain.toLowerCase();
7362
7447
  const matcher = this.domainMatcherFn ?? domainMatch;
7363
7448
  if (!matcher(reqHost, cd))
7364
7449
  return false;
@@ -7428,9 +7513,6 @@ var kinetex = (() => {
7428
7513
  * @returns Array of matching Cookie objects (direct references into storage)
7429
7514
  */
7430
7515
  getCookies(options) {
7431
- if (Math.random() < 0.01) {
7432
- this.clearExpired();
7433
- }
7434
7516
  const url = safeParseUrl(options.url);
7435
7517
  if (!url)
7436
7518
  return [];
@@ -7867,11 +7949,13 @@ var kinetex = (() => {
7867
7949
  if (this.domainMatcherFn) {
7868
7950
  return this.domainMatcherFn(reqHost, cookieDomain);
7869
7951
  }
7870
- if (reqHost === cookieDomain)
7952
+ const host = reqHost.toLowerCase();
7953
+ const domain = cookieDomain.toLowerCase();
7954
+ if (host === domain)
7871
7955
  return true;
7872
- if (isIPAddress(reqHost))
7956
+ if (isIPAddress(host))
7873
7957
  return false;
7874
- return reqHost.endsWith("." + cookieDomain);
7958
+ return host.endsWith("." + domain);
7875
7959
  }
7876
7960
  };
7877
7961
  }
@@ -8573,7 +8657,7 @@ var kinetex = (() => {
8573
8657
  * ```ts
8574
8658
  * const logger = createProductionLogger();
8575
8659
  * const child = logger.child({ requestId: "req-123", userId: "user-456" });
8576
- * child.logRequest(...); // Logs will include both requestId and userId
8660
+ * child.logRequest("req-123", "GET", "/users", {}, null, 1); // Logs include both requestId and userId
8577
8661
  * ```
8578
8662
  */
8579
8663
  child(context) {
@@ -8686,8 +8770,23 @@ var kinetex = (() => {
8686
8770
  events.push(final);
8687
8771
  return events;
8688
8772
  }
8689
- function sleep3(ms) {
8690
- return new Promise((r) => setTimeout(r, ms));
8773
+ function sleep3(ms, signal) {
8774
+ return new Promise((r) => {
8775
+ if (signal?.aborted) {
8776
+ r();
8777
+ return;
8778
+ }
8779
+ const onAbort = () => {
8780
+ clearTimeout(timer);
8781
+ signal?.removeEventListener("abort", onAbort);
8782
+ r();
8783
+ };
8784
+ const timer = setTimeout(() => {
8785
+ signal?.removeEventListener("abort", onAbort);
8786
+ r();
8787
+ }, ms);
8788
+ signal?.addEventListener("abort", onAbort, { once: true });
8789
+ });
8691
8790
  }
8692
8791
  var SSEParser, SSETransformStream, SSEClient, SSERouter, SSEServerResponse, SSEError, SSEMaxReconnectsError;
8693
8792
  var init_sse = __esm({
@@ -9064,8 +9163,8 @@ var kinetex = (() => {
9064
9163
  }
9065
9164
  if (!cfg.reconnect)
9066
9165
  throw err;
9067
- if (cfg.maxReconnects > 0 && reconnectAttempt >= cfg.maxReconnects) {
9068
- throw new SSEMaxReconnectsError(reconnectAttempt, cfg.url);
9166
+ if (cfg.maxReconnects > 0 && this.health.totalReconnects >= cfg.maxReconnects) {
9167
+ throw new SSEMaxReconnectsError(this.health.totalReconnects, cfg.url);
9069
9168
  }
9070
9169
  reconnectAttempt++;
9071
9170
  this.health.totalReconnects++;
@@ -9078,25 +9177,31 @@ var kinetex = (() => {
9078
9177
  clearTimeout(heartbeatTimer);
9079
9178
  heartbeatTimer = null;
9080
9179
  }
9081
- await sleep3(delay2);
9180
+ await sleep3(delay2, cfg.signal);
9181
+ if (cfg.signal?.aborted)
9182
+ break;
9082
9183
  parser.reset();
9083
9184
  continue;
9084
9185
  }
9085
9186
  this.health.connected = false;
9086
9187
  if (!cfg.reconnect)
9087
9188
  break;
9088
- if (cfg.maxReconnects > 0 && reconnectAttempt >= cfg.maxReconnects) {
9089
- throw new SSEMaxReconnectsError(reconnectAttempt, cfg.url);
9189
+ if (cfg.maxReconnects > 0 && this.health.totalReconnects >= cfg.maxReconnects) {
9190
+ throw new SSEMaxReconnectsError(this.health.totalReconnects, cfg.url);
9090
9191
  }
9091
9192
  reconnectAttempt++;
9092
9193
  this.health.totalReconnects++;
9093
- const delay = Math.min(reconnectDelay, cfg.maxReconnectDelayMs);
9194
+ this.health.reconnectAttempt = reconnectAttempt;
9195
+ const delay = Math.min(reconnectDelay + reconnectDelay * cfg.reconnectJitter * Math.random(), cfg.maxReconnectDelayMs);
9196
+ reconnectDelay = Math.min(reconnectDelay * 2, cfg.maxReconnectDelayMs);
9094
9197
  cfg.onReconnect(reconnectAttempt, delay);
9095
9198
  if (heartbeatTimer) {
9096
9199
  clearTimeout(heartbeatTimer);
9097
9200
  heartbeatTimer = null;
9098
9201
  }
9099
- await sleep3(delay);
9202
+ await sleep3(delay, cfg.signal);
9203
+ if (cfg.signal?.aborted)
9204
+ break;
9100
9205
  }
9101
9206
  this.health.connected = false;
9102
9207
  }
@@ -9515,7 +9620,8 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
9515
9620
  fetchHeaders["content-type"] = "application/json";
9516
9621
  const controller = new AbortController();
9517
9622
  const timer = config.timeoutMs > 0 ? setTimeout(() => controller.abort(), config.timeoutMs) : null;
9518
- signal?.addEventListener("abort", () => controller.abort(), { once: true });
9623
+ const onExternalAbort = () => controller.abort();
9624
+ signal?.addEventListener("abort", onExternalAbort, { once: true });
9519
9625
  let response;
9520
9626
  try {
9521
9627
  response = await config.fetch(fetchUrl, {
@@ -9534,6 +9640,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
9534
9640
  } finally {
9535
9641
  if (timer)
9536
9642
  clearTimeout(timer);
9643
+ signal?.removeEventListener("abort", onExternalAbort);
9537
9644
  }
9538
9645
  return parseGraphQLResponse(response, req);
9539
9646
  }
@@ -9892,7 +9999,8 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
9892
9999
  delete headers["content-type"];
9893
10000
  const form = buildMultipartBody(req, uploads);
9894
10001
  const controller = new AbortController();
9895
- options.signal?.addEventListener("abort", () => controller.abort(), { once: true });
10002
+ const onExternalAbort = () => controller.abort();
10003
+ options.signal?.addEventListener("abort", onExternalAbort, { once: true });
9896
10004
  this.config.onRequest(req);
9897
10005
  let response;
9898
10006
  try {
@@ -9906,6 +10014,8 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
9906
10014
  const clientErr = new GraphQLClientError(err instanceof Error ? err.message : "Upload network error", "ENETWORK", void 0, req, void 0, err);
9907
10015
  this.config.onError(clientErr, req);
9908
10016
  throw clientErr;
10017
+ } finally {
10018
+ options.signal?.removeEventListener("abort", onExternalAbort);
9909
10019
  }
9910
10020
  const gqlRes = await parseGraphQLResponse(response, req);
9911
10021
  this.config.onResponse(gqlRes, req);
@@ -9936,7 +10046,8 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
9936
10046
  async batch(requests, options = {}) {
9937
10047
  const headers = await buildHeaders(this.config);
9938
10048
  const controller = new AbortController();
9939
- options.signal?.addEventListener("abort", () => controller.abort(), { once: true });
10049
+ const onExternalAbort = () => controller.abort();
10050
+ options.signal?.addEventListener("abort", onExternalAbort, { once: true });
9940
10051
  let response;
9941
10052
  try {
9942
10053
  response = await this.config.fetch(this.config.url, {
@@ -9947,6 +10058,8 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
9947
10058
  });
9948
10059
  } catch (err) {
9949
10060
  throw new GraphQLClientError(err instanceof Error ? err.message : "Batch network error", "ENETWORK", void 0, requests[0], void 0, err);
10061
+ } finally {
10062
+ options.signal?.removeEventListener("abort", onExternalAbort);
9950
10063
  }
9951
10064
  const rawResults = sanitizeParsedJSON(await response.json());
9952
10065
  if (!Array.isArray(rawResults)) {
@@ -10574,12 +10687,16 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
10574
10687
  r();
10575
10688
  return;
10576
10689
  }
10577
- const timer = setTimeout(r, ms);
10578
- if (signal)
10579
- signal.addEventListener("abort", () => {
10580
- clearTimeout(timer);
10581
- r();
10582
- }, { once: true });
10690
+ const onAbort = () => {
10691
+ clearTimeout(timer);
10692
+ signal?.removeEventListener("abort", onAbort);
10693
+ r();
10694
+ };
10695
+ const timer = setTimeout(() => {
10696
+ signal?.removeEventListener("abort", onAbort);
10697
+ r();
10698
+ }, ms);
10699
+ signal?.addEventListener("abort", onAbort, { once: true });
10583
10700
  });
10584
10701
  }
10585
10702
  function toPaginationIterator(source) {
@@ -11079,35 +11196,58 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11079
11196
  }
11080
11197
  });
11081
11198
  }
11199
+ let reader = null;
11200
+ let onUploadAbort = null;
11201
+ const detach = () => {
11202
+ if (onUploadAbort) {
11203
+ options.signal?.removeEventListener("abort", onUploadAbort);
11204
+ onUploadAbort = null;
11205
+ }
11206
+ };
11082
11207
  const stream = new ReadableStream({
11083
- async start(controller) {
11084
- const reader = source.getReader();
11208
+ start(controller) {
11085
11209
  if (options.signal?.aborted) {
11086
- reader.cancel("aborted").catch(() => {
11087
- });
11088
11210
  controller.error(new DOMException("Upload aborted", "AbortError"));
11089
11211
  return;
11090
11212
  }
11091
- options.signal?.addEventListener("abort", () => {
11092
- reader.cancel("aborted").catch(() => {
11213
+ reader = source.getReader();
11214
+ onUploadAbort = () => {
11215
+ const r = reader;
11216
+ reader = null;
11217
+ void r?.cancel("aborted").catch(() => {
11093
11218
  });
11219
+ tracker.complete();
11094
11220
  controller.error(new DOMException("Upload aborted", "AbortError"));
11095
- }, { once: true });
11221
+ };
11222
+ options.signal?.addEventListener("abort", onUploadAbort, { once: true });
11223
+ },
11224
+ async pull(controller) {
11225
+ if (!reader)
11226
+ return;
11096
11227
  try {
11097
- while (true) {
11098
- const { done, value } = await reader.read();
11099
- if (done) {
11100
- tracker.complete();
11101
- controller.close();
11102
- break;
11103
- }
11104
- tracker.update(value.byteLength);
11105
- controller.enqueue(value);
11228
+ const { done, value } = await reader.read();
11229
+ if (done) {
11230
+ detach();
11231
+ reader.releaseLock();
11232
+ reader = null;
11233
+ tracker.complete();
11234
+ controller.close();
11235
+ return;
11106
11236
  }
11237
+ tracker.update(value.byteLength);
11238
+ controller.enqueue(value);
11107
11239
  } catch (err) {
11240
+ detach();
11108
11241
  tracker.complete();
11109
11242
  controller.error(err);
11110
11243
  }
11244
+ },
11245
+ cancel(reason) {
11246
+ detach();
11247
+ const r = reader;
11248
+ reader = null;
11249
+ void r?.cancel(reason).catch(() => {
11250
+ });
11111
11251
  }
11112
11252
  });
11113
11253
  return { stream, tracker };
@@ -11124,40 +11264,64 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11124
11264
  return { response, tracker };
11125
11265
  }
11126
11266
  const body = response.body;
11267
+ let reader = null;
11268
+ let aborted = false;
11269
+ let onAbort = null;
11270
+ const detach = () => {
11271
+ if (onAbort) {
11272
+ options.signal?.removeEventListener("abort", onAbort);
11273
+ onAbort = null;
11274
+ }
11275
+ };
11127
11276
  const trackedStream = new ReadableStream({
11128
- async start(controller) {
11129
- const reader = body.getReader();
11130
- let aborted = false;
11131
- const onAbort = () => {
11277
+ start(controller) {
11278
+ if (options.signal?.aborted) {
11279
+ aborted = true;
11280
+ controller.error(new DOMException("Download aborted", "AbortError"));
11281
+ return;
11282
+ }
11283
+ reader = body.getReader();
11284
+ onAbort = () => {
11132
11285
  if (aborted)
11133
11286
  return;
11134
11287
  aborted = true;
11135
- reader.cancel("aborted").catch(() => {
11288
+ const r = reader;
11289
+ reader = null;
11290
+ void r?.cancel("aborted").catch(() => {
11136
11291
  });
11137
11292
  controller.error(new DOMException("Download aborted", "AbortError"));
11138
11293
  };
11139
11294
  options.signal?.addEventListener("abort", onAbort, { once: true });
11295
+ },
11296
+ async pull(controller) {
11297
+ if (!reader)
11298
+ return;
11140
11299
  try {
11141
- while (true) {
11142
- const { done, value } = await reader.read();
11143
- if (done) {
11144
- tracker.complete();
11145
- controller.close();
11146
- break;
11147
- }
11148
- tracker.update(value.byteLength);
11149
- controller.enqueue(value);
11300
+ const { done, value } = await reader.read();
11301
+ if (done) {
11302
+ detach();
11303
+ reader.releaseLock();
11304
+ reader = null;
11305
+ tracker.complete();
11306
+ controller.close();
11307
+ return;
11150
11308
  }
11309
+ tracker.update(value.byteLength);
11310
+ controller.enqueue(value);
11151
11311
  } catch (err) {
11312
+ detach();
11152
11313
  if (!aborted) {
11153
11314
  tracker.complete();
11154
11315
  }
11155
11316
  controller.error(err);
11156
- } finally {
11157
- if (!aborted) {
11158
- options.signal?.removeEventListener("abort", onAbort);
11159
- }
11160
11317
  }
11318
+ },
11319
+ cancel(reason) {
11320
+ detach();
11321
+ const r = reader;
11322
+ reader = null;
11323
+ void r?.cancel(reason).catch(() => {
11324
+ });
11161
11325
  }
11162
11326
  });
11163
11327
  const trackedResponse = new Response(trackedStream, {
@@ -11165,6 +11329,16 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11165
11329
  statusText: response.statusText,
11166
11330
  headers: response.headers
11167
11331
  });
11332
+ for (const prop of ["url", "redirected", "type"]) {
11333
+ try {
11334
+ Object.defineProperty(trackedResponse, prop, {
11335
+ value: response[prop],
11336
+ enumerable: true,
11337
+ configurable: true
11338
+ });
11339
+ } catch {
11340
+ }
11341
+ }
11168
11342
  return { response: trackedResponse, tracker };
11169
11343
  }
11170
11344
  async function* streamWithProgress(stream, total, options = {}) {
@@ -11197,11 +11371,11 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11197
11371
  });
11198
11372
  throw new DOMException("Stream aborted", "AbortError");
11199
11373
  }
11374
+ const abortHandler = () => {
11375
+ reader.cancel("aborted").catch(() => {
11376
+ });
11377
+ };
11200
11378
  if (options.signal) {
11201
- const abortHandler = () => {
11202
- reader.cancel("aborted").catch(() => {
11203
- });
11204
- };
11205
11379
  options.signal.addEventListener("abort", abortHandler, { once: true });
11206
11380
  }
11207
11381
  try {
@@ -11214,6 +11388,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11214
11388
  }
11215
11389
  } finally {
11216
11390
  reader.releaseLock();
11391
+ options.signal?.removeEventListener("abort", abortHandler);
11217
11392
  }
11218
11393
  tracker.complete();
11219
11394
  const totalBytes = chunks.reduce((acc, c) => acc + c.byteLength, 0);
@@ -11344,11 +11519,19 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11344
11519
  downloadTracker.update(e.loaded - downloadTracker.bytesLoaded);
11345
11520
  });
11346
11521
  }
11347
- options.signal?.addEventListener("abort", () => {
11522
+ const onAbort = () => {
11348
11523
  xhr.abort();
11349
11524
  reject(new DOMException("Request aborted", "AbortError"));
11350
- }, { once: true });
11525
+ };
11526
+ const detach = () => options.signal?.removeEventListener("abort", onAbort);
11527
+ options.signal?.addEventListener("abort", onAbort, { once: true });
11528
+ if (options.signal?.aborted) {
11529
+ detach();
11530
+ onAbort();
11531
+ return;
11532
+ }
11351
11533
  xhr.addEventListener("load", () => {
11534
+ detach();
11352
11535
  downloadTracker.complete();
11353
11536
  const rawHeaders = xhr.getAllResponseHeaders();
11354
11537
  const headers = {};
@@ -11366,11 +11549,17 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11366
11549
  });
11367
11550
  });
11368
11551
  xhr.addEventListener("error", () => {
11552
+ detach();
11369
11553
  reject(new TypeError("Network request failed"));
11370
11554
  });
11371
11555
  xhr.addEventListener("timeout", () => {
11556
+ detach();
11372
11557
  reject(new TypeError("Request timed out"));
11373
11558
  });
11559
+ xhr.addEventListener("abort", () => {
11560
+ detach();
11561
+ reject(new DOMException("Request aborted", "AbortError"));
11562
+ });
11374
11563
  xhr.send(options.body ?? null);
11375
11564
  });
11376
11565
  }
@@ -11522,6 +11711,11 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11522
11711
  if (effectiveWindowMs <= 0) {
11523
11712
  this.inflight.delete(key);
11524
11713
  } else {
11714
+ const stale = this.timeouts.get(key);
11715
+ if (stale !== void 0) {
11716
+ clearTimeout(stale);
11717
+ this.timeouts.delete(key);
11718
+ }
11525
11719
  const timeoutId = setTimeout(() => {
11526
11720
  this.inflight.delete(key);
11527
11721
  this.timeouts.delete(key);
@@ -11563,13 +11757,6 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11563
11757
  get keys() {
11564
11758
  return [...this.inflight.keys()];
11565
11759
  }
11566
- /**
11567
- * Get comprehensive deduplication statistics.
11568
- * NOTE: inFlightCount and trackedKeys reflect snapshot time; entries may
11569
- * resolve asynchronously between sampling and return (non-blocking, acceptable).
11570
- *
11571
- * @returns Snapshot of hits, misses, hit rate, and tracked entries
11572
- */
11573
11760
  /**
11574
11761
  * Get comprehensive deduplication statistics.
11575
11762
  *
@@ -11712,9 +11899,16 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11712
11899
  }
11713
11900
  /** Snapshot of all counters — suitable for logging or dashboards. */
11714
11901
  get snapshot() {
11902
+ let failureCount = this._consecutiveFails;
11903
+ if (this.windowSize > 0) {
11904
+ failureCount = 0;
11905
+ for (const ok of this._window)
11906
+ if (!ok)
11907
+ failureCount++;
11908
+ }
11715
11909
  return {
11716
11910
  state: this._state,
11717
- failureCount: this.windowSize > 0 ? this._window.filter((v) => !v).length : this._consecutiveFails,
11911
+ failureCount,
11718
11912
  successCount: this._consecutiveSucc,
11719
11913
  lastFailureAt: this._lastFailureAt,
11720
11914
  lastSuccessAt: this._lastSuccessAt,
@@ -11740,6 +11934,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11740
11934
  this.cfg.onRejected?.(snap);
11741
11935
  return Promise.reject(new CircuitOpenError(this.key, snap));
11742
11936
  }
11937
+ let heldProbe = false;
11743
11938
  if (this._state === "HALF_OPEN") {
11744
11939
  if (this._inFlightProbes >= this.halfOpenConcurrency) {
11745
11940
  this._totalRejected++;
@@ -11748,15 +11943,16 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11748
11943
  return Promise.reject(new CircuitOpenError(this.key, snap));
11749
11944
  }
11750
11945
  this._inFlightProbes++;
11946
+ heldProbe = true;
11751
11947
  }
11752
11948
  this._totalRequests++;
11753
- return fn().then((result) => {
11754
- if (this._state === "HALF_OPEN")
11949
+ return Promise.resolve().then(fn).then((result) => {
11950
+ if (heldProbe)
11755
11951
  this._inFlightProbes = Math.max(0, this._inFlightProbes - 1);
11756
11952
  this._recordSuccess();
11757
11953
  return result;
11758
11954
  }, (err) => {
11759
- if (this._state === "HALF_OPEN")
11955
+ if (heldProbe)
11760
11956
  this._inFlightProbes = Math.max(0, this._inFlightProbes - 1);
11761
11957
  if (this._isCountableFailure(err)) {
11762
11958
  this._recordFailure();
@@ -12027,6 +12223,10 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
12027
12223
  __publicField(this, "_openWaiters", []);
12028
12224
  __publicField(this, "_correlations", /* @__PURE__ */ new Map());
12029
12225
  __publicField(this, "_aborted", false);
12226
+ /** Optional external abort signal from config (listener detached on close). */
12227
+ __publicField(this, "_externalSignal", null);
12228
+ /** Bound abort handler for the optional external `cfg.signal` (removed on close). */
12229
+ __publicField(this, "_onExternalAbort", () => this.close(1e3, "AbortSignal aborted"));
12030
12230
  // ── Async iterator ──────────────────────────────────────────────────────
12031
12231
  __publicField(this, "_iterQueue", []);
12032
12232
  __publicField(this, "_iterWaiter", null);
@@ -12090,7 +12290,8 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
12090
12290
  this._rooms.push({ room: r, joinedAt: 0 });
12091
12291
  }
12092
12292
  }
12093
- cfg.signal?.addEventListener("abort", () => this.close(1e3, "AbortSignal aborted"), {
12293
+ this._externalSignal = cfg.signal ?? null;
12294
+ this._externalSignal?.addEventListener("abort", this._onExternalAbort, {
12094
12295
  once: true
12095
12296
  });
12096
12297
  }
@@ -12171,6 +12372,8 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
12171
12372
  */
12172
12373
  close(code = 1e3, reason = "Client closed") {
12173
12374
  this._aborted = true;
12375
+ this._externalSignal?.removeEventListener("abort", this._onExternalAbort);
12376
+ this._externalSignal = null;
12174
12377
  this._stopAll();
12175
12378
  for (const w of this._drainWaiters.splice(0))
12176
12379
  w();
@@ -12712,6 +12915,10 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
12712
12915
  return this._maxReconnects === 0 || this._reconnAttempt < this._maxReconnects;
12713
12916
  }
12714
12917
  _scheduleRecon() {
12918
+ if (this._reconnTimer) {
12919
+ clearTimeout(this._reconnTimer);
12920
+ this._reconnTimer = null;
12921
+ }
12715
12922
  this._state = "RECONNECTING";
12716
12923
  this._reconnAttempt++;
12717
12924
  const exp = this._reconnectBaseMs * Math.pow(2, this._reconnAttempt - 1);
@@ -12719,13 +12926,11 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
12719
12926
  const delay = Math.floor(capped * (1 + this._reconnectJitter * Math.random()));
12720
12927
  this._cbReconnect?.(this._reconnAttempt, delay);
12721
12928
  this._reconnTimer = setTimeout(() => {
12929
+ this._reconnTimer = null;
12722
12930
  if (!this._aborted)
12723
12931
  this._doConnect(true).catch(() => {
12724
12932
  });
12725
12933
  }, delay);
12726
- if (typeof this._reconnTimer === "object" && this._reconnTimer !== null && "unref" in this._reconnTimer) {
12727
- this._reconnTimer.unref();
12728
- }
12729
12934
  }
12730
12935
  _tx(data) {
12731
12936
  const ws = this._ws;
@@ -12893,12 +13098,17 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
12893
13098
  init_cache();
12894
13099
 
12895
13100
  // dist/esm/interceptors.js
13101
+ init_cache();
12896
13102
  var _idSeq = 0;
12897
13103
  function nextId() {
12898
13104
  return `interceptor_${++_idSeq}`;
12899
13105
  }
12900
- function sortByPriority(arr) {
12901
- return [...arr].sort((a, b) => a.priority - b.priority);
13106
+ function insertByPriority(arr, entry) {
13107
+ const at = arr.findIndex((x) => x.priority > entry.priority);
13108
+ if (at === -1)
13109
+ arr.push(entry);
13110
+ else
13111
+ arr.splice(at, 0, entry);
12902
13112
  }
12903
13113
  var InterceptorManager = class {
12904
13114
  constructor() {
@@ -12916,7 +13126,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
12916
13126
  */
12917
13127
  useRequest(fn, opts = {}) {
12918
13128
  const id = opts.id ?? nextId();
12919
- this.requestInterceptors.push({
13129
+ insertByPriority(this.requestInterceptors, {
12920
13130
  id,
12921
13131
  priority: opts.priority ?? 0,
12922
13132
  once: opts.once ?? false,
@@ -12934,7 +13144,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
12934
13144
  */
12935
13145
  useResponse(fn, opts = {}) {
12936
13146
  const id = opts.id ?? nextId();
12937
- this.responseInterceptors.push({
13147
+ insertByPriority(this.responseInterceptors, {
12938
13148
  id,
12939
13149
  priority: opts.priority ?? 0,
12940
13150
  once: opts.once ?? false,
@@ -12952,7 +13162,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
12952
13162
  */
12953
13163
  useError(fn, opts = {}) {
12954
13164
  const id = opts.id ?? nextId();
12955
- this.errorInterceptors.push({
13165
+ insertByPriority(this.errorInterceptors, {
12956
13166
  id,
12957
13167
  priority: opts.priority ?? 0,
12958
13168
  once: opts.once ?? false,
@@ -13029,7 +13239,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
13029
13239
  async _run(ctx, dispatcher) {
13030
13240
  ctx.attempt++;
13031
13241
  const toEject = /* @__PURE__ */ new Set();
13032
- for (const interceptor of sortByPriority(this.requestInterceptors)) {
13242
+ for (const interceptor of this.requestInterceptors) {
13033
13243
  if (ctx.aborted)
13034
13244
  break;
13035
13245
  if (interceptor.condition && !interceptor.condition(ctx))
@@ -13069,7 +13279,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
13069
13279
  }
13070
13280
  async _runResponsePhase(ctx, dispatcher) {
13071
13281
  const toEject = /* @__PURE__ */ new Set();
13072
- for (const interceptor of sortByPriority(this.responseInterceptors)) {
13282
+ for (const interceptor of this.responseInterceptors) {
13073
13283
  if (ctx.aborted)
13074
13284
  break;
13075
13285
  if (interceptor.condition && !interceptor.condition(ctx))
@@ -13107,7 +13317,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
13107
13317
  }
13108
13318
  async _runErrorPhase(ctx, dispatcher) {
13109
13319
  const toEject = /* @__PURE__ */ new Set();
13110
- for (const interceptor of sortByPriority(this.errorInterceptors)) {
13320
+ for (const interceptor of this.errorInterceptors) {
13111
13321
  if (interceptor.condition && !interceptor.condition(ctx))
13112
13322
  continue;
13113
13323
  let result;
@@ -13310,27 +13520,33 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
13310
13520
  }, ms);
13311
13521
  const existing = ctx.request.signal;
13312
13522
  if (existing) {
13313
- existing.addEventListener("abort", () => {
13523
+ const onExistingAbort = () => {
13314
13524
  clearTimeout(timer);
13315
13525
  controller.abort(existing.reason);
13316
- }, { once: true });
13526
+ };
13527
+ existing.addEventListener("abort", onExistingAbort, { once: true });
13528
+ ctx.store.set(TIMEOUT_SIGNAL_KEY, { existing, onExistingAbort });
13317
13529
  }
13318
13530
  ctx.request = { ...ctx.request, signal: controller.signal };
13319
13531
  ctx.store.set(TIMEOUT_TIMER_KEY, timer);
13320
13532
  };
13321
- const responseInterceptor = (ctx) => {
13533
+ const cleanupTimeout = (ctx) => {
13322
13534
  const timer = ctx.store.get(TIMEOUT_TIMER_KEY);
13323
13535
  if (timer) {
13324
13536
  clearTimeout(timer);
13325
13537
  ctx.store.delete(TIMEOUT_TIMER_KEY);
13326
13538
  }
13539
+ const merged = ctx.store.get(TIMEOUT_SIGNAL_KEY);
13540
+ if (merged) {
13541
+ merged.existing.removeEventListener("abort", merged.onExistingAbort);
13542
+ ctx.store.delete(TIMEOUT_SIGNAL_KEY);
13543
+ }
13544
+ };
13545
+ const responseInterceptor = (ctx) => {
13546
+ cleanupTimeout(ctx);
13327
13547
  };
13328
13548
  const errorInterceptor = (ctx) => {
13329
- const timer = ctx.store.get(TIMEOUT_TIMER_KEY);
13330
- if (timer) {
13331
- clearTimeout(timer);
13332
- ctx.store.delete(TIMEOUT_TIMER_KEY);
13333
- }
13549
+ cleanupTimeout(ctx);
13334
13550
  };
13335
13551
  return {
13336
13552
  id: "timeout",
@@ -13340,6 +13556,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
13340
13556
  };
13341
13557
  }
13342
13558
  var TIMEOUT_TIMER_KEY = /* @__PURE__ */ Symbol("timeoutTimer");
13559
+ var TIMEOUT_SIGNAL_KEY = /* @__PURE__ */ Symbol("timeoutSignal");
13343
13560
  var TimeoutError2 = class extends Error {
13344
13561
  constructor(message, timeoutMs) {
13345
13562
  super(message);
@@ -13512,13 +13729,15 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
13512
13729
  var CACHE_STALE_KEY = /* @__PURE__ */ Symbol("cacheStale");
13513
13730
  function createDedupeInterceptor() {
13514
13731
  const inflight = /* @__PURE__ */ new Map();
13515
- function key(req) {
13516
- return `${req.method.toUpperCase()}:${req.url}`;
13732
+ async function key(req) {
13733
+ const authFp = await getAuthFingerprint(req.headers ?? {});
13734
+ return `${req.method.toUpperCase()}:${req.url}${authFp ? ":" + authFp : ""}`;
13517
13735
  }
13518
- const requestInterceptor = (ctx) => {
13736
+ const requestInterceptor = async (ctx) => {
13519
13737
  if (ctx.request.method.toUpperCase() !== "GET" && ctx.request.method.toUpperCase() !== "HEAD")
13520
13738
  return;
13521
- const k = key(ctx.request);
13739
+ const k = await key(ctx.request);
13740
+ ctx.store.set(DEDUPE_KEY, k);
13522
13741
  const slot = inflight.get(k);
13523
13742
  if (!slot) {
13524
13743
  inflight.set(k, {
@@ -13527,38 +13746,59 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
13527
13746
  });
13528
13747
  return;
13529
13748
  }
13530
- ctx.store.set(DEDUPE_QUEUED_KEY, k);
13531
13749
  return new Promise((resolve, reject) => {
13532
- slot.waiters.push({
13750
+ const signal = ctx.request.signal;
13751
+ const waiter = {
13533
13752
  resolve: (res) => resolve(res),
13534
- reject: (err) => reject(err)
13753
+ reject: (err) => reject(err),
13535
13754
  // Properly reject instead of throwing
13536
- });
13755
+ cleanup: () => signal?.removeEventListener("abort", onQueuedAbort)
13756
+ };
13757
+ const onQueuedAbort = () => {
13758
+ const at = slot.waiters.indexOf(waiter);
13759
+ if (at !== -1)
13760
+ slot.waiters.splice(at, 1);
13761
+ reject(new Error("Request aborted while queued for deduplication"));
13762
+ };
13763
+ if (signal?.aborted) {
13764
+ onQueuedAbort();
13765
+ return;
13766
+ }
13767
+ signal?.addEventListener("abort", onQueuedAbort, { once: true });
13768
+ slot.waiters.push(waiter);
13537
13769
  });
13538
13770
  };
13539
13771
  const responseInterceptor = (ctx) => {
13540
13772
  if (!ctx.response)
13541
13773
  return;
13542
- const k = key(ctx.request);
13774
+ const k = ctx.store.get(DEDUPE_KEY);
13775
+ if (!k)
13776
+ return;
13543
13777
  const slot = inflight.get(k);
13544
13778
  if (!slot)
13545
13779
  return;
13546
13780
  inflight.delete(k);
13547
- for (const w of slot.waiters)
13781
+ for (const w of slot.waiters.splice(0)) {
13782
+ w.cleanup();
13548
13783
  w.resolve(ctx.response);
13784
+ }
13549
13785
  };
13550
13786
  const errorInterceptor = (ctx) => {
13551
- const k = key(ctx.request);
13787
+ const k = ctx.store.get(DEDUPE_KEY);
13788
+ if (!k)
13789
+ return;
13552
13790
  const slot = inflight.get(k);
13553
13791
  if (!slot)
13554
13792
  return;
13555
13793
  inflight.delete(k);
13556
- for (const w of slot.waiters)
13794
+ for (const w of slot.waiters.splice(0)) {
13795
+ w.cleanup();
13557
13796
  w.reject(ctx.error);
13797
+ }
13558
13798
  };
13559
13799
  return { requestInterceptor, responseInterceptor, errorInterceptor };
13560
13800
  }
13561
- var DEDUPE_QUEUED_KEY = /* @__PURE__ */ Symbol("dedupeQueued");
13801
+ var DEDUPE_KEY = /* @__PURE__ */ Symbol("dedupeKey");
13562
13802
  var RATE_LIMIT_DEFAULTS = {
13563
13803
  limit: 60,
13564
13804
  windowMs: 6e4,
@@ -13924,7 +14164,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
13924
14164
  const role = roles[0]?.trim();
13925
14165
  if (!role)
13926
14166
  throw new NetworkError("No IAM role found in IMDS response");
13927
- const credsRes = await fetchWithTimeout(`${endpoint}/latest/meta-data/iam/security-credentials/${role}`, { headers: { "x-aws-ec2-metadata-token": token } }, timeout);
14167
+ const credsRes = await fetchWithTimeout(`${endpoint}/latest/meta-data/iam/security-credentials/${encodeURIComponent(role)}`, { headers: { "x-aws-ec2-metadata-token": token } }, timeout);
13928
14168
  if (!credsRes.ok)
13929
14169
  throw new NetworkError(`IMDS credentials fetch failed: ${credsRes.status}`);
13930
14170
  const data = await credsRes.json();
@@ -14925,9 +15165,24 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
14925
15165
  const response = await computeDigestResponse(challenge, username, password, method, uri, cnonce, nc);
14926
15166
  return formatDigestAuth(challenge, username, response, uri, cnonce, nc);
14927
15167
  }
15168
+ function createDigestAuthorizer() {
15169
+ const counters = /* @__PURE__ */ new Map();
15170
+ return async (wwwAuth, username, password, method, uri) => {
15171
+ const challenge = parseDigestChallenge(wwwAuth);
15172
+ const cnonce = randomBytes(5);
15173
+ const scope = `${challenge.realm}\0${challenge.nonce}`;
15174
+ const next = (counters.get(scope) ?? 0) + 1;
15175
+ counters.set(scope, next);
15176
+ const nc = next.toString(16).padStart(8, "0");
15177
+ const response = await computeDigestResponse(challenge, username, password, method, uri, cnonce, nc);
15178
+ return formatDigestAuth(challenge, username, response, uri, cnonce, nc);
15179
+ };
15180
+ }
14928
15181
 
14929
15182
  // dist/esm/client.js
14930
15183
  init_core();
15184
+ init_core();
15185
+ var DEFAULT_MAX_REDIRECTS = 20;
14931
15186
  var DEFAULT_RETRY = {
14932
15187
  maxRetries: 3,
14933
15188
  baseDelayMs: 300,
@@ -15046,6 +15301,59 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
15046
15301
  "passwd",
15047
15302
  "secret"
15048
15303
  ]);
15304
+ var INTERCEPTOR_RESEND_DEPTH = "__interceptorResendDepth";
15305
+ var MAX_INTERCEPTOR_RESENDS = 5;
15306
+ var HAR_REDACT_PARAMS = /* @__PURE__ */ new Set([
15307
+ "api_key",
15308
+ "apikey",
15309
+ "access_token",
15310
+ "refresh_token",
15311
+ "id_token",
15312
+ "token",
15313
+ "auth",
15314
+ "authorization",
15315
+ "key",
15316
+ "secret",
15317
+ "password",
15318
+ "passwd",
15319
+ "signature",
15320
+ "sig",
15321
+ "x-amz-signature",
15322
+ "x-amz-credential",
15323
+ "x-amz-security-token",
15324
+ "x-goog-signature",
15325
+ "sas",
15326
+ "session",
15327
+ "sessionid",
15328
+ "jwt",
15329
+ "code"
15330
+ ]);
15331
+ var HAR_MAX_BODY_CHARS = 8192;
15332
+ function isHARBodySafeToRecord(contentType) {
15333
+ if (!contentType)
15334
+ return false;
15335
+ const ct = contentType.toLowerCase();
15336
+ return ct.includes("json") || ct.includes("xml") || ct.includes("text/plain") || ct.includes("application/javascript");
15337
+ }
15338
+ function redactHARUrl(url) {
15339
+ try {
15340
+ const u = new URL(url);
15341
+ let changed = false;
15342
+ for (const key of [...u.searchParams.keys()]) {
15343
+ if (HAR_REDACT_PARAMS.has(key.toLowerCase())) {
15344
+ u.searchParams.set(key, "***REDACTED***");
15345
+ changed = true;
15346
+ }
15347
+ }
15348
+ if (u.hash && (u.hash.includes("token") || u.hash.includes("sig") || u.hash.length > 1)) {
15349
+ u.hash = "#***REDACTED***";
15350
+ changed = true;
15351
+ }
15352
+ return changed ? u.toString() : url;
15353
+ } catch {
15354
+ return url.replace(/([?&])(api_key|apikey|access_token|refresh_token|token|secret|password|signature|sig)=([^&#]*)/gi, "$1$2=***REDACTED***");
15355
+ }
15356
+ }
15049
15357
  function redactHARHeader(name, value) {
15050
15358
  return HAR_REDACT_HEADERS.has(name.toLowerCase()) ? { name, value: "***REDACTED***" } : { name, value };
15051
15359
  }
@@ -15081,9 +15389,9 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
15081
15389
  let receiveMs = 0;
15082
15390
  try {
15083
15391
  if (typeof performance !== "undefined" && typeof performance.getEntriesByType === "function") {
15084
- const entries = performance.getEntriesByType("resource");
15085
- const entry2 = entries.filter((e) => e.name === res.url).pop();
15086
- if (entry2 && entry2.requestStart > 0) {
15392
+ const entries = typeof performance.getEntriesByName === "function" ? performance.getEntriesByName(res.url) : performance.getEntriesByType("resource").filter((e) => e.name === res.url);
15393
+ const entry2 = entries[entries.length - 1];
15394
+ if (entry2 && entry2.requestStart > 0 && Date.now() - entry2.startTime < 6e4) {
15087
15395
  sendMs = Math.max(0, entry2.responseStart - entry2.requestStart);
15088
15396
  receiveMs = Math.max(0, entry2.responseEnd - entry2.responseStart);
15089
15397
  waitMs = Math.max(0, total - sendMs - receiveMs);
@@ -15098,12 +15406,16 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
15098
15406
  time: total,
15099
15407
  request: {
15100
15408
  method: req.method,
15101
- url: req.url,
15409
+ // Redacted: HAR logs are routinely exported and shared, and a query
15410
+ // string is just as leaky as a header (?api_key=, ?access_token=,
15411
+ // ?signature=). Previously only headers were redacted, so the full URL
15412
+ // and every query value landed in the log verbatim.
15413
+ url: redactHARUrl(req.url),
15102
15414
  httpVersion: res.httpVersion,
15103
15415
  headers: Object.entries(req.headers).map(([name, value]) => redactHARHeader(name, value)),
15104
15416
  queryString: (() => {
15105
15417
  try {
15106
- return Array.from(new URL(req.url).searchParams.entries()).map(([name, value]) => ({
15418
+ return Array.from(new URL(redactHARUrl(req.url)).searchParams.entries()).map(([name, value]) => ({
15107
15419
  name,
15108
15420
  value
15109
15421
  }));
@@ -15131,9 +15443,12 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
15131
15443
  content: {
15132
15444
  size: res.rawBody?.byteLength ?? 0,
15133
15445
  mimeType: res.headers["content-type"] ?? "application/octet-stream",
15134
- ...typeof res.data === "string" ? { text: res.data } : {}
15446
+ // Body text is only kept for non-HTML payloads and is truncated:
15447
+ // response bodies routinely carry tokens and PII.
15448
+ ...typeof res.data === "string" && isHARBodySafeToRecord(res.headers["content-type"]) ? { text: res.data.slice(0, HAR_MAX_BODY_CHARS) } : {}
15135
15449
  },
15136
- redirectURL: res.headers["location"] ?? "",
15450
+ // The Location header can itself carry a signed URL — redact it too.
15451
+ redirectURL: res.headers["location"] ? redactHARUrl(res.headers["location"]) : "",
15137
15452
  bodySize: res.rawBody?.byteLength ?? 0
15138
15453
  },
15139
15454
  timings: {
@@ -15225,22 +15540,25 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
15225
15540
  }
15226
15541
  return { ...req, headers };
15227
15542
  }
15543
+ var FETCH_SPEC_STRIPPED_ON_REDIRECT = /* @__PURE__ */ new Set(["authorization", "cookie", "proxy-authorization"]);
15544
+ function hasForwardedCredentials(headers, auth) {
15545
+ if (auth && auth.type === "apikey")
15546
+ return true;
15547
+ if (!headers)
15548
+ return false;
15549
+ for (const name of Object.keys(headers)) {
15550
+ const lower = name.toLowerCase();
15551
+ if (CROSS_ORIGIN_STRIP_HEADERS.has(lower) && !FETCH_SPEC_STRIPPED_ON_REDIRECT.has(lower)) {
15552
+ return true;
15553
+ }
15554
+ }
15555
+ return false;
15556
+ }
15228
15557
  var CROSS_ORIGIN_STRIP_HEADERS = /* @__PURE__ */ new Set([
15229
- "authorization",
15230
- "cookie",
15231
- "proxy-authorization",
15232
- "x-api-key",
15233
- "x-auth-token",
15234
- "x-access-token",
15235
- "x-refresh-token",
15236
- "x-csrf-token",
15237
- "x-session-id",
15238
- "x-session-token",
15239
- "x-secret",
15240
- "x-secret-key",
15241
- "x-private-key",
15242
- "api-key",
15243
- "apikey",
15558
+ ...CREDENTIAL_HEADERS,
15559
+ // Response-only per RFC 9110, so it can never legitimately appear on an
15560
+ // outgoing request — kept in the strip list as defence in depth for callers
15561
+ // that copy a full header bag (including response headers) onto a request.
15244
15562
  "www-authenticate"
15245
15563
  ]);
15246
15564
  function redactUserInfo(url) {
@@ -15520,6 +15838,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
15520
15838
  }
15521
15839
  if (config.auth?.type === "digest") {
15522
15840
  const digestConfig = config.auth;
15841
+ const digestAuthorizer = createDigestAuthorizer();
15523
15842
  this.interceptors.addResponse(async (ctx) => {
15524
15843
  if (!ctx.response)
15525
15844
  return;
@@ -15531,8 +15850,9 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
15531
15850
  if (ctx.request.meta.__digestRetried)
15532
15851
  return;
15533
15852
  const method = ctx.request.method;
15534
- const uri = new URL(ctx.request.url).pathname + new URL(ctx.request.url).search;
15535
- const authHeader = await createDigestAuthorization(wwwAuth, digestConfig.username, digestConfig.password, method, uri);
15853
+ const parsedUrl = new URL(ctx.request.url);
15854
+ const uri = parsedUrl.pathname + parsedUrl.search;
15855
+ const authHeader = await digestAuthorizer(wwwAuth, digestConfig.username, digestConfig.password, method, uri);
15536
15856
  return {
15537
15857
  ...ctx.request,
15538
15858
  headers: { ...ctx.request.headers, authorization: authHeader },
@@ -15628,22 +15948,24 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
15628
15948
  const errEject = this.useError(async (ctx) => {
15629
15949
  if (!ctx.error)
15630
15950
  return;
15951
+ const hookReq = {
15952
+ url: ctx.request.url,
15953
+ method: ctx.request.method,
15954
+ headers: ctx.request.headers,
15955
+ body: ctx.request.body,
15956
+ signal: ctx.request.signal,
15957
+ meta: ctx.request.meta
15958
+ };
15959
+ const errResponse = toHookResponse(ctx.error.response, hookReq);
15631
15960
  const hookErr = {
15632
15961
  error: ctx.error,
15633
- request: {
15634
- url: ctx.request.url,
15635
- method: ctx.request.method,
15636
- headers: ctx.request.headers,
15637
- body: ctx.request.body,
15638
- signal: ctx.request.signal,
15639
- meta: ctx.request.meta
15640
- },
15641
- response: null,
15962
+ request: hookReq,
15963
+ response: errResponse,
15642
15964
  attempt: ctx.attempt
15643
15965
  };
15644
15966
  const hookCtx = {
15645
15967
  request: hookErr.request,
15646
- response: null,
15968
+ response: errResponse,
15647
15969
  error: ctx.error,
15648
15970
  startedAt: ctx.startedAt,
15649
15971
  attempt: ctx.attempt,
@@ -16143,7 +16465,11 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16143
16465
  } else if (options.body instanceof ReadableStream) {
16144
16466
  throw new KinetexError(`maxRequestSize cannot be enforced for ReadableStream bodies \u2014 pass maxRequestSize: 0 to opt out, or buffer the body first`, "EVALIDATION");
16145
16467
  } else if (options.body && typeof options.body === "object") {
16146
- bodySize2 = new TextEncoder().encode(JSON.stringify(options.body)).byteLength;
16468
+ try {
16469
+ bodySize2 = new TextEncoder().encode(JSON.stringify(options.body)).byteLength;
16470
+ } catch (err) {
16471
+ throw new KinetexError(`Cannot measure request body size: ${err instanceof Error ? err.message : String(err)}`, "EVALIDATION");
16472
+ }
16147
16473
  }
16148
16474
  if (bodySize2 > maxRequestSize) {
16149
16475
  throw new KinetexError(`Request body size ${bodySize2} bytes exceeds limit of ${maxRequestSize} bytes`, "EVALIDATION");
@@ -16151,10 +16477,22 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16151
16477
  }
16152
16478
  let req = {
16153
16479
  url: fullUrl,
16154
- method,
16480
+ // Use the normalized method, not the caller's casing: `send(url, "patch")`
16481
+ // passed validation above but used to put the literal string "patch" on
16482
+ // the wire. fetch() only normalizes delete/get/head/options/post/put, so a
16483
+ // lowercase PATCH/CONNECT went out verbatim and servers answered 405.
16484
+ method: normalizedMethod,
16155
16485
  headers: mergeHeaders(this.cfg.headers, options.headers),
16486
+ // A plain object/array is accepted by the public API (RequestBody) and is
16487
+ // JSON-encoded in the block immediately below, so the request object only
16488
+ // ever holds a real BodyInit by the time this function returns.
16156
16489
  body: options.body ?? null,
16157
16490
  signal: options.signal ?? null,
16491
+ // Resolved once here so the manual redirect follower sees the same
16492
+ // effective values the caller asked for. Spread (not `??`) because the
16493
+ // project uses exactOptionalPropertyTypes.
16494
+ ...options.followRedirects !== void 0 || this.cfg.followRedirects !== void 0 ? { followRedirects: options.followRedirects ?? this.cfg.followRedirects } : {},
16495
+ ...options.maxRedirects !== void 0 || this.cfg.maxRedirects !== void 0 ? { maxRedirects: options.maxRedirects ?? this.cfg.maxRedirects } : {},
16158
16496
  meta: { ...options.meta },
16159
16497
  httpVersion: options.httpVersion ?? this.cfg.httpVersion ?? "HTTP/2"
16160
16498
  };
@@ -16165,6 +16503,12 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16165
16503
  body: JSON.stringify(req.body)
16166
16504
  };
16167
16505
  }
16506
+ if (req.body !== null && typeof URLSearchParams !== "undefined" && req.body instanceof URLSearchParams && !req.headers["content-type"]) {
16507
+ req = {
16508
+ ...req,
16509
+ headers: { ...req.headers, "content-type": "application/x-www-form-urlencoded" }
16510
+ };
16511
+ }
16168
16512
  const auth = options.auth !== false ? options.auth ?? this.cfg.auth : void 0;
16169
16513
  if (auth)
16170
16514
  req = await applyAuth(req, auth);
@@ -16177,31 +16521,44 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16177
16521
  req = { ...req, headers: { ...req.headers, cookie: cookieHeader } };
16178
16522
  }
16179
16523
  let _otelSpan = null;
16180
- if (this._otelTracer) {
16181
- _otelSpan = this._otelTracer.startSpan(`HTTP ${req.method}`, {
16182
- kind: 3
16183
- /* CLIENT */
16184
- });
16185
- const { traceparent, traceId, spanId } = buildTraceparent(_otelSpan);
16186
- _otelSpan.setAttribute("http.request.method", req.method);
16187
- _otelSpan.setAttribute("url.full", req.url);
16188
- try {
16189
- _otelSpan.setAttribute("server.address", new URL(req.url).hostname);
16190
- } catch {
16524
+ try {
16525
+ if (this._otelTracer) {
16526
+ _otelSpan = this._otelTracer.startSpan(`HTTP ${req.method}`, {
16527
+ kind: 3
16528
+ /* CLIENT */
16529
+ });
16530
+ const { traceparent, traceId, spanId } = buildTraceparent(_otelSpan);
16531
+ _otelSpan.setAttribute("http.request.method", req.method);
16532
+ _otelSpan.setAttribute("url.full", req.url);
16533
+ try {
16534
+ _otelSpan.setAttribute("server.address", new URL(req.url).hostname);
16535
+ } catch {
16536
+ }
16537
+ req = {
16538
+ ...req,
16539
+ headers: { ...req.headers, traceparent },
16540
+ meta: { ...req.meta, traceId, spanId }
16541
+ };
16542
+ } else if (req.meta["traceId"] && !req.headers["traceparent"]) {
16543
+ const traceId = String(req.meta["traceId"]);
16544
+ const spanId = randomHex(16);
16545
+ req = {
16546
+ ...req,
16547
+ headers: { ...req.headers, traceparent: `00-${traceId}-${spanId}-01` },
16548
+ meta: { ...req.meta, spanId }
16549
+ };
16191
16550
  }
16192
- req = {
16193
- ...req,
16194
- headers: { ...req.headers, traceparent },
16195
- meta: { ...req.meta, traceId, spanId }
16196
- };
16197
- } else if (req.meta["traceId"] && !req.headers["traceparent"]) {
16198
- const traceId = String(req.meta["traceId"]);
16199
- const spanId = randomHex(16);
16200
- req = {
16201
- ...req,
16202
- headers: { ...req.headers, traceparent: `00-${traceId}-${spanId}-01` },
16203
- meta: { ...req.meta, spanId }
16204
- };
16551
+ } catch (tracingErr) {
16552
+ if (_otelSpan) {
16553
+ _otelSpan.setStatus({
16554
+ code: 2,
16555
+ message: tracingErr instanceof Error ? tracingErr.message : String(tracingErr)
16556
+ });
16557
+ if (tracingErr instanceof Error)
16558
+ _otelSpan.recordException(tracingErr);
16559
+ _otelSpan.end();
16560
+ }
16561
+ throw tracingErr;
16205
16562
  }
16206
16563
  let _cbOrigin;
16207
16564
  if (this._circuitBreakerKeyFn) {
@@ -16218,8 +16575,11 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16218
16575
  }
16219
16576
  }
16220
16577
  const _execFactory = () => this._executeWithRetry(req, retryCfg, timeoutMs, options, startMs, wallClockMs);
16221
- const authFp = await getAuthFingerprint(req.headers ?? {});
16222
- const _dedupKey = `${req.method}:${req.url}${authFp ? ":" + authFp : ""}`;
16578
+ let _dedupKey = "";
16579
+ if (this._dedup) {
16580
+ const authFp = await getAuthFingerprint(req.headers ?? {});
16581
+ _dedupKey = `${req.method}:${req.url}${authFp ? ":" + authFp : ""}`;
16582
+ }
16223
16583
  const _dedupedFactory = this._dedup ? () => this._dedup.execute(req.method, _dedupKey, _execFactory).then((r) => r) : _execFactory;
16224
16584
  const _guardedFactory = this._circuitBreakers ? () => this._circuitBreakers.execute(_cbOrigin, _dedupedFactory).then((r) => r) : _dedupedFactory;
16225
16585
  try {
@@ -16257,7 +16617,10 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16257
16617
  while (true) {
16258
16618
  attempt++;
16259
16619
  if (req.signal?.aborted) {
16260
- throw createAbortError();
16620
+ throw createAbortError(req);
16621
+ }
16622
+ if (attempt > 1 && isNonReplayableBody(req.body)) {
16623
+ throw new KinetexError("Cannot retry a request whose body is a stream or Blob \u2014 the body was consumed by the first attempt. Buffer it first, or disable retry for this request.", "EVALIDATION", { request: req });
16261
16624
  }
16262
16625
  try {
16263
16626
  const res = await this._executeOnce(req, timeout, options, startMs, attempt, wallClockMs, retryCfg);
@@ -16283,6 +16646,15 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16283
16646
  }
16284
16647
  return res;
16285
16648
  } catch (err) {
16649
+ if (globalThis.__KINETEX_DEBUG_RETRY) {
16650
+ console.log("DBG catch", {
16651
+ attempt,
16652
+ maxRetries: retryCfg === false ? "FALSE" : retryCfg?.maxRetries,
16653
+ methods: retryCfg === false ? "FALSE" : retryCfg?.methods,
16654
+ code: err?.code,
16655
+ method: req.method
16656
+ });
16657
+ }
16286
16658
  if (retryCfg && attempt <= retryCfg.maxRetries) {
16287
16659
  const retryCtx = {
16288
16660
  request: req,
@@ -16317,11 +16689,22 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16317
16689
  // intermediate redirect responses. When a cookie jar is active we must follow
16318
16690
  // redirects ourselves one hop at a time so we can capture cookies at each step.
16319
16691
  /**
16320
- * Follow redirects manually, one hop at a time, to capture Set-Cookie headers.
16321
- * fetch() auto-follows redirects but silently drops Set-Cookie from intermediary hops.
16692
+ * Follow redirects manually, one hop at a time.
16693
+ *
16694
+ * Two independent reasons this path exists:
16695
+ * 1. fetch() auto-follows redirects but silently drops Set-Cookie from
16696
+ * intermediary hops, so an active cookie jar must see every hop itself.
16697
+ * 2. Per the Fetch spec, a cross-origin redirect only drops
16698
+ * `authorization` / `cookie` / `proxy-authorization`. Custom credential
16699
+ * headers (apikey, X-Company-Key, ...) are forwarded verbatim, so any
16700
+ * request carrying one must also be followed manually.
16701
+ *
16702
+ * @param jar - Optional cookie jar. When omitted, no cookie header is
16703
+ * rebuilt and intermediate Set-Cookie headers are ignored.
16322
16704
  */
16323
16705
  async _sendFollowingRedirects(req, timeout, jar, appliedAuth) {
16324
- const MAX_REDIRECTS = 20;
16706
+ const maxRedirects = Math.max(0, req.maxRedirects ?? DEFAULT_MAX_REDIRECTS);
16707
+ const followRedirects = req.followRedirects !== false && maxRedirects > 0;
16325
16708
  let currentReq = { ...req, redirect: "manual" };
16326
16709
  const origin0 = (() => {
16327
16710
  try {
@@ -16331,7 +16714,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16331
16714
  }
16332
16715
  })();
16333
16716
  const visited = /* @__PURE__ */ new Set();
16334
- for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
16717
+ for (let hop = 0; hop <= maxRedirects; hop++) {
16335
16718
  if (hop > 0 && appliedAuth) {
16336
16719
  const sameOrigin = (() => {
16337
16720
  try {
@@ -16374,11 +16757,13 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16374
16757
  });
16375
16758
  }
16376
16759
  visited.add(raw.url);
16377
- jar.processResponseHeaders(raw.headers, {
16760
+ jar?.processResponseHeaders(raw.headers, {
16378
16761
  url: raw.url
16379
16762
  });
16380
- if (hop === MAX_REDIRECTS) {
16381
- throw new KinetexError(`Too many redirects (exceeded ${MAX_REDIRECTS})`, "ENETWORK", {
16763
+ if (!followRedirects)
16764
+ return { ...raw, redirected: true };
16765
+ if (hop === maxRedirects) {
16766
+ throw new KinetexError(`Too many redirects (exceeded ${maxRedirects})`, "ENETWORK", {
16382
16767
  request: req
16383
16768
  });
16384
16769
  }
@@ -16397,6 +16782,12 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16397
16782
  if (protocol !== "http:" && protocol !== "https:") {
16398
16783
  throw new KinetexError(`Unsafe redirect to ${protocol} detected \u2014 only HTTP(S) allowed`, "ENETWORK", { request: req });
16399
16784
  }
16785
+ if (!isSafeURL(nextUrl)) {
16786
+ throw new KinetexError(`Unsafe redirect target blocked: ${redactUserInfo(location)}`, "EVALIDATION", { request: req });
16787
+ }
16788
+ if (this.cfg.httpsOnly && protocol !== "https:") {
16789
+ throw new KinetexError(`HTTPS-only mode enabled but redirect target uses ${protocol}`, "EVALIDATION", { request: req });
16790
+ }
16400
16791
  } catch (err) {
16401
16792
  if (err instanceof KinetexError)
16402
16793
  throw err;
@@ -16407,21 +16798,30 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16407
16798
  }
16408
16799
  const nextMethod = raw.status === 301 || raw.status === 302 || raw.status === 303 ? "GET" : currentReq.method;
16409
16800
  const nextBody = nextMethod === "GET" || nextMethod === "HEAD" ? null : currentReq.body;
16410
- const cookieHeader = jar.getCookieHeader({ url: nextUrl, http: true });
16411
16801
  const nextHeaders = { ...currentReq.headers };
16412
- if (cookieHeader) {
16413
- nextHeaders["cookie"] = cookieHeader;
16414
- } else {
16415
- delete nextHeaders["cookie"];
16416
- }
16802
+ let crossOrigin = false;
16417
16803
  try {
16418
- if (new URL(nextUrl).origin !== new URL(currentReq.url).origin) {
16419
- for (const h of CROSS_ORIGIN_STRIP_HEADERS) {
16420
- delete nextHeaders[h];
16421
- }
16422
- }
16804
+ crossOrigin = new URL(nextUrl).origin !== new URL(currentReq.url).origin;
16423
16805
  } catch {
16424
16806
  }
16807
+ if (crossOrigin) {
16808
+ for (const h of CROSS_ORIGIN_STRIP_HEADERS) {
16809
+ delete nextHeaders[h];
16810
+ }
16811
+ if (appliedAuth && appliedAuth.type === "apikey") {
16812
+ delete nextHeaders[appliedAuth.header.toLowerCase()];
16813
+ }
16814
+ }
16815
+ if (jar) {
16816
+ const cookieHeader = jar.getCookieHeader({ url: nextUrl, http: true });
16817
+ if (cookieHeader) {
16818
+ nextHeaders["cookie"] = cookieHeader;
16819
+ } else {
16820
+ delete nextHeaders["cookie"];
16821
+ }
16822
+ } else if (crossOrigin) {
16823
+ delete nextHeaders["cookie"];
16824
+ }
16425
16825
  currentReq = {
16426
16826
  ...currentReq,
16427
16827
  url: nextUrl,
@@ -16432,7 +16832,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16432
16832
  };
16433
16833
  continue;
16434
16834
  }
16435
- return raw;
16835
+ return hop > 0 ? { ...raw, redirected: true } : raw;
16436
16836
  }
16437
16837
  throw new KinetexError("Redirect loop", "ENETWORK", { request: req });
16438
16838
  }
@@ -16519,7 +16919,10 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16519
16919
  }
16520
16920
  }
16521
16921
  } finally {
16522
- (await this.getCache())?.clearSWRInFlight(cacheReq);
16922
+ try {
16923
+ (await this.getCache())?.clearSWRInFlight(cacheReq);
16924
+ } catch {
16925
+ }
16523
16926
  }
16524
16927
  })();
16525
16928
  }
@@ -16597,15 +17000,17 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16597
17000
  ...req,
16598
17001
  headers: {
16599
17002
  ...req.headers,
16600
- "accept-encoding": "gzip, deflate, br"
17003
+ "accept-encoding": DEFAULT_ACCEPT_ENCODING
16601
17004
  }
16602
17005
  };
16603
17006
  }
16604
17007
  const dispatchJar = await this.getCookieJar();
17008
+ const effectiveAuth = options.auth !== false ? options.auth ?? this.cfg.auth : void 0;
17009
+ const needsManualRedirects = dispatchJar !== null || hasForwardedCredentials(req.headers, effectiveAuth);
16605
17010
  this._trace(_traceId, "transport_send", "start", startMs, attempt);
16606
17011
  let raw;
16607
17012
  try {
16608
- raw = dispatchJar ? await this._sendFollowingRedirects(req, timeout, dispatchJar, options.auth !== false ? options.auth ?? this.cfg.auth : void 0) : await sendWithTimeout(this.transport, req, timeout);
17013
+ raw = needsManualRedirects ? await this._sendFollowingRedirects(req, timeout, dispatchJar ?? void 0, effectiveAuth) : await sendWithTimeout(this.transport, req, timeout);
16609
17014
  } catch (err) {
16610
17015
  if (_uploadStreamForCleanup) {
16611
17016
  try {
@@ -16649,9 +17054,10 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16649
17054
  },
16650
17055
  ...req.signal !== null ? { signal: req.signal } : {}
16651
17056
  });
17057
+ const source = bodyStream;
16652
17058
  bodyStream = new ReadableStream({
16653
17059
  async start(controller) {
16654
- const reader = bodyStream.getReader();
17060
+ const reader = source.getReader();
16655
17061
  try {
16656
17062
  while (true) {
16657
17063
  const { done, value } = await reader.read();
@@ -16669,8 +17075,9 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16669
17075
  reader.releaseLock();
16670
17076
  }
16671
17077
  },
16672
- cancel() {
16673
- bodyStream?.cancel();
17078
+ cancel(reason) {
17079
+ void source.cancel(reason).catch(() => {
17080
+ });
16674
17081
  }
16675
17082
  });
16676
17083
  }
@@ -16691,7 +17098,9 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16691
17098
  status: raw.status,
16692
17099
  statusText: raw.statusText,
16693
17100
  headers: raw.headers,
16694
- data: this.cfg.transformResponse ? this.cfg.transformResponse(data, {}) : data,
17101
+ // transformResponse is applied below, once `res` exists, so it receives
17102
+ // the real response object instead of the `{}` placeholder it used to get.
17103
+ data,
16695
17104
  rawBody,
16696
17105
  url: raw.url,
16697
17106
  cached: false,
@@ -16701,6 +17110,9 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16701
17110
  request: req,
16702
17111
  attempt
16703
17112
  };
17113
+ if (this.cfg.transformResponse) {
17114
+ res.data = this.cfg.transformResponse(res.data, res);
17115
+ }
16704
17116
  if (options.cache !== false && this.cfg.cache) {
16705
17117
  const cache = await this.getCache();
16706
17118
  if (cache && raw.status !== 304) {
@@ -16769,6 +17181,10 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16769
17181
  if ("status" in result && "headers" in result) {
16770
17182
  current = result;
16771
17183
  } else if ("url" in result && "method" in result && !("status" in result)) {
17184
+ const resendDepth = Number(_req.meta[INTERCEPTOR_RESEND_DEPTH] ?? 0);
17185
+ if (resendDepth >= MAX_INTERCEPTOR_RESENDS) {
17186
+ throw new KinetexError(`Response interceptor re-send limit reached (${MAX_INTERCEPTOR_RESENDS}) \u2014 refusing to loop`, "EVALIDATION", { request: _req });
17187
+ }
16772
17188
  if (retryCfg && attempt <= retryCfg.maxRetries) {
16773
17189
  const retryCtx = {
16774
17190
  request: _req,
@@ -16785,7 +17201,13 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16785
17201
  }, Promise.resolve());
16786
17202
  await sleep6(delay, _req.signal);
16787
17203
  }
16788
- return this._executeOnce(result, timeout, options, startMs, attempt + 1, void 0, retryCfg);
17204
+ return this._executeOnce({
17205
+ ...result,
17206
+ meta: {
17207
+ ...result.meta,
17208
+ [INTERCEPTOR_RESEND_DEPTH]: resendDepth + 1
17209
+ }
17210
+ }, timeout, options, startMs, attempt + 1, void 0, retryCfg);
16789
17211
  }
16790
17212
  }
16791
17213
  return current;
@@ -17063,7 +17485,10 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17063
17485
  * Call this when the client is no longer needed to prevent memory leaks.
17064
17486
  * @returns A promise that resolves when cleanup is complete.
17065
17487
  */
17066
- async destroy() {
17488
+ // Not `async`: nothing here awaits, so the returned promise is resolved
17489
+ // explicitly instead. The signature stays Promise<void> — callers and the
17490
+ // docs `await client.destroy()`.
17491
+ destroy() {
17067
17492
  for (const ws of this._wsClients) {
17068
17493
  try {
17069
17494
  ws.close(1e3, "Client destroyed");
@@ -17071,12 +17496,6 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17071
17496
  }
17072
17497
  }
17073
17498
  this._wsClients.clear();
17074
- if (this._cache) {
17075
- try {
17076
- await this._cache.clear();
17077
- } catch {
17078
- }
17079
- }
17080
17499
  if (IS_NODE && this.transport && "destroy" in this.transport) {
17081
17500
  this.transport.destroy();
17082
17501
  }
@@ -17086,6 +17505,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17086
17505
  this._circuitBreakers?.clear?.();
17087
17506
  this._otelTracer = null;
17088
17507
  this.interceptors.clear();
17508
+ return Promise.resolve();
17089
17509
  }
17090
17510
  };
17091
17511
  var FluentRequest = class {
@@ -17314,13 +17734,19 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17314
17734
  }).catch((err) => onError?.(err instanceof KinetexError ? err : new KinetexError(String(err), "EUNKNOWN")));
17315
17735
  }
17316
17736
  };
17317
- function createAbortError() {
17318
- if (typeof DOMException !== "undefined") {
17319
- return new DOMException("Aborted", "AbortError");
17320
- }
17321
- const err = new Error("Aborted");
17322
- err.name = "AbortError";
17323
- return err;
17737
+ function toHookResponse(res, request) {
17738
+ if (!res)
17739
+ return null;
17740
+ return {
17741
+ status: res.status,
17742
+ statusText: res.statusText,
17743
+ headers: res.headers,
17744
+ body: res.rawBody ?? null,
17745
+ request
17746
+ };
17747
+ }
17748
+ function createAbortError(request) {
17749
+ return new AbortError(request);
17324
17750
  }
17325
17751
  function mergeParams(a, b) {
17326
17752
  if (!a && !b)
@@ -17344,6 +17770,11 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17344
17770
  signal?.addEventListener("abort", onAbort, { once: true });
17345
17771
  });
17346
17772
  }
17773
+ function isNonReplayableBody(body) {
17774
+ if (body instanceof ReadableStream)
17775
+ return true;
17776
+ return typeof Blob !== "undefined" && body instanceof Blob;
17777
+ }
17347
17778
  function perfNow4() {
17348
17779
  return typeof performance !== "undefined" ? performance.now() : Date.now();
17349
17780
  }
@@ -17362,7 +17793,13 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17362
17793
  constructor(client, options = {}) {
17363
17794
  /** The parent Kinetex instance used to send requests. */
17364
17795
  __publicField(this, "_client");
17365
- /** Maximum number of requests to flush at once. */
17796
+ /**
17797
+ * Maximum number of requests taken out of the queue per flush.
17798
+ * NOTE: this is a batching size, NOT a concurrency limit — every request in a
17799
+ * batch is dispatched immediately and in parallel, and `flush()` drains the
17800
+ * whole queue the same way. Use `maxBatch` to bound how much is dispatched per
17801
+ * tick, and a semaphore or rate limiter to bound actual parallelism.
17802
+ */
17366
17803
  __publicField(this, "_maxBatch");
17367
17804
  /** Milliseconds to wait before flushing an incomplete batch. */
17368
17805
  __publicField(this, "_flushMs");
@@ -17371,8 +17808,16 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17371
17808
  /** Timer handle for deferred flush. */
17372
17809
  __publicField(this, "_timer", null);
17373
17810
  this._client = client;
17374
- this._maxBatch = options.maxBatch ?? 100;
17375
- this._flushMs = options.flushMs ?? 0;
17811
+ const maxBatch = options.maxBatch ?? 100;
17812
+ if (!Number.isInteger(maxBatch) || maxBatch < 1) {
17813
+ throw new RangeError(`BatchQueue maxBatch must be a positive integer (got ${maxBatch})`);
17814
+ }
17815
+ this._maxBatch = maxBatch;
17816
+ const flushMs = options.flushMs ?? 0;
17817
+ if (!Number.isFinite(flushMs) || flushMs < 0) {
17818
+ throw new RangeError(`BatchQueue flushMs must be a non-negative finite number (got ${flushMs})`);
17819
+ }
17820
+ this._flushMs = flushMs;
17376
17821
  }
17377
17822
  /**
17378
17823
  * Enqueue a request. Returns a promise that resolves when the batch
@@ -17425,7 +17870,8 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17425
17870
  * Loops until the queue is empty so items beyond maxBatch are not orphaned.
17426
17871
  */
17427
17872
  flush() {
17428
- while (this._queue.length > 0)
17873
+ let guard = this._queue.length + 1;
17874
+ while (this._queue.length > 0 && guard-- > 0)
17429
17875
  this._flushNow();
17430
17876
  }
17431
17877
  /** How many requests are currently queued (not yet sent). */
@@ -18420,28 +18866,27 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18420
18866
  }
18421
18867
  var denoTcpConnector = async (host, port, timeoutMs) => {
18422
18868
  const denoGlobal = globalThis;
18423
- let conn;
18424
- if (timeoutMs && timeoutMs > 0) {
18425
- conn = await Promise.race([
18426
- denoGlobal.Deno.connect({ hostname: host, port, transport: "tcp" }),
18427
- new Promise((_, rej) => setTimeout(() => rej(new Socks5Error("TCP connect to proxy timed out", "SOCKS5_TIMEOUT", true)), timeoutMs))
18428
- ]);
18429
- } else {
18430
- conn = await denoGlobal.Deno.connect({ hostname: host, port, transport: "tcp" });
18431
- }
18432
- const wrappedRead = async (buf) => {
18433
- if (timeoutMs && timeoutMs > 0) {
18434
- return Promise.race([
18435
- conn.read(buf).catch(() => null),
18436
- new Promise((_, rej) => setTimeout(() => rej(new Socks5Error("TCP read timed out", "SOCKS5_TIMEOUT", true)), timeoutMs))
18437
- ]);
18438
- }
18869
+ const withTimeout2 = async (work, message) => {
18870
+ if (!timeoutMs || timeoutMs <= 0)
18871
+ return work;
18872
+ let timer;
18439
18873
  try {
18440
- return await conn.read(buf);
18441
- } catch {
18442
- return null;
18874
+ return await Promise.race([
18875
+ work,
18876
+ new Promise((_, rej) => {
18877
+ timer = setTimeout(() => rej(new Socks5Error(message, "SOCKS5_TIMEOUT", true)), timeoutMs);
18878
+ })
18879
+ ]);
18880
+ } finally {
18881
+ if (timer !== void 0)
18882
+ clearTimeout(timer);
18443
18883
  }
18444
18884
  };
18885
+ const conn = await withTimeout2(denoGlobal.Deno.connect({ hostname: host, port, transport: "tcp" }), "TCP connect to proxy timed out");
18886
+ const wrappedRead = (buf) => {
18887
+ const read = conn.read(buf).catch(() => null);
18888
+ return withTimeout2(read, "TCP read timed out");
18889
+ };
18445
18890
  return {
18446
18891
  read: wrappedRead,
18447
18892
  write: (data) => conn.write(data),
@@ -18494,7 +18939,9 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18494
18939
  buffer = next;
18495
18940
  flushBuffer();
18496
18941
  });
18942
+ let ended = false;
18497
18943
  socket.on("end", () => {
18944
+ ended = true;
18498
18945
  if (pendingRead) {
18499
18946
  pendingRead(null);
18500
18947
  pendingRead = null;
@@ -18502,6 +18949,9 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18502
18949
  pendingBuf = null;
18503
18950
  }
18504
18951
  });
18952
+ socket.on("close", () => {
18953
+ ended = true;
18954
+ });
18505
18955
  socket.once("error", (err) => {
18506
18956
  clearTimeout(timer);
18507
18957
  lastError = err;
@@ -18520,6 +18970,10 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18520
18970
  rej(lastError);
18521
18971
  return;
18522
18972
  }
18973
+ if (ended && buffer.length === 0) {
18974
+ res(null);
18975
+ return;
18976
+ }
18523
18977
  if (buffer.length > 0) {
18524
18978
  const n = Math.min(buffer.length, buf.length);
18525
18979
  buf.set(buffer.subarray(0, n));