kinetex 1.2.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 (86) hide show
  1. package/README.md +919 -445
  2. package/dist/browser/kinetex.esm.js +18 -18
  3. package/dist/browser/kinetex.js +663 -246
  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 +46 -7
  12. package/dist/cjs/dedup.js +9 -7
  13. package/dist/cjs/digest.js +26 -0
  14. package/dist/cjs/headers.js +64 -8
  15. package/dist/cjs/interceptors.js +64 -22
  16. package/dist/cjs/logging.js +1 -1
  17. package/dist/cjs/pagination.js +14 -6
  18. package/dist/cjs/progress.js +122 -42
  19. package/dist/cjs/socks5.js +36 -21
  20. package/dist/cjs/sse.js +48 -11
  21. package/dist/cjs/worker.js +6 -6
  22. package/dist/cjs/ws.js +13 -6
  23. package/dist/esm/aws-sigv4.js +4 -1
  24. package/dist/esm/aws-sigv4.js.map +1 -1
  25. package/dist/esm/cache.js +52 -14
  26. package/dist/esm/cache.js.map +1 -1
  27. package/dist/esm/circuit-breaker.js +24 -4
  28. package/dist/esm/circuit-breaker.js.map +1 -1
  29. package/dist/esm/client.js +495 -131
  30. package/dist/esm/client.js.map +1 -1
  31. package/dist/esm/cookie-parser.js +7 -4
  32. package/dist/esm/cookie-parser.js.map +1 -1
  33. package/dist/esm/cookie-store.js +16 -8
  34. package/dist/esm/cookie-store.js.map +1 -1
  35. package/dist/esm/core.js +46 -7
  36. package/dist/esm/core.js.map +1 -1
  37. package/dist/esm/dedup.js +9 -7
  38. package/dist/esm/dedup.js.map +1 -1
  39. package/dist/esm/digest.js +26 -0
  40. package/dist/esm/digest.js.map +1 -1
  41. package/dist/esm/headers.js +64 -8
  42. package/dist/esm/headers.js.map +1 -1
  43. package/dist/esm/interceptors.js +64 -22
  44. package/dist/esm/interceptors.js.map +1 -1
  45. package/dist/esm/logging.js +1 -1
  46. package/dist/esm/pagination.js +14 -6
  47. package/dist/esm/pagination.js.map +1 -1
  48. package/dist/esm/progress.js +122 -42
  49. package/dist/esm/progress.js.map +1 -1
  50. package/dist/esm/socks5.js +36 -21
  51. package/dist/esm/socks5.js.map +1 -1
  52. package/dist/esm/sse.js +48 -11
  53. package/dist/esm/sse.js.map +1 -1
  54. package/dist/esm/types.js.map +1 -1
  55. package/dist/esm/worker.js +6 -6
  56. package/dist/esm/worker.js.map +1 -1
  57. package/dist/esm/ws.js +13 -6
  58. package/dist/esm/ws.js.map +1 -1
  59. package/dist/types/aws-sigv4.d.ts.map +1 -1
  60. package/dist/types/cache.d.ts +8 -1
  61. package/dist/types/cache.d.ts.map +1 -1
  62. package/dist/types/circuit-breaker.d.ts.map +1 -1
  63. package/dist/types/client.d.ts +29 -12
  64. package/dist/types/client.d.ts.map +1 -1
  65. package/dist/types/cookie-parser.d.ts.map +1 -1
  66. package/dist/types/cookie-store.d.ts.map +1 -1
  67. package/dist/types/core.d.ts +6 -0
  68. package/dist/types/core.d.ts.map +1 -1
  69. package/dist/types/dedup.d.ts +0 -7
  70. package/dist/types/dedup.d.ts.map +1 -1
  71. package/dist/types/digest.d.ts +14 -0
  72. package/dist/types/digest.d.ts.map +1 -1
  73. package/dist/types/headers.d.ts +25 -10
  74. package/dist/types/headers.d.ts.map +1 -1
  75. package/dist/types/interceptors.d.ts.map +1 -1
  76. package/dist/types/logging.d.ts +1 -1
  77. package/dist/types/pagination.d.ts.map +1 -1
  78. package/dist/types/progress.d.ts.map +1 -1
  79. package/dist/types/socks5.d.ts.map +1 -1
  80. package/dist/types/sse.d.ts.map +1 -1
  81. package/dist/types/types.d.ts +25 -2
  82. package/dist/types/types.d.ts.map +1 -1
  83. package/dist/types/worker.d.ts +6 -6
  84. package/dist/types/worker.d.ts.map +1 -1
  85. package/dist/types/ws.d.ts.map +1 -1
  86. package/package.json +2 -2
@@ -1,4 +1,4 @@
1
- /* kinetex v1.2.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;
@@ -902,6 +902,7 @@ var kinetex = (() => {
902
902
  // dist/esm/cache.js
903
903
  var cache_exports = {};
904
904
  __export(cache_exports, {
905
+ CREDENTIAL_HEADERS: () => CREDENTIAL_HEADERS,
905
906
  CloudflareKVAdapter: () => CloudflareKVAdapter,
906
907
  HTTPCache: () => HTTPCache,
907
908
  MemoryStorageAdapter: () => MemoryStorageAdapter,
@@ -993,18 +994,16 @@ var kinetex = (() => {
993
994
  return parts.join(";");
994
995
  }
995
996
  function normalizeCacheKeyPart(part) {
996
- const controlRanges = [
997
- String.fromCharCode(0) + "-" + String.fromCharCode(31),
998
- String.fromCharCode(127) + "-" + String.fromCharCode(159)
999
- ].join("");
1000
- const controlRegex = new RegExp(`[${controlRanges}]`, "g");
1001
- return part.replaceAll(controlRegex, "").replaceAll(/\s+/g, " ").trim();
997
+ return part.replaceAll(CONTROL_CHARS, "").replaceAll(/\s+/g, " ").trim();
1002
998
  }
1003
999
  async function getAuthFingerprint(headers) {
1004
- 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
+ }
1005
1004
  const parts = [];
1006
- for (const header of authHeaders) {
1007
- const value = headers[header.toLowerCase()];
1005
+ for (const header of CREDENTIAL_HEADERS) {
1006
+ const value = lowered.get(header);
1008
1007
  if (value)
1009
1008
  parts.push(`${header}=${value}`);
1010
1009
  }
@@ -1118,7 +1117,7 @@ var kinetex = (() => {
1118
1117
  function createTwoTierCache(l2, config) {
1119
1118
  return new HTTPCache({ ...config, storage: new TwoTierStorageAdapter(l2) });
1120
1119
  }
1121
- var MemoryStorageAdapter, WebStorageAdapter, CloudflareKVAdapter, TwoTierStorageAdapter, LRUNode, LRUTracker, HTTPCache;
1120
+ var MemoryStorageAdapter, WebStorageAdapter, CloudflareKVAdapter, TwoTierStorageAdapter, CONTROL_CHARS, CREDENTIAL_HEADERS, LRUNode, LRUTracker, HTTPCache;
1122
1121
  var init_cache = __esm({
1123
1122
  "dist/esm/cache.js"() {
1124
1123
  "use strict";
@@ -1286,6 +1285,24 @@ var kinetex = (() => {
1286
1285
  await Promise.all([this.l1.clear(), this.l2.clear()]);
1287
1286
  }
1288
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
+ ];
1289
1306
  LRUNode = class {
1290
1307
  constructor(key, prev = null, next = null) {
1291
1308
  __publicField(this, "key");
@@ -1400,6 +1417,8 @@ var kinetex = (() => {
1400
1417
  // 7 days
1401
1418
  honorCacheControl: config.honorCacheControl ?? true,
1402
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.
1403
1422
  cacheStatuses: config.cacheStatuses ?? [
1404
1423
  200,
1405
1424
  203,
@@ -1407,7 +1426,6 @@ var kinetex = (() => {
1407
1426
  206,
1408
1427
  300,
1409
1428
  301,
1410
- 304,
1411
1429
  404,
1412
1430
  405,
1413
1431
  410,
@@ -1774,6 +1792,9 @@ var kinetex = (() => {
1774
1792
  for (const tag of entry.tags) {
1775
1793
  this.tagIndex.get(tag)?.delete(key);
1776
1794
  }
1795
+ } else {
1796
+ for (const set of this.tagIndex.values())
1797
+ set.delete(key);
1777
1798
  }
1778
1799
  }
1779
1800
  async _ensureCapacity(newEntrySize) {
@@ -1966,6 +1987,23 @@ var kinetex = (() => {
1966
1987
  }
1967
1988
  return out;
1968
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
+ }
1969
2007
  function parseCacheControl2(value) {
1970
2008
  const d = {
1971
2009
  noCache: false,
@@ -1986,7 +2024,7 @@ var kinetex = (() => {
1986
2024
  staleWhileRevalidate: null,
1987
2025
  unknown: /* @__PURE__ */ new Map()
1988
2026
  };
1989
- for (const part of value.split(",")) {
2027
+ for (const part of splitCacheControlDirectives(value)) {
1990
2028
  const t = part.trim();
1991
2029
  const eq = t.indexOf("=");
1992
2030
  const k = (eq === -1 ? t : t.slice(0, eq)).trim().toLowerCase();
@@ -2310,11 +2348,30 @@ var kinetex = (() => {
2310
2348
  proto: xproto ?? null
2311
2349
  };
2312
2350
  }
2313
- function getClientIP(headers) {
2351
+ function getClientIP(headers, options = {}) {
2352
+ const trustedHops = Math.max(0, Math.trunc(options.trustedHops ?? 0));
2314
2353
  const fwd = normalizeForwardedHeaders(headers);
2315
- if (fwd.for.length > 0)
2316
- return fwd.for[0];
2317
- 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;
2318
2375
  }
2319
2376
  function parseRetryAfter(value) {
2320
2377
  const t = value.trim();
@@ -4456,6 +4513,7 @@ var kinetex = (() => {
4456
4513
  // dist/esm/core.js
4457
4514
  var core_exports = {};
4458
4515
  __export(core_exports, {
4516
+ DEFAULT_ACCEPT_ENCODING: () => DEFAULT_ACCEPT_ENCODING,
4459
4517
  FetchTransport: () => FetchTransport,
4460
4518
  HAS_NATIVE_FETCH: () => HAS_NATIVE_FETCH,
4461
4519
  HTTP_STATUS_TEXTS: () => HTTP_STATUS_TEXTS,
@@ -4509,6 +4567,11 @@ var kinetex = (() => {
4509
4567
  }
4510
4568
  }
4511
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
+ }
4512
4575
  if (IS_NODE && preferHTTP2 && !fetchFn) {
4513
4576
  return new NodeHTTP2Transport({
4514
4577
  ...sessionOptions?.sessionTTLMs !== void 0 ? { sessionTTLMs: sessionOptions.sessionTTLMs } : {},
@@ -4750,7 +4813,8 @@ var kinetex = (() => {
4750
4813
  } else if (typeof body === "string") {
4751
4814
  stream.end(body);
4752
4815
  } else {
4753
- stream.end();
4816
+ const bytes = await serializeRawBody(body);
4817
+ stream.end(bytes);
4754
4818
  }
4755
4819
  }
4756
4820
  async function pipeBodyToNodeReq(req, body) {
@@ -4769,8 +4833,18 @@ var kinetex = (() => {
4769
4833
  } else if (typeof body === "string") {
4770
4834
  req.end(body);
4771
4835
  } else {
4772
- 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());
4843
+ }
4844
+ if (typeof Blob !== "undefined" && body instanceof Blob) {
4845
+ return new Uint8Array(await body.arrayBuffer());
4773
4846
  }
4847
+ return new Uint8Array(0);
4774
4848
  }
4775
4849
  async function decompressBodyStream(body, headers) {
4776
4850
  if (!body)
@@ -4795,7 +4869,7 @@ var kinetex = (() => {
4795
4869
  const { applyDecompression: applyDecompression2 } = await Promise.resolve().then(() => (init_response(), response_exports));
4796
4870
  return applyDecompression2(body, headers);
4797
4871
  }
4798
- 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;
4799
4873
  var init_core = __esm({
4800
4874
  "dist/esm/core.js"() {
4801
4875
  "use strict";
@@ -4808,6 +4882,7 @@ var kinetex = (() => {
4808
4882
  _runtimeOverride = null;
4809
4883
  IS_NODE = RUNTIME === "node";
4810
4884
  HAS_NATIVE_FETCH = RUNTIME !== "node" || typeof globalThis.fetch === "function";
4885
+ DEFAULT_ACCEPT_ENCODING = "gzip, deflate, br";
4811
4886
  FetchTransport = class {
4812
4887
  /**
4813
4888
  * @param fetchFnOrOptions - Custom fetch function or options object
@@ -4855,7 +4930,7 @@ var kinetex = (() => {
4855
4930
  sanitizedHeaders[name] = value;
4856
4931
  }
4857
4932
  const ae = sanitizedHeaders["accept-encoding"];
4858
- 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) {
4859
4934
  delete sanitizedHeaders["accept-encoding"];
4860
4935
  }
4861
4936
  const init = {
@@ -5659,8 +5734,7 @@ var kinetex = (() => {
5659
5734
  return false;
5660
5735
  if (isIPAddress(cd))
5661
5736
  return false;
5662
- const psl = getPublicSuffix(rh);
5663
- if (psl === rh)
5737
+ if (isPublicSuffix(cd))
5664
5738
  return false;
5665
5739
  return true;
5666
5740
  }
@@ -7369,7 +7443,7 @@ var kinetex = (() => {
7369
7443
  let cookieDomain;
7370
7444
  let hostOnly;
7371
7445
  if (parsed.domain !== null && parsed.domain !== "") {
7372
- const cd = parsed.domain;
7446
+ const cd = parsed.domain.toLowerCase();
7373
7447
  const matcher = this.domainMatcherFn ?? domainMatch;
7374
7448
  if (!matcher(reqHost, cd))
7375
7449
  return false;
@@ -7439,9 +7513,6 @@ var kinetex = (() => {
7439
7513
  * @returns Array of matching Cookie objects (direct references into storage)
7440
7514
  */
7441
7515
  getCookies(options) {
7442
- if (Math.random() < 0.01) {
7443
- this.clearExpired();
7444
- }
7445
7516
  const url = safeParseUrl(options.url);
7446
7517
  if (!url)
7447
7518
  return [];
@@ -7878,11 +7949,13 @@ var kinetex = (() => {
7878
7949
  if (this.domainMatcherFn) {
7879
7950
  return this.domainMatcherFn(reqHost, cookieDomain);
7880
7951
  }
7881
- if (reqHost === cookieDomain)
7952
+ const host = reqHost.toLowerCase();
7953
+ const domain = cookieDomain.toLowerCase();
7954
+ if (host === domain)
7882
7955
  return true;
7883
- if (isIPAddress(reqHost))
7956
+ if (isIPAddress(host))
7884
7957
  return false;
7885
- return reqHost.endsWith("." + cookieDomain);
7958
+ return host.endsWith("." + domain);
7886
7959
  }
7887
7960
  };
7888
7961
  }
@@ -8584,7 +8657,7 @@ var kinetex = (() => {
8584
8657
  * ```ts
8585
8658
  * const logger = createProductionLogger();
8586
8659
  * const child = logger.child({ requestId: "req-123", userId: "user-456" });
8587
- * child.logRequest(...); // Logs will include both requestId and userId
8660
+ * child.logRequest("req-123", "GET", "/users", {}, null, 1); // Logs include both requestId and userId
8588
8661
  * ```
8589
8662
  */
8590
8663
  child(context) {
@@ -8697,8 +8770,23 @@ var kinetex = (() => {
8697
8770
  events.push(final);
8698
8771
  return events;
8699
8772
  }
8700
- function sleep3(ms) {
8701
- 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
+ });
8702
8790
  }
8703
8791
  var SSEParser, SSETransformStream, SSEClient, SSERouter, SSEServerResponse, SSEError, SSEMaxReconnectsError;
8704
8792
  var init_sse = __esm({
@@ -9075,8 +9163,8 @@ var kinetex = (() => {
9075
9163
  }
9076
9164
  if (!cfg.reconnect)
9077
9165
  throw err;
9078
- if (cfg.maxReconnects > 0 && reconnectAttempt >= cfg.maxReconnects) {
9079
- 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);
9080
9168
  }
9081
9169
  reconnectAttempt++;
9082
9170
  this.health.totalReconnects++;
@@ -9089,25 +9177,31 @@ var kinetex = (() => {
9089
9177
  clearTimeout(heartbeatTimer);
9090
9178
  heartbeatTimer = null;
9091
9179
  }
9092
- await sleep3(delay2);
9180
+ await sleep3(delay2, cfg.signal);
9181
+ if (cfg.signal?.aborted)
9182
+ break;
9093
9183
  parser.reset();
9094
9184
  continue;
9095
9185
  }
9096
9186
  this.health.connected = false;
9097
9187
  if (!cfg.reconnect)
9098
9188
  break;
9099
- if (cfg.maxReconnects > 0 && reconnectAttempt >= cfg.maxReconnects) {
9100
- 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);
9101
9191
  }
9102
9192
  reconnectAttempt++;
9103
9193
  this.health.totalReconnects++;
9104
- 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);
9105
9197
  cfg.onReconnect(reconnectAttempt, delay);
9106
9198
  if (heartbeatTimer) {
9107
9199
  clearTimeout(heartbeatTimer);
9108
9200
  heartbeatTimer = null;
9109
9201
  }
9110
- await sleep3(delay);
9202
+ await sleep3(delay, cfg.signal);
9203
+ if (cfg.signal?.aborted)
9204
+ break;
9111
9205
  }
9112
9206
  this.health.connected = false;
9113
9207
  }
@@ -10593,12 +10687,16 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
10593
10687
  r();
10594
10688
  return;
10595
10689
  }
10596
- const timer = setTimeout(r, ms);
10597
- if (signal)
10598
- signal.addEventListener("abort", () => {
10599
- clearTimeout(timer);
10600
- r();
10601
- }, { 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 });
10602
10700
  });
10603
10701
  }
10604
10702
  function toPaginationIterator(source) {
@@ -11098,38 +11196,58 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11098
11196
  }
11099
11197
  });
11100
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
+ };
11101
11207
  const stream = new ReadableStream({
11102
- async start(controller) {
11103
- const reader = source.getReader();
11208
+ start(controller) {
11104
11209
  if (options.signal?.aborted) {
11105
- reader.cancel("aborted").catch(() => {
11106
- });
11107
11210
  controller.error(new DOMException("Upload aborted", "AbortError"));
11108
11211
  return;
11109
11212
  }
11110
- const onUploadAbort = () => {
11111
- reader.cancel("aborted").catch(() => {
11213
+ reader = source.getReader();
11214
+ onUploadAbort = () => {
11215
+ const r = reader;
11216
+ reader = null;
11217
+ void r?.cancel("aborted").catch(() => {
11112
11218
  });
11219
+ tracker.complete();
11113
11220
  controller.error(new DOMException("Upload aborted", "AbortError"));
11114
11221
  };
11115
11222
  options.signal?.addEventListener("abort", onUploadAbort, { once: true });
11223
+ },
11224
+ async pull(controller) {
11225
+ if (!reader)
11226
+ return;
11116
11227
  try {
11117
- while (true) {
11118
- const { done, value } = await reader.read();
11119
- if (done) {
11120
- tracker.complete();
11121
- controller.close();
11122
- break;
11123
- }
11124
- tracker.update(value.byteLength);
11125
- 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;
11126
11236
  }
11237
+ tracker.update(value.byteLength);
11238
+ controller.enqueue(value);
11127
11239
  } catch (err) {
11240
+ detach();
11128
11241
  tracker.complete();
11129
11242
  controller.error(err);
11130
- } finally {
11131
- options.signal?.removeEventListener("abort", onUploadAbort);
11132
11243
  }
11244
+ },
11245
+ cancel(reason) {
11246
+ detach();
11247
+ const r = reader;
11248
+ reader = null;
11249
+ void r?.cancel(reason).catch(() => {
11250
+ });
11133
11251
  }
11134
11252
  });
11135
11253
  return { stream, tracker };
@@ -11146,40 +11264,64 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11146
11264
  return { response, tracker };
11147
11265
  }
11148
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
+ };
11149
11276
  const trackedStream = new ReadableStream({
11150
- async start(controller) {
11151
- const reader = body.getReader();
11152
- let aborted = false;
11153
- 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 = () => {
11154
11285
  if (aborted)
11155
11286
  return;
11156
11287
  aborted = true;
11157
- reader.cancel("aborted").catch(() => {
11288
+ const r = reader;
11289
+ reader = null;
11290
+ void r?.cancel("aborted").catch(() => {
11158
11291
  });
11159
11292
  controller.error(new DOMException("Download aborted", "AbortError"));
11160
11293
  };
11161
11294
  options.signal?.addEventListener("abort", onAbort, { once: true });
11295
+ },
11296
+ async pull(controller) {
11297
+ if (!reader)
11298
+ return;
11162
11299
  try {
11163
- while (true) {
11164
- const { done, value } = await reader.read();
11165
- if (done) {
11166
- tracker.complete();
11167
- controller.close();
11168
- break;
11169
- }
11170
- tracker.update(value.byteLength);
11171
- 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;
11172
11308
  }
11309
+ tracker.update(value.byteLength);
11310
+ controller.enqueue(value);
11173
11311
  } catch (err) {
11312
+ detach();
11174
11313
  if (!aborted) {
11175
11314
  tracker.complete();
11176
11315
  }
11177
11316
  controller.error(err);
11178
- } finally {
11179
- if (!aborted) {
11180
- options.signal?.removeEventListener("abort", onAbort);
11181
- }
11182
11317
  }
11318
+ },
11319
+ cancel(reason) {
11320
+ detach();
11321
+ const r = reader;
11322
+ reader = null;
11323
+ void r?.cancel(reason).catch(() => {
11324
+ });
11183
11325
  }
11184
11326
  });
11185
11327
  const trackedResponse = new Response(trackedStream, {
@@ -11187,6 +11329,16 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11187
11329
  statusText: response.statusText,
11188
11330
  headers: response.headers
11189
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
+ }
11190
11342
  return { response: trackedResponse, tracker };
11191
11343
  }
11192
11344
  async function* streamWithProgress(stream, total, options = {}) {
@@ -11367,11 +11519,19 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11367
11519
  downloadTracker.update(e.loaded - downloadTracker.bytesLoaded);
11368
11520
  });
11369
11521
  }
11370
- options.signal?.addEventListener("abort", () => {
11522
+ const onAbort = () => {
11371
11523
  xhr.abort();
11372
11524
  reject(new DOMException("Request aborted", "AbortError"));
11373
- }, { 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
+ }
11374
11533
  xhr.addEventListener("load", () => {
11534
+ detach();
11375
11535
  downloadTracker.complete();
11376
11536
  const rawHeaders = xhr.getAllResponseHeaders();
11377
11537
  const headers = {};
@@ -11389,11 +11549,17 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11389
11549
  });
11390
11550
  });
11391
11551
  xhr.addEventListener("error", () => {
11552
+ detach();
11392
11553
  reject(new TypeError("Network request failed"));
11393
11554
  });
11394
11555
  xhr.addEventListener("timeout", () => {
11556
+ detach();
11395
11557
  reject(new TypeError("Request timed out"));
11396
11558
  });
11559
+ xhr.addEventListener("abort", () => {
11560
+ detach();
11561
+ reject(new DOMException("Request aborted", "AbortError"));
11562
+ });
11397
11563
  xhr.send(options.body ?? null);
11398
11564
  });
11399
11565
  }
@@ -11545,6 +11711,11 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11545
11711
  if (effectiveWindowMs <= 0) {
11546
11712
  this.inflight.delete(key);
11547
11713
  } else {
11714
+ const stale = this.timeouts.get(key);
11715
+ if (stale !== void 0) {
11716
+ clearTimeout(stale);
11717
+ this.timeouts.delete(key);
11718
+ }
11548
11719
  const timeoutId = setTimeout(() => {
11549
11720
  this.inflight.delete(key);
11550
11721
  this.timeouts.delete(key);
@@ -11586,13 +11757,6 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11586
11757
  get keys() {
11587
11758
  return [...this.inflight.keys()];
11588
11759
  }
11589
- /**
11590
- * Get comprehensive deduplication statistics.
11591
- * NOTE: inFlightCount and trackedKeys reflect snapshot time; entries may
11592
- * resolve asynchronously between sampling and return (non-blocking, acceptable).
11593
- *
11594
- * @returns Snapshot of hits, misses, hit rate, and tracked entries
11595
- */
11596
11760
  /**
11597
11761
  * Get comprehensive deduplication statistics.
11598
11762
  *
@@ -11735,9 +11899,16 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11735
11899
  }
11736
11900
  /** Snapshot of all counters — suitable for logging or dashboards. */
11737
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
+ }
11738
11909
  return {
11739
11910
  state: this._state,
11740
- failureCount: this.windowSize > 0 ? this._window.filter((v) => !v).length : this._consecutiveFails,
11911
+ failureCount,
11741
11912
  successCount: this._consecutiveSucc,
11742
11913
  lastFailureAt: this._lastFailureAt,
11743
11914
  lastSuccessAt: this._lastSuccessAt,
@@ -11763,6 +11934,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11763
11934
  this.cfg.onRejected?.(snap);
11764
11935
  return Promise.reject(new CircuitOpenError(this.key, snap));
11765
11936
  }
11937
+ let heldProbe = false;
11766
11938
  if (this._state === "HALF_OPEN") {
11767
11939
  if (this._inFlightProbes >= this.halfOpenConcurrency) {
11768
11940
  this._totalRejected++;
@@ -11771,15 +11943,16 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11771
11943
  return Promise.reject(new CircuitOpenError(this.key, snap));
11772
11944
  }
11773
11945
  this._inFlightProbes++;
11946
+ heldProbe = true;
11774
11947
  }
11775
11948
  this._totalRequests++;
11776
- return fn().then((result) => {
11777
- if (this._state === "HALF_OPEN")
11949
+ return Promise.resolve().then(fn).then((result) => {
11950
+ if (heldProbe)
11778
11951
  this._inFlightProbes = Math.max(0, this._inFlightProbes - 1);
11779
11952
  this._recordSuccess();
11780
11953
  return result;
11781
11954
  }, (err) => {
11782
- if (this._state === "HALF_OPEN")
11955
+ if (heldProbe)
11783
11956
  this._inFlightProbes = Math.max(0, this._inFlightProbes - 1);
11784
11957
  if (this._isCountableFailure(err)) {
11785
11958
  this._recordFailure();
@@ -12742,6 +12915,10 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
12742
12915
  return this._maxReconnects === 0 || this._reconnAttempt < this._maxReconnects;
12743
12916
  }
12744
12917
  _scheduleRecon() {
12918
+ if (this._reconnTimer) {
12919
+ clearTimeout(this._reconnTimer);
12920
+ this._reconnTimer = null;
12921
+ }
12745
12922
  this._state = "RECONNECTING";
12746
12923
  this._reconnAttempt++;
12747
12924
  const exp = this._reconnectBaseMs * Math.pow(2, this._reconnAttempt - 1);
@@ -12749,13 +12926,11 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
12749
12926
  const delay = Math.floor(capped * (1 + this._reconnectJitter * Math.random()));
12750
12927
  this._cbReconnect?.(this._reconnAttempt, delay);
12751
12928
  this._reconnTimer = setTimeout(() => {
12929
+ this._reconnTimer = null;
12752
12930
  if (!this._aborted)
12753
12931
  this._doConnect(true).catch(() => {
12754
12932
  });
12755
12933
  }, delay);
12756
- if (typeof this._reconnTimer === "object" && this._reconnTimer !== null && "unref" in this._reconnTimer) {
12757
- this._reconnTimer.unref();
12758
- }
12759
12934
  }
12760
12935
  _tx(data) {
12761
12936
  const ws = this._ws;
@@ -12923,12 +13098,17 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
12923
13098
  init_cache();
12924
13099
 
12925
13100
  // dist/esm/interceptors.js
13101
+ init_cache();
12926
13102
  var _idSeq = 0;
12927
13103
  function nextId() {
12928
13104
  return `interceptor_${++_idSeq}`;
12929
13105
  }
12930
- function sortByPriority(arr) {
12931
- 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);
12932
13112
  }
12933
13113
  var InterceptorManager = class {
12934
13114
  constructor() {
@@ -12946,7 +13126,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
12946
13126
  */
12947
13127
  useRequest(fn, opts = {}) {
12948
13128
  const id = opts.id ?? nextId();
12949
- this.requestInterceptors.push({
13129
+ insertByPriority(this.requestInterceptors, {
12950
13130
  id,
12951
13131
  priority: opts.priority ?? 0,
12952
13132
  once: opts.once ?? false,
@@ -12964,7 +13144,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
12964
13144
  */
12965
13145
  useResponse(fn, opts = {}) {
12966
13146
  const id = opts.id ?? nextId();
12967
- this.responseInterceptors.push({
13147
+ insertByPriority(this.responseInterceptors, {
12968
13148
  id,
12969
13149
  priority: opts.priority ?? 0,
12970
13150
  once: opts.once ?? false,
@@ -12982,7 +13162,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
12982
13162
  */
12983
13163
  useError(fn, opts = {}) {
12984
13164
  const id = opts.id ?? nextId();
12985
- this.errorInterceptors.push({
13165
+ insertByPriority(this.errorInterceptors, {
12986
13166
  id,
12987
13167
  priority: opts.priority ?? 0,
12988
13168
  once: opts.once ?? false,
@@ -13059,7 +13239,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
13059
13239
  async _run(ctx, dispatcher) {
13060
13240
  ctx.attempt++;
13061
13241
  const toEject = /* @__PURE__ */ new Set();
13062
- for (const interceptor of sortByPriority(this.requestInterceptors)) {
13242
+ for (const interceptor of this.requestInterceptors) {
13063
13243
  if (ctx.aborted)
13064
13244
  break;
13065
13245
  if (interceptor.condition && !interceptor.condition(ctx))
@@ -13099,7 +13279,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
13099
13279
  }
13100
13280
  async _runResponsePhase(ctx, dispatcher) {
13101
13281
  const toEject = /* @__PURE__ */ new Set();
13102
- for (const interceptor of sortByPriority(this.responseInterceptors)) {
13282
+ for (const interceptor of this.responseInterceptors) {
13103
13283
  if (ctx.aborted)
13104
13284
  break;
13105
13285
  if (interceptor.condition && !interceptor.condition(ctx))
@@ -13137,7 +13317,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
13137
13317
  }
13138
13318
  async _runErrorPhase(ctx, dispatcher) {
13139
13319
  const toEject = /* @__PURE__ */ new Set();
13140
- for (const interceptor of sortByPriority(this.errorInterceptors)) {
13320
+ for (const interceptor of this.errorInterceptors) {
13141
13321
  if (interceptor.condition && !interceptor.condition(ctx))
13142
13322
  continue;
13143
13323
  let result;
@@ -13549,13 +13729,15 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
13549
13729
  var CACHE_STALE_KEY = /* @__PURE__ */ Symbol("cacheStale");
13550
13730
  function createDedupeInterceptor() {
13551
13731
  const inflight = /* @__PURE__ */ new Map();
13552
- function key(req) {
13553
- 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 : ""}`;
13554
13735
  }
13555
- const requestInterceptor = (ctx) => {
13736
+ const requestInterceptor = async (ctx) => {
13556
13737
  if (ctx.request.method.toUpperCase() !== "GET" && ctx.request.method.toUpperCase() !== "HEAD")
13557
13738
  return;
13558
- const k = key(ctx.request);
13739
+ const k = await key(ctx.request);
13740
+ ctx.store.set(DEDUPE_KEY, k);
13559
13741
  const slot = inflight.get(k);
13560
13742
  if (!slot) {
13561
13743
  inflight.set(k, {
@@ -13564,38 +13746,59 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
13564
13746
  });
13565
13747
  return;
13566
13748
  }
13567
- ctx.store.set(DEDUPE_QUEUED_KEY, k);
13568
13749
  return new Promise((resolve, reject) => {
13569
- slot.waiters.push({
13750
+ const signal = ctx.request.signal;
13751
+ const waiter = {
13570
13752
  resolve: (res) => resolve(res),
13571
- reject: (err) => reject(err)
13753
+ reject: (err) => reject(err),
13572
13754
  // Properly reject instead of throwing
13573
- });
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);
13574
13769
  });
13575
13770
  };
13576
13771
  const responseInterceptor = (ctx) => {
13577
13772
  if (!ctx.response)
13578
13773
  return;
13579
- const k = key(ctx.request);
13774
+ const k = ctx.store.get(DEDUPE_KEY);
13775
+ if (!k)
13776
+ return;
13580
13777
  const slot = inflight.get(k);
13581
13778
  if (!slot)
13582
13779
  return;
13583
13780
  inflight.delete(k);
13584
- for (const w of slot.waiters)
13781
+ for (const w of slot.waiters.splice(0)) {
13782
+ w.cleanup();
13585
13783
  w.resolve(ctx.response);
13784
+ }
13586
13785
  };
13587
13786
  const errorInterceptor = (ctx) => {
13588
- const k = key(ctx.request);
13787
+ const k = ctx.store.get(DEDUPE_KEY);
13788
+ if (!k)
13789
+ return;
13589
13790
  const slot = inflight.get(k);
13590
13791
  if (!slot)
13591
13792
  return;
13592
13793
  inflight.delete(k);
13593
- for (const w of slot.waiters)
13794
+ for (const w of slot.waiters.splice(0)) {
13795
+ w.cleanup();
13594
13796
  w.reject(ctx.error);
13797
+ }
13595
13798
  };
13596
13799
  return { requestInterceptor, responseInterceptor, errorInterceptor };
13597
13800
  }
13598
- var DEDUPE_QUEUED_KEY = /* @__PURE__ */ Symbol("dedupeQueued");
13801
+ var DEDUPE_KEY = /* @__PURE__ */ Symbol("dedupeKey");
13599
13802
  var RATE_LIMIT_DEFAULTS = {
13600
13803
  limit: 60,
13601
13804
  windowMs: 6e4,
@@ -13961,7 +14164,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
13961
14164
  const role = roles[0]?.trim();
13962
14165
  if (!role)
13963
14166
  throw new NetworkError("No IAM role found in IMDS response");
13964
- 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);
13965
14168
  if (!credsRes.ok)
13966
14169
  throw new NetworkError(`IMDS credentials fetch failed: ${credsRes.status}`);
13967
14170
  const data = await credsRes.json();
@@ -14962,9 +15165,24 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
14962
15165
  const response = await computeDigestResponse(challenge, username, password, method, uri, cnonce, nc);
14963
15166
  return formatDigestAuth(challenge, username, response, uri, cnonce, nc);
14964
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
+ }
14965
15181
 
14966
15182
  // dist/esm/client.js
14967
15183
  init_core();
15184
+ init_core();
15185
+ var DEFAULT_MAX_REDIRECTS = 20;
14968
15186
  var DEFAULT_RETRY = {
14969
15187
  maxRetries: 3,
14970
15188
  baseDelayMs: 300,
@@ -15083,6 +15301,59 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
15083
15301
  "passwd",
15084
15302
  "secret"
15085
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
+ }
15086
15357
  function redactHARHeader(name, value) {
15087
15358
  return HAR_REDACT_HEADERS.has(name.toLowerCase()) ? { name, value: "***REDACTED***" } : { name, value };
15088
15359
  }
@@ -15118,9 +15389,9 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
15118
15389
  let receiveMs = 0;
15119
15390
  try {
15120
15391
  if (typeof performance !== "undefined" && typeof performance.getEntriesByType === "function") {
15121
- const entries = performance.getEntriesByType("resource");
15122
- const entry2 = entries.filter((e) => e.name === res.url).pop();
15123
- 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) {
15124
15395
  sendMs = Math.max(0, entry2.responseStart - entry2.requestStart);
15125
15396
  receiveMs = Math.max(0, entry2.responseEnd - entry2.responseStart);
15126
15397
  waitMs = Math.max(0, total - sendMs - receiveMs);
@@ -15135,12 +15406,16 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
15135
15406
  time: total,
15136
15407
  request: {
15137
15408
  method: req.method,
15138
- 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),
15139
15414
  httpVersion: res.httpVersion,
15140
15415
  headers: Object.entries(req.headers).map(([name, value]) => redactHARHeader(name, value)),
15141
15416
  queryString: (() => {
15142
15417
  try {
15143
- 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]) => ({
15144
15419
  name,
15145
15420
  value
15146
15421
  }));
@@ -15168,9 +15443,12 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
15168
15443
  content: {
15169
15444
  size: res.rawBody?.byteLength ?? 0,
15170
15445
  mimeType: res.headers["content-type"] ?? "application/octet-stream",
15171
- ...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) } : {}
15172
15449
  },
15173
- 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"]) : "",
15174
15452
  bodySize: res.rawBody?.byteLength ?? 0
15175
15453
  },
15176
15454
  timings: {
@@ -15262,22 +15540,25 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
15262
15540
  }
15263
15541
  return { ...req, headers };
15264
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
+ }
15265
15557
  var CROSS_ORIGIN_STRIP_HEADERS = /* @__PURE__ */ new Set([
15266
- "authorization",
15267
- "cookie",
15268
- "proxy-authorization",
15269
- "x-api-key",
15270
- "x-auth-token",
15271
- "x-access-token",
15272
- "x-refresh-token",
15273
- "x-csrf-token",
15274
- "x-session-id",
15275
- "x-session-token",
15276
- "x-secret",
15277
- "x-secret-key",
15278
- "x-private-key",
15279
- "api-key",
15280
- "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.
15281
15562
  "www-authenticate"
15282
15563
  ]);
15283
15564
  function redactUserInfo(url) {
@@ -15557,6 +15838,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
15557
15838
  }
15558
15839
  if (config.auth?.type === "digest") {
15559
15840
  const digestConfig = config.auth;
15841
+ const digestAuthorizer = createDigestAuthorizer();
15560
15842
  this.interceptors.addResponse(async (ctx) => {
15561
15843
  if (!ctx.response)
15562
15844
  return;
@@ -15568,8 +15850,9 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
15568
15850
  if (ctx.request.meta.__digestRetried)
15569
15851
  return;
15570
15852
  const method = ctx.request.method;
15571
- const uri = new URL(ctx.request.url).pathname + new URL(ctx.request.url).search;
15572
- 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);
15573
15856
  return {
15574
15857
  ...ctx.request,
15575
15858
  headers: { ...ctx.request.headers, authorization: authHeader },
@@ -15665,22 +15948,24 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
15665
15948
  const errEject = this.useError(async (ctx) => {
15666
15949
  if (!ctx.error)
15667
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);
15668
15960
  const hookErr = {
15669
15961
  error: ctx.error,
15670
- request: {
15671
- url: ctx.request.url,
15672
- method: ctx.request.method,
15673
- headers: ctx.request.headers,
15674
- body: ctx.request.body,
15675
- signal: ctx.request.signal,
15676
- meta: ctx.request.meta
15677
- },
15678
- response: null,
15962
+ request: hookReq,
15963
+ response: errResponse,
15679
15964
  attempt: ctx.attempt
15680
15965
  };
15681
15966
  const hookCtx = {
15682
15967
  request: hookErr.request,
15683
- response: null,
15968
+ response: errResponse,
15684
15969
  error: ctx.error,
15685
15970
  startedAt: ctx.startedAt,
15686
15971
  attempt: ctx.attempt,
@@ -16180,7 +16465,11 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16180
16465
  } else if (options.body instanceof ReadableStream) {
16181
16466
  throw new KinetexError(`maxRequestSize cannot be enforced for ReadableStream bodies \u2014 pass maxRequestSize: 0 to opt out, or buffer the body first`, "EVALIDATION");
16182
16467
  } else if (options.body && typeof options.body === "object") {
16183
- 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
+ }
16184
16473
  }
16185
16474
  if (bodySize2 > maxRequestSize) {
16186
16475
  throw new KinetexError(`Request body size ${bodySize2} bytes exceeds limit of ${maxRequestSize} bytes`, "EVALIDATION");
@@ -16188,10 +16477,22 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16188
16477
  }
16189
16478
  let req = {
16190
16479
  url: fullUrl,
16191
- 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,
16192
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.
16193
16489
  body: options.body ?? null,
16194
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 } : {},
16195
16496
  meta: { ...options.meta },
16196
16497
  httpVersion: options.httpVersion ?? this.cfg.httpVersion ?? "HTTP/2"
16197
16498
  };
@@ -16202,6 +16503,12 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16202
16503
  body: JSON.stringify(req.body)
16203
16504
  };
16204
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
+ }
16205
16512
  const auth = options.auth !== false ? options.auth ?? this.cfg.auth : void 0;
16206
16513
  if (auth)
16207
16514
  req = await applyAuth(req, auth);
@@ -16214,31 +16521,44 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16214
16521
  req = { ...req, headers: { ...req.headers, cookie: cookieHeader } };
16215
16522
  }
16216
16523
  let _otelSpan = null;
16217
- if (this._otelTracer) {
16218
- _otelSpan = this._otelTracer.startSpan(`HTTP ${req.method}`, {
16219
- kind: 3
16220
- /* CLIENT */
16221
- });
16222
- const { traceparent, traceId, spanId } = buildTraceparent(_otelSpan);
16223
- _otelSpan.setAttribute("http.request.method", req.method);
16224
- _otelSpan.setAttribute("url.full", req.url);
16225
- try {
16226
- _otelSpan.setAttribute("server.address", new URL(req.url).hostname);
16227
- } 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
+ };
16228
16550
  }
16229
- req = {
16230
- ...req,
16231
- headers: { ...req.headers, traceparent },
16232
- meta: { ...req.meta, traceId, spanId }
16233
- };
16234
- } else if (req.meta["traceId"] && !req.headers["traceparent"]) {
16235
- const traceId = String(req.meta["traceId"]);
16236
- const spanId = randomHex(16);
16237
- req = {
16238
- ...req,
16239
- headers: { ...req.headers, traceparent: `00-${traceId}-${spanId}-01` },
16240
- meta: { ...req.meta, spanId }
16241
- };
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;
16242
16562
  }
16243
16563
  let _cbOrigin;
16244
16564
  if (this._circuitBreakerKeyFn) {
@@ -16255,8 +16575,11 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16255
16575
  }
16256
16576
  }
16257
16577
  const _execFactory = () => this._executeWithRetry(req, retryCfg, timeoutMs, options, startMs, wallClockMs);
16258
- const authFp = await getAuthFingerprint(req.headers ?? {});
16259
- 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
+ }
16260
16583
  const _dedupedFactory = this._dedup ? () => this._dedup.execute(req.method, _dedupKey, _execFactory).then((r) => r) : _execFactory;
16261
16584
  const _guardedFactory = this._circuitBreakers ? () => this._circuitBreakers.execute(_cbOrigin, _dedupedFactory).then((r) => r) : _dedupedFactory;
16262
16585
  try {
@@ -16294,7 +16617,10 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16294
16617
  while (true) {
16295
16618
  attempt++;
16296
16619
  if (req.signal?.aborted) {
16297
- 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 });
16298
16624
  }
16299
16625
  try {
16300
16626
  const res = await this._executeOnce(req, timeout, options, startMs, attempt, wallClockMs, retryCfg);
@@ -16320,6 +16646,15 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16320
16646
  }
16321
16647
  return res;
16322
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
+ }
16323
16658
  if (retryCfg && attempt <= retryCfg.maxRetries) {
16324
16659
  const retryCtx = {
16325
16660
  request: req,
@@ -16354,11 +16689,22 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16354
16689
  // intermediate redirect responses. When a cookie jar is active we must follow
16355
16690
  // redirects ourselves one hop at a time so we can capture cookies at each step.
16356
16691
  /**
16357
- * Follow redirects manually, one hop at a time, to capture Set-Cookie headers.
16358
- * 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.
16359
16704
  */
16360
16705
  async _sendFollowingRedirects(req, timeout, jar, appliedAuth) {
16361
- const MAX_REDIRECTS = 20;
16706
+ const maxRedirects = Math.max(0, req.maxRedirects ?? DEFAULT_MAX_REDIRECTS);
16707
+ const followRedirects = req.followRedirects !== false && maxRedirects > 0;
16362
16708
  let currentReq = { ...req, redirect: "manual" };
16363
16709
  const origin0 = (() => {
16364
16710
  try {
@@ -16368,7 +16714,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16368
16714
  }
16369
16715
  })();
16370
16716
  const visited = /* @__PURE__ */ new Set();
16371
- for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
16717
+ for (let hop = 0; hop <= maxRedirects; hop++) {
16372
16718
  if (hop > 0 && appliedAuth) {
16373
16719
  const sameOrigin = (() => {
16374
16720
  try {
@@ -16411,11 +16757,13 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16411
16757
  });
16412
16758
  }
16413
16759
  visited.add(raw.url);
16414
- jar.processResponseHeaders(raw.headers, {
16760
+ jar?.processResponseHeaders(raw.headers, {
16415
16761
  url: raw.url
16416
16762
  });
16417
- if (hop === MAX_REDIRECTS) {
16418
- 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", {
16419
16767
  request: req
16420
16768
  });
16421
16769
  }
@@ -16434,6 +16782,12 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16434
16782
  if (protocol !== "http:" && protocol !== "https:") {
16435
16783
  throw new KinetexError(`Unsafe redirect to ${protocol} detected \u2014 only HTTP(S) allowed`, "ENETWORK", { request: req });
16436
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
+ }
16437
16791
  } catch (err) {
16438
16792
  if (err instanceof KinetexError)
16439
16793
  throw err;
@@ -16444,21 +16798,30 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16444
16798
  }
16445
16799
  const nextMethod = raw.status === 301 || raw.status === 302 || raw.status === 303 ? "GET" : currentReq.method;
16446
16800
  const nextBody = nextMethod === "GET" || nextMethod === "HEAD" ? null : currentReq.body;
16447
- const cookieHeader = jar.getCookieHeader({ url: nextUrl, http: true });
16448
16801
  const nextHeaders = { ...currentReq.headers };
16449
- if (cookieHeader) {
16450
- nextHeaders["cookie"] = cookieHeader;
16451
- } else {
16452
- delete nextHeaders["cookie"];
16453
- }
16802
+ let crossOrigin = false;
16454
16803
  try {
16455
- if (new URL(nextUrl).origin !== new URL(currentReq.url).origin) {
16456
- for (const h of CROSS_ORIGIN_STRIP_HEADERS) {
16457
- delete nextHeaders[h];
16458
- }
16459
- }
16804
+ crossOrigin = new URL(nextUrl).origin !== new URL(currentReq.url).origin;
16460
16805
  } catch {
16461
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
+ }
16462
16825
  currentReq = {
16463
16826
  ...currentReq,
16464
16827
  url: nextUrl,
@@ -16469,7 +16832,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16469
16832
  };
16470
16833
  continue;
16471
16834
  }
16472
- return raw;
16835
+ return hop > 0 ? { ...raw, redirected: true } : raw;
16473
16836
  }
16474
16837
  throw new KinetexError("Redirect loop", "ENETWORK", { request: req });
16475
16838
  }
@@ -16556,7 +16919,10 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16556
16919
  }
16557
16920
  }
16558
16921
  } finally {
16559
- (await this.getCache())?.clearSWRInFlight(cacheReq);
16922
+ try {
16923
+ (await this.getCache())?.clearSWRInFlight(cacheReq);
16924
+ } catch {
16925
+ }
16560
16926
  }
16561
16927
  })();
16562
16928
  }
@@ -16634,15 +17000,17 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16634
17000
  ...req,
16635
17001
  headers: {
16636
17002
  ...req.headers,
16637
- "accept-encoding": "gzip, deflate, br"
17003
+ "accept-encoding": DEFAULT_ACCEPT_ENCODING
16638
17004
  }
16639
17005
  };
16640
17006
  }
16641
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);
16642
17010
  this._trace(_traceId, "transport_send", "start", startMs, attempt);
16643
17011
  let raw;
16644
17012
  try {
16645
- 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);
16646
17014
  } catch (err) {
16647
17015
  if (_uploadStreamForCleanup) {
16648
17016
  try {
@@ -16686,9 +17054,10 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16686
17054
  },
16687
17055
  ...req.signal !== null ? { signal: req.signal } : {}
16688
17056
  });
17057
+ const source = bodyStream;
16689
17058
  bodyStream = new ReadableStream({
16690
17059
  async start(controller) {
16691
- const reader = bodyStream.getReader();
17060
+ const reader = source.getReader();
16692
17061
  try {
16693
17062
  while (true) {
16694
17063
  const { done, value } = await reader.read();
@@ -16706,8 +17075,9 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16706
17075
  reader.releaseLock();
16707
17076
  }
16708
17077
  },
16709
- cancel() {
16710
- bodyStream?.cancel();
17078
+ cancel(reason) {
17079
+ void source.cancel(reason).catch(() => {
17080
+ });
16711
17081
  }
16712
17082
  });
16713
17083
  }
@@ -16728,7 +17098,9 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16728
17098
  status: raw.status,
16729
17099
  statusText: raw.statusText,
16730
17100
  headers: raw.headers,
16731
- 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,
16732
17104
  rawBody,
16733
17105
  url: raw.url,
16734
17106
  cached: false,
@@ -16738,6 +17110,9 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16738
17110
  request: req,
16739
17111
  attempt
16740
17112
  };
17113
+ if (this.cfg.transformResponse) {
17114
+ res.data = this.cfg.transformResponse(res.data, res);
17115
+ }
16741
17116
  if (options.cache !== false && this.cfg.cache) {
16742
17117
  const cache = await this.getCache();
16743
17118
  if (cache && raw.status !== 304) {
@@ -16806,6 +17181,10 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16806
17181
  if ("status" in result && "headers" in result) {
16807
17182
  current = result;
16808
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
+ }
16809
17188
  if (retryCfg && attempt <= retryCfg.maxRetries) {
16810
17189
  const retryCtx = {
16811
17190
  request: _req,
@@ -16822,7 +17201,13 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16822
17201
  }, Promise.resolve());
16823
17202
  await sleep6(delay, _req.signal);
16824
17203
  }
16825
- 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);
16826
17211
  }
16827
17212
  }
16828
17213
  return current;
@@ -17100,7 +17485,10 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17100
17485
  * Call this when the client is no longer needed to prevent memory leaks.
17101
17486
  * @returns A promise that resolves when cleanup is complete.
17102
17487
  */
17103
- 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() {
17104
17492
  for (const ws of this._wsClients) {
17105
17493
  try {
17106
17494
  ws.close(1e3, "Client destroyed");
@@ -17108,12 +17496,6 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17108
17496
  }
17109
17497
  }
17110
17498
  this._wsClients.clear();
17111
- if (this._cache) {
17112
- try {
17113
- await this._cache.clear();
17114
- } catch {
17115
- }
17116
- }
17117
17499
  if (IS_NODE && this.transport && "destroy" in this.transport) {
17118
17500
  this.transport.destroy();
17119
17501
  }
@@ -17123,6 +17505,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17123
17505
  this._circuitBreakers?.clear?.();
17124
17506
  this._otelTracer = null;
17125
17507
  this.interceptors.clear();
17508
+ return Promise.resolve();
17126
17509
  }
17127
17510
  };
17128
17511
  var FluentRequest = class {
@@ -17351,13 +17734,19 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17351
17734
  }).catch((err) => onError?.(err instanceof KinetexError ? err : new KinetexError(String(err), "EUNKNOWN")));
17352
17735
  }
17353
17736
  };
17354
- function createAbortError() {
17355
- if (typeof DOMException !== "undefined") {
17356
- return new DOMException("Aborted", "AbortError");
17357
- }
17358
- const err = new Error("Aborted");
17359
- err.name = "AbortError";
17360
- 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);
17361
17750
  }
17362
17751
  function mergeParams(a, b) {
17363
17752
  if (!a && !b)
@@ -17381,6 +17770,11 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17381
17770
  signal?.addEventListener("abort", onAbort, { once: true });
17382
17771
  });
17383
17772
  }
17773
+ function isNonReplayableBody(body) {
17774
+ if (body instanceof ReadableStream)
17775
+ return true;
17776
+ return typeof Blob !== "undefined" && body instanceof Blob;
17777
+ }
17384
17778
  function perfNow4() {
17385
17779
  return typeof performance !== "undefined" ? performance.now() : Date.now();
17386
17780
  }
@@ -17399,7 +17793,13 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17399
17793
  constructor(client, options = {}) {
17400
17794
  /** The parent Kinetex instance used to send requests. */
17401
17795
  __publicField(this, "_client");
17402
- /** 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
+ */
17403
17803
  __publicField(this, "_maxBatch");
17404
17804
  /** Milliseconds to wait before flushing an incomplete batch. */
17405
17805
  __publicField(this, "_flushMs");
@@ -17408,8 +17808,16 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17408
17808
  /** Timer handle for deferred flush. */
17409
17809
  __publicField(this, "_timer", null);
17410
17810
  this._client = client;
17411
- this._maxBatch = options.maxBatch ?? 100;
17412
- 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;
17413
17821
  }
17414
17822
  /**
17415
17823
  * Enqueue a request. Returns a promise that resolves when the batch
@@ -17462,7 +17870,8 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17462
17870
  * Loops until the queue is empty so items beyond maxBatch are not orphaned.
17463
17871
  */
17464
17872
  flush() {
17465
- while (this._queue.length > 0)
17873
+ let guard = this._queue.length + 1;
17874
+ while (this._queue.length > 0 && guard-- > 0)
17466
17875
  this._flushNow();
17467
17876
  }
17468
17877
  /** How many requests are currently queued (not yet sent). */
@@ -18457,28 +18866,27 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18457
18866
  }
18458
18867
  var denoTcpConnector = async (host, port, timeoutMs) => {
18459
18868
  const denoGlobal = globalThis;
18460
- let conn;
18461
- if (timeoutMs && timeoutMs > 0) {
18462
- conn = await Promise.race([
18463
- denoGlobal.Deno.connect({ hostname: host, port, transport: "tcp" }),
18464
- new Promise((_, rej) => setTimeout(() => rej(new Socks5Error("TCP connect to proxy timed out", "SOCKS5_TIMEOUT", true)), timeoutMs))
18465
- ]);
18466
- } else {
18467
- conn = await denoGlobal.Deno.connect({ hostname: host, port, transport: "tcp" });
18468
- }
18469
- const wrappedRead = async (buf) => {
18470
- if (timeoutMs && timeoutMs > 0) {
18471
- return Promise.race([
18472
- conn.read(buf).catch(() => null),
18473
- new Promise((_, rej) => setTimeout(() => rej(new Socks5Error("TCP read timed out", "SOCKS5_TIMEOUT", true)), timeoutMs))
18474
- ]);
18475
- }
18869
+ const withTimeout2 = async (work, message) => {
18870
+ if (!timeoutMs || timeoutMs <= 0)
18871
+ return work;
18872
+ let timer;
18476
18873
  try {
18477
- return await conn.read(buf);
18478
- } catch {
18479
- 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);
18480
18883
  }
18481
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
+ };
18482
18890
  return {
18483
18891
  read: wrappedRead,
18484
18892
  write: (data) => conn.write(data),
@@ -18531,7 +18939,9 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18531
18939
  buffer = next;
18532
18940
  flushBuffer();
18533
18941
  });
18942
+ let ended = false;
18534
18943
  socket.on("end", () => {
18944
+ ended = true;
18535
18945
  if (pendingRead) {
18536
18946
  pendingRead(null);
18537
18947
  pendingRead = null;
@@ -18539,6 +18949,9 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18539
18949
  pendingBuf = null;
18540
18950
  }
18541
18951
  });
18952
+ socket.on("close", () => {
18953
+ ended = true;
18954
+ });
18542
18955
  socket.once("error", (err) => {
18543
18956
  clearTimeout(timer);
18544
18957
  lastError = err;
@@ -18557,6 +18970,10 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18557
18970
  rej(lastError);
18558
18971
  return;
18559
18972
  }
18973
+ if (ended && buffer.length === 0) {
18974
+ res(null);
18975
+ return;
18976
+ }
18560
18977
  if (buffer.length > 0) {
18561
18978
  const n = Math.min(buffer.length, buf.length);
18562
18979
  buf.set(buffer.subarray(0, n));