@fedify/fedify 2.3.15-dev.2482 → 2.3.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/{builder-CocMrVkL.mjs → builder-B6vSXYxn.mjs} +2 -2
  2. package/dist/{client-CAM_bQXx.d.cts → client-BIBzqEIp.d.cts} +0 -1
  3. package/dist/{client-1He2YuBF.mjs → client-DpUNYKla.mjs} +113 -64
  4. package/dist/{client-CSddvgWN.d.ts → client-jE7oGvRh.d.ts} +0 -1
  5. package/dist/compat/mod.d.cts +1 -1
  6. package/dist/compat/mod.d.ts +1 -1
  7. package/dist/compat/transformers.test.mjs +1 -1
  8. package/dist/{context-MOrCC7yl.d.cts → context-CE_Tj_Oc.d.cts} +28 -2
  9. package/dist/{context-BWbdE_MZ.d.ts → context-H_5tZXg-.d.ts} +28 -2
  10. package/dist/{deno-po8iekMB.mjs → deno-BCuL3mid.mjs} +1 -1
  11. package/dist/docloader-D2dr1_6B.mjs +125 -0
  12. package/dist/federation/builder.test.mjs +1 -1
  13. package/dist/federation/handler.test.mjs +5 -5
  14. package/dist/federation/idempotency.test.mjs +2 -2
  15. package/dist/federation/inbox.test.mjs +1 -1
  16. package/dist/federation/metrics.test.mjs +1 -1
  17. package/dist/federation/middleware.test.mjs +390 -29
  18. package/dist/federation/mod.cjs +1 -1
  19. package/dist/federation/mod.d.cts +2 -2
  20. package/dist/federation/mod.d.ts +2 -2
  21. package/dist/federation/mod.js +1 -1
  22. package/dist/federation/send.test.mjs +498 -88
  23. package/dist/federation/temporal.test.mjs +1 -1
  24. package/dist/federation/webfinger.test.mjs +1 -1
  25. package/dist/{http-Cdu8BGtv.js → http-BkjxY3U0.js} +172 -103
  26. package/dist/{http-BNCLeQJ-.mjs → http-CSDcThvz.mjs} +196 -98
  27. package/dist/{http-DQmNpfym.d.ts → http-CZPYFK3b.d.cts} +1 -2
  28. package/dist/{http-DiWQB8Mr.d.cts → http-DmP0sr2l.d.ts} +1 -2
  29. package/dist/{http-WcSdFbt1.cjs → http-gOkAiIe1.cjs} +173 -104
  30. package/dist/{inbox-g1sd_Vjn.mjs → inbox-BHxez4d5.mjs} +2 -2
  31. package/dist/{key-BR2TRTXR.mjs → key-D8IMQ72M.mjs} +2 -2
  32. package/dist/{kv-cache-Bw_gduSm.cjs → kv-cache-Alm5UmJa.cjs} +59 -17
  33. package/dist/{kv-cache-8SigUtvX.mjs → kv-cache-Blr9nDDp.mjs} +1 -1
  34. package/dist/{kv-cache-C0AqpjoS.js → kv-cache-CkXgU-ak.js} +60 -18
  35. package/dist/{ld-CSNsWdYE.mjs → ld-CVxxE1Pp.mjs} +3 -3
  36. package/dist/{metrics-BS7YzLVA.mjs → metrics-SYIZHFf_.mjs} +1 -1
  37. package/dist/{middleware-D-SL8Bkj.cjs → middleware-4yPyyoF5.cjs} +236 -98
  38. package/dist/{middleware-B1RRLN7m.mjs → middleware-CfskJGfV.mjs} +80 -40
  39. package/dist/{middleware-Dlo7NnZl.mjs → middleware-D3JWfiCu.mjs} +1 -1
  40. package/dist/{middleware-WL-JfDiK.js → middleware-DhN7c65B.js} +237 -99
  41. package/dist/{mod-LmD03uRA.d.ts → mod-CDFFESfL.d.ts} +1 -1
  42. package/dist/{mod-Bid79LFU.d.cts → mod-C_gFmeNq.d.cts} +1 -1
  43. package/dist/{mod-Co1M3Ux0.d.cts → mod-DDVkmZ_h.d.cts} +2 -8
  44. package/dist/{mod-B6PwkUMU.d.cts → mod-DDjtbkZO.d.cts} +1 -1
  45. package/dist/{mod-Br8yJNcE.d.ts → mod-DMjhnFbg.d.ts} +2 -8
  46. package/dist/{mod-TLEvgT1i.d.ts → mod-HQXFvOHs.d.ts} +1 -1
  47. package/dist/mod.cjs +5 -5
  48. package/dist/mod.d.cts +6 -6
  49. package/dist/mod.d.ts +6 -6
  50. package/dist/mod.js +5 -5
  51. package/dist/nodeinfo/client.test.mjs +337 -19
  52. package/dist/nodeinfo/handler.test.mjs +1 -1
  53. package/dist/nodeinfo/mod.cjs +1 -1
  54. package/dist/nodeinfo/mod.d.cts +1 -1
  55. package/dist/nodeinfo/mod.d.ts +1 -1
  56. package/dist/nodeinfo/mod.js +1 -1
  57. package/dist/otel/exporter.test.mjs +292 -30
  58. package/dist/otel/mod.cjs +36 -30
  59. package/dist/otel/mod.d.cts +13 -0
  60. package/dist/otel/mod.d.ts +13 -0
  61. package/dist/otel/mod.js +36 -30
  62. package/dist/{owner-DeggvxcG.mjs → owner-BluLVPf0.mjs} +2 -2
  63. package/dist/{proof-teNNYmFh.cjs → proof-CR-HwI0l.cjs} +1 -1
  64. package/dist/{proof-DMcUrrp8.mjs → proof-Co6nQBVR.mjs} +3 -3
  65. package/dist/{proof-v-IzWx7x.js → proof-Dg0d9QBo.js} +1 -1
  66. package/dist/{send-BsthcvPG.mjs → send-D_DnqOFA.mjs} +171 -73
  67. package/dist/sig/http.test.mjs +528 -2
  68. package/dist/sig/key.test.mjs +2 -2
  69. package/dist/sig/ld.test.mjs +2 -2
  70. package/dist/sig/mod.cjs +2 -2
  71. package/dist/sig/mod.d.cts +2 -2
  72. package/dist/sig/mod.d.ts +2 -2
  73. package/dist/sig/mod.js +2 -2
  74. package/dist/sig/owner.test.mjs +1 -1
  75. package/dist/sig/proof.test.mjs +1 -1
  76. package/dist/{temporal-Cji8Y8eQ.mjs → temporal-B8FUNkXB.mjs} +1 -1
  77. package/dist/testing/mod.d.mts +26 -0
  78. package/dist/{types-miAYfiGO.js → types-B1_pRFE_.js} +112 -63
  79. package/dist/{types-DRPjmHVV.cjs → types-DOvyymM4.cjs} +111 -62
  80. package/dist/utils/docloader.test.mjs +318 -34
  81. package/dist/utils/kv-cache.test.mjs +1 -1
  82. package/dist/utils/mod.cjs +1 -1
  83. package/dist/utils/mod.d.cts +1 -1
  84. package/dist/utils/mod.d.ts +1 -1
  85. package/dist/utils/mod.js +1 -1
  86. package/package.json +8 -8
  87. package/dist/docloader-BVj35vgR.mjs +0 -83
@@ -11,7 +11,7 @@ let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conve
11
11
  let byte_encodings_base64 = require("byte-encodings/base64");
12
12
  //#region deno.json
13
13
  var name = "@fedify/fedify";
14
- var version = "2.3.15-dev.2482+41d364a2";
14
+ var version = "2.3.15";
15
15
  //#endregion
16
16
  //#region src/sig/accept.ts
17
17
  /**
@@ -2484,10 +2484,10 @@ async function createRedirectRequest(request, location, body, validateRedirect)
2484
2484
  cache: request.cache
2485
2485
  });
2486
2486
  }
2487
- async function fetchDoubleKnockRequest(request, signedRequest, signal) {
2487
+ async function fetchDoubleKnockRequest(session, request, signedRequest, signal) {
2488
2488
  const maxAttempts = request.method === "GET" || request.method === "HEAD" ? 2 : 1;
2489
2489
  for (let attempt = 1;; attempt++) try {
2490
- return await fetch(signedRequest, {
2490
+ return await session.fetch(signedRequest, {
2491
2491
  redirect: "manual",
2492
2492
  signal
2493
2493
  });
@@ -2538,118 +2538,187 @@ function getAbortReason(signal) {
2538
2538
  return signal.reason ?? new DOMException("The operation was aborted.", "AbortError");
2539
2539
  }
2540
2540
  /**
2541
- * Performs a double-knock request to the given URL. For the details of
2542
- * double-knocking, see
2543
- * <https://swicg.github.io/activitypub-http-signature/#how-to-upgrade-supported-versions>.
2541
+ * Performs a double-knock request like {@link doubleKnock}, opening a session
2542
+ * for every hop with the given function, and passes the final response to
2543
+ * the given function. The final session is closed after `consume` settles,
2544
+ * so `consume` must read or cancel the response body before it returns.
2545
+ * This is not exported from any entry point.
2544
2546
  * @param request The request to send.
2545
2547
  * @param identity The identity to use for signing the request.
2546
2548
  * @param options The options for double-knock requests.
2547
- * @returns The response to the request.
2548
- * @since 1.6.0
2549
+ * @param openHop The function to open a session for each hop. The session of
2550
+ * a hop is closed before the next hop is opened.
2551
+ * @param consume The function to consume the final response.
2552
+ * @returns The value returned by `consume`.
2549
2553
  */
2550
- async function doubleKnock(request, identity, options = {}) {
2551
- return await doubleKnockInternal(request, identity, options);
2554
+ async function doubleKnockScoped(request, identity, options, openHop, consume) {
2555
+ const { response, session } = await doubleKnockCore(request, identity, options, openHop);
2556
+ try {
2557
+ return await consume(response);
2558
+ } catch (error) {
2559
+ (0, _logtape_logtape.getLogger)([
2560
+ "fedify",
2561
+ "sig",
2562
+ "http"
2563
+ ]).debug("Failed to consume the response from {url}; closing the session: {error}", {
2564
+ url: request.url,
2565
+ error
2566
+ });
2567
+ throw error;
2568
+ } finally {
2569
+ await session.close();
2570
+ }
2552
2571
  }
2553
- async function doubleKnockInternal(request, identity, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
2554
- const { specDeterminer, log, tracerProvider, signal } = options;
2555
- const maximumRedirection = options.maxRedirection ?? DEFAULT_MAX_REDIRECTION;
2556
- visited.add(request.url);
2557
- const origin = new URL(request.url).origin;
2558
- const firstTrySpec = specDeterminer == null ? "rfc9421" : await specDeterminer.determineSpec(origin);
2559
- const body = options.body !== void 0 ? options.body : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
2560
- let signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
2561
- spec: firstTrySpec,
2562
- tracerProvider,
2563
- body
2564
- });
2565
- log?.(signedRequest);
2566
- let response = await fetchDoubleKnockRequest(request, signedRequest, signal);
2567
- if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
2568
- if (redirected >= maximumRedirection) throw new _fedify_vocab_runtime.FetchError(request.url, `Too many redirections (${redirected + 1})`);
2569
- const redirectRequest = await createRedirectRequest(request, response.headers.get("Location"), body, options.validateRedirect);
2570
- if (visited.has(redirectRequest.url)) throw new _fedify_vocab_runtime.FetchError(request.url, `Redirect loop detected: ${redirectRequest.url}`);
2571
- return doubleKnockInternal(redirectRequest, identity, {
2572
- ...options,
2573
- body
2574
- }, redirected + 1, visited);
2575
- } else if (response.status === 400 || response.status === 401 || response.status > 401) {
2576
- const logger = (0, _logtape_logtape.getLogger)([
2572
+ function isRedirectResponse(response) {
2573
+ return response.status >= 300 && response.status < 400 && response.headers.has("Location");
2574
+ }
2575
+ /**
2576
+ * Starts cancelling the body of a response that is not passed to the caller,
2577
+ * without waiting for it to settle. The cancellation of a branch of a tee
2578
+ * (e.g., a body cloned by a fetch mock) settles only after the other branch
2579
+ * is cancelled too, per the Streams standard, so awaiting it could block
2580
+ * forever. Failures are only logged.
2581
+ */
2582
+ function cancelResponseBody(response, url) {
2583
+ response.body?.cancel().catch((error) => {
2584
+ (0, _logtape_logtape.getLogger)([
2577
2585
  "fedify",
2578
2586
  "sig",
2579
2587
  "http"
2580
- ]);
2581
- const acceptSigHeader = response.headers.get("Accept-Signature");
2582
- if (acceptSigHeader != null) {
2583
- const entries = validateAcceptSignature(parseAcceptSignature(acceptSigHeader));
2584
- const localKeyId = identity.keyId.href;
2585
- const localAlg = "rsa-v1_5-sha256";
2586
- let fulfilled = false;
2587
- let challengeRequest;
2588
- for (const entry of entries) {
2589
- const rfc9421 = fulfillAcceptSignature(entry, localKeyId, localAlg);
2590
- if (rfc9421 == null) continue;
2591
- logger.debug("Received Accept-Signature challenge; accumulating label {label} and components {components}.", {
2592
- label: rfc9421.label,
2593
- components: rfc9421.components
2594
- });
2595
- try {
2596
- challengeRequest = await signRequest(challengeRequest ?? request, identity.privateKey, identity.keyId, {
2597
- spec: "rfc9421",
2598
- tracerProvider,
2599
- body,
2600
- rfc9421
2588
+ ]).debug("Failed to cancel the response body from {url}: {error}", {
2589
+ url,
2590
+ error
2591
+ });
2592
+ });
2593
+ }
2594
+ async function doubleKnockCore(request, identity, options, openHop) {
2595
+ const { specDeterminer, log, tracerProvider, signal } = options;
2596
+ const maximumRedirection = options.maxRedirection ?? DEFAULT_MAX_REDIRECTION;
2597
+ const visited = /* @__PURE__ */ new Set();
2598
+ const body = options.body !== void 0 ? options.body : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
2599
+ let session = await openHop(request.url);
2600
+ try {
2601
+ for (let redirected = 0;; redirected++) {
2602
+ visited.add(request.url);
2603
+ const origin = new URL(request.url).origin;
2604
+ const firstTrySpec = specDeterminer == null ? "rfc9421" : await specDeterminer.determineSpec(origin);
2605
+ let signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
2606
+ spec: firstTrySpec,
2607
+ tracerProvider,
2608
+ body
2609
+ });
2610
+ log?.(signedRequest);
2611
+ let response = await fetchDoubleKnockRequest(session, request, signedRequest, signal);
2612
+ const firstRedirected = isRedirectResponse(response);
2613
+ if (!firstRedirected && (response.status === 400 || response.status === 401 || response.status > 401)) {
2614
+ const logger = (0, _logtape_logtape.getLogger)([
2615
+ "fedify",
2616
+ "sig",
2617
+ "http"
2618
+ ]);
2619
+ const acceptSigHeader = response.headers.get("Accept-Signature");
2620
+ if (acceptSigHeader != null) {
2621
+ const entries = validateAcceptSignature(parseAcceptSignature(acceptSigHeader));
2622
+ const localKeyId = identity.keyId.href;
2623
+ const localAlg = "rsa-v1_5-sha256";
2624
+ let fulfilled = false;
2625
+ let challengeRequest;
2626
+ for (const entry of entries) {
2627
+ const rfc9421 = fulfillAcceptSignature(entry, localKeyId, localAlg);
2628
+ if (rfc9421 == null) continue;
2629
+ logger.debug("Received Accept-Signature challenge; accumulating label {label} and components {components}.", {
2630
+ label: rfc9421.label,
2631
+ components: rfc9421.components
2632
+ });
2633
+ try {
2634
+ challengeRequest = await signRequest(challengeRequest ?? request, identity.privateKey, identity.keyId, {
2635
+ spec: "rfc9421",
2636
+ tracerProvider,
2637
+ body,
2638
+ rfc9421
2639
+ });
2640
+ fulfilled = true;
2641
+ } catch (error) {
2642
+ logger.debug("Failed to fulfill Accept-Signature challenge entry {label}: {error}", {
2643
+ label: entry.label,
2644
+ error
2645
+ });
2646
+ }
2647
+ }
2648
+ if (fulfilled && challengeRequest != null) {
2649
+ cancelResponseBody(response, request.url);
2650
+ signedRequest = challengeRequest;
2651
+ log?.(signedRequest);
2652
+ response = await session.fetch(signedRequest, {
2653
+ redirect: "manual",
2654
+ signal
2655
+ });
2656
+ }
2657
+ if (fulfilled && !isRedirectResponse(response)) {
2658
+ if (response.status < 300) {
2659
+ await specDeterminer?.rememberSpec(origin, "rfc9421");
2660
+ return {
2661
+ response,
2662
+ session
2663
+ };
2664
+ }
2665
+ if (response.status !== 400 && response.status !== 401) return {
2666
+ response,
2667
+ session
2668
+ };
2669
+ }
2670
+ }
2671
+ if (!isRedirectResponse(response)) {
2672
+ const spec = firstTrySpec === "draft-cavage-http-signatures-12" ? "rfc9421" : "draft-cavage-http-signatures-12";
2673
+ (0, _logtape_logtape.getLogger)([
2674
+ "fedify",
2675
+ "sig",
2676
+ "http"
2677
+ ]).debug("Failed to verify with the spec {spec} ({status} {statusText}); retrying with spec {secondSpec}... (double-knocking)", {
2678
+ spec: firstTrySpec,
2679
+ secondSpec: spec,
2680
+ status: response.status,
2681
+ statusText: response.statusText
2601
2682
  });
2602
- fulfilled = true;
2603
- } catch (error) {
2604
- logger.debug("Failed to fulfill Accept-Signature challenge entry {label}: {error}", {
2605
- label: entry.label,
2606
- error
2683
+ cancelResponseBody(response, request.url);
2684
+ signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
2685
+ spec,
2686
+ tracerProvider,
2687
+ body
2607
2688
  });
2689
+ log?.(signedRequest);
2690
+ response = await fetchDoubleKnockRequest(session, request, signedRequest, signal);
2691
+ if (!isRedirectResponse(response) && response.status !== 400 && response.status !== 401) await specDeterminer?.rememberSpec(origin, spec);
2608
2692
  }
2609
- }
2610
- if (fulfilled && challengeRequest != null) {
2611
- signedRequest = challengeRequest;
2612
- log?.(signedRequest);
2613
- response = await fetch(signedRequest, {
2614
- redirect: "manual",
2615
- signal
2616
- });
2617
- if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) return doubleKnock(await createRedirectRequest(request, response.headers.get("Location"), body, options.validateRedirect), identity, {
2618
- ...options,
2619
- body
2620
- });
2621
- }
2622
- if (fulfilled && response.status < 300) {
2623
- await specDeterminer?.rememberSpec(origin, "rfc9421");
2624
- return response;
2625
- }
2626
- if (fulfilled && response.status !== 400 && response.status !== 401) return response;
2627
- }
2628
- const spec = firstTrySpec === "draft-cavage-http-signatures-12" ? "rfc9421" : "draft-cavage-http-signatures-12";
2629
- logger.debug("Failed to verify with the spec {spec} ({status} {statusText}); retrying with spec {secondSpec}... (double-knocking)", {
2630
- spec: firstTrySpec,
2631
- secondSpec: spec,
2632
- status: response.status,
2633
- statusText: response.statusText
2634
- });
2635
- signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
2636
- spec,
2637
- tracerProvider,
2638
- body
2639
- });
2640
- log?.(signedRequest);
2641
- response = await fetchDoubleKnockRequest(request, signedRequest, signal);
2642
- if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
2693
+ } else if (!firstRedirected) await specDeterminer?.rememberSpec(origin, firstTrySpec);
2694
+ if (!isRedirectResponse(response)) return {
2695
+ response,
2696
+ session
2697
+ };
2698
+ cancelResponseBody(response, request.url);
2699
+ await session.close();
2643
2700
  if (redirected >= maximumRedirection) throw new _fedify_vocab_runtime.FetchError(request.url, `Too many redirections (${redirected + 1})`);
2644
- const redirectRequest = await createRedirectRequest(request, response.headers.get("Location"), body, options.validateRedirect);
2701
+ const location = response.headers.get("Location");
2702
+ const redirectRequest = await createRedirectRequest(request, location, body, options.validateRedirect);
2645
2703
  if (visited.has(redirectRequest.url)) throw new _fedify_vocab_runtime.FetchError(request.url, `Redirect loop detected: ${redirectRequest.url}`);
2646
- return doubleKnockInternal(redirectRequest, identity, {
2647
- ...options,
2648
- body
2649
- }, redirected + 1, visited);
2650
- } else if (response.status !== 400 && response.status !== 401) await specDeterminer?.rememberSpec(origin, spec);
2651
- } else await specDeterminer?.rememberSpec(origin, firstTrySpec);
2652
- return response;
2704
+ request = redirectRequest;
2705
+ session = await openHop(request.url);
2706
+ }
2707
+ } catch (error) {
2708
+ try {
2709
+ await session.close();
2710
+ } catch (closeError) {
2711
+ (0, _logtape_logtape.getLogger)([
2712
+ "fedify",
2713
+ "sig",
2714
+ "http"
2715
+ ]).debug("Failed to close the session for {url}: {error}", {
2716
+ url: request.url,
2717
+ error: closeError
2718
+ });
2719
+ }
2720
+ throw error;
2721
+ }
2653
2722
  }
2654
2723
  /**
2655
2724
  * Performs a timing-safe equality comparison between two `Uint8Array` values.
@@ -2679,10 +2748,10 @@ function timingSafeEqual(a, b) {
2679
2748
  return result === 0;
2680
2749
  }
2681
2750
  //#endregion
2682
- Object.defineProperty(exports, "doubleKnock", {
2751
+ Object.defineProperty(exports, "doubleKnockScoped", {
2683
2752
  enumerable: true,
2684
2753
  get: function() {
2685
- return doubleKnock;
2754
+ return doubleKnockScoped;
2686
2755
  }
2687
2756
  });
2688
2757
  Object.defineProperty(exports, "exportJwk", {
@@ -1,8 +1,8 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-po8iekMB.mjs";
5
- import { g as recordInboxActivity, n as getDurationMs, r as getFederationMetrics } from "./metrics-BS7YzLVA.mjs";
4
+ import { n as version, t as name } from "./deno-BCuL3mid.mjs";
5
+ import { g as recordInboxActivity, n as getDurationMs, r as getFederationMetrics } from "./metrics-SYIZHFf_.mjs";
6
6
  import { getLogger } from "@logtape/logtape";
7
7
  import { getTypeId } from "@fedify/vocab";
8
8
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
@@ -1,8 +1,8 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-po8iekMB.mjs";
5
- import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-BS7YzLVA.mjs";
4
+ import { n as version, t as name } from "./deno-BCuL3mid.mjs";
5
+ import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-SYIZHFf_.mjs";
6
6
  import { getLogger } from "@logtape/logtape";
7
7
  import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
8
8
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
@@ -1,10 +1,11 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  require("./chunk-DDcVe30Y.cjs");
4
- const require_http = require("./http-WcSdFbt1.cjs");
4
+ const require_http = require("./http-gOkAiIe1.cjs");
5
5
  let _logtape_logtape = require("@logtape/logtape");
6
6
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
7
7
  let es_toolkit = require("es-toolkit");
8
+ let _fedify_vocab_runtime_internal = require("@fedify/vocab-runtime/internal");
8
9
  //#region src/utils/docloader.ts
9
10
  const logger$1 = (0, _logtape_logtape.getLogger)([
10
11
  "fedify",
@@ -25,12 +26,34 @@ const DEFAULT_MAX_REDIRECTION = 20;
25
26
  * @throws {TypeError} If the key is invalid or unsupported.
26
27
  * @since 0.4.0
27
28
  */
28
- function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedirection, userAgent, specDeterminer, tracerProvider } = {}) {
29
+ function getAuthenticatedDocumentLoader(identity, options = {}) {
30
+ return createAuthenticatedDocumentLoader(identity, options);
31
+ }
32
+ /**
33
+ * The placeholder that the alternate document callback of
34
+ * `getRemoteDocument()` returns, so that the alternate document is loaded
35
+ * after the current hop is closed.
36
+ */
37
+ const ALTERNATE_PLACEHOLDER = Object.freeze({
38
+ contextUrl: null,
39
+ document: null,
40
+ documentUrl: "about:blank"
41
+ });
42
+ /**
43
+ * The implementation of {@link getAuthenticatedDocumentLoader}. This is not
44
+ * exported from any entry point.
45
+ * @param identity The identity to get the document loader for.
46
+ * @param options The options for the document loader.
47
+ * @param deps The dependencies, for tests.
48
+ * @returns The authenticated document loader.
49
+ * @throws {TypeError} If the key is invalid or unsupported.
50
+ */
51
+ function createAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedirection, userAgent, specDeterminer, tracerProvider } = {}, deps = {}) {
29
52
  require_http.validateCryptoKey(identity.privateKey);
30
53
  async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
31
54
  options?.signal?.throwIfAborted();
32
55
  let currentUrl = new URL(url).href;
33
- await validateUrl(currentUrl);
56
+ validateUrl(currentUrl);
34
57
  visited.add(currentUrl);
35
58
  const originalRequest = (0, _fedify_vocab_runtime.createActivityPubRequest)(currentUrl, { userAgent });
36
59
  function follow(nextUrl) {
@@ -39,31 +62,44 @@ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedi
39
62
  if (visited.has(nextUrl)) throw new _fedify_vocab_runtime.FetchError(currentUrl, `Redirect loop detected: ${nextUrl}`);
40
63
  redirected++;
41
64
  }
42
- const response = await require_http.doubleKnock(originalRequest, identity, {
65
+ const result = await require_http.doubleKnockScoped(originalRequest, identity, {
43
66
  maxRedirection,
44
67
  specDeterminer,
45
68
  log: (0, es_toolkit.curry)(_fedify_vocab_runtime.logRequest)(logger$1),
46
69
  tracerProvider,
47
70
  signal: options?.signal,
48
- validateRedirect: async (nextUrl) => {
71
+ validateRedirect: (nextUrl) => {
49
72
  follow(nextUrl);
50
- await validateUrl(nextUrl);
73
+ validateUrl(nextUrl);
51
74
  visited.add(nextUrl);
52
75
  currentUrl = nextUrl;
53
76
  }
77
+ }, (hopUrl) => openHop(hopUrl, options?.signal), async (response) => {
78
+ let alternateUrl;
79
+ const document = await (0, _fedify_vocab_runtime.getRemoteDocument)(currentUrl, response, (url) => {
80
+ alternateUrl = url;
81
+ return Promise.resolve(ALTERNATE_PLACEHOLDER);
82
+ });
83
+ if (alternateUrl == null) return { document };
84
+ if (!response.bodyUsed && response.body?.locked !== true) response.body?.cancel().catch((error) => {
85
+ logger$1.debug("Failed to cancel the response body from {url}: {error}", {
86
+ url: currentUrl,
87
+ error
88
+ });
89
+ });
90
+ return { alternateUrl };
54
91
  });
55
- return (0, _fedify_vocab_runtime.getRemoteDocument)(currentUrl, response, (alternateUrl) => {
56
- follow(alternateUrl);
57
- return load(alternateUrl, options, redirected, visited);
58
- });
92
+ if ("document" in result) return result.document;
93
+ follow(result.alternateUrl);
94
+ return load(result.alternateUrl, options, redirected, visited);
59
95
  }
60
- async function validateUrl(url) {
61
- const parsedUrl = new URL(url);
62
- const protocol = parsedUrl.protocol;
63
- if (protocol !== "http:" && protocol !== "https:") throw new _fedify_vocab_runtime.UrlError(`Unsupported protocol: ${protocol}`);
64
- if (parsedUrl.username !== "" || parsedUrl.password !== "") throw new _fedify_vocab_runtime.UrlError("URLs with credentials are not allowed");
65
- if (!allowPrivateAddress) try {
66
- await (0, _fedify_vocab_runtime.validatePublicUrl)(url);
96
+ function openUnvalidatedHop(url, signal) {
97
+ if (deps.openHop != null) return deps.openHop(url);
98
+ return allowPrivateAddress ? (0, _fedify_vocab_runtime_internal.openDirectFetch)(url) : (0, _fedify_vocab_runtime_internal.openPublicFetch)(url, { signal });
99
+ }
100
+ async function openHop(url, signal) {
101
+ try {
102
+ return await openUnvalidatedHop(url, signal);
67
103
  } catch (error) {
68
104
  if (error instanceof _fedify_vocab_runtime.UrlError) if (error.reason === "dns") logger$1.debug("DNS lookup failed for {url}", {
69
105
  url,
@@ -76,6 +112,12 @@ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedi
76
112
  throw error;
77
113
  }
78
114
  }
115
+ function validateUrl(url) {
116
+ const parsedUrl = new URL(url);
117
+ const protocol = parsedUrl.protocol;
118
+ if (protocol !== "http:" && protocol !== "https:") throw new _fedify_vocab_runtime.UrlError(`Unsupported protocol: ${protocol}`);
119
+ if (parsedUrl.username !== "" || parsedUrl.password !== "") throw new _fedify_vocab_runtime.UrlError("URLs with credentials are not allowed");
120
+ }
79
121
  return (url, options) => load(url, options);
80
122
  }
81
123
  //#endregion
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { p as recordDocumentCache } from "./metrics-BS7YzLVA.mjs";
4
+ import { p as recordDocumentCache } from "./metrics-SYIZHFf_.mjs";
5
5
  import { getLogger } from "@logtape/logtape";
6
6
  import { preloadedContexts } from "@fedify/vocab-runtime";
7
7
  //#region src/utils/kv-cache.ts
@@ -1,9 +1,10 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { T as recordDocumentCache, f as validateCryptoKey, t as doubleKnock } from "./http-Cdu8BGtv.js";
3
+ import { T as recordDocumentCache, f as validateCryptoKey, t as doubleKnockScoped } from "./http-BkjxY3U0.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
- import { FetchError, UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
5
+ import { FetchError, UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts } from "@fedify/vocab-runtime";
6
6
  import { curry } from "es-toolkit";
7
+ import { openDirectFetch, openPublicFetch } from "@fedify/vocab-runtime/internal";
7
8
  //#region src/utils/docloader.ts
8
9
  const logger$1 = getLogger([
9
10
  "fedify",
@@ -24,12 +25,34 @@ const DEFAULT_MAX_REDIRECTION = 20;
24
25
  * @throws {TypeError} If the key is invalid or unsupported.
25
26
  * @since 0.4.0
26
27
  */
27
- function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedirection, userAgent, specDeterminer, tracerProvider } = {}) {
28
+ function getAuthenticatedDocumentLoader(identity, options = {}) {
29
+ return createAuthenticatedDocumentLoader(identity, options);
30
+ }
31
+ /**
32
+ * The placeholder that the alternate document callback of
33
+ * `getRemoteDocument()` returns, so that the alternate document is loaded
34
+ * after the current hop is closed.
35
+ */
36
+ const ALTERNATE_PLACEHOLDER = Object.freeze({
37
+ contextUrl: null,
38
+ document: null,
39
+ documentUrl: "about:blank"
40
+ });
41
+ /**
42
+ * The implementation of {@link getAuthenticatedDocumentLoader}. This is not
43
+ * exported from any entry point.
44
+ * @param identity The identity to get the document loader for.
45
+ * @param options The options for the document loader.
46
+ * @param deps The dependencies, for tests.
47
+ * @returns The authenticated document loader.
48
+ * @throws {TypeError} If the key is invalid or unsupported.
49
+ */
50
+ function createAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedirection, userAgent, specDeterminer, tracerProvider } = {}, deps = {}) {
28
51
  validateCryptoKey(identity.privateKey);
29
52
  async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
30
53
  options?.signal?.throwIfAborted();
31
54
  let currentUrl = new URL(url).href;
32
- await validateUrl(currentUrl);
55
+ validateUrl(currentUrl);
33
56
  visited.add(currentUrl);
34
57
  const originalRequest = createActivityPubRequest(currentUrl, { userAgent });
35
58
  function follow(nextUrl) {
@@ -38,31 +61,44 @@ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedi
38
61
  if (visited.has(nextUrl)) throw new FetchError(currentUrl, `Redirect loop detected: ${nextUrl}`);
39
62
  redirected++;
40
63
  }
41
- const response = await doubleKnock(originalRequest, identity, {
64
+ const result = await doubleKnockScoped(originalRequest, identity, {
42
65
  maxRedirection,
43
66
  specDeterminer,
44
67
  log: curry(logRequest)(logger$1),
45
68
  tracerProvider,
46
69
  signal: options?.signal,
47
- validateRedirect: async (nextUrl) => {
70
+ validateRedirect: (nextUrl) => {
48
71
  follow(nextUrl);
49
- await validateUrl(nextUrl);
72
+ validateUrl(nextUrl);
50
73
  visited.add(nextUrl);
51
74
  currentUrl = nextUrl;
52
75
  }
76
+ }, (hopUrl) => openHop(hopUrl, options?.signal), async (response) => {
77
+ let alternateUrl;
78
+ const document = await getRemoteDocument(currentUrl, response, (url) => {
79
+ alternateUrl = url;
80
+ return Promise.resolve(ALTERNATE_PLACEHOLDER);
81
+ });
82
+ if (alternateUrl == null) return { document };
83
+ if (!response.bodyUsed && response.body?.locked !== true) response.body?.cancel().catch((error) => {
84
+ logger$1.debug("Failed to cancel the response body from {url}: {error}", {
85
+ url: currentUrl,
86
+ error
87
+ });
88
+ });
89
+ return { alternateUrl };
53
90
  });
54
- return getRemoteDocument(currentUrl, response, (alternateUrl) => {
55
- follow(alternateUrl);
56
- return load(alternateUrl, options, redirected, visited);
57
- });
91
+ if ("document" in result) return result.document;
92
+ follow(result.alternateUrl);
93
+ return load(result.alternateUrl, options, redirected, visited);
58
94
  }
59
- async function validateUrl(url) {
60
- const parsedUrl = new URL(url);
61
- const protocol = parsedUrl.protocol;
62
- if (protocol !== "http:" && protocol !== "https:") throw new UrlError(`Unsupported protocol: ${protocol}`);
63
- if (parsedUrl.username !== "" || parsedUrl.password !== "") throw new UrlError("URLs with credentials are not allowed");
64
- if (!allowPrivateAddress) try {
65
- await validatePublicUrl(url);
95
+ function openUnvalidatedHop(url, signal) {
96
+ if (deps.openHop != null) return deps.openHop(url);
97
+ return allowPrivateAddress ? openDirectFetch(url) : openPublicFetch(url, { signal });
98
+ }
99
+ async function openHop(url, signal) {
100
+ try {
101
+ return await openUnvalidatedHop(url, signal);
66
102
  } catch (error) {
67
103
  if (error instanceof UrlError) if (error.reason === "dns") logger$1.debug("DNS lookup failed for {url}", {
68
104
  url,
@@ -75,6 +111,12 @@ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedi
75
111
  throw error;
76
112
  }
77
113
  }
114
+ function validateUrl(url) {
115
+ const parsedUrl = new URL(url);
116
+ const protocol = parsedUrl.protocol;
117
+ if (protocol !== "http:" && protocol !== "https:") throw new UrlError(`Unsupported protocol: ${protocol}`);
118
+ if (parsedUrl.username !== "" || parsedUrl.password !== "") throw new UrlError("URLs with credentials are not allowed");
119
+ }
78
120
  return (url, options) => load(url, options);
79
121
  }
80
122
  //#endregion
@@ -1,9 +1,9 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-po8iekMB.mjs";
5
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-BS7YzLVA.mjs";
6
- import { r as fetchKey, s as validateCryptoKey } from "./key-BR2TRTXR.mjs";
4
+ import { n as version, t as name } from "./deno-BCuL3mid.mjs";
5
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-SYIZHFf_.mjs";
6
+ import { r as fetchKey, s as validateCryptoKey } from "./key-D8IMQ72M.mjs";
7
7
  import { getLogger } from "@logtape/logtape";
8
8
  import { Activity, CryptographicKey, Object as Object$1, getTypeId } from "@fedify/vocab";
9
9
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -1,7 +1,7 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-po8iekMB.mjs";
4
+ import { n as version, t as name } from "./deno-BCuL3mid.mjs";
5
5
  import { metrics } from "@opentelemetry/api";
6
6
  import { FetchError } from "@fedify/vocab-runtime";
7
7
  //#region src/federation/metrics.ts