@fedify/fedify 2.3.14 → 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-C6yXExXl.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-y3L4Oa94.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 +455 -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-DNM9KyIO.js → http-BkjxY3U0.js} +172 -103
  26. package/dist/{http-BCSf1KIY.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-D77Wjrcg.cjs → http-gOkAiIe1.cjs} +173 -104
  30. package/dist/{inbox-NObX26c9.mjs → inbox-BHxez4d5.mjs} +2 -2
  31. package/dist/{key-APURZ0ky.mjs → key-D8IMQ72M.mjs} +2 -2
  32. package/dist/{kv-cache-Bg4utAuv.cjs → kv-cache-Alm5UmJa.cjs} +59 -17
  33. package/dist/{kv-cache-C69dzgQi.mjs → kv-cache-Blr9nDDp.mjs} +1 -1
  34. package/dist/{kv-cache-LBCwHNLu.js → kv-cache-CkXgU-ak.js} +60 -18
  35. package/dist/{ld-BoLqbDaL.mjs → ld-CVxxE1Pp.mjs} +3 -3
  36. package/dist/{metrics-DBSl8wEr.mjs → metrics-SYIZHFf_.mjs} +1 -1
  37. package/dist/{middleware-BQUQ0pPl.cjs → middleware-4yPyyoF5.cjs} +237 -99
  38. package/dist/{middleware-CZ8tI1qL.mjs → middleware-CfskJGfV.mjs} +81 -41
  39. package/dist/{middleware-DAqo2eOY.mjs → middleware-D3JWfiCu.mjs} +1 -1
  40. package/dist/{middleware-CRd1JmuL.js → middleware-DhN7c65B.js} +238 -100
  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-DjKvngeb.mjs → owner-BluLVPf0.mjs} +2 -2
  63. package/dist/{proof-CWTnYhjS.cjs → proof-CR-HwI0l.cjs} +1 -1
  64. package/dist/{proof-D2oFOdNA.mjs → proof-Co6nQBVR.mjs} +3 -3
  65. package/dist/{proof-s9sy44kx.js → proof-Dg0d9QBo.js} +1 -1
  66. package/dist/{send-xDK5UnL-.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-C6icc5n9.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-yuyzZC9a.mjs +0 -83
@@ -2,10 +2,10 @@ const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  const require_chunk = require("./chunk-DDcVe30Y.cjs");
4
4
  const require_transformers = require("./transformers-NeAONrAq.cjs");
5
- const require_http = require("./http-D77Wjrcg.cjs");
6
- const require_types = require("./types-DRPjmHVV.cjs");
7
- const require_proof = require("./proof-CWTnYhjS.cjs");
8
- const require_kv_cache = require("./kv-cache-Bg4utAuv.cjs");
5
+ const require_http = require("./http-gOkAiIe1.cjs");
6
+ const require_types = require("./types-DOvyymM4.cjs");
7
+ const require_proof = require("./proof-CR-HwI0l.cjs");
8
+ const require_kv_cache = require("./kv-cache-Alm5UmJa.cjs");
9
9
  let _logtape_logtape = require("@logtape/logtape");
10
10
  let _fedify_uri_template = require("@fedify/uri-template");
11
11
  let _fedify_vocab = require("@fedify/vocab");
@@ -17,6 +17,7 @@ let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conve
17
17
  let _fedify_vocab_runtime_jsonld = require("@fedify/vocab-runtime/jsonld");
18
18
  _fedify_vocab_runtime_jsonld = require_chunk.__toESM(_fedify_vocab_runtime_jsonld, 1);
19
19
  let _fedify_webfinger = require("@fedify/webfinger");
20
+ let _fedify_vocab_runtime_internal = require("@fedify/vocab-runtime/internal");
20
21
  let _opentelemetry_sdk_metrics = require("@opentelemetry/sdk-metrics");
21
22
  let node_url = require("node:url");
22
23
  //#region src/federation/activity-listener.ts
@@ -3423,9 +3424,10 @@ function extractInboxes({ recipients, preferSharedInbox, excludeBaseUris }) {
3423
3424
  *
3424
3425
  * @param parameters The parameters for sending the activity.
3425
3426
  * See also {@link SendActivityParameters}.
3427
+ * @param deps The dependencies, for tests.
3426
3428
  * @throws {Error} If the activity fails to send.
3427
3429
  */
3428
- function sendActivity(options) {
3430
+ function sendActivity(options, deps = {}) {
3429
3431
  const tracerProvider = options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider();
3430
3432
  return tracerProvider.getTracer(require_http.name, require_http.version).startActiveSpan("activitypub.send_activity", {
3431
3433
  kind: _opentelemetry_api.SpanKind.CLIENT,
@@ -3437,7 +3439,7 @@ function sendActivity(options) {
3437
3439
  await sendActivityInternal({
3438
3440
  ...options,
3439
3441
  tracerProvider
3440
- }, span);
3442
+ }, span, deps);
3441
3443
  } catch (e) {
3442
3444
  span.setStatus({
3443
3445
  code: _opentelemetry_api.SpanStatusCode.ERROR,
@@ -3490,13 +3492,14 @@ async function readLimitedResponseBody(response, maxBytes) {
3490
3492
  totalBytes += value.length;
3491
3493
  }
3492
3494
  } finally {
3495
+ if (truncated) reader.cancel().catch(() => {});
3493
3496
  reader.releaseLock();
3494
3497
  }
3495
3498
  let result = chunks.join("");
3496
3499
  if (truncated) result += "… (truncated)";
3497
3500
  return result;
3498
3501
  }
3499
- async function sendActivityInternal({ activity, activityId, activityType, keys, inbox, allowPrivateAddress, headers, specDeterminer, meterProvider, tracerProvider }, span) {
3502
+ async function sendActivityInternal({ activity, activityId, activityType, keys, inbox, allowPrivateAddress, headers, specDeterminer, meterProvider, tracerProvider }, span, deps) {
3500
3503
  const logger = (0, _logtape_logtape.getLogger)([
3501
3504
  "fedify",
3502
3505
  "federation",
@@ -3505,9 +3508,11 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
3505
3508
  const federationMetrics = require_http.getFederationMetrics(meterProvider);
3506
3509
  const started = performance.now();
3507
3510
  let deliverySuccess = false;
3508
- async function validateUrl(url) {
3509
- if (!allowPrivateAddress) try {
3510
- await (0, _fedify_vocab_runtime.validatePublicUrl)(url);
3511
+ const openUnvalidatedHop = deps.openHop ?? (allowPrivateAddress ? _fedify_vocab_runtime_internal.openDirectFetch : (url) => (0, _fedify_vocab_runtime_internal.openPublicFetch)(url));
3512
+ async function openHop(url) {
3513
+ let session;
3514
+ try {
3515
+ session = await openUnvalidatedHop(url);
3511
3516
  } catch (error) {
3512
3517
  if (error instanceof _fedify_vocab_runtime.UrlError && error.reason === "dns") {
3513
3518
  logger.error("DNS resolution failed for URL: {url}", {
@@ -3524,78 +3529,86 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
3524
3529
  });
3525
3530
  throw error;
3526
3531
  }
3532
+ return session;
3527
3533
  }
3534
+ let initialSession;
3528
3535
  try {
3529
- await validateUrl(inbox.href);
3536
+ initialSession = await openHop(inbox.href);
3530
3537
  } catch (error) {
3531
3538
  if (error instanceof _fedify_vocab_runtime.FetchError) federationMetrics.recordDelivery(inbox, require_http.getDurationMs(started), false, activityType);
3532
3539
  throw error;
3533
3540
  }
3534
- headers = new Headers(headers);
3535
- headers.set("Content-Type", "application/activity+json");
3536
- const request = new Request(inbox, {
3537
- method: "POST",
3538
- headers,
3539
- body: JSON.stringify(activity),
3540
- redirect: "manual"
3541
- });
3542
- let rsaKey = null;
3543
- for (const key of keys) if (key.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
3544
- rsaKey = key;
3545
- break;
3546
- }
3547
- if (rsaKey == null) logger.warn("No supported key found to sign the request to {inbox}. The request will be sent without a signature. In order to sign the request, at least one RSASSA-PKCS1-v1_5 key must be provided.", {
3548
- inbox: inbox.href,
3549
- keys: keys.map((pair) => ({
3550
- keyId: pair.keyId.href,
3551
- privateKey: pair.privateKey
3552
- }))
3553
- });
3554
- let response;
3541
+ let initialPending = true;
3542
+ const openDeliveryHop = (url) => {
3543
+ if (initialPending) {
3544
+ initialPending = false;
3545
+ return Promise.resolve(initialSession);
3546
+ }
3547
+ return openHop(url);
3548
+ };
3555
3549
  try {
3556
- response = rsaKey == null ? await fetchWithValidatedRedirects(request, validateUrl) : await require_http.doubleKnock(request, rsaKey, {
3557
- tracerProvider,
3558
- specDeterminer,
3559
- validateRedirect: validateUrl
3550
+ headers = new Headers(headers);
3551
+ headers.set("Content-Type", "application/activity+json");
3552
+ const request = new Request(inbox, {
3553
+ method: "POST",
3554
+ headers,
3555
+ body: JSON.stringify(activity),
3556
+ redirect: "manual"
3560
3557
  });
3561
- } catch (error) {
3562
- const failure = error instanceof _fedify_vocab_runtime.FetchError || error instanceof _fedify_vocab_runtime.UrlError ? error : createFetchError(inbox.href, error);
3563
- logger.error("Failed to send activity {activityId} to {inbox}:\n{error}", {
3564
- activityId,
3558
+ let rsaKey = null;
3559
+ for (const key of keys) if (key.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
3560
+ rsaKey = key;
3561
+ break;
3562
+ }
3563
+ if (rsaKey == null) logger.warn("No supported key found to sign the request to {inbox}. The request will be sent without a signature. In order to sign the request, at least one RSASSA-PKCS1-v1_5 key must be provided.", {
3565
3564
  inbox: inbox.href,
3566
- error: failure
3565
+ keys: keys.map((pair) => ({
3566
+ keyId: pair.keyId.href,
3567
+ privateKey: pair.privateKey
3568
+ }))
3567
3569
  });
3568
- federationMetrics.recordDelivery(inbox, require_http.getDurationMs(started), false, activityType);
3569
- throw failure;
3570
- }
3571
- try {
3572
- if (!response.ok) {
3573
- let error;
3574
- try {
3575
- error = await readLimitedResponseBody(response, MAX_ERROR_RESPONSE_BODY_BYTES);
3576
- } catch (_) {
3577
- error = "";
3578
- }
3579
- logger.error("Failed to send activity {activityId} to {inbox} ({status} {statusText}):\n{error}", {
3570
+ let outcome;
3571
+ try {
3572
+ outcome = rsaKey == null ? await fetchWithValidatedRedirects(request, openDeliveryHop, (response) => consumeSendActivityResponse(response, inbox)) : await require_http.doubleKnockScoped(request, rsaKey, {
3573
+ tracerProvider,
3574
+ specDeterminer
3575
+ }, openDeliveryHop, (response) => consumeSendActivityResponse(response, inbox));
3576
+ } catch (error) {
3577
+ const failure = error instanceof _fedify_vocab_runtime.FetchError || error instanceof _fedify_vocab_runtime.UrlError ? error : createFetchError(inbox.href, error);
3578
+ logger.error("Failed to send activity {activityId} to {inbox}:\n{error}", {
3580
3579
  activityId,
3581
3580
  inbox: inbox.href,
3582
- status: response.status,
3583
- statusText: response.statusText,
3584
- error
3581
+ error: failure
3585
3582
  });
3586
- throw new SendActivityError(inbox, response.status, `Failed to send activity ${activityId} to ${inbox.href} (${response.status} ${response.statusText}):\n${error}`, error, response.headers);
3583
+ federationMetrics.recordDelivery(inbox, require_http.getDurationMs(started), false, activityType);
3584
+ throw failure;
3585
+ }
3586
+ try {
3587
+ if (!outcome.ok) {
3588
+ const { status, statusText, error, responseHeaders } = outcome;
3589
+ logger.error("Failed to send activity {activityId} to {inbox} ({status} {statusText}):\n{error}", {
3590
+ activityId,
3591
+ inbox: inbox.href,
3592
+ status,
3593
+ statusText,
3594
+ error
3595
+ });
3596
+ throw new SendActivityError(inbox, status, `Failed to send activity ${activityId} to ${inbox.href} (${status} ${statusText}):\n${error}`, error, responseHeaders);
3597
+ }
3598
+ deliverySuccess = true;
3599
+ const eventAttributes = {
3600
+ "activitypub.inbox.url": inbox.href,
3601
+ "activitypub.activity.id": activityId ?? ""
3602
+ };
3603
+ if (activityType != null) eventAttributes["activitypub.activity.type"] = activityType;
3604
+ const actorId = getActivityActorId(activity);
3605
+ if (actorId != null) eventAttributes["activitypub.actor.id"] = actorId;
3606
+ span.addEvent("activitypub.activity.sent", eventAttributes);
3607
+ } finally {
3608
+ federationMetrics.recordDelivery(inbox, require_http.getDurationMs(started), deliverySuccess, activityType);
3587
3609
  }
3588
- deliverySuccess = true;
3589
- const eventAttributes = {
3590
- "activitypub.inbox.url": inbox.href,
3591
- "activitypub.activity.id": activityId ?? ""
3592
- };
3593
- if (activityType != null) eventAttributes["activitypub.activity.type"] = activityType;
3594
- const actorId = getActivityActorId(activity);
3595
- if (actorId != null) eventAttributes["activitypub.actor.id"] = actorId;
3596
- span.addEvent("activitypub.activity.sent", eventAttributes);
3597
3610
  } finally {
3598
- federationMetrics.recordDelivery(inbox, require_http.getDurationMs(started), deliverySuccess, activityType);
3611
+ if (initialPending) await initialSession.close();
3599
3612
  }
3600
3613
  }
3601
3614
  function createFetchError(url, cause) {
@@ -3603,10 +3616,75 @@ function createFetchError(url, cause) {
3603
3616
  error.cause = cause;
3604
3617
  return error;
3605
3618
  }
3606
- async function fetchWithValidatedRedirects(request, validateUrl) {
3619
+ /**
3620
+ * Starts cancelling the body of a response without waiting for it to settle.
3621
+ * The cancellation of a branch of a tee (e.g., a body cloned by a fetch mock)
3622
+ * settles only after the other branch is cancelled too, per the Streams
3623
+ * standard, so awaiting it could block forever. Failures are only logged.
3624
+ */
3625
+ function cancelResponseBody(response, url) {
3626
+ response.body?.cancel().catch((error) => {
3627
+ (0, _logtape_logtape.getLogger)([
3628
+ "fedify",
3629
+ "federation",
3630
+ "outbox"
3631
+ ]).debug("Failed to cancel the response body from {url}: {error}", {
3632
+ url,
3633
+ error
3634
+ });
3635
+ });
3636
+ }
3637
+ /**
3638
+ * Reads what is needed from the final response of a delivery while its
3639
+ * session is open: the body of a successful response is cancelled, and at
3640
+ * most 1 KiB of an error response body is read.
3641
+ */
3642
+ async function consumeSendActivityResponse(response, inbox) {
3643
+ if (response.ok) {
3644
+ cancelResponseBody(response, response.url || inbox.href);
3645
+ return { ok: true };
3646
+ }
3647
+ let error;
3648
+ try {
3649
+ error = await readLimitedResponseBody(response, MAX_ERROR_RESPONSE_BODY_BYTES);
3650
+ } catch (_) {
3651
+ error = "";
3652
+ }
3653
+ return {
3654
+ ok: false,
3655
+ status: response.status,
3656
+ statusText: response.statusText,
3657
+ error,
3658
+ responseHeaders: new Headers(response.headers)
3659
+ };
3660
+ }
3661
+ /**
3662
+ * Sends a request without signing it, following redirects manually like the
3663
+ * Fetch standard does. Every hop is opened with `openHop()`, which validates
3664
+ * the destination, after the previous hop is closed. The final response is
3665
+ * passed to `consume()`, and its session is closed after that.
3666
+ * This is exported only for tests; it is not exported from any entry point.
3667
+ * @param request The request to send.
3668
+ * @param openHop The function to open a session for each hop.
3669
+ * @param consume The function to consume the final response.
3670
+ * @returns The value returned by `consume`.
3671
+ */
3672
+ async function fetchWithValidatedRedirects(request, openHop, consume) {
3673
+ const logger = (0, _logtape_logtape.getLogger)([
3674
+ "fedify",
3675
+ "federation",
3676
+ "outbox"
3677
+ ]);
3607
3678
  let body = await request.clone().text();
3608
3679
  for (let redirects = 0;; redirects++) {
3609
- const response = await fetch(request, { redirect: "manual" });
3680
+ const session = await openHop(request.url);
3681
+ let response;
3682
+ try {
3683
+ response = await session.fetch(request, { redirect: "manual" });
3684
+ } catch (error) {
3685
+ await closeAfterError(session, logger);
3686
+ throw error;
3687
+ }
3610
3688
  const location = response.headers.get("Location");
3611
3689
  if (![
3612
3690
  301,
@@ -3614,11 +3692,21 @@ async function fetchWithValidatedRedirects(request, validateUrl) {
3614
3692
  303,
3615
3693
  307,
3616
3694
  308
3617
- ].includes(response.status) || location == null) return response;
3618
- await response.body?.cancel();
3695
+ ].includes(response.status) || location == null) try {
3696
+ return await consume(response);
3697
+ } catch (error) {
3698
+ logger.debug("Failed to consume the response from {url}; closing the session: {error}", {
3699
+ url: request.url,
3700
+ error
3701
+ });
3702
+ throw error;
3703
+ } finally {
3704
+ await session.close();
3705
+ }
3706
+ cancelResponseBody(response, request.url);
3707
+ await session.close();
3619
3708
  if (redirects >= 20) throw new TypeError("Too many redirects");
3620
3709
  const url = new URL(location, request.url);
3621
- await validateUrl(url.href);
3622
3710
  const headers = new Headers(request.headers);
3623
3711
  if (url.origin !== new URL(request.url).origin) {
3624
3712
  headers.delete("Authorization");
@@ -3646,6 +3734,16 @@ async function fetchWithValidatedRedirects(request, validateUrl) {
3646
3734
  });
3647
3735
  }
3648
3736
  }
3737
+ async function closeAfterError(session, logger) {
3738
+ try {
3739
+ await session.close();
3740
+ } catch (error) {
3741
+ logger.debug("Failed to close the session for {url}: {error}", {
3742
+ url: session.url.href,
3743
+ error
3744
+ });
3745
+ }
3746
+ }
3649
3747
  /**
3650
3748
  * An error that is thrown when an activity fails to send to a remote inbox.
3651
3749
  * It contains structured information about the failure, including the HTTP
@@ -5506,7 +5604,7 @@ var FederationImpl = class extends FederationBuilderImpl {
5506
5604
  onNotAcceptable ??= notAcceptable;
5507
5605
  onUnauthorized ??= unauthorized;
5508
5606
  const url = new URL(request.url);
5509
- const route = this.router.route(url.pathname);
5607
+ const route = this.router.route(url.pathname, { allowUnencodedColon: true });
5510
5608
  if (route == null) {
5511
5609
  metricState.endpoint = "not_found";
5512
5610
  return await onNotFound(request);
@@ -6497,44 +6595,84 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
6497
6595
  }
6498
6596
  }), values);
6499
6597
  }
6500
- #signedKey = void 0;
6598
+ #signedKey = null;
6599
+ #signedKeyOwner = null;
6600
+ #signatureChecks = /* @__PURE__ */ new WeakMap();
6601
+ #getSignatureCheck(documentLoader, contextLoader) {
6602
+ let checks = this.#signatureChecks.get(documentLoader);
6603
+ if (checks == null) {
6604
+ checks = /* @__PURE__ */ new WeakMap();
6605
+ this.#signatureChecks.set(documentLoader, checks);
6606
+ }
6607
+ let check = checks.get(contextLoader);
6608
+ if (check == null) {
6609
+ check = {};
6610
+ checks.set(contextLoader, check);
6611
+ }
6612
+ return check;
6613
+ }
6501
6614
  async getSignedKey(options = {}) {
6502
6615
  if (this.#signedKey != null) return this.#signedKey;
6503
- return this.#signedKey = await require_http.verifyRequest(this.request, {
6616
+ const documentLoader = options.documentLoader ?? this.documentLoader;
6617
+ const contextLoader = options.contextLoader ?? this.contextLoader;
6618
+ const check = this.#getSignatureCheck(documentLoader, contextLoader);
6619
+ if (check.key != null) return check.key;
6620
+ const promise = require_http.verifyRequest(this.request, {
6504
6621
  ...this,
6505
- contextLoader: options.contextLoader ?? this.contextLoader,
6506
- documentLoader: options.documentLoader ?? this.documentLoader,
6622
+ contextLoader,
6623
+ documentLoader,
6507
6624
  timeWindow: this.federation.signatureTimeWindow,
6508
6625
  meterProvider: this.meterProvider,
6509
6626
  tracerProvider: options.tracerProvider ?? this.tracerProvider
6627
+ }).then((key) => {
6628
+ if (key != null) this.#signedKey ??= key;
6629
+ return key;
6630
+ }).catch((error) => {
6631
+ if (check.key === promise) check.key = void 0;
6632
+ throw error;
6510
6633
  });
6634
+ check.key = promise;
6635
+ return await promise;
6511
6636
  }
6512
- #signedKeyOwner = void 0;
6513
6637
  async getSignedKeyOwner(options = {}) {
6514
6638
  if (this.#signedKeyOwner != null) return this.#signedKeyOwner;
6515
- const key = await this.getSignedKey(options);
6516
- if (key == null) return this.#signedKeyOwner = null;
6517
- try {
6518
- return this.#signedKeyOwner = await require_proof.getKeyOwner(key, {
6519
- contextLoader: options.contextLoader ?? this.contextLoader,
6520
- documentLoader: options.documentLoader ?? this.documentLoader,
6521
- tracerProvider: options.tracerProvider ?? this.tracerProvider
6522
- });
6523
- } catch (error) {
6524
- if (error instanceof _fedify_vocab_runtime.FetchError) {
6525
- (0, _logtape_logtape.getLogger)([
6526
- "fedify",
6527
- "federation",
6528
- "actor"
6529
- ]).warn("Failed to fetch the key owner {keyOwner} of {keyId} while verifying the request signature; treating the request as unauthenticated: {error}", {
6530
- keyId: key.id?.href,
6531
- keyOwner: error.url.href,
6532
- error
6533
- });
6534
- return this.#signedKeyOwner = null;
6639
+ const normalizedOptions = {
6640
+ documentLoader: options.documentLoader ?? this.documentLoader,
6641
+ contextLoader: options.contextLoader ?? this.contextLoader,
6642
+ tracerProvider: options.tracerProvider ?? this.tracerProvider
6643
+ };
6644
+ const check = this.#getSignatureCheck(normalizedOptions.documentLoader, normalizedOptions.contextLoader);
6645
+ if (check.owner != null) return check.owner;
6646
+ const promise = this.getSignedKey(normalizedOptions).then(async (key) => {
6647
+ if (key == null) {
6648
+ if (check.owner === promise) check.owner = void 0;
6649
+ return null;
6535
6650
  }
6651
+ try {
6652
+ const owner = await require_proof.getKeyOwner(key, normalizedOptions);
6653
+ if (owner != null) this.#signedKeyOwner ??= owner;
6654
+ return owner;
6655
+ } catch (error) {
6656
+ if (error instanceof _fedify_vocab_runtime.FetchError) {
6657
+ (0, _logtape_logtape.getLogger)([
6658
+ "fedify",
6659
+ "federation",
6660
+ "actor"
6661
+ ]).warn("Failed to fetch the key owner {keyOwner} of {keyId} while verifying the request signature; treating the request as unauthenticated: {error}", {
6662
+ keyId: key.id?.href,
6663
+ keyOwner: error.url.href,
6664
+ error
6665
+ });
6666
+ return null;
6667
+ }
6668
+ throw error;
6669
+ }
6670
+ }).catch((error) => {
6671
+ if (check.owner === promise) check.owner = void 0;
6536
6672
  throw error;
6537
- }
6673
+ });
6674
+ check.owner = promise;
6675
+ return await promise;
6538
6676
  }
6539
6677
  };
6540
6678
  function forwardActivity(ctx, loggerCategory, forwarder, recipients, options) {
@@ -1,27 +1,27 @@
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-y3L4Oa94.mjs";
5
- import { a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, n as getDurationMs, o as isAbortError, r as getFederationMetrics, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "./metrics-DBSl8wEr.mjs";
4
+ import { n as version, t as name } from "./deno-BCuL3mid.mjs";
5
+ import { a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, n as getDurationMs, o as isAbortError, r as getFederationMetrics, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "./metrics-SYIZHFf_.mjs";
6
6
  import { t as formatAcceptSignature } from "./accept-Dd__NiUL.mjs";
7
- import { o as importJwk, s as validateCryptoKey, t as exportJwk } from "./key-APURZ0ky.mjs";
8
- import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-BCSf1KIY.mjs";
9
- import { t as getAuthenticatedDocumentLoader } from "./docloader-yuyzZC9a.mjs";
10
- import { n as kvCache } from "./kv-cache-C69dzgQi.mjs";
7
+ import { o as importJwk, s as validateCryptoKey, t as exportJwk } from "./key-D8IMQ72M.mjs";
8
+ import { d as verifyRequestDetailed, s as parseRfc9421SignatureInput, u as verifyRequest } from "./http-CSDcThvz.mjs";
9
+ import { n as getAuthenticatedDocumentLoader } from "./docloader-D2dr1_6B.mjs";
10
+ import { n as kvCache } from "./kv-cache-Blr9nDDp.mjs";
11
11
  import { t as KvKeyCache } from "./keycache-BimEQucx.mjs";
12
- import { _ as wrapContextLoaderForJsonLd, a as compactJsonLd, c as getNormalizationContextLoader, d as isClearlyMalformedContextReference, f as isInvalidUrlTypeError, l as hasSignature, m as verifyCompactJsonLd, p as signJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as hasSignatureLike } from "./ld-BoLqbDaL.mjs";
13
- import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-DjKvngeb.mjs";
12
+ import { _ as wrapContextLoaderForJsonLd, a as compactJsonLd, c as getNormalizationContextLoader, d as isClearlyMalformedContextReference, f as isInvalidUrlTypeError, l as hasSignature, m as verifyCompactJsonLd, p as signJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as hasSignatureLike } from "./ld-CVxxE1Pp.mjs";
13
+ import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-BluLVPf0.mjs";
14
14
  import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-D4iTv7uM.mjs";
15
- import { i as verifyObject, n as hasProofLike, r as signObject } from "./proof-D2oFOdNA.mjs";
16
- import { d as readBoundedText, l as BodyTooLargeError, t as getNodeInfo, u as MAX_BODY_SIZE } from "./client-1He2YuBF.mjs";
15
+ import { i as verifyObject, n as hasProofLike, r as signObject } from "./proof-Co6nQBVR.mjs";
16
+ import { d as MAX_BODY_SIZE, f as readBoundedText, t as getNodeInfo, u as BodyTooLargeError } from "./client-DpUNYKla.mjs";
17
17
  import { t as nodeInfoToJson } from "./types-J53Kw7so.mjs";
18
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-xDK5UnL-.mjs";
19
- import { n as FederationBuilderImpl, t as ACTOR_ALIAS_PREFIX } from "./builder-C6yXExXl.mjs";
18
+ import { i as sendActivity, n as extractInboxes, t as SendActivityError } from "./send-D_DnqOFA.mjs";
19
+ import { n as FederationBuilderImpl, t as ACTOR_ALIAS_PREFIX } from "./builder-B6vSXYxn.mjs";
20
20
  import { t as CircuitBreaker } from "./circuit-breaker-D1fxthDI.mjs";
21
21
  import { t as buildCollectionSynchronizationHeader } from "./collection-Cc3DVAhE.mjs";
22
- import { n as routeActivity, t as actorsHaveSameOrigin } from "./inbox-NObX26c9.mjs";
22
+ import { n as routeActivity, t as actorsHaveSameOrigin } from "./inbox-BHxez4d5.mjs";
23
23
  import { t as acceptsJsonLd } from "./negotiation-DDstyBvc.mjs";
24
- import { t as hasMalformedKnownTemporalLiteral } from "./temporal-C6icc5n9.mjs";
24
+ import { t as hasMalformedKnownTemporalLiteral } from "./temporal-B8FUNkXB.mjs";
25
25
  import { t as createExponentialBackoffPolicy } from "./retry-CXg_MBI-.mjs";
26
26
  import { getLogger, withContext } from "@logtape/logtape";
27
27
  import { RouterError } from "@fedify/uri-template";
@@ -3556,7 +3556,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3556
3556
  onNotAcceptable ??= notAcceptable;
3557
3557
  onUnauthorized ??= unauthorized;
3558
3558
  const url = new URL(request.url);
3559
- const route = this.router.route(url.pathname);
3559
+ const route = this.router.route(url.pathname, { allowUnencodedColon: true });
3560
3560
  if (route == null) {
3561
3561
  metricState.endpoint = "not_found";
3562
3562
  return await onNotFound(request);
@@ -4547,44 +4547,84 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
4547
4547
  }
4548
4548
  }), values);
4549
4549
  }
4550
- #signedKey = void 0;
4550
+ #signedKey = null;
4551
+ #signedKeyOwner = null;
4552
+ #signatureChecks = /* @__PURE__ */ new WeakMap();
4553
+ #getSignatureCheck(documentLoader, contextLoader) {
4554
+ let checks = this.#signatureChecks.get(documentLoader);
4555
+ if (checks == null) {
4556
+ checks = /* @__PURE__ */ new WeakMap();
4557
+ this.#signatureChecks.set(documentLoader, checks);
4558
+ }
4559
+ let check = checks.get(contextLoader);
4560
+ if (check == null) {
4561
+ check = {};
4562
+ checks.set(contextLoader, check);
4563
+ }
4564
+ return check;
4565
+ }
4551
4566
  async getSignedKey(options = {}) {
4552
4567
  if (this.#signedKey != null) return this.#signedKey;
4553
- return this.#signedKey = await verifyRequest(this.request, {
4568
+ const documentLoader = options.documentLoader ?? this.documentLoader;
4569
+ const contextLoader = options.contextLoader ?? this.contextLoader;
4570
+ const check = this.#getSignatureCheck(documentLoader, contextLoader);
4571
+ if (check.key != null) return check.key;
4572
+ const promise = verifyRequest(this.request, {
4554
4573
  ...this,
4555
- contextLoader: options.contextLoader ?? this.contextLoader,
4556
- documentLoader: options.documentLoader ?? this.documentLoader,
4574
+ contextLoader,
4575
+ documentLoader,
4557
4576
  timeWindow: this.federation.signatureTimeWindow,
4558
4577
  meterProvider: this.meterProvider,
4559
4578
  tracerProvider: options.tracerProvider ?? this.tracerProvider
4579
+ }).then((key) => {
4580
+ if (key != null) this.#signedKey ??= key;
4581
+ return key;
4582
+ }).catch((error) => {
4583
+ if (check.key === promise) check.key = void 0;
4584
+ throw error;
4560
4585
  });
4586
+ check.key = promise;
4587
+ return await promise;
4561
4588
  }
4562
- #signedKeyOwner = void 0;
4563
4589
  async getSignedKeyOwner(options = {}) {
4564
4590
  if (this.#signedKeyOwner != null) return this.#signedKeyOwner;
4565
- const key = await this.getSignedKey(options);
4566
- if (key == null) return this.#signedKeyOwner = null;
4567
- try {
4568
- return this.#signedKeyOwner = await getKeyOwner(key, {
4569
- contextLoader: options.contextLoader ?? this.contextLoader,
4570
- documentLoader: options.documentLoader ?? this.documentLoader,
4571
- tracerProvider: options.tracerProvider ?? this.tracerProvider
4572
- });
4573
- } catch (error) {
4574
- if (error instanceof FetchError) {
4575
- getLogger([
4576
- "fedify",
4577
- "federation",
4578
- "actor"
4579
- ]).warn("Failed to fetch the key owner {keyOwner} of {keyId} while verifying the request signature; treating the request as unauthenticated: {error}", {
4580
- keyId: key.id?.href,
4581
- keyOwner: error.url.href,
4582
- error
4583
- });
4584
- return this.#signedKeyOwner = null;
4591
+ const normalizedOptions = {
4592
+ documentLoader: options.documentLoader ?? this.documentLoader,
4593
+ contextLoader: options.contextLoader ?? this.contextLoader,
4594
+ tracerProvider: options.tracerProvider ?? this.tracerProvider
4595
+ };
4596
+ const check = this.#getSignatureCheck(normalizedOptions.documentLoader, normalizedOptions.contextLoader);
4597
+ if (check.owner != null) return check.owner;
4598
+ const promise = this.getSignedKey(normalizedOptions).then(async (key) => {
4599
+ if (key == null) {
4600
+ if (check.owner === promise) check.owner = void 0;
4601
+ return null;
4585
4602
  }
4603
+ try {
4604
+ const owner = await getKeyOwner(key, normalizedOptions);
4605
+ if (owner != null) this.#signedKeyOwner ??= owner;
4606
+ return owner;
4607
+ } catch (error) {
4608
+ if (error instanceof FetchError) {
4609
+ getLogger([
4610
+ "fedify",
4611
+ "federation",
4612
+ "actor"
4613
+ ]).warn("Failed to fetch the key owner {keyOwner} of {keyId} while verifying the request signature; treating the request as unauthenticated: {error}", {
4614
+ keyId: key.id?.href,
4615
+ keyOwner: error.url.href,
4616
+ error
4617
+ });
4618
+ return null;
4619
+ }
4620
+ throw error;
4621
+ }
4622
+ }).catch((error) => {
4623
+ if (check.owner === promise) check.owner = void 0;
4586
4624
  throw error;
4587
- }
4625
+ });
4626
+ check.owner = promise;
4627
+ return await promise;
4588
4628
  }
4589
4629
  };
4590
4630
  function forwardActivity(ctx, loggerCategory, forwarder, recipients, options) {
@@ -1,5 +1,5 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as FederationImpl } from "./middleware-CZ8tI1qL.mjs";
4
+ import { n as FederationImpl } from "./middleware-CfskJGfV.mjs";
5
5
  export { FederationImpl };