kinetex 1.4.0 → 1.5.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 +170 -46
  2. package/dist/browser/kinetex.esm.js +29 -29
  3. package/dist/browser/kinetex.js +1143 -791
  4. package/dist/browser/kinetex.min.js +29 -29
  5. package/dist/cjs/aws-sigv4.js +7 -14
  6. package/dist/cjs/cache.js +9 -0
  7. package/dist/cjs/circuit-breaker.js +8 -1
  8. package/dist/cjs/client.js +350 -95
  9. package/dist/cjs/cookie-parser.js +28 -1
  10. package/dist/cjs/core.js +227 -5
  11. package/dist/cjs/dedup.js +93 -10
  12. package/dist/cjs/digest.js +36 -15
  13. package/dist/cjs/graphql.js +2 -11
  14. package/dist/cjs/headers.js +24 -1
  15. package/dist/cjs/interceptors.js +6 -17
  16. package/dist/cjs/lifecycle.js +3 -19
  17. package/dist/cjs/logging.js +12 -29
  18. package/dist/cjs/progress.js +1 -3
  19. package/dist/cjs/proxy.js +19 -2
  20. package/dist/cjs/response.js +202 -75
  21. package/dist/cjs/socks5.js +5 -0
  22. package/dist/cjs/utils.js +115 -27
  23. package/dist/cjs/ws.js +43 -2
  24. package/dist/esm/aws-sigv4.js +7 -14
  25. package/dist/esm/aws-sigv4.js.map +1 -1
  26. package/dist/esm/cache.js +9 -0
  27. package/dist/esm/cache.js.map +1 -1
  28. package/dist/esm/circuit-breaker.js +8 -1
  29. package/dist/esm/circuit-breaker.js.map +1 -1
  30. package/dist/esm/client.js +350 -95
  31. package/dist/esm/client.js.map +1 -1
  32. package/dist/esm/cookie-parser.js +28 -1
  33. package/dist/esm/cookie-parser.js.map +1 -1
  34. package/dist/esm/core.js +227 -5
  35. package/dist/esm/core.js.map +1 -1
  36. package/dist/esm/dedup.js +93 -10
  37. package/dist/esm/dedup.js.map +1 -1
  38. package/dist/esm/digest.js +36 -15
  39. package/dist/esm/digest.js.map +1 -1
  40. package/dist/esm/graphql.js +2 -11
  41. package/dist/esm/graphql.js.map +1 -1
  42. package/dist/esm/headers.js +24 -1
  43. package/dist/esm/headers.js.map +1 -1
  44. package/dist/esm/interceptors.js +6 -17
  45. package/dist/esm/interceptors.js.map +1 -1
  46. package/dist/esm/lifecycle.js +3 -19
  47. package/dist/esm/lifecycle.js.map +1 -1
  48. package/dist/esm/logging.js +12 -29
  49. package/dist/esm/logging.js.map +1 -1
  50. package/dist/esm/progress.js +1 -3
  51. package/dist/esm/progress.js.map +1 -1
  52. package/dist/esm/proxy.js +19 -2
  53. package/dist/esm/proxy.js.map +1 -1
  54. package/dist/esm/response.js +202 -75
  55. package/dist/esm/response.js.map +1 -1
  56. package/dist/esm/socks5.js +5 -0
  57. package/dist/esm/socks5.js.map +1 -1
  58. package/dist/esm/types.js.map +1 -1
  59. package/dist/esm/url.js.map +1 -1
  60. package/dist/esm/utils.js +115 -27
  61. package/dist/esm/utils.js.map +1 -1
  62. package/dist/esm/ws.js +43 -2
  63. package/dist/esm/ws.js.map +1 -1
  64. package/dist/types/aws-sigv4.d.ts.map +1 -1
  65. package/dist/types/cache.d.ts.map +1 -1
  66. package/dist/types/circuit-breaker.d.ts +1 -1
  67. package/dist/types/circuit-breaker.d.ts.map +1 -1
  68. package/dist/types/client.d.ts.map +1 -1
  69. package/dist/types/cookie-parser.d.ts.map +1 -1
  70. package/dist/types/core.d.ts +113 -0
  71. package/dist/types/core.d.ts.map +1 -1
  72. package/dist/types/dedup.d.ts +38 -3
  73. package/dist/types/dedup.d.ts.map +1 -1
  74. package/dist/types/digest.d.ts.map +1 -1
  75. package/dist/types/graphql.d.ts.map +1 -1
  76. package/dist/types/headers.d.ts +23 -4
  77. package/dist/types/headers.d.ts.map +1 -1
  78. package/dist/types/interceptors.d.ts +24 -38
  79. package/dist/types/interceptors.d.ts.map +1 -1
  80. package/dist/types/lifecycle.d.ts.map +1 -1
  81. package/dist/types/logging.d.ts.map +1 -1
  82. package/dist/types/progress.d.ts.map +1 -1
  83. package/dist/types/proxy.d.ts.map +1 -1
  84. package/dist/types/response.d.ts +42 -1
  85. package/dist/types/response.d.ts.map +1 -1
  86. package/dist/types/socks5.d.ts.map +1 -1
  87. package/dist/types/types.d.ts +105 -23
  88. package/dist/types/types.d.ts.map +1 -1
  89. package/dist/types/url.d.ts +0 -11
  90. package/dist/types/url.d.ts.map +1 -1
  91. package/dist/types/utils.d.ts +69 -1
  92. package/dist/types/utils.d.ts.map +1 -1
  93. package/dist/types/ws.d.ts +0 -1
  94. package/dist/types/ws.d.ts.map +1 -1
  95. package/package.json +10 -4
@@ -1,4 +1,4 @@
1
- /* kinetex v1.4.0 | MIT | https://github.com/kinetexjs/kinetex */
1
+ /* kinetex v1.5.0 | MIT | https://github.com/kinetexjs/kinetex */
2
2
  "use strict";
3
3
  var kinetex = (() => {
4
4
  var __create = Object.create;
@@ -422,7 +422,7 @@ var kinetex = (() => {
422
422
  }
423
423
  return false;
424
424
  }
425
- function isSafeURL(url, allowedSchemes = ["http", "https"]) {
425
+ function isSafeURL(url, allowedSchemes = ["http", "https"], options = {}) {
426
426
  try {
427
427
  const parsed = typeof url === "string" ? new URL(url) : url;
428
428
  const scheme = parsed.protocol.replace(/:$/, "").toLowerCase();
@@ -432,20 +432,22 @@ var kinetex = (() => {
432
432
  if (FORBIDDEN_SCHEMES.has(scheme)) {
433
433
  return false;
434
434
  }
435
- let host = parsed.hostname.toLowerCase();
436
- const isV6Literal = host.startsWith("[") && host.endsWith("]");
437
- if (isV6Literal)
438
- host = host.slice(1, -1);
439
- if (host === "localhost" || host.endsWith(".localhost") || host === "0.0.0.0")
440
- return false;
441
- if (isV6Literal || host.includes(":")) {
442
- const v6 = parseIPv6Host(host);
443
- if (v6 === null || isBlockedIPv6(v6))
444
- return false;
445
- } else {
446
- const v4 = parseIPv4Host(host);
447
- if (v4 !== null && isBlockedIPv4(v4))
435
+ if (!options.allowPrivateNetwork) {
436
+ let host = parsed.hostname.toLowerCase();
437
+ const isV6Literal = host.startsWith("[") && host.endsWith("]");
438
+ if (isV6Literal)
439
+ host = host.slice(1, -1);
440
+ if (host === "localhost" || host.endsWith(".localhost") || host === "0.0.0.0")
448
441
  return false;
442
+ if (isV6Literal || host.includes(":")) {
443
+ const v6 = parseIPv6Host(host);
444
+ if (v6 === null || isBlockedIPv6(v6))
445
+ return false;
446
+ } else {
447
+ const v4 = parseIPv4Host(host);
448
+ if (v4 !== null && isBlockedIPv4(v4))
449
+ return false;
450
+ }
449
451
  }
450
452
  if (parsed.hostname.includes("..") || parsed.pathname.includes("..")) {
451
453
  return false;
@@ -465,6 +467,19 @@ var kinetex = (() => {
465
467
  return false;
466
468
  }
467
469
  }
470
+ function isBlockedAddress(address) {
471
+ const host = address.trim().toLowerCase();
472
+ if (host.startsWith("[") && host.endsWith("]"))
473
+ return true;
474
+ if (host === "" || host === "localhost" || host.endsWith(".localhost"))
475
+ return true;
476
+ if (host.includes(":")) {
477
+ const v6 = parseIPv6Host(host);
478
+ return v6 === null || isBlockedIPv6(v6);
479
+ }
480
+ const v4 = parseIPv4Host(host);
481
+ return v4 === null || isBlockedIPv4(v4);
482
+ }
468
483
  function deepClone(value) {
469
484
  if (value === null || typeof value !== "object") {
470
485
  return value;
@@ -530,7 +545,7 @@ var kinetex = (() => {
530
545
  }
531
546
  return String(error);
532
547
  }
533
- function safeStringify(value) {
548
+ function safeStringify(value, circularMarker = "[Circular]") {
534
549
  const seen = /* @__PURE__ */ new Set();
535
550
  try {
536
551
  return JSON.stringify(value, (_key, v) => {
@@ -538,7 +553,7 @@ var kinetex = (() => {
538
553
  return v.toString();
539
554
  if (typeof v === "object" && v !== null) {
540
555
  if (seen.has(v))
541
- return "[Circular]";
556
+ return circularMarker;
542
557
  seen.add(v);
543
558
  }
544
559
  return v;
@@ -564,7 +579,7 @@ var kinetex = (() => {
564
579
  return null;
565
580
  }
566
581
  }
567
- function perfNow2() {
582
+ function perfNow() {
568
583
  const p = getProcess();
569
584
  if (p?.hrtime) {
570
585
  const [sec, ns] = p.hrtime();
@@ -578,12 +593,12 @@ var kinetex = (() => {
578
593
  }
579
594
  function sleep(ms, signal) {
580
595
  if (signal?.aborted) {
581
- return Promise.reject(_abortError());
596
+ return Promise.reject(abortError());
582
597
  }
583
598
  return new Promise((resolve, reject) => {
584
599
  const onAbort = () => {
585
600
  clearTimeout(timer);
586
- reject(_abortError());
601
+ reject(abortError());
587
602
  };
588
603
  const timer = setTimeout(() => {
589
604
  signal?.removeEventListener("abort", onAbort);
@@ -620,6 +635,22 @@ var kinetex = (() => {
620
635
  }
621
636
  return null;
622
637
  }
638
+ function isSensitiveHeader(name, sensitive) {
639
+ return sensitive.has(name.toLowerCase());
640
+ }
641
+ function redactHeaders(headers, sensitive, mask) {
642
+ const out = {};
643
+ for (const [k, v] of Object.entries(headers)) {
644
+ out[k] = isSensitiveHeader(k, sensitive) ? mask : v;
645
+ }
646
+ return out;
647
+ }
648
+ async function sha256Hex(data) {
649
+ const raw = typeof data === "string" ? new TextEncoder().encode(data) : data;
650
+ const buf = raw.buffer.slice(raw.byteOffset, raw.byteOffset + raw.byteLength);
651
+ const hash = await crypto.subtle.digest("SHA-256", buf);
652
+ return Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("");
653
+ }
623
654
  function uint8ArrayToBase64(bytes) {
624
655
  const CHUNK = 24576;
625
656
  if (bytes.byteLength <= CHUNK) {
@@ -661,7 +692,7 @@ var kinetex = (() => {
661
692
  const abort = () => {
662
693
  for (const s of validSignals)
663
694
  s.removeEventListener("abort", abort);
664
- controller.abort(_abortError());
695
+ controller.abort(abortError());
665
696
  };
666
697
  for (const s of validSignals)
667
698
  s.addEventListener("abort", abort, { once: true });
@@ -691,11 +722,11 @@ var kinetex = (() => {
691
722
  }
692
723
  return Array.from(arr).map((b) => b.toString(16).padStart(2, "0")).join("");
693
724
  }
694
- function _abortError() {
725
+ function abortError(message = "Aborted") {
695
726
  if (typeof DOMException !== "undefined") {
696
- return new DOMException("Aborted", "AbortError");
727
+ return new DOMException(message, "AbortError");
697
728
  }
698
- const err = new Error("Aborted");
729
+ const err = new Error(message);
699
730
  err.name = "AbortError";
700
731
  return err;
701
732
  }
@@ -1124,6 +1155,9 @@ var kinetex = (() => {
1124
1155
  if (honor && d.noStore) {
1125
1156
  return { ttlMs: 0, swrMs: 0, staleOnError: 0, shouldCache: false };
1126
1157
  }
1158
+ if (honor && d.private) {
1159
+ return { ttlMs: 0, swrMs: 0, staleOnError: 0, shouldCache: false };
1160
+ }
1127
1161
  const maxAgeSecs = d.sMaxAge ?? d.maxAge;
1128
1162
  let ttlMs;
1129
1163
  if (maxAgeSecs !== null) {
@@ -2300,7 +2334,7 @@ var kinetex = (() => {
2300
2334
  continue;
2301
2335
  const k = t.slice(0, eq).trim().toLowerCase();
2302
2336
  let v = t.slice(eq + 1).trim();
2303
- if (v.startsWith('"') && v.endsWith('"'))
2337
+ if (v.length >= 2 && v.startsWith('"') && v.endsWith('"'))
2304
2338
  v = v.slice(1, -1);
2305
2339
  params.set(k, v);
2306
2340
  }
@@ -3895,522 +3929,217 @@ var kinetex = (() => {
3895
3929
  }
3896
3930
  });
3897
3931
 
3898
- // dist/esm/proxy.js
3899
- var proxy_exports = {};
3900
- __export(proxy_exports, {
3901
- connectThroughProxy: () => connectThroughProxy
3932
+ // dist/esm/response.js
3933
+ var response_exports = {};
3934
+ __export(response_exports, {
3935
+ ContentTypeError: () => ContentTypeError,
3936
+ HTTPResponseError: () => HTTPResponseError,
3937
+ ResponseCache: () => ResponseCache,
3938
+ ResponseDecodeError: () => ResponseDecodeError,
3939
+ ResponseSizeLimitError: () => ResponseSizeLimitError,
3940
+ applyDecompression: () => applyDecompression,
3941
+ assertOk: () => assertOk,
3942
+ assertOkJSON: () => assertOkJSON,
3943
+ createLimitedReader: () => createLimitedReader,
3944
+ decodeBody: () => decodeBody,
3945
+ decompressStream: () => decompressStream,
3946
+ diffResponses: () => diffResponses,
3947
+ extractServerTiming: () => extractServerTiming,
3948
+ isBinary: () => isBinary,
3949
+ isJSON: () => isJSON,
3950
+ isText: () => isText,
3951
+ limitAction: () => limitAction,
3952
+ normalizeHeaders: () => normalizeHeaders2,
3953
+ normalizeResponse: () => normalizeResponse,
3954
+ parseContentType: () => parseContentType2,
3955
+ parseMultipartResponse: () => parseMultipartResponse,
3956
+ readBlob: () => readBlob,
3957
+ readBodyWithLimit: () => readBodyWithLimit,
3958
+ readBytes: () => readBytes,
3959
+ readFormData: () => readFormData,
3960
+ readJSON: () => readJSON,
3961
+ readJSONStream: () => readJSONStream,
3962
+ readNDJSON: () => readNDJSON,
3963
+ readStream: () => readStream,
3964
+ readText: () => readText
3902
3965
  });
3903
- function isSocksProxy(url) {
3904
- return /^socks5h?:/i.test(url);
3966
+ function normalizeHeaders2(headers) {
3967
+ return normalizeHeaders(headers);
3905
3968
  }
3906
- function proxyAuthHeader(proxy) {
3907
- if (proxy.username === void 0)
3908
- return void 0;
3909
- const raw = `${proxy.username}:${proxy.password ?? ""}`;
3910
- const bytes = new TextEncoder().encode(raw);
3911
- let binary = "";
3912
- for (const b of bytes)
3913
- binary += String.fromCharCode(b);
3914
- const b64 = typeof globalThis.btoa === "function" ? globalThis.btoa(binary) : (
3915
- // Node without btoa (or a non-Latin1 username) — encode explicitly.
3916
- Buffer.from(bytes).toString("base64")
3917
- );
3918
- return `Basic ${b64}`;
3969
+ function normalizeResponse(response) {
3970
+ return {
3971
+ status: response.status,
3972
+ statusText: response.statusText,
3973
+ headers: normalizeHeaders2(response.headers),
3974
+ url: response.url,
3975
+ redirected: response.redirected,
3976
+ ok: response.ok,
3977
+ bodyUsed: response.bodyUsed
3978
+ };
3919
3979
  }
3920
- function parseConnectStatus(head) {
3921
- const statusLine = head.split("\r\n", 1)[0] ?? "";
3922
- const match = /^HTTP\/1\.[01] (\d{3})(?: (.*))?$/.exec(statusLine);
3923
- if (!match)
3980
+ function parseContentType2(value) {
3981
+ if (!value)
3924
3982
  return null;
3925
- return { status: Number(match[1]), reason: match[2] ?? "" };
3983
+ if (value.length > MAX_HEADER_LENGTH)
3984
+ return null;
3985
+ const parsed = parseContentType(value);
3986
+ if (!parsed)
3987
+ return null;
3988
+ return {
3989
+ mediaType: parsed.mediaType,
3990
+ type: parsed.type,
3991
+ subtype: parsed.subtype,
3992
+ charset: parsed.charset,
3993
+ boundary: parsed.boundary
3994
+ };
3926
3995
  }
3927
- async function connectThroughProxy(proxy, target, options = {}) {
3928
- const errMeta = options.request !== void 0 ? { request: options.request } : {};
3929
- if (isSocksProxy(proxy.url)) {
3930
- throw new KinetexError(`proxy url "${proxy.url}" is a SOCKS proxy, which needs a different mechanism. Use createSocks5Tunnel() from kinetex/socks5 and supply your own fetch over the tunnel.`, "EVALIDATION", errMeta);
3931
- }
3932
- let proxyUrl;
3933
- try {
3934
- proxyUrl = new URL(proxy.url);
3935
- } catch (err) {
3936
- throw new KinetexError(`proxy url "${proxy.url}" is not a valid URL`, "EVALIDATION", {
3937
- ...errMeta,
3938
- cause: err
3996
+ function isJSON(contentType) {
3997
+ if (!contentType)
3998
+ return false;
3999
+ const info = parseContentType2(contentType);
4000
+ if (!info)
4001
+ return false;
4002
+ return info.mediaType === "application/json" || info.subtype.endsWith("+json");
4003
+ }
4004
+ function isText(contentType) {
4005
+ if (!contentType)
4006
+ return false;
4007
+ const info = parseContentType2(contentType);
4008
+ return info?.type === "text";
4009
+ }
4010
+ function isBinary(contentType) {
4011
+ return !isText(contentType) && !isJSON(contentType);
4012
+ }
4013
+ function readWithAbort(reader, signal) {
4014
+ if (!signal)
4015
+ return reader.read();
4016
+ if (signal.aborted)
4017
+ return Promise.resolve({ done: true, value: void 0 });
4018
+ return new Promise((resolve, reject) => {
4019
+ const onAbort = () => {
4020
+ void reader.cancel("aborted").catch(() => {
4021
+ });
4022
+ resolve({ done: true, value: void 0 });
4023
+ };
4024
+ signal.addEventListener("abort", onAbort, { once: true });
4025
+ reader.read().then(resolve, reject).finally(() => {
4026
+ signal.removeEventListener("abort", onAbort);
3939
4027
  });
4028
+ });
4029
+ }
4030
+ function normaliseOnExceed(mode) {
4031
+ return mode === "truncate" || mode === "abort" ? mode : "throw";
4032
+ }
4033
+ function limitAction(chunk, bytesRead, limit, onExceed, makeError) {
4034
+ const newTotal = bytesRead + chunk.byteLength;
4035
+ if (newTotal <= limit.maxBytes)
4036
+ return { kind: "keep" };
4037
+ limit.onExceedCallback?.(newTotal, limit.maxBytes);
4038
+ if (onExceed === "throw")
4039
+ throw makeError(newTotal, limit.maxBytes);
4040
+ if (onExceed === "truncate") {
4041
+ const remaining = limit.maxBytes - bytesRead;
4042
+ return { kind: "truncate", chunk: chunk.slice(0, remaining) };
3940
4043
  }
3941
- if (proxyUrl.protocol !== "http:" && proxyUrl.protocol !== "https:") {
3942
- throw new KinetexError(`proxy url scheme "${proxyUrl.protocol}" is not supported; use http:, https:, socks5: or socks5h:`, "EVALIDATION", errMeta);
3943
- }
3944
- const net = await import("node:net");
3945
- const tls = await import("node:tls");
3946
- const proxyPort = proxyUrl.port ? Number(proxyUrl.port) : proxyUrl.protocol === "https:" ? 443 : 80;
3947
- const targetPort = target.port ? Number(target.port) : target.protocol === "https:" ? 443 : 80;
3948
- const authority = `${target.hostname}:${targetPort}`;
3949
- const timeoutMs = options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
3950
- const baseSocket = proxyUrl.protocol === "https:" ? await openTlsSocket(tls, {
3951
- host: proxyUrl.hostname,
3952
- port: proxyPort,
3953
- servername: proxyUrl.hostname,
3954
- ...options.ca !== void 0 ? { ca: options.ca } : {}
3955
- }, timeoutMs, options.signal, errMeta, `proxy ${proxyUrl.host}`) : await openTcpSocket(net, { host: proxyUrl.hostname, port: proxyPort }, timeoutMs, options.signal, errMeta, `proxy ${proxyUrl.host}`);
3956
- const auth = proxyAuthHeader(proxy);
3957
- const connectLines = [
3958
- `CONNECT ${authority} HTTP/1.1`,
3959
- `Host: ${authority}`,
3960
- // RFC 9110 §9.3.6: proxies that support persistent tunnels are expected to
3961
- // treat a tunnel as still-open even without this, but sending it is what
3962
- // every real client does and some proxies misbehave without it.
3963
- "Proxy-Connection: Keep-Alive",
3964
- ...auth !== void 0 ? [`Proxy-Authorization: ${auth}`] : [],
3965
- "",
3966
- ""
3967
- ];
3968
- baseSocket.write(connectLines.join("\r\n"));
3969
- let head = "";
3970
- const headResult = await new Promise((resolve) => {
3971
- const drain = () => {
3972
- for (; ; ) {
3973
- const chunk = baseSocket.read();
3974
- if (chunk === null)
3975
- return;
3976
- head += chunk.toString("latin1");
4044
+ return { kind: "stop" };
4045
+ }
4046
+ async function readBodyWithLimit(stream, url, limit, signal) {
4047
+ const onExceed = normaliseOnExceed(limit.onExceed);
4048
+ const reader = stream.getReader();
4049
+ const chunks = [];
4050
+ let bytesRead = 0;
4051
+ try {
4052
+ while (true) {
4053
+ if (signal?.aborted) {
4054
+ await reader.cancel("aborted");
4055
+ throw new DOMException("Response reading aborted", "AbortError");
3977
4056
  }
3978
- };
3979
- const onReadable = () => {
3980
- drain();
3981
- const end = head.indexOf("\r\n\r\n");
3982
- if (end === -1) {
3983
- if (head.length > MAX_HEAD_BYTES) {
3984
- cleanup();
3985
- resolve({
3986
- ok: false,
3987
- err: new KinetexError("Proxy sent an oversized CONNECT response", "EPROXY", errMeta)
3988
- });
4057
+ const { done, value } = await readWithAbort(reader, signal);
4058
+ if (done) {
4059
+ if (signal?.aborted) {
4060
+ throw new DOMException("Response reading aborted", "AbortError");
3989
4061
  }
3990
- return;
4062
+ break;
3991
4063
  }
3992
- cleanup();
3993
- const parsed = parseConnectStatus(head.slice(0, end));
3994
- if (!parsed) {
3995
- resolve({
3996
- ok: false,
3997
- err: new KinetexError(`Proxy returned a malformed CONNECT response: ${JSON.stringify(head.slice(0, end).split("\r\n", 1)[0] ?? "")}`, "EPROXY", errMeta)
3998
- });
3999
- return;
4064
+ let action;
4065
+ try {
4066
+ action = limitAction(value, bytesRead, limit, onExceed, (n, max) => new ResponseSizeLimitError(n, max, url));
4067
+ } catch (err) {
4068
+ await reader.cancel("size limit exceeded");
4069
+ throw err;
4000
4070
  }
4001
- if (parsed.status >= 200 && parsed.status < 300) {
4002
- const tail = head.slice(end + 4);
4003
- if (tail.length > 0)
4004
- baseSocket.unshift(Buffer.from(tail, "latin1"));
4005
- resolve({ ok: true });
4006
- return;
4071
+ if (action.kind === "stop") {
4072
+ await reader.cancel("size limit exceeded");
4073
+ break;
4007
4074
  }
4008
- if (parsed.status === 407) {
4009
- resolve({
4010
- ok: false,
4011
- err: new KinetexError("Proxy requires authentication (407). Set `username`/`password` on the proxy config, or embed them in the proxy URL.", "EPROXY", errMeta)
4012
- });
4013
- return;
4075
+ if (action.kind === "truncate") {
4076
+ chunks.push(action.chunk);
4077
+ bytesRead += action.chunk.byteLength;
4078
+ break;
4014
4079
  }
4015
- resolve({
4016
- ok: false,
4017
- err: new KinetexError(`Proxy refused CONNECT to ${authority}: ${parsed.status} ${parsed.reason}`.trim(), "EPROXY", errMeta)
4080
+ chunks.push(value);
4081
+ bytesRead += value.byteLength;
4082
+ }
4083
+ } finally {
4084
+ if (signal?.aborted) {
4085
+ await reader.cancel("aborted").catch(() => {
4018
4086
  });
4019
- };
4020
- const onError = (e) => {
4021
- cleanup();
4022
- resolve({
4023
- ok: false,
4024
- err: new KinetexError(`Proxy connection failed: ${e.message}`, "ENETWORK", {
4025
- ...errMeta,
4026
- cause: e
4027
- })
4028
- });
4029
- };
4030
- const onClosedEarly = () => {
4031
- cleanup();
4032
- resolve({
4033
- ok: false,
4034
- err: new KinetexError(`Proxy closed the connection before answering CONNECT for ${authority}`, "EPROXY", errMeta)
4035
- });
4036
- };
4037
- const onTimeout = () => {
4038
- cleanup();
4039
- baseSocket.destroy();
4040
- resolve({
4041
- ok: false,
4042
- err: new KinetexError(`Proxy did not respond to CONNECT for ${authority} within ${timeoutMs}ms`, "ETIMEOUT", errMeta)
4043
- });
4044
- };
4045
- const onAbort = () => {
4046
- cleanup();
4047
- baseSocket.destroy();
4048
- resolve({
4049
- ok: false,
4050
- err: new KinetexError("Proxy connection aborted", "EABORT", errMeta)
4051
- });
4052
- };
4053
- const timer = setTimeout(onTimeout, timeoutMs);
4054
- if (typeof timer.unref === "function") {
4055
- timer.unref();
4056
- }
4057
- function cleanup() {
4058
- clearTimeout(timer);
4059
- baseSocket.off("readable", onReadable);
4060
- baseSocket.off("error", onError);
4061
- baseSocket.off("end", onClosedEarly);
4062
- baseSocket.off("close", onClosedEarly);
4063
- baseSocket.off("timeout", onTimeout);
4064
- options.signal?.removeEventListener("abort", onAbort);
4065
4087
  }
4066
- baseSocket.on("readable", onReadable);
4067
- baseSocket.once("error", onError);
4068
- baseSocket.once("end", onClosedEarly);
4069
- baseSocket.once("close", onClosedEarly);
4070
- baseSocket.setTimeout(timeoutMs, onTimeout);
4071
- options.signal?.addEventListener("abort", onAbort, { once: true });
4072
- drain();
4073
- });
4074
- if (!headResult.ok) {
4075
- baseSocket.destroy();
4076
- throw headResult.err;
4088
+ reader.releaseLock();
4077
4089
  }
4078
- if (target.protocol !== "https:") {
4079
- baseSocket.setTimeout(0);
4080
- return baseSocket;
4090
+ return concatUint8Arrays2(chunks);
4091
+ }
4092
+ function resolveCharset(charset) {
4093
+ if (!charset)
4094
+ return "utf-8";
4095
+ const key = charset.toLowerCase().trim();
4096
+ if (Object.prototype.hasOwnProperty.call(CHARSET_ALIASES, key)) {
4097
+ return CHARSET_ALIASES[key];
4081
4098
  }
4082
- return await new Promise((resolve, reject) => {
4083
- const onTunnelTimeout = () => {
4084
- done();
4085
- baseSocket.destroy();
4086
- reject(new KinetexError(`TLS handshake through proxy to ${target.hostname} did not complete within ${timeoutMs}ms`, "ETIMEOUT", errMeta));
4087
- };
4088
- const onTunnelAbort = () => {
4089
- done();
4090
- baseSocket.destroy();
4091
- reject(new KinetexError("Proxy connection aborted", "EABORT", errMeta));
4092
- };
4093
- const tunnelTimer = setTimeout(onTunnelTimeout, timeoutMs);
4094
- if (typeof tunnelTimer.unref === "function") {
4095
- tunnelTimer.unref();
4096
- }
4097
- options.signal?.addEventListener("abort", onTunnelAbort, { once: true });
4098
- function done() {
4099
- clearTimeout(tunnelTimer);
4100
- options.signal?.removeEventListener("abort", onTunnelAbort);
4101
- }
4102
- const secure = tls.connect(
4103
- {
4104
- socket: baseSocket,
4105
- servername: options.servername ?? target.hostname,
4106
- ...options.ca !== void 0 ? { ca: options.ca } : {}
4107
- },
4108
- // `rejectUnauthorized` is left at its default (true), so a peer that
4109
- // fails verification emits `error` rather than reaching this callback.
4110
- // Verification therefore cannot be bypassed here, and the callback
4111
- // only ever runs for a verified handshake.
4112
- () => {
4113
- done();
4114
- baseSocket.setTimeout(0);
4115
- resolve(secure);
4116
- }
4117
- );
4118
- secure.once("error", (e) => {
4119
- done();
4120
- baseSocket.destroy();
4121
- reject(new KinetexError(`TLS handshake through proxy failed for ${target.hostname}: ${e.message}`, "ENETWORK", { ...errMeta, cause: e }));
4122
- });
4123
- });
4099
+ return "utf-8";
4124
4100
  }
4125
- async function openTcpSocket(net, opts, timeoutMs, signal, errMeta, what) {
4126
- return await new Promise((resolve, reject) => {
4127
- const socket = net.connect({ host: opts.host, port: opts.port });
4128
- const onError = (e) => {
4129
- cleanup();
4130
- reject(new KinetexError(`Cannot reach ${what}: ${e.message}`, "ENETWORK", {
4131
- ...errMeta,
4132
- cause: e
4133
- }));
4134
- };
4135
- const timer = setTimeout(() => {
4136
- cleanup();
4137
- socket.destroy();
4138
- reject(new KinetexError(`Connecting to ${what} timed out`, "ETIMEOUT", errMeta));
4139
- }, timeoutMs);
4140
- if (typeof timer.unref === "function") {
4141
- timer.unref();
4142
- }
4143
- const onAbort = () => {
4144
- cleanup();
4145
- socket.destroy();
4146
- reject(new KinetexError("Proxy connection aborted", "EABORT", errMeta));
4147
- };
4148
- function cleanup() {
4149
- clearTimeout(timer);
4150
- socket.off("error", onError);
4151
- signal?.removeEventListener("abort", onAbort);
4152
- }
4153
- socket.once("error", onError);
4154
- socket.once("connect", () => {
4155
- cleanup();
4156
- resolve(socket);
4157
- });
4158
- if (signal?.aborted)
4159
- onAbort();
4160
- else
4161
- signal?.addEventListener("abort", onAbort, { once: true });
4162
- });
4101
+ function detectBOM(bytes) {
4102
+ if (bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191)
4103
+ return "utf-8";
4104
+ if (bytes[0] === 255 && bytes[1] === 254)
4105
+ return "utf-16le";
4106
+ if (bytes[0] === 254 && bytes[1] === 255)
4107
+ return "utf-16be";
4108
+ return null;
4163
4109
  }
4164
- async function openTlsSocket(tls, opts, timeoutMs, signal, errMeta, what) {
4165
- return await new Promise((resolve, reject) => {
4166
- const socket = tls.connect({
4167
- host: opts.host,
4168
- port: opts.port,
4169
- servername: opts.servername,
4170
- ...opts.ca !== void 0 ? { ca: opts.ca } : {}
4171
- }, () => {
4172
- cleanup();
4173
- resolve(socket);
4174
- });
4175
- const onError = (e) => {
4176
- cleanup();
4177
- reject(new KinetexError(`Cannot reach ${what}: ${e.message}`, "ENETWORK", {
4178
- ...errMeta,
4179
- cause: e
4180
- }));
4181
- };
4182
- const timer = setTimeout(() => {
4183
- cleanup();
4184
- socket.destroy();
4185
- reject(new KinetexError(`Connecting to ${what} timed out`, "ETIMEOUT", errMeta));
4186
- }, timeoutMs);
4187
- if (typeof timer.unref === "function") {
4188
- timer.unref();
4110
+ function decodeBody(bytes, charset, url) {
4111
+ const detected = detectBOM(bytes);
4112
+ const resolved = detected ?? resolveCharset(charset);
4113
+ const data = detected ? bytes.slice(detected === "utf-8" ? 3 : 2) : bytes;
4114
+ try {
4115
+ return new TextDecoder(resolved, { fatal: true }).decode(data);
4116
+ } catch {
4117
+ try {
4118
+ return new TextDecoder(resolved, { fatal: false }).decode(data);
4119
+ } catch (err2) {
4120
+ throw new ResponseDecodeError(String(err2), resolved, url ?? "<unknown url>");
4189
4121
  }
4190
- const onAbort = () => {
4191
- cleanup();
4192
- socket.destroy();
4193
- reject(new KinetexError("Proxy connection aborted", "EABORT", errMeta));
4194
- };
4195
- function cleanup() {
4196
- clearTimeout(timer);
4197
- socket.off("error", onError);
4198
- signal?.removeEventListener("abort", onAbort);
4122
+ }
4123
+ }
4124
+ function createSizeLimitedDecompression(stream, encoding, maxDecompressedSize = 1e8) {
4125
+ const decompressed = decompressStream(stream, encoding);
4126
+ let bytesRead = 0;
4127
+ const sizeLimitTransform = new TransformStream({
4128
+ transform(chunk, controller) {
4129
+ bytesRead += chunk.byteLength;
4130
+ if (bytesRead > maxDecompressedSize) {
4131
+ controller.error(new ResponseSizeLimitError(bytesRead, maxDecompressedSize, "decompression"));
4132
+ return;
4133
+ }
4134
+ controller.enqueue(chunk);
4199
4135
  }
4200
- socket.once("error", onError);
4201
- if (signal?.aborted)
4202
- onAbort();
4203
- else
4204
- signal?.addEventListener("abort", onAbort, { once: true });
4205
4136
  });
4137
+ return decompressed.pipeThrough(sizeLimitTransform);
4206
4138
  }
4207
- var DEFAULT_CONNECT_TIMEOUT_MS, MAX_HEAD_BYTES;
4208
- var init_proxy = __esm({
4209
- "dist/esm/proxy.js"() {
4210
- "use strict";
4211
- init_types();
4212
- DEFAULT_CONNECT_TIMEOUT_MS = 3e4;
4213
- MAX_HEAD_BYTES = 64 * 1024;
4214
- }
4215
- });
4216
-
4217
- // dist/esm/response.js
4218
- var response_exports = {};
4219
- __export(response_exports, {
4220
- ContentTypeError: () => ContentTypeError,
4221
- HTTPResponseError: () => HTTPResponseError,
4222
- ResponseCache: () => ResponseCache,
4223
- ResponseDecodeError: () => ResponseDecodeError,
4224
- ResponseSizeLimitError: () => ResponseSizeLimitError,
4225
- applyDecompression: () => applyDecompression,
4226
- assertOk: () => assertOk,
4227
- assertOkJSON: () => assertOkJSON,
4228
- createLimitedReader: () => createLimitedReader,
4229
- decodeBody: () => decodeBody,
4230
- decompressStream: () => decompressStream,
4231
- diffResponses: () => diffResponses,
4232
- extractServerTiming: () => extractServerTiming,
4233
- isBinary: () => isBinary,
4234
- isJSON: () => isJSON,
4235
- isText: () => isText,
4236
- normalizeHeaders: () => normalizeHeaders2,
4237
- normalizeResponse: () => normalizeResponse,
4238
- parseContentType: () => parseContentType2,
4239
- parseMultipartResponse: () => parseMultipartResponse,
4240
- readBlob: () => readBlob,
4241
- readBodyWithLimit: () => readBodyWithLimit,
4242
- readBytes: () => readBytes,
4243
- readFormData: () => readFormData,
4244
- readJSON: () => readJSON,
4245
- readJSONStream: () => readJSONStream,
4246
- readNDJSON: () => readNDJSON,
4247
- readStream: () => readStream,
4248
- readText: () => readText
4249
- });
4250
- function normalizeHeaders2(headers) {
4251
- return normalizeHeaders(headers);
4252
- }
4253
- function normalizeResponse(response) {
4254
- return {
4255
- status: response.status,
4256
- statusText: response.statusText,
4257
- headers: normalizeHeaders2(response.headers),
4258
- url: response.url,
4259
- redirected: response.redirected,
4260
- ok: response.ok,
4261
- bodyUsed: response.bodyUsed
4262
- };
4263
- }
4264
- function parseContentType2(value) {
4265
- if (!value)
4266
- return null;
4267
- if (value.length > MAX_HEADER_LENGTH)
4268
- return null;
4269
- const parsed = parseContentType(value);
4270
- if (!parsed)
4271
- return null;
4272
- return {
4273
- mediaType: parsed.mediaType,
4274
- type: parsed.type,
4275
- subtype: parsed.subtype,
4276
- charset: parsed.charset,
4277
- boundary: parsed.boundary
4278
- };
4279
- }
4280
- function isJSON(contentType) {
4281
- if (!contentType)
4282
- return false;
4283
- const info = parseContentType2(contentType);
4284
- if (!info)
4285
- return false;
4286
- return info.mediaType === "application/json" || info.subtype.endsWith("+json");
4287
- }
4288
- function isText(contentType) {
4289
- if (!contentType)
4290
- return false;
4291
- const info = parseContentType2(contentType);
4292
- return info?.type === "text";
4293
- }
4294
- function isBinary(contentType) {
4295
- return !isText(contentType) && !isJSON(contentType);
4296
- }
4297
- function readWithAbort(reader, signal) {
4298
- if (!signal)
4299
- return reader.read();
4300
- if (signal.aborted)
4301
- return Promise.resolve({ done: true, value: void 0 });
4302
- return new Promise((resolve, reject) => {
4303
- const onAbort = () => {
4304
- void reader.cancel("aborted").catch(() => {
4305
- });
4306
- resolve({ done: true, value: void 0 });
4307
- };
4308
- signal.addEventListener("abort", onAbort, { once: true });
4309
- reader.read().then(resolve, reject).finally(() => {
4310
- signal.removeEventListener("abort", onAbort);
4311
- });
4312
- });
4313
- }
4314
- function normaliseOnExceed(mode) {
4315
- return mode === "truncate" || mode === "abort" ? mode : "throw";
4316
- }
4317
- async function readBodyWithLimit(stream, url, limit, signal) {
4318
- const onExceed = normaliseOnExceed(limit.onExceed);
4319
- const reader = stream.getReader();
4320
- const chunks = [];
4321
- let bytesRead = 0;
4322
- try {
4323
- while (true) {
4324
- if (signal?.aborted) {
4325
- await reader.cancel("aborted");
4326
- throw new DOMException("Response reading aborted", "AbortError");
4327
- }
4328
- const { done, value } = await readWithAbort(reader, signal);
4329
- if (done) {
4330
- if (signal?.aborted) {
4331
- throw new DOMException("Response reading aborted", "AbortError");
4332
- }
4333
- break;
4334
- }
4335
- const newTotal = bytesRead + value.byteLength;
4336
- if (newTotal > limit.maxBytes) {
4337
- limit.onExceedCallback?.(newTotal, limit.maxBytes);
4338
- if (onExceed === "throw") {
4339
- await reader.cancel("size limit exceeded");
4340
- throw new ResponseSizeLimitError(newTotal, limit.maxBytes, url);
4341
- }
4342
- if (onExceed === "abort") {
4343
- await reader.cancel("size limit exceeded");
4344
- break;
4345
- }
4346
- if (onExceed === "truncate") {
4347
- const remaining = limit.maxBytes - bytesRead;
4348
- if (remaining > 0) {
4349
- chunks.push(value.slice(0, remaining));
4350
- bytesRead += remaining;
4351
- }
4352
- break;
4353
- }
4354
- }
4355
- chunks.push(value);
4356
- bytesRead += value.byteLength;
4357
- }
4358
- } finally {
4359
- reader.releaseLock();
4360
- }
4361
- return concatUint8Arrays2(chunks);
4362
- }
4363
- function resolveCharset(charset) {
4364
- if (!charset)
4365
- return "utf-8";
4366
- const key = charset.toLowerCase().trim();
4367
- if (Object.prototype.hasOwnProperty.call(CHARSET_ALIASES, key)) {
4368
- return CHARSET_ALIASES[key];
4369
- }
4370
- return "utf-8";
4371
- }
4372
- function detectBOM(bytes) {
4373
- if (bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191)
4374
- return "utf-8";
4375
- if (bytes[0] === 255 && bytes[1] === 254)
4376
- return "utf-16le";
4377
- if (bytes[0] === 254 && bytes[1] === 255)
4378
- return "utf-16be";
4379
- return null;
4380
- }
4381
- function decodeBody(bytes, charset, url) {
4382
- const detected = detectBOM(bytes);
4383
- const resolved = detected ?? resolveCharset(charset);
4384
- const data = detected ? bytes.slice(detected === "utf-8" ? 3 : 2) : bytes;
4385
- try {
4386
- return new TextDecoder(resolved, { fatal: true }).decode(data);
4387
- } catch {
4388
- try {
4389
- return new TextDecoder(resolved, { fatal: false }).decode(data);
4390
- } catch (err2) {
4391
- throw new ResponseDecodeError(String(err2), resolved, url);
4392
- }
4393
- }
4394
- }
4395
- function createSizeLimitedDecompression(stream, encoding, maxDecompressedSize = 1e8) {
4396
- const decompressed = decompressStream(stream, encoding);
4397
- let bytesRead = 0;
4398
- const sizeLimitTransform = new TransformStream({
4399
- transform(chunk, controller) {
4400
- bytesRead += chunk.byteLength;
4401
- if (bytesRead > maxDecompressedSize) {
4402
- controller.error(new ResponseSizeLimitError(bytesRead, maxDecompressedSize, "decompression"));
4403
- return;
4404
- }
4405
- controller.enqueue(chunk);
4406
- }
4407
- });
4408
- return decompressed.pipeThrough(sizeLimitTransform);
4409
- }
4410
- function decompressStream(stream, encoding) {
4411
- const normalized = encoding.trim().toLowerCase();
4412
- if (normalized === "br") {
4413
- return brotliDecompressStream(stream);
4139
+ function decompressStream(stream, encoding) {
4140
+ const normalized = encoding.trim().toLowerCase();
4141
+ if (normalized === "br") {
4142
+ return brotliDecompressStream(stream);
4414
4143
  }
4415
4144
  if (normalized === "zstd") {
4416
4145
  return zstdDecompressStream(stream);
@@ -4419,12 +4148,65 @@ var kinetex = (() => {
4419
4148
  return stream;
4420
4149
  try {
4421
4150
  const format = normalized;
4422
- const ds = new DecompressionStream(format);
4423
- return stream.pipeThrough(ds);
4151
+ const decoder = new DecompressionStream(format);
4152
+ return decodeUnlessEmpty(stream, decoder);
4424
4153
  } catch {
4425
4154
  return stream;
4426
4155
  }
4427
4156
  }
4157
+ function decodeUnlessEmpty(source, decoder) {
4158
+ const reader = source.getReader();
4159
+ return new ReadableStream({
4160
+ async start(controller) {
4161
+ let first = null;
4162
+ for (; ; ) {
4163
+ const next = await reader.read().catch(() => ({ done: true, value: void 0 }));
4164
+ if (next.done || next.value.byteLength > 0) {
4165
+ first = next;
4166
+ break;
4167
+ }
4168
+ }
4169
+ if (first.done) {
4170
+ controller.close();
4171
+ return;
4172
+ }
4173
+ const rehydrated = new ReadableStream({
4174
+ start(c) {
4175
+ c.enqueue(first.value);
4176
+ },
4177
+ async pull(c) {
4178
+ const next = await reader.read();
4179
+ if (next.done)
4180
+ c.close();
4181
+ else
4182
+ c.enqueue(next.value);
4183
+ },
4184
+ cancel(reason) {
4185
+ void reader.cancel(reason).catch(() => void 0);
4186
+ }
4187
+ });
4188
+ const out = rehydrated.pipeThrough(decoder);
4189
+ const pump = out.getReader();
4190
+ void (async () => {
4191
+ try {
4192
+ for (; ; ) {
4193
+ const chunk = await pump.read();
4194
+ if (chunk.done) {
4195
+ controller.close();
4196
+ return;
4197
+ }
4198
+ controller.enqueue(chunk.value);
4199
+ }
4200
+ } catch (err) {
4201
+ controller.error(err);
4202
+ }
4203
+ })();
4204
+ },
4205
+ cancel(reason) {
4206
+ void reader.cancel(reason).catch(() => void 0);
4207
+ }
4208
+ });
4209
+ }
4428
4210
  function brotliDecompressStream(stream) {
4429
4211
  if (!isNodeRuntime())
4430
4212
  return stream;
@@ -4441,6 +4223,7 @@ var kinetex = (() => {
4441
4223
  let settled = false;
4442
4224
  let reader = null;
4443
4225
  let cancelRequested = false;
4226
+ let sawBytes = false;
4444
4227
  return new ReadableStream({
4445
4228
  async start(controller) {
4446
4229
  const zlib = await import("node:zlib");
@@ -4474,9 +4257,19 @@ var kinetex = (() => {
4474
4257
  while (true) {
4475
4258
  const { done, value } = await reader.read();
4476
4259
  if (done) {
4260
+ if (!sawBytes) {
4261
+ if (!settled) {
4262
+ settled = true;
4263
+ controller.close();
4264
+ }
4265
+ br.destroy();
4266
+ break;
4267
+ }
4477
4268
  br.end();
4478
4269
  break;
4479
4270
  }
4271
+ if (value.byteLength > 0)
4272
+ sawBytes = true;
4480
4273
  br.write(value);
4481
4274
  }
4482
4275
  } catch (e) {
@@ -4874,9 +4667,9 @@ var kinetex = (() => {
4874
4667
  };
4875
4668
  }).filter((m) => m.name);
4876
4669
  }
4877
- async function readBodyBytes(response, url, options) {
4670
+ function readBodyBytes(response, url, options) {
4878
4671
  if (!response.body)
4879
- return new Uint8Array(0);
4672
+ return Promise.resolve(new Uint8Array(0));
4880
4673
  const headers = normalizeHeaders2(response.headers);
4881
4674
  let stream = response.body;
4882
4675
  if (options.decompress !== false) {
@@ -4888,30 +4681,7 @@ var kinetex = (() => {
4888
4681
  if (options.sizeLimit) {
4889
4682
  return readBodyWithLimit(stream, url, options.sizeLimit, options.signal);
4890
4683
  }
4891
- const reader = stream.getReader();
4892
- const chunks = [];
4893
- try {
4894
- while (true) {
4895
- if (options.signal?.aborted) {
4896
- throw new DOMException("Response reading aborted", "AbortError");
4897
- }
4898
- const { done, value } = await readWithAbort(reader, options.signal);
4899
- if (done) {
4900
- if (options.signal?.aborted) {
4901
- throw new DOMException("Response reading aborted", "AbortError");
4902
- }
4903
- break;
4904
- }
4905
- chunks.push(value);
4906
- }
4907
- } finally {
4908
- if (options.signal?.aborted) {
4909
- await reader.cancel("aborted").catch(() => {
4910
- });
4911
- }
4912
- reader.releaseLock();
4913
- }
4914
- return concatUint8Arrays2(chunks);
4684
+ return readBodyWithLimit(stream, url, { maxBytes: Infinity }, options.signal);
4915
4685
  }
4916
4686
  function applySizeLimit(stream, url, limit, signal) {
4917
4687
  const onExceed = normaliseOnExceed(limit.onExceed);
@@ -4937,27 +4707,20 @@ var kinetex = (() => {
4937
4707
  controller.close();
4938
4708
  break;
4939
4709
  }
4940
- const newTotal = bytesRead + value.byteLength;
4941
- if (newTotal > limit.maxBytes) {
4942
- limit.onExceedCallback?.(newTotal, limit.maxBytes);
4943
- if (onExceed === "throw") {
4944
- await reader.cancel("size limit");
4945
- controller.error(new ResponseSizeLimitError(newTotal, limit.maxBytes, url));
4946
- return;
4947
- }
4948
- if (onExceed === "abort") {
4949
- await reader.cancel("size limit");
4950
- controller.close();
4951
- return;
4952
- }
4953
- if (onExceed === "truncate") {
4954
- const remaining = limit.maxBytes - bytesRead;
4955
- if (remaining > 0)
4956
- controller.enqueue(value.slice(0, remaining));
4957
- controller.close();
4958
- await reader.cancel("size limit");
4959
- return;
4960
- }
4710
+ let action;
4711
+ try {
4712
+ action = limitAction(value, bytesRead, limit, onExceed, (n, max) => new ResponseSizeLimitError(n, max, url));
4713
+ } catch (err) {
4714
+ await reader.cancel("size limit exceeded");
4715
+ controller.error(err);
4716
+ return;
4717
+ }
4718
+ if (action.kind !== "keep") {
4719
+ if (action.kind === "truncate")
4720
+ controller.enqueue(action.chunk);
4721
+ controller.close();
4722
+ await reader.cancel("size limit exceeded");
4723
+ return;
4961
4724
  }
4962
4725
  bytesRead += value.byteLength;
4963
4726
  controller.enqueue(value);
@@ -5195,6 +4958,330 @@ var kinetex = (() => {
5195
4958
  }
5196
4959
  });
5197
4960
 
4961
+ // dist/esm/proxy.js
4962
+ var proxy_exports = {};
4963
+ __export(proxy_exports, {
4964
+ connectThroughProxy: () => connectThroughProxy
4965
+ });
4966
+ function isIpLiteral(host) {
4967
+ const h = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
4968
+ return h.includes(":") || /^\d+(\.\d+)*$/.test(h);
4969
+ }
4970
+ function isSocksProxy(url) {
4971
+ return /^socks5h?:/i.test(url);
4972
+ }
4973
+ function proxyAuthHeader(proxy) {
4974
+ if (proxy.username === void 0)
4975
+ return void 0;
4976
+ const raw = `${proxy.username}:${proxy.password ?? ""}`;
4977
+ const bytes = new TextEncoder().encode(raw);
4978
+ let binary = "";
4979
+ for (const b of bytes)
4980
+ binary += String.fromCharCode(b);
4981
+ const b64 = typeof globalThis.btoa === "function" ? globalThis.btoa(binary) : (
4982
+ // Node without btoa (or a non-Latin1 username) — encode explicitly.
4983
+ Buffer.from(bytes).toString("base64")
4984
+ );
4985
+ return `Basic ${b64}`;
4986
+ }
4987
+ function parseConnectStatus(head) {
4988
+ const statusLine = head.split("\r\n", 1)[0] ?? "";
4989
+ const match = /^HTTP\/1\.[01] (\d{3})(?: (.*))?$/.exec(statusLine);
4990
+ if (!match)
4991
+ return null;
4992
+ return { status: Number(match[1]), reason: match[2] ?? "" };
4993
+ }
4994
+ async function connectThroughProxy(proxy, target, options = {}) {
4995
+ const errMeta = options.request !== void 0 ? { request: options.request } : {};
4996
+ if (isSocksProxy(proxy.url)) {
4997
+ throw new KinetexError(`proxy url "${proxy.url}" is a SOCKS proxy, which needs a different mechanism. Use createSocks5Tunnel() from kinetex/socks5 and supply your own fetch over the tunnel.`, "EVALIDATION", errMeta);
4998
+ }
4999
+ let proxyUrl;
5000
+ try {
5001
+ proxyUrl = new URL(proxy.url);
5002
+ } catch (err) {
5003
+ throw new KinetexError(`proxy url "${proxy.url}" is not a valid URL`, "EVALIDATION", {
5004
+ ...errMeta,
5005
+ cause: err
5006
+ });
5007
+ }
5008
+ if (proxyUrl.protocol !== "http:" && proxyUrl.protocol !== "https:") {
5009
+ throw new KinetexError(`proxy url scheme "${proxyUrl.protocol}" is not supported; use http:, https:, socks5: or socks5h:`, "EVALIDATION", errMeta);
5010
+ }
5011
+ const net = await import("node:net");
5012
+ const tls = await import("node:tls");
5013
+ const proxyPort = proxyUrl.port ? Number(proxyUrl.port) : proxyUrl.protocol === "https:" ? 443 : 80;
5014
+ const targetPort = target.port ? Number(target.port) : target.protocol === "https:" ? 443 : 80;
5015
+ const authority = `${target.hostname}:${targetPort}`;
5016
+ const timeoutMs = options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
5017
+ const baseSocket = proxyUrl.protocol === "https:" ? await openTlsSocket(tls, {
5018
+ host: proxyUrl.hostname,
5019
+ port: proxyPort,
5020
+ servername: proxyUrl.hostname,
5021
+ ...options.ca !== void 0 ? { ca: options.ca } : {}
5022
+ }, timeoutMs, options.signal, errMeta, `proxy ${proxyUrl.host}`) : await openTcpSocket(net, { host: proxyUrl.hostname, port: proxyPort }, timeoutMs, options.signal, errMeta, `proxy ${proxyUrl.host}`);
5023
+ const auth = proxyAuthHeader(proxy);
5024
+ const connectLines = [
5025
+ `CONNECT ${authority} HTTP/1.1`,
5026
+ `Host: ${authority}`,
5027
+ // RFC 9110 §9.3.6: proxies that support persistent tunnels are expected to
5028
+ // treat a tunnel as still-open even without this, but sending it is what
5029
+ // every real client does and some proxies misbehave without it.
5030
+ "Proxy-Connection: Keep-Alive",
5031
+ ...auth !== void 0 ? [`Proxy-Authorization: ${auth}`] : [],
5032
+ "",
5033
+ ""
5034
+ ];
5035
+ baseSocket.write(connectLines.join("\r\n"));
5036
+ let head = "";
5037
+ const headResult = await new Promise((resolve) => {
5038
+ const drain = () => {
5039
+ for (; ; ) {
5040
+ const chunk = baseSocket.read();
5041
+ if (chunk === null)
5042
+ return;
5043
+ head += chunk.toString("latin1");
5044
+ }
5045
+ };
5046
+ const onReadable = () => {
5047
+ drain();
5048
+ const end = head.indexOf("\r\n\r\n");
5049
+ if (end === -1) {
5050
+ if (head.length > MAX_HEAD_BYTES) {
5051
+ cleanup();
5052
+ resolve({
5053
+ ok: false,
5054
+ err: new KinetexError("Proxy sent an oversized CONNECT response", "EPROXY", errMeta)
5055
+ });
5056
+ }
5057
+ return;
5058
+ }
5059
+ cleanup();
5060
+ const parsed = parseConnectStatus(head.slice(0, end));
5061
+ if (!parsed) {
5062
+ resolve({
5063
+ ok: false,
5064
+ err: new KinetexError(`Proxy returned a malformed CONNECT response: ${JSON.stringify(head.slice(0, end).split("\r\n", 1)[0] ?? "")}`, "EPROXY", errMeta)
5065
+ });
5066
+ return;
5067
+ }
5068
+ if (parsed.status >= 200 && parsed.status < 300) {
5069
+ const tail = head.slice(end + 4);
5070
+ if (tail.length > 0)
5071
+ baseSocket.unshift(Buffer.from(tail, "latin1"));
5072
+ resolve({ ok: true });
5073
+ return;
5074
+ }
5075
+ if (parsed.status === 407) {
5076
+ resolve({
5077
+ ok: false,
5078
+ err: new KinetexError("Proxy requires authentication (407). Set `username`/`password` on the proxy config, or embed them in the proxy URL.", "EPROXY", errMeta)
5079
+ });
5080
+ return;
5081
+ }
5082
+ resolve({
5083
+ ok: false,
5084
+ err: new KinetexError(`Proxy refused CONNECT to ${authority}: ${parsed.status} ${parsed.reason}`.trim(), "EPROXY", errMeta)
5085
+ });
5086
+ };
5087
+ const onError = (e) => {
5088
+ cleanup();
5089
+ resolve({
5090
+ ok: false,
5091
+ err: new KinetexError(`Proxy connection failed: ${e.message}`, "ENETWORK", {
5092
+ ...errMeta,
5093
+ cause: e
5094
+ })
5095
+ });
5096
+ };
5097
+ const onClosedEarly = () => {
5098
+ cleanup();
5099
+ resolve({
5100
+ ok: false,
5101
+ err: new KinetexError(`Proxy closed the connection before answering CONNECT for ${authority}`, "EPROXY", errMeta)
5102
+ });
5103
+ };
5104
+ const onTimeout = () => {
5105
+ cleanup();
5106
+ baseSocket.destroy();
5107
+ resolve({
5108
+ ok: false,
5109
+ err: new KinetexError(`Proxy did not respond to CONNECT for ${authority} within ${timeoutMs}ms`, "ETIMEOUT", errMeta)
5110
+ });
5111
+ };
5112
+ const onAbort = () => {
5113
+ cleanup();
5114
+ baseSocket.destroy();
5115
+ resolve({
5116
+ ok: false,
5117
+ err: new KinetexError("Proxy connection aborted", "EABORT", errMeta)
5118
+ });
5119
+ };
5120
+ const timer = setTimeout(onTimeout, timeoutMs);
5121
+ if (typeof timer.unref === "function") {
5122
+ timer.unref();
5123
+ }
5124
+ function cleanup() {
5125
+ clearTimeout(timer);
5126
+ baseSocket.off("readable", onReadable);
5127
+ baseSocket.off("error", onError);
5128
+ baseSocket.off("end", onClosedEarly);
5129
+ baseSocket.off("close", onClosedEarly);
5130
+ baseSocket.off("timeout", onTimeout);
5131
+ options.signal?.removeEventListener("abort", onAbort);
5132
+ }
5133
+ baseSocket.on("readable", onReadable);
5134
+ baseSocket.once("error", onError);
5135
+ baseSocket.once("end", onClosedEarly);
5136
+ baseSocket.once("close", onClosedEarly);
5137
+ baseSocket.setTimeout(timeoutMs, onTimeout);
5138
+ options.signal?.addEventListener("abort", onAbort, { once: true });
5139
+ drain();
5140
+ });
5141
+ if (!headResult.ok) {
5142
+ baseSocket.destroy();
5143
+ throw headResult.err;
5144
+ }
5145
+ if (target.protocol !== "https:") {
5146
+ baseSocket.setTimeout(0);
5147
+ return baseSocket;
5148
+ }
5149
+ return await new Promise((resolve, reject) => {
5150
+ const onTunnelTimeout = () => {
5151
+ done();
5152
+ baseSocket.destroy();
5153
+ reject(new KinetexError(`TLS handshake through proxy to ${target.hostname} did not complete within ${timeoutMs}ms`, "ETIMEOUT", errMeta));
5154
+ };
5155
+ const onTunnelAbort = () => {
5156
+ done();
5157
+ baseSocket.destroy();
5158
+ reject(new KinetexError("Proxy connection aborted", "EABORT", errMeta));
5159
+ };
5160
+ const tunnelTimer = setTimeout(onTunnelTimeout, timeoutMs);
5161
+ if (typeof tunnelTimer.unref === "function") {
5162
+ tunnelTimer.unref();
5163
+ }
5164
+ options.signal?.addEventListener("abort", onTunnelAbort, { once: true });
5165
+ function done() {
5166
+ clearTimeout(tunnelTimer);
5167
+ options.signal?.removeEventListener("abort", onTunnelAbort);
5168
+ }
5169
+ const sni = options.servername ?? target.hostname;
5170
+ const secure = tls.connect(
5171
+ {
5172
+ socket: baseSocket,
5173
+ ...sni !== void 0 && !isIpLiteral(sni) ? { servername: sni } : {},
5174
+ ...options.ca !== void 0 ? { ca: options.ca } : {}
5175
+ },
5176
+ // `rejectUnauthorized` is left at its default (true), so a peer that
5177
+ // fails verification emits `error` rather than reaching this callback.
5178
+ // Verification therefore cannot be bypassed here, and the callback
5179
+ // only ever runs for a verified handshake.
5180
+ () => {
5181
+ done();
5182
+ baseSocket.setTimeout(0);
5183
+ resolve(secure);
5184
+ }
5185
+ );
5186
+ secure.once("error", (e) => {
5187
+ done();
5188
+ baseSocket.destroy();
5189
+ reject(new KinetexError(`TLS handshake through proxy failed for ${target.hostname}: ${e.message}`, "ENETWORK", { ...errMeta, cause: e }));
5190
+ });
5191
+ });
5192
+ }
5193
+ async function openTcpSocket(net, opts, timeoutMs, signal, errMeta, what) {
5194
+ return await new Promise((resolve, reject) => {
5195
+ const socket = net.connect({ host: opts.host, port: opts.port });
5196
+ const onError = (e) => {
5197
+ cleanup();
5198
+ reject(new KinetexError(`Cannot reach ${what}: ${e.message}`, "ENETWORK", {
5199
+ ...errMeta,
5200
+ cause: e
5201
+ }));
5202
+ };
5203
+ const timer = setTimeout(() => {
5204
+ cleanup();
5205
+ socket.destroy();
5206
+ reject(new KinetexError(`Connecting to ${what} timed out`, "ETIMEOUT", errMeta));
5207
+ }, timeoutMs);
5208
+ if (typeof timer.unref === "function") {
5209
+ timer.unref();
5210
+ }
5211
+ const onAbort = () => {
5212
+ cleanup();
5213
+ socket.destroy();
5214
+ reject(new KinetexError("Proxy connection aborted", "EABORT", errMeta));
5215
+ };
5216
+ function cleanup() {
5217
+ clearTimeout(timer);
5218
+ socket.off("error", onError);
5219
+ signal?.removeEventListener("abort", onAbort);
5220
+ }
5221
+ socket.once("error", onError);
5222
+ socket.once("connect", () => {
5223
+ cleanup();
5224
+ resolve(socket);
5225
+ });
5226
+ if (signal?.aborted)
5227
+ onAbort();
5228
+ else
5229
+ signal?.addEventListener("abort", onAbort, { once: true });
5230
+ });
5231
+ }
5232
+ async function openTlsSocket(tls, opts, timeoutMs, signal, errMeta, what) {
5233
+ return await new Promise((resolve, reject) => {
5234
+ const socket = tls.connect({
5235
+ host: opts.host,
5236
+ port: opts.port,
5237
+ ...isIpLiteral(opts.servername) ? {} : { servername: opts.servername },
5238
+ ...opts.ca !== void 0 ? { ca: opts.ca } : {}
5239
+ }, () => {
5240
+ cleanup();
5241
+ resolve(socket);
5242
+ });
5243
+ const onError = (e) => {
5244
+ cleanup();
5245
+ reject(new KinetexError(`Cannot reach ${what}: ${e.message}`, "ENETWORK", {
5246
+ ...errMeta,
5247
+ cause: e
5248
+ }));
5249
+ };
5250
+ const timer = setTimeout(() => {
5251
+ cleanup();
5252
+ socket.destroy();
5253
+ reject(new KinetexError(`Connecting to ${what} timed out`, "ETIMEOUT", errMeta));
5254
+ }, timeoutMs);
5255
+ if (typeof timer.unref === "function") {
5256
+ timer.unref();
5257
+ }
5258
+ const onAbort = () => {
5259
+ cleanup();
5260
+ socket.destroy();
5261
+ reject(new KinetexError("Proxy connection aborted", "EABORT", errMeta));
5262
+ };
5263
+ function cleanup() {
5264
+ clearTimeout(timer);
5265
+ socket.off("error", onError);
5266
+ signal?.removeEventListener("abort", onAbort);
5267
+ }
5268
+ socket.once("error", onError);
5269
+ if (signal?.aborted)
5270
+ onAbort();
5271
+ else
5272
+ signal?.addEventListener("abort", onAbort, { once: true });
5273
+ });
5274
+ }
5275
+ var DEFAULT_CONNECT_TIMEOUT_MS, MAX_HEAD_BYTES;
5276
+ var init_proxy = __esm({
5277
+ "dist/esm/proxy.js"() {
5278
+ "use strict";
5279
+ init_types();
5280
+ DEFAULT_CONNECT_TIMEOUT_MS = 3e4;
5281
+ MAX_HEAD_BYTES = 64 * 1024;
5282
+ }
5283
+ });
5284
+
5198
5285
  // dist/esm/core.js
5199
5286
  var core_exports = {};
5200
5287
  __export(core_exports, {
@@ -5205,10 +5292,13 @@ var kinetex = (() => {
5205
5292
  IS_NODE: () => IS_NODE,
5206
5293
  NodeHTTP2Transport: () => NodeHTTP2Transport,
5207
5294
  RUNTIME: () => RUNTIME,
5295
+ blockedHostError: () => blockedHostError,
5296
+ createPinnedLookup: () => createPinnedLookup,
5208
5297
  createTransport: () => createTransport,
5209
5298
  decompressBodyStream: () => decompressBodyStream,
5210
5299
  detectRuntime: () => detectRuntime,
5211
5300
  encodeMultipart: () => encodeMultipart,
5301
+ filterSafeAddresses: () => filterSafeAddresses,
5212
5302
  getEffectiveRuntime: () => getEffectiveRuntime,
5213
5303
  normalizeHeaders: () => normalizeHeaders3,
5214
5304
  parseBody: () => parseBody,
@@ -5274,6 +5364,7 @@ var kinetex = (() => {
5274
5364
  ...sessionOptions?.connectTimeoutMs !== void 0 ? { connectTimeoutMs: sessionOptions.connectTimeoutMs } : {},
5275
5365
  ...sessionOptions?.requestTimeoutMs !== void 0 ? { requestTimeoutMs: sessionOptions.requestTimeoutMs } : {},
5276
5366
  ...transportOptions?.strict !== void 0 ? { strict: transportOptions.strict } : {},
5367
+ ...transportOptions?.allowPrivateNetwork !== void 0 ? { allowPrivateNetwork: transportOptions.allowPrivateNetwork } : {},
5277
5368
  ...transportOptions?.onDroppedHeader !== void 0 ? { onDroppedHeader: transportOptions.onDroppedHeader } : {},
5278
5369
  ...transportOptions?.ca !== void 0 ? { ca: transportOptions.ca } : {},
5279
5370
  ...transportOptions?.proxy !== void 0 ? { proxy: transportOptions.proxy } : {}
@@ -5283,7 +5374,11 @@ var kinetex = (() => {
5283
5374
  fetchFn: fetchFn ?? globalThis.fetch,
5284
5375
  ...transportOptions?.strict !== void 0 ? { strict: transportOptions.strict } : {},
5285
5376
  ...transportOptions?.onDroppedHeader !== void 0 ? { onDroppedHeader: transportOptions.onDroppedHeader } : {},
5286
- ...transportOptions?.dispatcher !== void 0 ? { dispatcher: transportOptions.dispatcher } : {}
5377
+ ...transportOptions?.dispatcher !== void 0 ? { dispatcher: transportOptions.dispatcher } : {},
5378
+ // Reaches the pre-flight DNS screen in FetchTransport. It was accepted by
5379
+ // createTransport and then dropped, so every fetch-based runtime silently
5380
+ // had the screen off.
5381
+ ...transportOptions?.allowPrivateNetwork !== void 0 ? { allowPrivateNetwork: transportOptions.allowPrivateNetwork } : {}
5287
5382
  });
5288
5383
  }
5289
5384
  async function sendWithTimeout(transport, request, timeoutMs) {
@@ -5348,10 +5443,11 @@ var kinetex = (() => {
5348
5443
  if (done)
5349
5444
  break;
5350
5445
  if (maxBytes > 0) {
5351
- const newTotal = total + value.byteLength;
5352
- if (newTotal > maxBytes) {
5446
+ try {
5447
+ limitAction(value, total, { maxBytes }, "throw", (n, max) => new SizeLimitError(n, max));
5448
+ } catch (err) {
5353
5449
  await reader.cancel("size limit exceeded");
5354
- throw new SizeLimitError(newTotal, maxBytes);
5450
+ throw err;
5355
5451
  }
5356
5452
  }
5357
5453
  chunks.push(value);
@@ -5410,6 +5506,41 @@ var kinetex = (() => {
5410
5506
  }
5411
5507
  return raw;
5412
5508
  }
5509
+ function filterSafeAddresses(addresses) {
5510
+ return addresses.filter((a) => a.family !== 0 && !isBlockedAddress(a.address));
5511
+ }
5512
+ function blockedHostError(url, addresses) {
5513
+ return new KinetexError(`URL "${url}" resolved only to blocked addresses (${addresses.map((a) => a.address).join(", ") || "none"})`, "EBLOCKEDHOST");
5514
+ }
5515
+ function createPinnedLookup() {
5516
+ return (hostname, options, callback) => {
5517
+ const fail = (err) => {
5518
+ callback(err);
5519
+ };
5520
+ const wantsAll = options.all === true;
5521
+ const resolved = { ...options, all: true };
5522
+ (0, import_node_dns.lookup)(hostname, resolved, (err, addresses) => {
5523
+ if (err) {
5524
+ fail(err);
5525
+ return;
5526
+ }
5527
+ const safe = filterSafeAddresses(addresses ?? []);
5528
+ if (safe.length === 0) {
5529
+ const blocked = (addresses ?? []).map((a) => a.address).join(", ") || "no addresses";
5530
+ const e = new Error(`Refusing to connect to ${hostname}: it resolves only to a blocked address (${blocked})`);
5531
+ e.code = "EBLOCKEDHOST";
5532
+ fail(e);
5533
+ return;
5534
+ }
5535
+ if (wantsAll) {
5536
+ callback(null, safe);
5537
+ return;
5538
+ }
5539
+ const first = safe[0];
5540
+ callback(null, first.address, first.family);
5541
+ });
5542
+ };
5543
+ }
5413
5544
  function normalizeHeaders3(headers) {
5414
5545
  return normalizeHeaders(headers);
5415
5546
  }
@@ -5615,14 +5746,17 @@ Content-Type: ${value.type || "application/octet-stream"}\r
5615
5746
  const { applyDecompression: applyDecompression2 } = await Promise.resolve().then(() => (init_response(), response_exports));
5616
5747
  return applyDecompression2(body, headers);
5617
5748
  }
5618
- var g, RUNTIME, _runtimeOverride, KNOWN_RUNTIMES, IS_NODE, HAS_NATIVE_FETCH, DEFAULT_ACCEPT_ENCODING, FetchTransport, NodeHTTP2Transport, nodeReadableToWebStream, HTTP_STATUS_TEXTS;
5749
+ var import_node_dns, import_node_net, g, RUNTIME, _runtimeOverride, KNOWN_RUNTIMES, IS_NODE, HAS_NATIVE_FETCH, DEFAULT_ACCEPT_ENCODING, FetchTransport, NodeHTTP2Transport, PINNED_LOOKUP, nodeReadableToWebStream, HTTP_STATUS_TEXTS;
5619
5750
  var init_core = __esm({
5620
5751
  "dist/esm/core.js"() {
5621
5752
  "use strict";
5622
5753
  init_types();
5623
5754
  init_utils();
5624
5755
  init_headers();
5756
+ import_node_dns = __require("node:dns");
5757
+ import_node_net = __require("node:net");
5625
5758
  init_utils();
5759
+ init_response();
5626
5760
  g = globalThis;
5627
5761
  RUNTIME = detectRuntime();
5628
5762
  _runtimeOverride = null;
@@ -5647,17 +5781,72 @@ Content-Type: ${value.type || "application/octet-stream"}\r
5647
5781
  __publicField(this, "strict");
5648
5782
  __publicField(this, "onDroppedHeader");
5649
5783
  __publicField(this, "dispatcher");
5784
+ /** Per-request timeout; omitted means no timeout. */
5785
+ __publicField(this, "requestTimeoutMs");
5786
+ /**
5787
+ * Permit connections to hosts resolving into private/loopback space.
5788
+ * Default: false.
5789
+ */
5790
+ __publicField(this, "_allowPrivateNetwork");
5650
5791
  if (typeof fetchFnOrOptions === "function") {
5651
5792
  this.fetchFn = fetchFnOrOptions;
5652
5793
  this.strict = false;
5653
5794
  this.onDroppedHeader = void 0;
5654
5795
  this.dispatcher = void 0;
5796
+ this.requestTimeoutMs = void 0;
5797
+ this._allowPrivateNetwork = false;
5655
5798
  } else {
5656
5799
  this.fetchFn = fetchFnOrOptions.fetchFn ?? globalThis.fetch;
5657
5800
  this.strict = fetchFnOrOptions.strict ?? false;
5658
5801
  this.onDroppedHeader = fetchFnOrOptions.onDroppedHeader;
5659
5802
  this.dispatcher = fetchFnOrOptions.dispatcher;
5803
+ this.requestTimeoutMs = fetchFnOrOptions.requestTimeoutMs;
5804
+ this._allowPrivateNetwork = fetchFnOrOptions.allowPrivateNetwork ?? false;
5805
+ }
5806
+ }
5807
+ /**
5808
+ * Refuse a host that resolves into blocked address space.
5809
+ *
5810
+ * The Node transports close DNS rebinding properly: they resolve once and
5811
+ * pin the checked address to the socket (`createPinnedLookup`). `fetch` has no
5812
+ * resolver hook — undici owns the connection and exposes no way to install
5813
+ * one — so the only place to intervene is before the call.
5814
+ *
5815
+ * That closes the case that matters in practice: a name resolving to loopback
5816
+ * or the cloud metadata address is refused outright, which is the vector
5817
+ * behind the classic SSRF-through-DNS attacks. What it cannot close is a
5818
+ * resolver that answers public on this lookup and private on the connect a
5819
+ * moment later. Closing *that* needs a resolver hook `fetch` does not offer,
5820
+ * and claiming otherwise would be worse than saying so.
5821
+ *
5822
+ * The host has already passed the lexical screen by this point, so this is a
5823
+ * second line of defence rather than the first.
5824
+ */
5825
+ async assertReachableHost(url) {
5826
+ if (this._allowPrivateNetwork)
5827
+ return;
5828
+ if (this.fetchFn !== globalThis.fetch)
5829
+ return;
5830
+ let parsed;
5831
+ try {
5832
+ parsed = new URL(url);
5833
+ } catch {
5834
+ return;
5835
+ }
5836
+ if (!parsed.hostname || (0, import_node_net.isIP)(parsed.hostname))
5837
+ return;
5838
+ let addresses;
5839
+ try {
5840
+ addresses = await new Promise((resolve, reject) => {
5841
+ (0, import_node_dns.lookup)(parsed.hostname, { all: true }, (err, res) => err ? reject(err) : resolve(res));
5842
+ });
5843
+ } catch {
5844
+ return;
5660
5845
  }
5846
+ const safe = filterSafeAddresses(addresses);
5847
+ if (safe.length > 0)
5848
+ return;
5849
+ throw blockedHostError(url, addresses);
5661
5850
  }
5662
5851
  /**
5663
5852
  * Send a request via the native fetch() API.
@@ -5670,6 +5859,7 @@ Content-Type: ${value.type || "application/octet-stream"}\r
5670
5859
  * @returns Raw response from the server
5671
5860
  */
5672
5861
  async send(req) {
5862
+ await this.assertReachableHost(req.url);
5673
5863
  const sanitizedHeaders = {};
5674
5864
  for (const [name, value] of Object.entries(req.headers ?? {})) {
5675
5865
  if (typeof name !== "string" || typeof value !== "string")
@@ -5716,15 +5906,41 @@ Content-Type: ${value.type || "application/octet-stream"}\r
5716
5906
  if (this.dispatcher !== void 0) {
5717
5907
  init.dispatcher = this.dispatcher;
5718
5908
  }
5909
+ const timeoutMs = this.requestTimeoutMs;
5910
+ const timeoutCtl = timeoutMs === void 0 ? void 0 : new AbortController();
5911
+ let timedOut = false;
5912
+ const onCallerAbort = () => timeoutCtl?.abort();
5913
+ if (timeoutCtl && req.signal) {
5914
+ if (req.signal.aborted)
5915
+ timeoutCtl.abort();
5916
+ else
5917
+ req.signal.addEventListener("abort", onCallerAbort, { once: true });
5918
+ }
5919
+ const timer = timeoutCtl && timeoutMs !== void 0 ? setTimeout(() => {
5920
+ timedOut = true;
5921
+ timeoutCtl.abort();
5922
+ }, timeoutMs) : void 0;
5923
+ const initWithSignal = timeoutCtl && !init.signal ? { ...init, signal: timeoutCtl.signal } : init;
5719
5924
  let response;
5720
5925
  try {
5721
- response = await this.fetchFn(req.url, init);
5926
+ response = await this.fetchFn(req.url, initWithSignal);
5722
5927
  } catch (err) {
5928
+ if (timer !== void 0)
5929
+ clearTimeout(timer);
5930
+ if (timeoutCtl && req.signal)
5931
+ req.signal.removeEventListener("abort", onCallerAbort);
5932
+ if (timedOut) {
5933
+ throw new KinetexError(`HTTP/1.1 request to ${req.url} timed out after ${timeoutMs}ms`, "ETIMEOUT", { request: req, cause: err });
5934
+ }
5723
5935
  if (isAbortError(err)) {
5724
5936
  throw new KinetexError("Request was aborted", "EABORT", { request: req, cause: err });
5725
5937
  }
5726
5938
  throw new KinetexError(err instanceof Error ? err.message : "Network request failed", "ENETWORK", { request: req, cause: err });
5727
5939
  }
5940
+ if (timer !== void 0)
5941
+ clearTimeout(timer);
5942
+ if (timeoutCtl && req.signal)
5943
+ req.signal.removeEventListener("abort", onCallerAbort);
5728
5944
  const headers = normalizeHeaders3(response.headers);
5729
5945
  const httpVersion = detectHTTPVersion(response, headers);
5730
5946
  let alreadyDecompressed = true;
@@ -5787,6 +6003,8 @@ Content-Type: ${value.type || "application/octet-stream"}\r
5787
6003
  * sockets, which is a lot to hold open for a legacy fallback), and gives
5788
6004
  * callers a way to opt out.
5789
6005
  */
6006
+ /** Whether to permit connections into private/loopback address space. */
6007
+ __publicField(this, "_allowPrivateNetwork");
5790
6008
  __publicField(this, "_http1KeepAlive");
5791
6009
  __publicField(this, "_http1MaxSockets");
5792
6010
  __publicField(this, "_http1Agent", null);
@@ -5795,6 +6013,7 @@ Content-Type: ${value.type || "application/octet-stream"}\r
5795
6013
  this.sessionTTLMs = options.sessionTTLMs ?? 5 * 6e4;
5796
6014
  this.pingIntervalMs = options.pingIntervalMs ?? 3e4;
5797
6015
  this.maxSessions = options.maxSessions ?? 100;
6016
+ this._allowPrivateNetwork = options.allowPrivateNetwork ?? false;
5798
6017
  this._http1KeepAlive = options.http1KeepAlive ?? true;
5799
6018
  this._http1MaxSockets = options.http1MaxSockets ?? 16;
5800
6019
  this._proxy = options.proxy;
@@ -5856,6 +6075,9 @@ Content-Type: ${value.type || "application/octet-stream"}\r
5856
6075
  const session = await new Promise((resolve, reject) => {
5857
6076
  const s = http2.connect(origin, {
5858
6077
  rejectUnauthorized: true,
6078
+ // Resolve-and-check, so a hostname that points at a private address is
6079
+ // refused before anything is dialled. See `createPinnedLookup`.
6080
+ ...this._allowPrivateNetwork ? {} : { lookup: PINNED_LOOKUP },
5859
6081
  ...this._ca !== void 0 ? { ca: this._ca } : {},
5860
6082
  // Already connected (and already TLS-wrapped for an https origin),
5861
6083
  // so node:http2 must use it as-is rather than negotiating again.
@@ -6154,7 +6376,11 @@ Content-Type: ${value.type || "application/octet-stream"}\r
6154
6376
  if (!this._http1Fallback) {
6155
6377
  const opts = {
6156
6378
  fetchFn: globalThis.fetch,
6157
- strict: this._strict
6379
+ strict: this._strict,
6380
+ // The h2 path enforces `_requestTimeoutMs` itself; without passing it
6381
+ // down, `new NodeHTTP2Transport({ requestTimeoutMs: 500 })` hung for
6382
+ // the full response time on any non-https URL or HTTP/1.1 request.
6383
+ requestTimeoutMs: this._requestTimeoutMs
6158
6384
  };
6159
6385
  if (this._onDroppedHeader !== void 0)
6160
6386
  opts.onDroppedHeader = this._onDroppedHeader;
@@ -6191,7 +6417,9 @@ Content-Type: ${value.type || "application/octet-stream"}\r
6191
6417
  // `ca` was accepted by the transport but never reached this path, so a
6192
6418
  // private or self-signed peer could not be reached without disabling
6193
6419
  // verification process-wide.
6194
- ...this._ca !== void 0 ? { ca: this._ca } : {}
6420
+ ...this._ca !== void 0 ? { ca: this._ca } : {},
6421
+ // Same reason as the HTTP/2 path above.
6422
+ ...this._allowPrivateNetwork ? {} : { lookup: PINNED_LOOKUP }
6195
6423
  };
6196
6424
  return new Promise((resolve, reject) => {
6197
6425
  const httpReq = https.request(options, (httpRes) => {
@@ -6306,6 +6534,7 @@ Content-Type: ${value.type || "application/octet-stream"}\r
6306
6534
  this._http1Agent = null;
6307
6535
  }
6308
6536
  };
6537
+ PINNED_LOOKUP = createPinnedLookup();
6309
6538
  nodeReadableToWebStream = nodeDuplexToReadable;
6310
6539
  HTTP_STATUS_TEXTS = {
6311
6540
  100: "Continue",
@@ -6534,7 +6763,7 @@ Content-Type: ${value.type || "application/octet-stream"}\r
6534
6763
  const basic = delim >= 0 ? input.slice(0, delim) : "";
6535
6764
  for (let j = 0; j < basic.length; j++) {
6536
6765
  const cp = basic.charCodeAt(j);
6537
- if (cp > 122 || cp > 90 && cp < 97 || cp > 57 && cp < 65 || cp < 48)
6766
+ if (cp >= 128 || cp < 32 || cp === 127)
6538
6767
  return input;
6539
6768
  output.push(cp);
6540
6769
  }
@@ -6590,6 +6819,9 @@ Content-Type: ${value.type || "application/octet-stream"}\r
6590
6819
  }
6591
6820
  }
6592
6821
  function decodeIDNLabel(label) {
6822
+ if (label.includes(".")) {
6823
+ return label.split(".").map((part) => decodeIDNLabel(part)).join(".");
6824
+ }
6593
6825
  if (!label.toLowerCase().startsWith("xn--")) {
6594
6826
  return label;
6595
6827
  }
@@ -9083,21 +9315,6 @@ Content-Type: ${value.type || "application/octet-stream"}\r
9083
9315
  }
9084
9316
  return { name: "Error", message: String(err) };
9085
9317
  }
9086
- function safeStringify2(value) {
9087
- try {
9088
- const seen = /* @__PURE__ */ new WeakSet();
9089
- return JSON.stringify(value, (_k, v) => {
9090
- if (v !== null && typeof v === "object") {
9091
- if (seen.has(v))
9092
- return "[circular]";
9093
- seen.add(v);
9094
- }
9095
- return v;
9096
- }) ?? String(value);
9097
- } catch {
9098
- return String(value);
9099
- }
9100
- }
9101
9318
  function truncateToBytes(str, maxBytes) {
9102
9319
  const enc = new TextEncoder();
9103
9320
  const total = enc.encode(str).byteLength;
@@ -9114,9 +9331,6 @@ Content-Type: ${value.type || "application/octet-stream"}\r
9114
9331
  }
9115
9332
  return { text: str.slice(0, end), removed: total - used };
9116
9333
  }
9117
- function perfNow3() {
9118
- return typeof performance !== "undefined" ? performance.now() : Date.now();
9119
- }
9120
9334
  function createLogger(config) {
9121
9335
  return new HTTPLogger(config);
9122
9336
  }
@@ -9148,7 +9362,7 @@ Content-Type: ${value.type || "application/octet-stream"}\r
9148
9362
  }
9149
9363
  });
9150
9364
  }
9151
- var getNodeEnv, LogLevel, DEFAULT_REDACT_HEADERS, DEFAULT_REDACT_PARAMS, DEFAULT_ALLOWED_BODY_TYPES, ConsoleTransport, hasStdout, JSONTransport, BatchingTransport, RemoteTransport, MultiTransport, _logIdSeq, Redactor, _HTTPLogger, HTTPLogger;
9365
+ var getNodeEnv, LogLevel, DEFAULT_REDACT_HEADERS, DEFAULT_REDACT_PARAMS, DEFAULT_ALLOWED_BODY_TYPES, ConsoleTransport, hasStdout, JSONTransport, BatchingTransport, RemoteTransport, MultiTransport, _logIdSeq, Redactor, _HTTPLogger, HTTPLogger, CIRCULAR_MARKER, safeStringify2;
9152
9366
  var init_logging = __esm({
9153
9367
  "dist/esm/logging.js"() {
9154
9368
  "use strict";
@@ -9468,11 +9682,7 @@ Content-Type: ${value.type || "application/octet-stream"}\r
9468
9682
  }
9469
9683
  /** Redact sensitive header values, replacing them with "***". */
9470
9684
  redactHeaders(headers) {
9471
- const out = {};
9472
- for (const [k, v] of Object.entries(headers)) {
9473
- out[k] = this.headerSet.has(k.toLowerCase()) ? "***" : v;
9474
- }
9475
- return out;
9685
+ return redactHeaders(headers, this.headerSet, "***");
9476
9686
  }
9477
9687
  /**
9478
9688
  * Redact sensitive query parameters from a URL.
@@ -9519,7 +9729,7 @@ Content-Type: ${value.type || "application/octet-stream"}\r
9519
9729
  }
9520
9730
  if (!this.paramSet.has(decoded.toLowerCase()))
9521
9731
  return pair;
9522
- return eq === -1 ? `${name}=***` : `${name}=***`;
9732
+ return `${name}=***`;
9523
9733
  });
9524
9734
  return `${base}?${params.join("&")}${fragment}`;
9525
9735
  }
@@ -9597,7 +9807,7 @@ Content-Type: ${value.type || "application/octet-stream"}\r
9597
9807
  }
9598
9808
  /** Evict oldest or stale (abandoned) active IDs to prevent unbounded memory growth. */
9599
9809
  _cleanupActiveIds() {
9600
- const now2 = perfNow3();
9810
+ const now2 = perfNow();
9601
9811
  if (this.activeIds.size > _HTTPLogger.MAX_ACTIVE_IDS) {
9602
9812
  const entries = Array.from(this.activeIds.entries());
9603
9813
  const toRemove = entries.slice(0, entries.length - Math.floor(_HTTPLogger.MAX_ACTIVE_IDS * 0.8));
@@ -9628,7 +9838,7 @@ Content-Type: ${value.type || "application/octet-stream"}\r
9628
9838
  if (this.activeIds.size > 500) {
9629
9839
  this._cleanupActiveIds();
9630
9840
  }
9631
- this.activeIds.set(requestId, { startMs: perfNow3(), method, url });
9841
+ this.activeIds.set(requestId, { startMs: perfNow(), method, url });
9632
9842
  const ct = headers["content-type"] ?? headers["Content-Type"] ?? null;
9633
9843
  const redacted = this.redactor.redactBody(body, ct, false);
9634
9844
  const entry = {
@@ -9661,7 +9871,7 @@ Content-Type: ${value.type || "application/octet-stream"}\r
9661
9871
  */
9662
9872
  logResponse(requestId, status, statusText, headers, body, attempt, cached, meta = {}) {
9663
9873
  const active = this.activeIds.get(requestId);
9664
- const dur = active ? perfNow3() - active.startMs : 0;
9874
+ const dur = active ? perfNow() - active.startMs : 0;
9665
9875
  const method = active?.method ?? "GET";
9666
9876
  const url = active?.url ?? "";
9667
9877
  this.activeIds.delete(requestId);
@@ -9701,7 +9911,7 @@ Content-Type: ${value.type || "application/octet-stream"}\r
9701
9911
  */
9702
9912
  logError(requestId, error, status, attempt, meta = {}) {
9703
9913
  const active = this.activeIds.get(requestId);
9704
- const dur = active ? perfNow3() - active.startMs : 0;
9914
+ const dur = active ? perfNow() - active.startMs : 0;
9705
9915
  const method = active?.method ?? "GET";
9706
9916
  const url = active?.url ?? "";
9707
9917
  this.activeIds.delete(requestId);
@@ -9801,6 +10011,8 @@ Content-Type: ${value.type || "application/octet-stream"}\r
9801
10011
  /** Time after which an abandoned active ID is evicted */
9802
10012
  __publicField(_HTTPLogger, "IDLE_TIMEOUT_MS", 6e4);
9803
10013
  HTTPLogger = _HTTPLogger;
10014
+ CIRCULAR_MARKER = "[circular]";
10015
+ safeStringify2 = (value) => safeStringify(value, CIRCULAR_MARKER);
9804
10016
  }
9805
10017
  });
9806
10018
 
@@ -10588,9 +10800,6 @@ Content-Type: ${value.type || "application/octet-stream"}\r
10588
10800
  const message = graphQLErrors.map((e) => e.message).join("; ");
10589
10801
  return new GraphQLClientError(message, "EGRAPHQL", graphQLErrors, req, res);
10590
10802
  }
10591
- function sleep3(ms) {
10592
- return new Promise((resolve) => setTimeout(resolve, ms));
10593
- }
10594
10803
  function buildLinkChain(links, terminal) {
10595
10804
  return links.reduceRight((next, link) => (op) => link(op, next), terminal);
10596
10805
  }
@@ -10607,12 +10816,6 @@ Content-Type: ${value.type || "application/octet-stream"}\r
10607
10816
  const m = trimmed.match(/^\s*(?:query|mutation|subscription)\s+([A-Za-z_][A-Za-z0-9_]*)/im);
10608
10817
  return m ? m[1] : null;
10609
10818
  }
10610
- async function sha256Hex3(str) {
10611
- const encoded = new TextEncoder().encode(str);
10612
- const buf = encoded.buffer.slice(encoded.byteOffset, encoded.byteOffset + encoded.byteLength);
10613
- const hash = await crypto.subtle.digest("SHA-256", buf);
10614
- return Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("");
10615
- }
10616
10819
  async function buildHeaders(config, extra = {}) {
10617
10820
  const base = {
10618
10821
  "content-type": "application/json",
@@ -10784,7 +10987,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
10784
10987
  let fetchBody = null;
10785
10988
  const fetchHeaders = { ...headers };
10786
10989
  if (apqMode === "omitQuery" || apqMode === "full") {
10787
- const hash = getAPQHashFn ? await getAPQHashFn(req.query) : await sha256Hex3(req.query);
10990
+ const hash = getAPQHashFn ? await getAPQHashFn(req.query) : await sha256Hex(req.query);
10788
10991
  const extensions = {
10789
10992
  ...req.extensions,
10790
10993
  persistedQuery: { version: 1, sha256Hash: hash }
@@ -10901,7 +11104,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
10901
11104
  let lastErr;
10902
11105
  for (let attempt = 0; attempt <= max; attempt++) {
10903
11106
  if (attempt > 0)
10904
- await sleep3(delay * Math.pow(2, attempt - 1));
11107
+ await sleep(delay * Math.pow(2, attempt - 1));
10905
11108
  try {
10906
11109
  return await next(op);
10907
11110
  } catch (err) {
@@ -10926,6 +11129,8 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
10926
11129
  "use strict";
10927
11130
  init_types();
10928
11131
  init_utils();
11132
+ init_utils();
11133
+ init_utils();
10929
11134
  GraphQLClientError = class extends Error {
10930
11135
  /**
10931
11136
  * @param message - Human-readable error description
@@ -11056,7 +11261,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11056
11261
  _GraphQLClient.apqHashToQuery.delete(cached.hash);
11057
11262
  }
11058
11263
  _GraphQLClient._apqMisses++;
11059
- const hash = await sha256Hex3(query);
11264
+ const hash = await sha256Hex(query);
11060
11265
  const now2 = Date.now();
11061
11266
  const existingEntry = _GraphQLClient.apqHashToQuery.get(hash);
11062
11267
  if (existingEntry && existingEntry.query !== query) {
@@ -11424,7 +11629,7 @@ ${text}`, "ENETWORK", void 0, requests[0], void 0, new Error(`HTTP ${response.st
11424
11629
  let lastErr;
11425
11630
  for (let attempt = 0; attempt <= this.config.retries; attempt++) {
11426
11631
  if (attempt > 0)
11427
- await sleep3(this.config.retryDelayMs * Math.pow(2, attempt - 1));
11632
+ await sleep(this.config.retryDelayMs * Math.pow(2, attempt - 1));
11428
11633
  try {
11429
11634
  return await this._executeRaw(req, signal);
11430
11635
  } catch (err) {
@@ -11586,7 +11791,7 @@ ${text}`, "ENETWORK", void 0, requests[0], void 0, new Error(`HTTP ${response.st
11586
11791
  if (config.maxPages && pageNum >= config.maxPages)
11587
11792
  break;
11588
11793
  if (pageNum > 0 && config.delayMs && config.delayMs > 0) {
11589
- await sleep4(config.delayMs, config.signal);
11794
+ await sleep2(config.delayMs, config.signal);
11590
11795
  }
11591
11796
  let response;
11592
11797
  try {
@@ -11967,7 +12172,7 @@ ${text}`, "ENETWORK", void 0, requests[0], void 0, new Error(`HTTP ${response.st
11967
12172
  }
11968
12173
  return state;
11969
12174
  }
11970
- function sleep4(ms, signal) {
12175
+ function sleep2(ms, signal) {
11971
12176
  return new Promise((r) => {
11972
12177
  if (signal?.aborted) {
11973
12178
  r();
@@ -12333,7 +12538,7 @@ ${text}`, "ENETWORK", void 0, requests[0], void 0, new Error(`HTTP ${response.st
12333
12538
  pathSegments: () => pathSegments,
12334
12539
  percentDecode: () => percentDecode,
12335
12540
  percentEncode: () => percentEncode,
12336
- perfNow: () => perfNow2,
12541
+ perfNow: () => perfNow,
12337
12542
  pickQuery: () => pickQuery,
12338
12543
  presignRequest: () => presignRequest,
12339
12544
  readBlob: () => readBlob,
@@ -12396,6 +12601,7 @@ ${text}`, "ENETWORK", void 0, requests[0], void 0, new Error(`HTTP ${response.st
12396
12601
  });
12397
12602
 
12398
12603
  // dist/esm/progress.js
12604
+ init_utils();
12399
12605
  var ProgressTracker = class {
12400
12606
  constructor(total, options = {}) {
12401
12607
  __publicField(this, "loaded", 0);
@@ -12986,11 +13192,43 @@ ${text}`, "ENETWORK", void 0, requests[0], void 0, new Error(`HTTP ${response.st
12986
13192
  }
12987
13193
  };
12988
13194
  }
12989
- function perfNow() {
12990
- return typeof performance !== "undefined" ? performance.now() : Date.now();
12991
- }
12992
13195
 
12993
13196
  // dist/esm/dedup.js
13197
+ var NON_IDENTITY_HEADERS = [
13198
+ "traceparent",
13199
+ "tracestate",
13200
+ "baggage",
13201
+ "x-request-id",
13202
+ "x-correlation-id",
13203
+ "x-trace-id"
13204
+ ];
13205
+ async function identityFingerprint(headers) {
13206
+ if (headers === void 0)
13207
+ return "";
13208
+ const lowered = /* @__PURE__ */ new Map();
13209
+ for (const [name, value] of Object.entries(headers)) {
13210
+ lowered.set(name.toLowerCase(), value);
13211
+ }
13212
+ const parts = [];
13213
+ for (const name of NON_IDENTITY_HEADERS)
13214
+ lowered.delete(name);
13215
+ for (const name of [...lowered.keys()].sort()) {
13216
+ parts.push(`${name}=${lowered.get(name)}`);
13217
+ }
13218
+ if (parts.length === 0)
13219
+ return "";
13220
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(parts.join("|")));
13221
+ return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
13222
+ }
13223
+ function dedupKeyFrom(method, url, fingerprint) {
13224
+ return fingerprint ? `${method}:${url}:${fingerprint}` : `${method}:${url}`;
13225
+ }
13226
+ function defaultDedupKey(method, url, headers) {
13227
+ if (headers === void 0)
13228
+ return `${method}::${url}`;
13229
+ const identity = Object.entries(headers).filter(([name]) => !NON_IDENTITY_HEADERS.includes(name.toLowerCase())).map(([name, value]) => `${name.toLowerCase()}=${value}`).sort();
13230
+ return identity.length === 0 ? `${method}::${url}` : `${method}::${url}::${identity.join("&")}`;
13231
+ }
12994
13232
  var DedupMap = class {
12995
13233
  /**
12996
13234
  * @param options - Deduplication configuration
@@ -12998,6 +13236,9 @@ ${text}`, "ENETWORK", void 0, requests[0], void 0, new Error(`HTTP ${response.st
12998
13236
  constructor(options = {}) {
12999
13237
  __publicField(this, "inflight", /* @__PURE__ */ new Map());
13000
13238
  __publicField(this, "timeouts", /* @__PURE__ */ new Map());
13239
+ /** Window timers, overridable for tests. See {@link DedupOptions.timers}. */
13240
+ __publicField(this, "_setTimeout");
13241
+ __publicField(this, "_clearTimeout");
13001
13242
  __publicField(this, "windowMs");
13002
13243
  __publicField(this, "methods");
13003
13244
  __publicField(this, "keyFn");
@@ -13007,8 +13248,10 @@ ${text}`, "ENETWORK", void 0, requests[0], void 0, new Error(`HTTP ${response.st
13007
13248
  __publicField(this, "_misses", 0);
13008
13249
  this.windowMs = options.windowMs ?? 0;
13009
13250
  this.methods = new Set((options.methods ?? ["GET", "HEAD"]).map((m) => m.toUpperCase()));
13010
- this.keyFn = options.keyFn ?? ((m, url) => `${m}::${url}`);
13251
+ this.keyFn = options.keyFn ?? defaultDedupKey;
13011
13252
  this._globalSignal = options.signal ?? null;
13253
+ this._setTimeout = options.timers?.setTimeout ?? ((fn, ms) => setTimeout(fn, ms));
13254
+ this._clearTimeout = options.timers?.clearTimeout ?? ((h) => clearTimeout(h));
13012
13255
  }
13013
13256
  /**
13014
13257
  * Execute `factory` for the given method/url, or return the in-flight
@@ -13032,9 +13275,12 @@ ${text}`, "ENETWORK", void 0, requests[0], void 0, new Error(`HTTP ${response.st
13032
13275
  * entry and creating a new one. The first caller becomes the "leader" and all
13033
13276
  * subsequent callers within the same tick share the same promise.
13034
13277
  *
13035
- * Note: The default `keyFn` does NOT include headers in the deduplication key.
13036
- * Two requests with different auth tokens to the same URL will NOT be deduplicated
13037
- * unless you provide a custom `keyFn` that includes headers.
13278
+ * Note: the default key includes the request headers, so two requests to the
13279
+ * same URL carrying different `Authorization` or `Cookie` are separate
13280
+ * requests and are never coalesced. A key of method + URL alone was the
13281
+ * default once, and it handed one caller's response to another: 50 callers
13282
+ * aside, even two callers for different tenants shared a single response.
13283
+ * See {@link defaultDedupKey}.
13038
13284
  */
13039
13285
  execute(method, url, factory, headers, windowMs, signal) {
13040
13286
  const effectiveWindowMs = windowMs ?? this.windowMs;
@@ -13077,7 +13323,7 @@ ${text}`, "ENETWORK", void 0, requests[0], void 0, new Error(`HTTP ${response.st
13077
13323
  this.inflight.delete(key);
13078
13324
  const existingTimeout = this.timeouts.get(key);
13079
13325
  if (existingTimeout !== void 0) {
13080
- clearTimeout(existingTimeout);
13326
+ this._clearTimeout(existingTimeout);
13081
13327
  this.timeouts.delete(key);
13082
13328
  }
13083
13329
  } : null;
@@ -13102,10 +13348,10 @@ ${text}`, "ENETWORK", void 0, requests[0], void 0, new Error(`HTTP ${response.st
13102
13348
  } else {
13103
13349
  const stale = this.timeouts.get(key);
13104
13350
  if (stale !== void 0) {
13105
- clearTimeout(stale);
13351
+ this._clearTimeout(stale);
13106
13352
  this.timeouts.delete(key);
13107
13353
  }
13108
- const timeoutId = setTimeout(() => {
13354
+ const timeoutId = this._setTimeout(() => {
13109
13355
  this.inflight.delete(key);
13110
13356
  this.timeouts.delete(key);
13111
13357
  }, effectiveWindowMs);
@@ -13119,7 +13365,7 @@ ${text}`, "ENETWORK", void 0, requests[0], void 0, new Error(`HTTP ${response.st
13119
13365
  this.inflight.delete(key);
13120
13366
  const existingTimeout = this.timeouts.get(key);
13121
13367
  if (existingTimeout !== void 0) {
13122
- clearTimeout(existingTimeout);
13368
+ this._clearTimeout(existingTimeout);
13123
13369
  this.timeouts.delete(key);
13124
13370
  }
13125
13371
  throw err;
@@ -13176,7 +13422,7 @@ ${text}`, "ENETWORK", void 0, requests[0], void 0, new Error(`HTTP ${response.st
13176
13422
  clear() {
13177
13423
  this.inflight.clear();
13178
13424
  for (const timeoutId of this.timeouts.values()) {
13179
- clearTimeout(timeoutId);
13425
+ this._clearTimeout(timeoutId);
13180
13426
  }
13181
13427
  this.timeouts.clear();
13182
13428
  }
@@ -13190,7 +13436,7 @@ ${text}`, "ENETWORK", void 0, requests[0], void 0, new Error(`HTTP ${response.st
13190
13436
  invalidate(key) {
13191
13437
  const timeoutId = this.timeouts.get(key);
13192
13438
  if (timeoutId) {
13193
- clearTimeout(timeoutId);
13439
+ this._clearTimeout(timeoutId);
13194
13440
  this.timeouts.delete(key);
13195
13441
  }
13196
13442
  return this.inflight.delete(key);
@@ -13467,7 +13713,7 @@ ${text}`, "ENETWORK", void 0, requests[0], void 0, new Error(`HTTP ${response.st
13467
13713
  const code = err.code;
13468
13714
  if (this.failureFilter.networkErrors && code === "ENETWORK")
13469
13715
  return true;
13470
- if (this.failureFilter.timeouts && code === "ETIMEOUT")
13716
+ if (this.failureFilter.timeouts && (code === "ETIMEOUT" || code === "ETIMEDOUT"))
13471
13717
  return true;
13472
13718
  if (this.failureFilter.serverErrors) {
13473
13719
  const status = err.status;
@@ -13547,6 +13793,18 @@ ${text}`, "ENETWORK", void 0, requests[0], void 0, new Error(`HTTP ${response.st
13547
13793
 
13548
13794
  // dist/esm/ws.js
13549
13795
  init_utils();
13796
+ function validateHandshakeHeaders(headers) {
13797
+ if (!headers)
13798
+ return;
13799
+ for (const [name, value] of Object.entries(headers)) {
13800
+ if (!isValidHeaderName(name)) {
13801
+ throw new WSError(`Invalid WebSocket handshake header name: ${JSON.stringify(name)}`);
13802
+ }
13803
+ if (!isValidHeaderValue(value)) {
13804
+ throw new WSError(`Invalid value for WebSocket handshake header ${JSON.stringify(name)}: must be a string of printable characters with no control characters`);
13805
+ }
13806
+ }
13807
+ }
13550
13808
  var WSError = class extends Error {
13551
13809
  /**
13552
13810
  * @param message - Human-readable error description
@@ -13679,6 +13937,7 @@ ${text}`, "ENETWORK", void 0, requests[0], void 0, new Error(`HTTP ${response.st
13679
13937
  this._url = cfg.url;
13680
13938
  this._protocols = cfg.protocols;
13681
13939
  this._headers = cfg.headers;
13940
+ validateHandshakeHeaders(cfg.headers);
13682
13941
  this._maxReconnects = cfg.maxReconnects ?? 10;
13683
13942
  this._reconnectBaseMs = cfg.reconnectBaseMs ?? 1e3;
13684
13943
  this._reconnectMaxMs = cfg.reconnectMaxMs ?? 3e4;
@@ -14396,7 +14655,13 @@ ${text}`, "ENETWORK", void 0, requests[0], void 0, new Error(`HTTP ${response.st
14396
14655
  }
14397
14656
  /** Buffer an outgoing message and track bytes for backpressure. */
14398
14657
  _bufferMessage(data, byteLen) {
14399
- if (this._bufferMessages && !this._aborted) {
14658
+ if (!this._bufferMessages) {
14659
+ if (!this._aborted) {
14660
+ this._cbError?.(new WSError(`WebSocket message dropped: the socket is not open and bufferMessages is disabled (${byteLen} bytes). Reconnect, or enable bufferMessages to queue sends made while reconnecting.`));
14661
+ }
14662
+ return;
14663
+ }
14664
+ if (!this._aborted) {
14400
14665
  const evicted = this._buffer.length >= this._maxBufferSize ? this._buffer.shift() : void 0;
14401
14666
  if (evicted !== void 0) {
14402
14667
  this._bufferedBytes -= typeof evicted === "string" ? new TextEncoder().encode(evicted).byteLength : evicted.byteLength;
@@ -14516,6 +14781,7 @@ ${text}`, "ENETWORK", void 0, requests[0], void 0, new Error(`HTTP ${response.st
14516
14781
 
14517
14782
  // dist/esm/interceptors.js
14518
14783
  init_cache();
14784
+ init_utils();
14519
14785
  init_types();
14520
14786
  init_headers();
14521
14787
  var _idSeq = 0;
@@ -14824,7 +15090,7 @@ ${text}`, "ENETWORK", void 0, requests[0], void 0, new Error(`HTTP ${response.st
14824
15090
  return;
14825
15091
  const delay = computeDelay(ctx.attempt, retryAfterMs(ctx));
14826
15092
  cfg.onRetry?.(ctx, delay);
14827
- await sleep2(delay);
15093
+ await sleep(delay);
14828
15094
  return { ...ctx.request };
14829
15095
  };
14830
15096
  const errorInterceptor = async (ctx) => {
@@ -14832,7 +15098,7 @@ ${text}`, "ENETWORK", void 0, requests[0], void 0, new Error(`HTTP ${response.st
14832
15098
  return;
14833
15099
  const delay = computeDelay(ctx.attempt, null);
14834
15100
  cfg.onRetry?.(ctx, delay);
14835
- await sleep2(delay);
15101
+ await sleep(delay);
14836
15102
  ctx.error = null;
14837
15103
  throw new RetrySignal(ctx.request);
14838
15104
  };
@@ -14997,13 +15263,7 @@ ${text}`, "ENETWORK", void 0, requests[0], void 0, new Error(`HTTP ${response.st
14997
15263
  function createLoggingInterceptor(config = {}) {
14998
15264
  const cfg = { ...LOG_DEFAULTS, ...config };
14999
15265
  const redactSet = new Set(cfg.redactHeaders.map((h) => h.toLowerCase()));
15000
- function redactHeaders(headers) {
15001
- const out = {};
15002
- for (const [k, v] of Object.entries(headers)) {
15003
- out[k] = redactSet.has(k.toLowerCase()) ? "**REDACTED**" : v;
15004
- }
15005
- return out;
15006
- }
15266
+ const redactHeaders2 = (headers) => redactHeaders(headers, redactSet, "**REDACTED**");
15007
15267
  function makeEntry(type, ctx) {
15008
15268
  const requestId = ctx.store.get(REQUEST_ID_KEY) ?? "unknown";
15009
15269
  return {
@@ -15016,7 +15276,7 @@ ${text}`, "ENETWORK", void 0, requests[0], void 0, new Error(`HTTP ${response.st
15016
15276
  durationMs: type !== "request" ? now() - ctx.startedAt : null,
15017
15277
  attempt: ctx.attempt,
15018
15278
  error: ctx.error instanceof Error ? ctx.error.message : null,
15019
- headers: redactHeaders(ctx.request.headers ?? {})
15279
+ headers: redactHeaders2(ctx.request.headers ?? {})
15020
15280
  };
15021
15281
  }
15022
15282
  const requestInterceptor = (ctx) => {
@@ -15552,12 +15812,7 @@ ${text}`, "ENETWORK", void 0, requests[0], void 0, new Error(`HTTP ${response.st
15552
15812
  };
15553
15813
  return { requestInterceptor, responseInterceptor, errorInterceptor, snapshot, reset };
15554
15814
  }
15555
- function now() {
15556
- return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
15557
- }
15558
- function sleep2(ms) {
15559
- return new Promise((resolve) => setTimeout(resolve, ms));
15560
- }
15815
+ var now = perfNow;
15561
15816
  function computeBodySize(body) {
15562
15817
  if (!body)
15563
15818
  return 0;
@@ -15624,6 +15879,10 @@ ${text}`, "ENETWORK", void 0, requests[0], void 0, new Error(`HTTP ${response.st
15624
15879
 
15625
15880
  // dist/esm/aws-sigv4.js
15626
15881
  init_types();
15882
+ init_utils();
15883
+ function toHex(bytes) {
15884
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
15885
+ }
15627
15886
  function staticCredentials(creds) {
15628
15887
  return () => Promise.resolve(creds);
15629
15888
  }
@@ -15793,16 +16052,6 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
15793
16052
  const sig = await crypto.subtle.sign("HMAC", cryptoKey, enc.encode(data));
15794
16053
  return new Uint8Array(sig);
15795
16054
  }
15796
- async function sha256Hex(data) {
15797
- const enc = new TextEncoder();
15798
- const raw = typeof data === "string" ? enc.encode(data) : data;
15799
- const buf = raw.buffer.slice(raw.byteOffset, raw.byteOffset + raw.byteLength);
15800
- const hashBuf = await crypto.subtle.digest("SHA-256", buf);
15801
- return toHex(new Uint8Array(hashBuf));
15802
- }
15803
- function toHex(bytes) {
15804
- return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
15805
- }
15806
16055
  var DEFAULT_UNSIGNED_HEADERS = /* @__PURE__ */ new Set([
15807
16056
  "user-agent",
15808
16057
  "x-forwarded-for",
@@ -16294,6 +16543,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16294
16543
 
16295
16544
  // dist/esm/digest.js
16296
16545
  init_utils();
16546
+ init_utils();
16297
16547
  function md5(input) {
16298
16548
  const bytes = [];
16299
16549
  for (let i = 0; i < input.length; i++) {
@@ -16458,11 +16708,11 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16458
16708
  for (let offset = 0; offset < bytes.length; offset += 64) {
16459
16709
  const M = [];
16460
16710
  for (let i = 0; i < 16; i++) {
16461
- const b02 = bytes[offset + i * 4];
16462
- const b1 = bytes[offset + i * 4 + 1];
16463
- const b2 = bytes[offset + i * 4 + 2];
16464
- const b3 = bytes[offset + i * 4 + 3];
16465
- M[i] = (b02 | b1 << 8 | b2 << 16 | b3 << 24) >>> 0;
16711
+ const w0 = bytes[offset + i * 4];
16712
+ const w1 = bytes[offset + i * 4 + 1];
16713
+ const w2 = bytes[offset + i * 4 + 2];
16714
+ const w3 = bytes[offset + i * 4 + 3];
16715
+ M[i] = (w0 | w1 << 8 | w2 << 16 | w3 << 24) >>> 0;
16466
16716
  }
16467
16717
  let A = a0, B = b0, C = c0, D = d0;
16468
16718
  for (let i = 0; i < 64; i++) {
@@ -16503,7 +16753,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16503
16753
  function hexToBytes(hex) {
16504
16754
  const out = new Uint8Array(hex.length / 2);
16505
16755
  for (let i = 0; i < out.length; i++) {
16506
- out[i] = parseInt(hex.substr(i * 2, 2), 16);
16756
+ out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
16507
16757
  }
16508
16758
  return out;
16509
16759
  }
@@ -16557,12 +16807,6 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16557
16807
  throw new Error("Digest challenge missing 'nonce'");
16558
16808
  return challenge;
16559
16809
  }
16560
- async function sha256Hex2(data) {
16561
- const enc = new TextEncoder();
16562
- const buf = await crypto.subtle.digest("SHA-256", enc.encode(data));
16563
- const bytes = new Uint8Array(buf);
16564
- return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
16565
- }
16566
16810
  function sha512256Hex(data) {
16567
16811
  const K = [
16568
16812
  0x428a2f98d728ae22n,
@@ -16747,7 +16991,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16747
16991
  return md5(s);
16748
16992
  if (algo === "SHA-512-256")
16749
16993
  return sha512256Hex(s);
16750
- return await sha256Hex2(s);
16994
+ return await sha256Hex(s);
16751
16995
  }
16752
16996
  const user = challenge.userhash ? uint8ArrayToBase64(hexToBytes(await hash(`${username}:${realm}`))) : username;
16753
16997
  let ha1 = await hash(`${user}:${realm}:${password}`);
@@ -16778,7 +17022,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16778
17022
  throw new Error(`Unsupported digest algorithm: ${challenge.algorithm}`);
16779
17023
  }
16780
17024
  const { algo } = resolved;
16781
- const digest = algo === "MD5" ? md5(`${username}:${challenge.realm}`) : algo === "SHA-512-256" ? sha512256Hex(`${username}:${challenge.realm}`) : await sha256Hex2(`${username}:${challenge.realm}`);
17025
+ const digest = algo === "MD5" ? md5(`${username}:${challenge.realm}`) : algo === "SHA-512-256" ? sha512256Hex(`${username}:${challenge.realm}`) : await sha256Hex(`${username}:${challenge.realm}`);
16782
17026
  return uint8ArrayToBase64(hexToBytes(digest));
16783
17027
  }
16784
17028
  function resolveHashAlgo(algorithm) {
@@ -16836,12 +17080,26 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16836
17080
  }
16837
17081
  function createDigestAuthorizer() {
16838
17082
  const counters = /* @__PURE__ */ new Map();
17083
+ const MAX_TRACKED_NONCES = 64;
17084
+ const rememberNonce = (scope, value) => {
17085
+ if (!counters.has(scope)) {
17086
+ counters.set(scope, value);
17087
+ if (counters.size > MAX_TRACKED_NONCES) {
17088
+ const oldest = counters.keys().next();
17089
+ if (!oldest.done)
17090
+ counters.delete(oldest.value);
17091
+ }
17092
+ return;
17093
+ }
17094
+ counters.delete(scope);
17095
+ counters.set(scope, value);
17096
+ };
16839
17097
  return async (wwwAuth, username, password, method, uri) => {
16840
17098
  const challenge = parseDigestChallenge(wwwAuth);
16841
17099
  const cnonce = randomBytes(5);
16842
17100
  const scope = `${challenge.realm}\0${challenge.nonce}`;
16843
17101
  const next = (counters.get(scope) ?? 0) + 1;
16844
- counters.set(scope, next);
17102
+ rememberNonce(scope, next);
16845
17103
  const nc = next.toString(16).padStart(8, "0");
16846
17104
  const response = await computeDigestResponse(challenge, username, password, method, uri, cnonce, nc);
16847
17105
  const usernameStar = await computeUsernameStar(challenge, username);
@@ -16947,6 +17205,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16947
17205
  function randomHex(len) {
16948
17206
  return randomBytes(len / 2);
16949
17207
  }
17208
+ var DEFAULT_MAX_RESPONSE_SIZE = 100 * 1024 * 1024;
16950
17209
  var HAR_REDACT_HEADERS = /* @__PURE__ */ new Set([
16951
17210
  "authorization",
16952
17211
  "proxy-authorization",
@@ -17000,6 +17259,63 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17000
17259
  "jwt",
17001
17260
  "code"
17002
17261
  ]);
17262
+ function scrubKnownSecrets(text, req) {
17263
+ const secrets = [];
17264
+ const add = (v) => {
17265
+ if (typeof v === "string" && v.length >= 8)
17266
+ secrets.push(v);
17267
+ };
17268
+ for (const [name, value] of Object.entries(req.headers ?? {})) {
17269
+ if (isSensitiveHeader(name, HAR_REDACT_HEADERS) && typeof value === "string") {
17270
+ add(value);
17271
+ const sp = value.indexOf(" ");
17272
+ if (sp !== -1)
17273
+ add(value.slice(sp + 1));
17274
+ }
17275
+ }
17276
+ let query;
17277
+ try {
17278
+ query = new URL(req.url).searchParams;
17279
+ } catch {
17280
+ query = new URLSearchParams();
17281
+ }
17282
+ for (const [key, value] of query.entries()) {
17283
+ if (HAR_REDACT_PARAMS.has(key.toLowerCase()) && typeof value === "string" && value.length >= 8) {
17284
+ secrets.push(value);
17285
+ }
17286
+ }
17287
+ let out = text;
17288
+ for (const secret of secrets)
17289
+ out = out.split(secret).join(HAR_REDACT_MASK);
17290
+ return out;
17291
+ }
17292
+ function redactHARBody(text, mimeType) {
17293
+ if (!/json/i.test(mimeType))
17294
+ return text;
17295
+ let parsed;
17296
+ try {
17297
+ parsed = JSON.parse(text);
17298
+ } catch {
17299
+ return text;
17300
+ }
17301
+ const walk = (node) => {
17302
+ if (Array.isArray(node))
17303
+ return node.map(walk);
17304
+ if (node !== null && typeof node === "object") {
17305
+ const out = {};
17306
+ for (const [key, value] of Object.entries(node)) {
17307
+ out[key] = HAR_REDACT_PARAMS.has(key.toLowerCase()) ? HAR_REDACT_MASK : walk(value);
17308
+ }
17309
+ return out;
17310
+ }
17311
+ return node;
17312
+ };
17313
+ try {
17314
+ return JSON.stringify(walk(parsed));
17315
+ } catch {
17316
+ return text;
17317
+ }
17318
+ }
17003
17319
  var HAR_MAX_BODY_CHARS = 8192;
17004
17320
  function isHARBodySafeToRecord(contentType) {
17005
17321
  if (!contentType)
@@ -17026,8 +17342,12 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17026
17342
  return url.replace(/([?&])(api_key|apikey|access_token|refresh_token|token|secret|password|signature|sig)=([^&#]*)/gi, "$1$2=***REDACTED***");
17027
17343
  }
17028
17344
  }
17345
+ var HAR_REDACT_MASK = "***REDACTED***";
17029
17346
  function redactHARHeader(name, value) {
17030
- return HAR_REDACT_HEADERS.has(name.toLowerCase()) ? { name, value: "***REDACTED***" } : { name, value };
17347
+ return {
17348
+ name,
17349
+ value: isSensitiveHeader(name, HAR_REDACT_HEADERS) ? HAR_REDACT_MASK : value
17350
+ };
17031
17351
  }
17032
17352
  function harPostData(req) {
17033
17353
  if (!req.body)
@@ -17045,7 +17365,8 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17045
17365
  } else {
17046
17366
  return {};
17047
17367
  }
17048
- return { postData: { mimeType, text: text.slice(0, HAR_MAX_BODY_CHARS) } };
17368
+ const redacted = scrubKnownSecrets(redactHARBody(text, mimeType), req);
17369
+ return { postData: { mimeType, text: redacted.slice(0, HAR_MAX_BODY_CHARS) } };
17049
17370
  }
17050
17371
  var HARRecorder = class {
17051
17372
  /**
@@ -17146,7 +17467,12 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17146
17467
  mimeType: res.headers["content-type"] ?? "application/octet-stream",
17147
17468
  // Body text is only kept for non-HTML payloads and is truncated:
17148
17469
  // response bodies routinely carry tokens and PII.
17149
- ...typeof res.data === "string" && isHARBodySafeToRecord(res.headers["content-type"]) ? { text: res.data.slice(0, HAR_MAX_BODY_CHARS) } : {}
17470
+ ...typeof res.data === "string" && isHARBodySafeToRecord(res.headers["content-type"]) ? {
17471
+ // Same reason as the request side: a `text/plain` body has no
17472
+ // field names to redact by, and a server that echoes the token
17473
+ // back would otherwise put it in the export verbatim.
17474
+ text: scrubKnownSecrets(res.data, req).slice(0, HAR_MAX_BODY_CHARS)
17475
+ } : {}
17150
17476
  },
17151
17477
  // The Location header can itself carry a signed URL — redact it too.
17152
17478
  redirectURL: res.headers["location"] ? redactHARUrl(res.headers["location"]) : "",
@@ -17261,8 +17587,32 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17261
17587
  return url.replace(/\/\/[^/@]*@/, "//");
17262
17588
  }
17263
17589
  }
17264
- function buildURL(base, url, params, allowedSchemes = ["http", "https"]) {
17590
+ function buildURL(base, url, params, allowedSchemes = ["http", "https"], allowPrivateNetwork = false) {
17265
17591
  const MAX_QUERY_PARAM_COUNT = 100;
17592
+ const forEachQueryParam = (params2, emit) => {
17593
+ let paramCount = 0;
17594
+ const count = () => {
17595
+ paramCount++;
17596
+ if (paramCount > MAX_QUERY_PARAM_COUNT) {
17597
+ throw new KinetexError(`Query parameter count ${paramCount} exceeds limit of ${MAX_QUERY_PARAM_COUNT}`, "EVALIDATION");
17598
+ }
17599
+ };
17600
+ for (const [key, value] of Object.entries(params2)) {
17601
+ if (value === null || value === void 0)
17602
+ continue;
17603
+ if (Array.isArray(value)) {
17604
+ for (const v of value) {
17605
+ if (v === null || v === void 0)
17606
+ continue;
17607
+ count();
17608
+ emit(key, String(v), true);
17609
+ }
17610
+ } else {
17611
+ count();
17612
+ emit(key, String(value), false);
17613
+ }
17614
+ }
17615
+ };
17266
17616
  const MAX_URL_LENGTH = 8192;
17267
17617
  let full;
17268
17618
  if (/^https?:\/\//i.test(url)) {
@@ -17280,44 +17630,27 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17280
17630
  }
17281
17631
  }
17282
17632
  if (!params || Object.keys(params).length === 0) {
17283
- if (!isSafeURL(full, allowedSchemes)) {
17633
+ if (!isSafeURL(full, allowedSchemes, { allowPrivateNetwork })) {
17284
17634
  throw new KinetexError(`URL "${redactUserInfo(full)}" failed safety check \u2014 blocked private/loopback address or forbidden scheme`, "EVALIDATION");
17285
17635
  }
17286
17636
  return full;
17287
17637
  }
17288
17638
  try {
17289
17639
  const u = new URL(full);
17290
- if (!isSafeURL(u, allowedSchemes)) {
17640
+ if (!isSafeURL(u, allowedSchemes, { allowPrivateNetwork })) {
17291
17641
  throw new KinetexError(`URL "${redactUserInfo(full)}" failed safety check \u2014 blocked private/loopback address or forbidden scheme`, "EVALIDATION");
17292
17642
  }
17293
- let paramCount = 0;
17294
- for (const [key, value] of Object.entries(params)) {
17295
- if (value === null || value === void 0)
17296
- continue;
17297
- const strValue = String(value);
17298
- if (Array.isArray(value)) {
17299
- for (const v of value) {
17300
- if (v !== null && v !== void 0) {
17301
- paramCount++;
17302
- if (paramCount > MAX_QUERY_PARAM_COUNT) {
17303
- throw new KinetexError(`Query parameter count ${paramCount} exceeds limit of ${MAX_QUERY_PARAM_COUNT}`, "EVALIDATION");
17304
- }
17305
- u.searchParams.append(key, String(v));
17306
- }
17307
- }
17308
- } else {
17309
- paramCount++;
17310
- if (paramCount > MAX_QUERY_PARAM_COUNT) {
17311
- throw new KinetexError(`Query parameter count ${paramCount} exceeds limit of ${MAX_QUERY_PARAM_COUNT}`, "EVALIDATION");
17312
- }
17313
- u.searchParams.set(key, strValue);
17314
- }
17315
- }
17643
+ forEachQueryParam(params, (key, value, fromArray) => {
17644
+ if (fromArray)
17645
+ u.searchParams.append(key, value);
17646
+ else
17647
+ u.searchParams.set(key, value);
17648
+ });
17316
17649
  const result = u.toString();
17317
17650
  if (result.length > MAX_URL_LENGTH) {
17318
17651
  throw new KinetexError(`URL length ${result.length} bytes exceeds limit of ${MAX_URL_LENGTH} bytes`, "EVALIDATION");
17319
17652
  }
17320
- if (!isSafeURL(result)) {
17653
+ if (!isSafeURL(result, allowedSchemes, { allowPrivateNetwork })) {
17321
17654
  throw new KinetexError(`URL "${redactUserInfo(result)}" failed safety check \u2014 blocked private/loopback address or forbidden scheme`, "EVALIDATION");
17322
17655
  }
17323
17656
  return result;
@@ -17330,33 +17663,14 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17330
17663
  full += "?";
17331
17664
  }
17332
17665
  const paramParts = [];
17333
- let paramCount = 0;
17334
- for (const [key, value] of Object.entries(params ?? {})) {
17335
- if (value === null || value === void 0)
17336
- continue;
17337
- if (Array.isArray(value)) {
17338
- for (const v of value) {
17339
- if (v !== null && v !== void 0) {
17340
- paramCount++;
17341
- if (paramCount > MAX_QUERY_PARAM_COUNT) {
17342
- throw new KinetexError(`Query parameter count ${paramCount} exceeds limit of ${MAX_QUERY_PARAM_COUNT}`, "EVALIDATION");
17343
- }
17344
- paramParts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(v))}`);
17345
- }
17346
- }
17347
- } else {
17348
- paramCount++;
17349
- if (paramCount > MAX_QUERY_PARAM_COUNT) {
17350
- throw new KinetexError(`Query parameter count ${paramCount} exceeds limit of ${MAX_QUERY_PARAM_COUNT}`, "EVALIDATION");
17351
- }
17352
- paramParts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
17353
- }
17354
- }
17666
+ forEachQueryParam(params ?? {}, (key, value) => {
17667
+ paramParts.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
17668
+ });
17355
17669
  const result = full + paramParts.join("&");
17356
17670
  if (result.length > MAX_URL_LENGTH) {
17357
17671
  throw new KinetexError(`URL length ${result.length} bytes exceeds limit of ${MAX_URL_LENGTH} bytes`, "EVALIDATION");
17358
17672
  }
17359
- if (!isSafeURL(result)) {
17673
+ if (!isSafeURL(result, allowedSchemes, { allowPrivateNetwork })) {
17360
17674
  throw new KinetexError(`URL "${redactUserInfo(result)}" failed safety check \u2014 blocked private/loopback address or forbidden scheme`, "EVALIDATION");
17361
17675
  }
17362
17676
  return result;
@@ -17386,11 +17700,26 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17386
17700
  }
17387
17701
  return out;
17388
17702
  }
17703
+ function loggableBody(body) {
17704
+ if (body === null || body === void 0)
17705
+ return null;
17706
+ if (typeof body === "string")
17707
+ return body;
17708
+ if (body instanceof Uint8Array)
17709
+ return body;
17710
+ try {
17711
+ return JSON.stringify(body);
17712
+ } catch {
17713
+ return null;
17714
+ }
17715
+ }
17389
17716
  function shouldRetry(cfg, ctx) {
17390
17717
  if (ctx.attempt > cfg.maxRetries)
17391
17718
  return false;
17392
- if (!cfg.methods.includes(ctx.request.method))
17719
+ const hasIdempotencyKey = Object.keys(ctx.request.headers ?? {}).some((name) => name.toLowerCase() === "idempotency-key" && ctx.request.headers[name] !== "");
17720
+ if (!hasIdempotencyKey && !cfg.methods.includes(ctx.request.method)) {
17393
17721
  return false;
17722
+ }
17394
17723
  if (cfg.shouldRetry)
17395
17724
  return cfg.shouldRetry(ctx);
17396
17725
  if (ctx.error) {
@@ -17417,6 +17746,10 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17417
17746
  return false;
17418
17747
  case "EREDIRECT":
17419
17748
  return false;
17749
+ // A blocked host is a permanent refusal, not a transport hiccup; retrying
17750
+ // resolves the same name to the same blocked address.
17751
+ case "EBLOCKEDHOST":
17752
+ return false;
17420
17753
  case "EUNKNOWN":
17421
17754
  return false;
17422
17755
  default: {
@@ -17542,7 +17875,9 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17542
17875
  {
17543
17876
  ...config.strictHeaders ? { strict: true } : {},
17544
17877
  ...config.dispatcher !== void 0 ? { dispatcher: config.dispatcher } : {},
17545
- ...config.proxy !== void 0 ? { proxy: config.proxy } : {}
17878
+ ...config.proxy !== void 0 ? { proxy: config.proxy } : {},
17879
+ ...config.allowPrivateNetwork !== void 0 ? { allowPrivateNetwork: config.allowPrivateNetwork } : {},
17880
+ ...config.ca !== void 0 ? { ca: config.ca } : {}
17546
17881
  }
17547
17882
  );
17548
17883
  if (config.interceptors) {
@@ -17639,7 +17974,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
17639
17974
  status: ctx.response.status,
17640
17975
  statusText: ctx.response.statusText,
17641
17976
  headers: ctx.response.headers,
17642
- body: ctx.response.rawBody ?? null,
17977
+ body: ctx.response.body ?? null,
17643
17978
  request: {
17644
17979
  url: ctx.request.url,
17645
17980
  method: ctx.request.method,
@@ -18011,12 +18346,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18011
18346
  * ```
18012
18347
  */
18013
18348
  async ws(url, options = {}) {
18014
- const fullURL = buildURL(this.cfg.baseURL, url, this.cfg.params, [
18015
- "http",
18016
- "https",
18017
- "ws",
18018
- "wss"
18019
- ]);
18349
+ const fullURL = buildURL(this.cfg.baseURL, url, this.cfg.params, ["http", "https", "ws", "wss"], this.cfg.allowPrivateNetwork);
18020
18350
  const headers = mergeHeaders(this.cfg.headers, options.headers);
18021
18351
  const authHeaders = {};
18022
18352
  const auth = this.cfg.auth;
@@ -18044,7 +18374,11 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18044
18374
  }
18045
18375
  const jar = await this.getCookieJar();
18046
18376
  if (jar) {
18047
- const cookieHeader = jar.getCookieHeader({ url: fullURL, http: false });
18377
+ const cookieHeader = jar.getCookieHeader({
18378
+ url: fullURL,
18379
+ http: false,
18380
+ sameSiteContext: "strict"
18381
+ });
18048
18382
  if (cookieHeader)
18049
18383
  headers["cookie"] = cookieHeader;
18050
18384
  }
@@ -18052,7 +18386,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18052
18386
  const wsUrl = new URL(fullURL);
18053
18387
  if (this.cfg.baseURL) {
18054
18388
  const baseUrl = new URL(this.cfg.baseURL);
18055
- const wsIsSecure = wsUrl.protocol === "wss:";
18389
+ const wsIsSecure = wsUrl.protocol === "wss:" || wsUrl.protocol === "https:";
18056
18390
  const httpIsSecure = baseUrl.protocol === "https:" || baseUrl.protocol === "wss:";
18057
18391
  if (wsIsSecure !== httpIsSecure || wsUrl.host !== baseUrl.host) {
18058
18392
  throw new KinetexError(`WebSocket origin ${wsUrl.origin} does not match baseURL origin ${baseUrl.origin}`, "EVALIDATION");
@@ -18100,6 +18434,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18100
18434
  this._wsClients.add(client);
18101
18435
  await client.connect().catch((err) => {
18102
18436
  this._wsClients.delete(client);
18437
+ client.destroy();
18103
18438
  throw err;
18104
18439
  });
18105
18440
  return client;
@@ -18118,10 +18453,14 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18118
18453
  * @returns A promise resolving to the parsed response.
18119
18454
  */
18120
18455
  async send(url, method, options = {}) {
18121
- const startMs = perfNow4();
18456
+ const startMs = perfNow();
18122
18457
  const wallClockMs = Date.now();
18123
18458
  const timeoutMs = options.timeout !== void 0 ? options.timeout : this.cfg.timeout ?? 3e4;
18124
- const retryCfg = options.retry === false ? false : { ...this.retryConfig, ...options.retry ?? {} };
18459
+ const retryCfg = options.retry === false ? false : {
18460
+ ...this.retryConfig,
18461
+ ...options.retry ?? {},
18462
+ ...options.retry?.totalTimeoutMs !== void 0 ? {} : { totalTimeoutMs: options.totalTimeoutMs ?? this.cfg.totalTimeoutMs ?? timeoutMs }
18463
+ };
18125
18464
  const VALID_METHODS = [
18126
18465
  "GET",
18127
18466
  "POST",
@@ -18137,7 +18476,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18137
18476
  if (!VALID_METHODS.includes(normalizedMethod)) {
18138
18477
  throw new KinetexError(`Invalid HTTP method '${method}'. Valid methods: ${VALID_METHODS.join(", ")}`, "EVALIDATION");
18139
18478
  }
18140
- const fullUrl = buildURL(options.baseURL ?? this.cfg.baseURL, url, mergeParams(this.cfg.params, options.params));
18479
+ const fullUrl = buildURL(options.baseURL ?? this.cfg.baseURL, url, mergeParams(this.cfg.params, options.params), void 0, this.cfg.allowPrivateNetwork === true);
18141
18480
  const perRequestProxy = options.proxy;
18142
18481
  if (perRequestProxy) {
18143
18482
  throw new KinetexError("A per-request `proxy` is not supported because the transport pools one connection per origin \u2014 set `proxy` on the client instead, or use a custom `fetch` with a proxy agent for per-request routing. SOCKS5 requires createSocks5Tunnel() from kinetex/socks5.", "EVALIDATION");
@@ -18238,11 +18577,16 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18238
18577
  req = await this.cfg.transformRequest(req);
18239
18578
  const jar = await this.getCookieJar();
18240
18579
  if (jar) {
18241
- const cookieHeader = jar.getCookieHeader({ url: req.url, http: true });
18580
+ const cookieHeader = jar.getCookieHeader({
18581
+ url: req.url,
18582
+ http: true,
18583
+ sameSiteContext: "strict"
18584
+ });
18242
18585
  if (cookieHeader)
18243
18586
  req = { ...req, headers: { ...req.headers, cookie: cookieHeader } };
18244
18587
  }
18245
18588
  let _otelSpan = null;
18589
+ let _dedupKey = "";
18246
18590
  try {
18247
18591
  if (this._otelTracer) {
18248
18592
  _otelSpan = this._otelTracer.startSpan(`HTTP ${req.method}`, {
@@ -18270,6 +18614,10 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18270
18614
  meta: { ...req.meta, spanId }
18271
18615
  };
18272
18616
  }
18617
+ if (this._dedup) {
18618
+ const identity = await identityFingerprint(req.headers);
18619
+ _dedupKey = dedupKeyFrom(req.method, req.url, identity);
18620
+ }
18273
18621
  } catch (tracingErr) {
18274
18622
  if (_otelSpan) {
18275
18623
  _otelSpan.setStatus({
@@ -18297,11 +18645,6 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18297
18645
  }
18298
18646
  }
18299
18647
  const _execFactory = () => this._executeWithRetry(req, retryCfg, timeoutMs, options, startMs, wallClockMs);
18300
- let _dedupKey = "";
18301
- if (this._dedup) {
18302
- const authFp = await getAuthFingerprint(req.headers ?? {});
18303
- _dedupKey = `${req.method}:${req.url}${authFp ? ":" + authFp : ""}`;
18304
- }
18305
18648
  const _dedupedFactory = this._dedup ? () => this._dedup.execute(req.method, _dedupKey, _execFactory).then((r) => r) : _execFactory;
18306
18649
  const _guardedFactory = this._circuitBreakers ? () => this._circuitBreakers.execute(_cbOrigin, _dedupedFactory).then((r) => r) : _dedupedFactory;
18307
18650
  try {
@@ -18416,12 +18759,16 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18416
18759
  const doRetry = retryCfg.shouldRetry ? await retryCfg.shouldRetry(retryCtx) : shouldRetry(retryCfg, retryCtx);
18417
18760
  if (doRetry) {
18418
18761
  const delay = computeRetryDelay(retryCfg, attempt, getRetryAfterMs(res.headers));
18762
+ if (retryCfg.totalTimeoutMs !== void 0 && retryCfg.totalTimeoutMs > 0) {
18763
+ if (perfNow() - startMs + delay >= retryCfg.totalTimeoutMs)
18764
+ return res;
18765
+ }
18419
18766
  await retryCfg.onRetry?.(retryCtx, delay);
18420
18767
  await this.cfg.hooks?.onRetry?.reduce(async (p, fn) => {
18421
18768
  await p;
18422
18769
  await fn(retryCtx);
18423
18770
  }, Promise.resolve());
18424
- await sleep5(delay, req.signal);
18771
+ await sleep3(delay, req.signal);
18425
18772
  continue;
18426
18773
  }
18427
18774
  }
@@ -18459,12 +18806,17 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18459
18806
  const doRetry = clockSkewCorrected ? true : retryCfg.shouldRetry ? await retryCfg.shouldRetry(retryCtx) : shouldRetry(retryCfg, retryCtx);
18460
18807
  if (doRetry) {
18461
18808
  const delay = computeRetryDelay(retryCfg, attempt, errResponse ? getRetryAfterMs(errResponse.headers) : null);
18809
+ if (retryCfg.totalTimeoutMs !== void 0 && retryCfg.totalTimeoutMs > 0) {
18810
+ if (perfNow() - startMs + delay >= retryCfg.totalTimeoutMs) {
18811
+ throw err;
18812
+ }
18813
+ }
18462
18814
  await retryCfg.onRetry?.(retryCtx, delay);
18463
18815
  await this.cfg.hooks?.onRetry?.reduce(async (p, fn) => {
18464
18816
  await p;
18465
18817
  await fn(retryCtx);
18466
18818
  }, Promise.resolve());
18467
- await sleep5(delay, req.signal);
18819
+ await sleep3(delay, req.signal);
18468
18820
  continue;
18469
18821
  }
18470
18822
  }
@@ -18530,7 +18882,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18530
18882
  request: currentReq,
18531
18883
  response: null,
18532
18884
  error: null,
18533
- startedAt: perfNow4(),
18885
+ startedAt: perfNow(),
18534
18886
  attempt: 1,
18535
18887
  meta: currentReq.meta
18536
18888
  });
@@ -18575,7 +18927,9 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18575
18927
  if (protocol !== "http:" && protocol !== "https:") {
18576
18928
  throw new KinetexError(`Unsafe redirect to ${protocol} detected \u2014 only HTTP(S) allowed`, "ENETWORK", { request: req });
18577
18929
  }
18578
- if (!isSafeURL(nextUrl)) {
18930
+ if (!isSafeURL(nextUrl, void 0, {
18931
+ allowPrivateNetwork: this.cfg.allowPrivateNetwork === true
18932
+ })) {
18579
18933
  throw new KinetexError(`Unsafe redirect target blocked: ${redactUserInfo(location)}`, "EVALIDATION", { request: req });
18580
18934
  }
18581
18935
  if (this.cfg.httpsOnly && protocol !== "https:") {
@@ -18591,6 +18945,9 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18591
18945
  }
18592
18946
  const nextMethod = raw.status === 301 || raw.status === 302 || raw.status === 303 ? "GET" : currentReq.method;
18593
18947
  const nextBody = nextMethod === "GET" || nextMethod === "HEAD" ? null : currentReq.body;
18948
+ if (nextBody !== null && isNonReplayableBody(nextBody)) {
18949
+ throw new KinetexError("Cannot follow this redirect: a 307/308 preserves the method and body, and this request's body is a stream or Blob that the first attempt already consumed. Buffer it first, or disable redirects for this request.", "EVALIDATION", { request: currentReq });
18950
+ }
18594
18951
  const nextHeaders = { ...currentReq.headers };
18595
18952
  let crossOrigin = false;
18596
18953
  try {
@@ -18606,7 +18963,11 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18606
18963
  }
18607
18964
  }
18608
18965
  if (jar) {
18609
- const cookieHeader = jar.getCookieHeader({ url: nextUrl, http: true });
18966
+ const cookieHeader = jar.getCookieHeader({
18967
+ url: nextUrl,
18968
+ http: true,
18969
+ sameSiteContext: "strict"
18970
+ });
18610
18971
  if (cookieHeader) {
18611
18972
  nextHeaders["cookie"] = cookieHeader;
18612
18973
  } else {
@@ -18659,6 +19020,10 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18659
19020
  }
18660
19021
  }
18661
19022
  this._trace(_traceId, "lifecycle_before", "end", startMs, attempt);
19023
+ const _logger = await this.getLogger();
19024
+ if (_logger) {
19025
+ _logger.logRequest(_traceId, req.method, req.url, req.headers, loggableBody(req.body), attempt);
19026
+ }
18662
19027
  const forceRefresh = options.cache !== false && options.cache?.forceRefresh === true;
18663
19028
  if (options.cache !== false && this.cfg.cache) {
18664
19029
  const cache = await this.getCache();
@@ -18879,7 +19244,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18879
19244
  }
18880
19245
  });
18881
19246
  }
18882
- const maxSize = options.maxResponseSize ?? this.cfg.maxResponseSize ?? 0;
19247
+ const maxSize = options.maxResponseSize ?? this.cfg.maxResponseSize ?? DEFAULT_MAX_RESPONSE_SIZE;
18883
19248
  const rawBody = await readRawBody(bodyStream, maxSize, raw.url, req.signal);
18884
19249
  const jar = await this.getCookieJar();
18885
19250
  if (jar) {
@@ -18891,7 +19256,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18891
19256
  this._trace(_traceId, "response_parse", "start", startMs, attempt);
18892
19257
  const data = await parseBody(rawBody, ct, options.parseResponse, options.parseFailure, raw.headers, raw.url);
18893
19258
  this._trace(_traceId, "response_parse", "end", startMs, attempt);
18894
- const durationMs = perfNow4() - startMs;
19259
+ const durationMs = perfNow() - startMs;
18895
19260
  const res = {
18896
19261
  status: raw.status,
18897
19262
  statusText: raw.statusText,
@@ -18900,6 +19265,9 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18900
19265
  // the real response object instead of the `{}` placeholder it used to get.
18901
19266
  data,
18902
19267
  rawBody,
19268
+ // The same bytes in the shape an interceptor sees, so one response
19269
+ // object satisfies both the public and the pipeline views.
19270
+ body: rawBody,
18903
19271
  url: raw.url,
18904
19272
  cached: false,
18905
19273
  redirected: raw.redirected,
@@ -18917,9 +19285,9 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18917
19285
  await cache.set({ url: req.url, method: req.method, headers: req.headers }, { status: raw.status, statusText: raw.statusText, headers: raw.headers, body: rawBody }, { tags: options.tags ?? [] });
18918
19286
  }
18919
19287
  }
18920
- const logger = await this.getLogger();
19288
+ const logger = _logger ?? await this.getLogger();
18921
19289
  if (logger) {
18922
- const requestId = ctx.store.get("requestId") ?? req.meta["requestId"] ?? "unknown";
19290
+ const requestId = ctx.store.get("requestId") ?? req.meta["requestId"] ?? _traceId;
18923
19291
  logger.logResponse(requestId, res.status, res.statusText, res.headers, rawBody, attempt, false);
18924
19292
  }
18925
19293
  this.harRecorder?.record(req, res, wallClockMs ?? Date.now() - res.durationMs);
@@ -18997,7 +19365,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
18997
19365
  await p;
18998
19366
  await fn2(retryCtx);
18999
19367
  }, Promise.resolve());
19000
- await sleep5(delay, _req.signal);
19368
+ await sleep3(delay, _req.signal);
19001
19369
  }
19002
19370
  return this._executeOnce({
19003
19371
  ...result,
@@ -19051,7 +19419,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
19051
19419
  const bodyData = raw.body;
19052
19420
  const rawBody = bodyData instanceof Uint8Array ? bodyData : typeof bodyData === "string" ? new TextEncoder().encode(bodyData) : null;
19053
19421
  const data = rawBody ? await parseBody(rawBody, raw.headers["content-type"] ?? null, options.parseResponse, options.parseFailure, raw.headers, "url" in raw ? raw.url : req.url) : null;
19054
- const durationMs = perfNow4() - startMs;
19422
+ const durationMs = perfNow() - startMs;
19055
19423
  const responseURL = "url" in raw ? raw.url : req.url;
19056
19424
  const wasRedirected = "redirected" in raw ? raw.redirected : false;
19057
19425
  const responseHTTPVersion = "httpVersion" in raw ? raw.httpVersion : "HTTP/1.1";
@@ -19059,6 +19427,8 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
19059
19427
  status: raw.status,
19060
19428
  statusText: raw.statusText,
19061
19429
  headers: raw.headers,
19430
+ // Present so the object satisfies both response views at once.
19431
+ body: rawBody,
19062
19432
  data,
19063
19433
  rawBody,
19064
19434
  url: responseURL,
@@ -19175,7 +19545,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
19175
19545
  async sse(url, options = {}) {
19176
19546
  const { SSEClient: SSEClient2 } = await Promise.resolve().then(() => (init_sse(), sse_exports));
19177
19547
  const headers = mergeHeaders(this.cfg.headers, options.headers);
19178
- const fullURL = buildURL(this.cfg.baseURL, url, this.cfg.params);
19548
+ const fullURL = buildURL(this.cfg.baseURL, url, this.cfg.params, void 0, this.cfg.allowPrivateNetwork);
19179
19549
  const pipeFetch = async (input, init) => {
19180
19550
  const reqUrl = typeof input === "string" ? input : input.url;
19181
19551
  const fi = init;
@@ -19214,7 +19584,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
19214
19584
  async graphql(url, options = {}) {
19215
19585
  const { GraphQLClient: GraphQLClient2 } = await Promise.resolve().then(() => (init_graphql(), graphql_exports));
19216
19586
  const headers = mergeHeaders(this.cfg.headers, options.headers);
19217
- const fullURL = buildURL(this.cfg.baseURL, url, this.cfg.params);
19587
+ const fullURL = buildURL(this.cfg.baseURL, url, this.cfg.params, void 0, this.cfg.allowPrivateNetwork);
19218
19588
  const pipeFetch = async (input, init) => {
19219
19589
  const reqUrl = typeof input === "string" ? input : input.url;
19220
19590
  const fi = init;
@@ -19252,7 +19622,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
19252
19622
  */
19253
19623
  async paginate(url, options) {
19254
19624
  const { createPagePaginator: createPagePaginator2 } = await Promise.resolve().then(() => (init_pagination(), pagination_exports));
19255
- const fullURL = buildURL(this.cfg.baseURL, url, this.cfg.params);
19625
+ const fullURL = buildURL(this.cfg.baseURL, url, this.cfg.params, void 0, this.cfg.allowPrivateNetwork);
19256
19626
  const pipeFetch = async (input, init) => {
19257
19627
  const reqUrl = typeof input === "string" ? input : input.url;
19258
19628
  const fi = init;
@@ -19579,7 +19949,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
19579
19949
  return void 0;
19580
19950
  return { ...a, ...b };
19581
19951
  }
19582
- function sleep5(ms, signal) {
19952
+ function sleep3(ms, signal) {
19583
19953
  return new Promise((resolve, reject) => {
19584
19954
  if (signal?.aborted) {
19585
19955
  reject(createAbortError());
@@ -19601,9 +19971,6 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
19601
19971
  return true;
19602
19972
  return typeof Blob !== "undefined" && body instanceof Blob;
19603
19973
  }
19604
- function perfNow4() {
19605
- return typeof performance !== "undefined" ? performance.now() : Date.now();
19606
- }
19607
19974
  function createMethodCircuitBreakerKey(req) {
19608
19975
  try {
19609
19976
  return `${req.method}:${new URL(req.url).origin}`;
@@ -19739,6 +20106,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
19739
20106
  init_headers();
19740
20107
 
19741
20108
  // dist/esm/lifecycle.js
20109
+ init_utils();
19742
20110
  var HookEmitter = class {
19743
20111
  constructor() {
19744
20112
  __publicField(this, "listeners", /* @__PURE__ */ new Map());
@@ -20286,12 +20654,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
20286
20654
  function createLoggingHooks(options = {}) {
20287
20655
  const log = options.logger ?? ((msg, data) => console.log(msg, JSON.stringify(data)));
20288
20656
  const redact = new Set((options.redactHeaders ?? ["authorization", "cookie", "set-cookie", "proxy-authorization"]).map((h) => h.toLowerCase()));
20289
- function safeHeaders(h) {
20290
- const out = {};
20291
- for (const [k, v] of Object.entries(h))
20292
- out[k] = redact.has(k.toLowerCase()) ? "***" : v;
20293
- return out;
20294
- }
20657
+ const safeHeaders = (h) => redactHeaders(h, redact, "***");
20295
20658
  return {
20296
20659
  beforeRequest: (req) => {
20297
20660
  log("\u2192 request", {
@@ -20320,11 +20683,11 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
20320
20683
  const TIMING_KEY = "__kinetex_timing__";
20321
20684
  return {
20322
20685
  beforeRequest: (_req, ctx) => {
20323
- ctx.meta[TIMING_KEY] = perfNow5();
20686
+ ctx.meta[TIMING_KEY] = perfNow();
20324
20687
  },
20325
20688
  afterResponse: (_res, ctx) => {
20326
20689
  const start = ctx.meta[TIMING_KEY];
20327
- const elapsed = start !== void 0 ? perfNow5() - start : null;
20690
+ const elapsed = start !== void 0 ? perfNow() - start : null;
20328
20691
  if (elapsed !== null) {
20329
20692
  ctx.meta.durationMs = elapsed;
20330
20693
  }
@@ -20347,7 +20710,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
20347
20710
  beforeRequest: (req, ctx) => {
20348
20711
  if (req.signal?.aborted) {
20349
20712
  ctx.meta.aborted = true;
20350
- throw createAbortError2("Request aborted before sending");
20713
+ throw abortError("Request aborted before sending");
20351
20714
  }
20352
20715
  },
20353
20716
  onCancel: (evt) => {
@@ -20355,23 +20718,12 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
20355
20718
  }
20356
20719
  };
20357
20720
  }
20358
- function createAbortError2(message) {
20359
- if (typeof DOMException !== "undefined") {
20360
- return new DOMException(message, "AbortError");
20361
- }
20362
- const err = new Error(message);
20363
- err.name = "AbortError";
20364
- return err;
20365
- }
20366
- function perfNow5() {
20367
- return typeof performance !== "undefined" ? performance.now() : Date.now();
20368
- }
20369
20721
  function createHookContext(request, overrides = {}) {
20370
20722
  return {
20371
20723
  request,
20372
20724
  response: null,
20373
20725
  error: null,
20374
- startedAt: perfNow5(),
20726
+ startedAt: perfNow(),
20375
20727
  attempt: 1,
20376
20728
  meta: {},
20377
20729
  ...overrides
@@ -20656,12 +21008,12 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
20656
21008
  const retriable = err instanceof Socks5Error && err.retriable;
20657
21009
  if (!retriable || attempt === maxRetries)
20658
21010
  break;
20659
- await sleep6(delayMs * Math.pow(2, attempt));
21011
+ await sleep4(delayMs * Math.pow(2, attempt));
20660
21012
  }
20661
21013
  }
20662
21014
  throw lastError;
20663
21015
  }
20664
- function sleep6(ms) {
21016
+ function sleep4(ms) {
20665
21017
  return new Promise((r) => setTimeout(r, ms));
20666
21018
  }
20667
21019
  var TRANSIENT_DIAL_CODES = /* @__PURE__ */ new Set([