@fedify/fedify 2.2.19 → 2.2.20

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 (84) hide show
  1. package/dist/{builder-UmydCxtK.mjs → builder-BolQ56ab.mjs} +2 -2
  2. package/dist/{client-CAM_bQXx.d.cts → client-BIBzqEIp.d.cts} +0 -1
  3. package/dist/{client-9F_u23dc.mjs → client-DhxaVgOv.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-tJbL_Hkw.d.cts → context-CFQGaq5x.d.cts} +28 -2
  9. package/dist/{context-OkwEpc3-.d.ts → context-DdbLVV_k.d.ts} +28 -2
  10. package/dist/{deno-CD-8UlUi.mjs → deno-BUj4roJc.mjs} +1 -1
  11. package/dist/docloader-CbtF_0cD.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/middleware.test.mjs +389 -28
  17. package/dist/federation/mod.cjs +1 -1
  18. package/dist/federation/mod.d.cts +2 -2
  19. package/dist/federation/mod.d.ts +2 -2
  20. package/dist/federation/mod.js +1 -1
  21. package/dist/federation/send.test.mjs +445 -70
  22. package/dist/federation/temporal.test.mjs +1 -1
  23. package/dist/federation/webfinger.test.mjs +1 -1
  24. package/dist/{http-Dl00HXr4.d.ts → http-9IFUxdkp.d.cts} +1 -2
  25. package/dist/{http-DJK2udgX.js → http-C5g_VrA_.js} +172 -103
  26. package/dist/{http-BBBUbwKh.mjs → http-CQMVisM0.mjs} +195 -97
  27. package/dist/{http-DrUG4fPS.cjs → http-D8I7LBfx.cjs} +173 -104
  28. package/dist/{http-DqZhyEso.d.cts → http-SbfezERn.d.ts} +1 -2
  29. package/dist/{inbox-Dca_0fwc.mjs → inbox-CD8IDILN.mjs} +1 -1
  30. package/dist/{key-DolXUaLH.mjs → key-C3o3DGap.mjs} +1 -1
  31. package/dist/{kv-cache-DDm80t1K.js → kv-cache-CSKXXleR.js} +60 -18
  32. package/dist/{kv-cache-Bk3V1Tiu.cjs → kv-cache-DC9LcfoC.cjs} +59 -17
  33. package/dist/{ld-rTdIfZ63.mjs → ld-ijSr-0AB.mjs} +2 -2
  34. package/dist/{middleware-CpXoRHs3.mjs → middleware-CYAaUTi5.mjs} +78 -38
  35. package/dist/{middleware-BWOwEGpy.cjs → middleware-DNueR0GD.cjs} +184 -59
  36. package/dist/{middleware-B4-Rzdi3.cjs → middleware-DmcY2TWS.cjs} +1 -1
  37. package/dist/{middleware-BimXKBcP.js → middleware-sjhz30sm.js} +184 -59
  38. package/dist/{middleware-DYK_res8.mjs → middleware-yqqfI7oJ.mjs} +1 -1
  39. package/dist/{mod-B6LmugJ4.d.ts → mod-BRnwh2oy.d.ts} +2 -8
  40. package/dist/{mod-D2JyWtW3.d.cts → mod-BYWp77-z.d.cts} +1 -1
  41. package/dist/{mod-DQGjRJUG.d.ts → mod-BuRRdsfD.d.ts} +1 -1
  42. package/dist/{mod-ej_rLx8h.d.ts → mod-CM5_J_cx.d.ts} +1 -1
  43. package/dist/{mod-a6kmB8xD.d.cts → mod-CVSKw-5H.d.cts} +1 -1
  44. package/dist/{mod-V2vDnYNu.d.cts → mod-Co7LEK9X.d.cts} +2 -8
  45. package/dist/mod.cjs +5 -5
  46. package/dist/mod.d.cts +6 -6
  47. package/dist/mod.d.ts +6 -6
  48. package/dist/mod.js +5 -5
  49. package/dist/nodeinfo/client.test.mjs +337 -19
  50. package/dist/nodeinfo/handler.test.mjs +1 -1
  51. package/dist/nodeinfo/mod.cjs +1 -1
  52. package/dist/nodeinfo/mod.d.cts +1 -1
  53. package/dist/nodeinfo/mod.d.ts +1 -1
  54. package/dist/nodeinfo/mod.js +1 -1
  55. package/dist/otel/exporter.test.mjs +292 -30
  56. package/dist/otel/mod.cjs +36 -30
  57. package/dist/otel/mod.d.cts +13 -0
  58. package/dist/otel/mod.d.ts +13 -0
  59. package/dist/otel/mod.js +36 -30
  60. package/dist/{owner-CVfzyM5V.mjs → owner-B8kXzD0q.mjs} +2 -2
  61. package/dist/{proof-gOGM-sJS.mjs → proof-CBQOjOgA.mjs} +2 -2
  62. package/dist/{proof-CaQ4KG6y.js → proof-DKX6nASL.js} +1 -1
  63. package/dist/{proof-DhaTTn-P.cjs → proof-qiuGJCD7.cjs} +1 -1
  64. package/dist/{send-D35Va72B.mjs → send-DRpisX_f.mjs} +117 -32
  65. package/dist/sig/http.test.mjs +528 -2
  66. package/dist/sig/key.test.mjs +2 -2
  67. package/dist/sig/ld.test.mjs +2 -2
  68. package/dist/sig/mod.cjs +2 -2
  69. package/dist/sig/mod.d.cts +2 -2
  70. package/dist/sig/mod.d.ts +2 -2
  71. package/dist/sig/mod.js +2 -2
  72. package/dist/sig/owner.test.mjs +1 -1
  73. package/dist/sig/proof.test.mjs +1 -1
  74. package/dist/{temporal-CA4Y9hBJ.mjs → temporal-BQGEINad.mjs} +1 -1
  75. package/dist/testing/mod.d.mts +26 -1
  76. package/dist/{types-miAYfiGO.js → types-B1_pRFE_.js} +112 -63
  77. package/dist/{types-DRPjmHVV.cjs → types-DOvyymM4.cjs} +111 -62
  78. package/dist/utils/docloader.test.mjs +318 -34
  79. package/dist/utils/mod.cjs +1 -1
  80. package/dist/utils/mod.d.cts +1 -1
  81. package/dist/utils/mod.d.ts +1 -1
  82. package/dist/utils/mod.js +1 -1
  83. package/package.json +6 -6
  84. package/dist/docloader-D1ghurDJ.mjs +0 -83
@@ -2,26 +2,26 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { n as RouterError } from "./router-CrMLXoOr.mjs";
5
- import { n as version, t as name } from "./deno-CD-8UlUi.mjs";
5
+ import { n as version, t as name } from "./deno-BUj4roJc.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-DolXUaLH.mjs";
8
- import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-BBBUbwKh.mjs";
9
- import { t as getAuthenticatedDocumentLoader } from "./docloader-D1ghurDJ.mjs";
7
+ import { o as importJwk, s as validateCryptoKey, t as exportJwk } from "./key-C3o3DGap.mjs";
8
+ import { d as verifyRequestDetailed, s as parseRfc9421SignatureInput, u as verifyRequest } from "./http-CQMVisM0.mjs";
9
+ import { n as getAuthenticatedDocumentLoader } from "./docloader-CbtF_0cD.mjs";
10
10
  import { n as kvCache } from "./kv-cache-FsT1ljhs.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-rTdIfZ63.mjs";
13
- import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-CVfzyM5V.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-ijSr-0AB.mjs";
13
+ import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-B8kXzD0q.mjs";
14
14
  import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-amccRMrV.mjs";
15
- import { i as verifyObject, n as hasProofLike, r as signObject } from "./proof-gOGM-sJS.mjs";
16
- import { d as readBoundedText, l as BodyTooLargeError, t as getNodeInfo, u as MAX_BODY_SIZE } from "./client-9F_u23dc.mjs";
15
+ import { i as verifyObject, n as hasProofLike, r as signObject } from "./proof-CBQOjOgA.mjs";
16
+ import { d as MAX_BODY_SIZE, f as readBoundedText, t as getNodeInfo, u as BodyTooLargeError } from "./client-DhxaVgOv.mjs";
17
17
  import { t as nodeInfoToJson } from "./types-J53Kw7so.mjs";
18
- import { t as FederationBuilderImpl } from "./builder-UmydCxtK.mjs";
18
+ import { t as FederationBuilderImpl } from "./builder-BolQ56ab.mjs";
19
19
  import { t as buildCollectionSynchronizationHeader } from "./collection-D-HqUuA2.mjs";
20
- import { n as routeActivity, t as actorsHaveSameOrigin } from "./inbox-Dca_0fwc.mjs";
20
+ import { n as routeActivity, t as actorsHaveSameOrigin } from "./inbox-CD8IDILN.mjs";
21
21
  import { t as acceptsJsonLd } from "./negotiation-SQvQgUqe.mjs";
22
- import { t as hasMalformedKnownTemporalLiteral } from "./temporal-CA4Y9hBJ.mjs";
22
+ import { t as hasMalformedKnownTemporalLiteral } from "./temporal-BQGEINad.mjs";
23
23
  import { t as createExponentialBackoffPolicy } from "./retry-v_sGLH1d.mjs";
24
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-D35Va72B.mjs";
24
+ import { i as sendActivity, n as extractInboxes, t as SendActivityError } from "./send-DRpisX_f.mjs";
25
25
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
26
26
  import { lookupWebFinger } from "@fedify/webfinger";
27
27
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
@@ -3539,43 +3539,83 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
3539
3539
  }
3540
3540
  }), values);
3541
3541
  }
3542
- #signedKey = void 0;
3542
+ #signedKey = null;
3543
+ #signedKeyOwner = null;
3544
+ #signatureChecks = /* @__PURE__ */ new WeakMap();
3545
+ #getSignatureCheck(documentLoader, contextLoader) {
3546
+ let checks = this.#signatureChecks.get(documentLoader);
3547
+ if (checks == null) {
3548
+ checks = /* @__PURE__ */ new WeakMap();
3549
+ this.#signatureChecks.set(documentLoader, checks);
3550
+ }
3551
+ let check = checks.get(contextLoader);
3552
+ if (check == null) {
3553
+ check = {};
3554
+ checks.set(contextLoader, check);
3555
+ }
3556
+ return check;
3557
+ }
3543
3558
  async getSignedKey(options = {}) {
3544
3559
  if (this.#signedKey != null) return this.#signedKey;
3545
- return this.#signedKey = await verifyRequest(this.request, {
3560
+ const documentLoader = options.documentLoader ?? this.documentLoader;
3561
+ const contextLoader = options.contextLoader ?? this.contextLoader;
3562
+ const check = this.#getSignatureCheck(documentLoader, contextLoader);
3563
+ if (check.key != null) return check.key;
3564
+ const promise = verifyRequest(this.request, {
3546
3565
  ...this,
3547
- contextLoader: options.contextLoader ?? this.contextLoader,
3548
- documentLoader: options.documentLoader ?? this.documentLoader,
3566
+ contextLoader,
3567
+ documentLoader,
3549
3568
  timeWindow: this.federation.signatureTimeWindow,
3550
3569
  tracerProvider: options.tracerProvider ?? this.tracerProvider
3570
+ }).then((key) => {
3571
+ if (key != null) this.#signedKey ??= key;
3572
+ return key;
3573
+ }).catch((error) => {
3574
+ if (check.key === promise) check.key = void 0;
3575
+ throw error;
3551
3576
  });
3577
+ check.key = promise;
3578
+ return await promise;
3552
3579
  }
3553
- #signedKeyOwner = void 0;
3554
3580
  async getSignedKeyOwner(options = {}) {
3555
3581
  if (this.#signedKeyOwner != null) return this.#signedKeyOwner;
3556
- const key = await this.getSignedKey(options);
3557
- if (key == null) return this.#signedKeyOwner = null;
3558
- try {
3559
- return this.#signedKeyOwner = await getKeyOwner(key, {
3560
- contextLoader: options.contextLoader ?? this.contextLoader,
3561
- documentLoader: options.documentLoader ?? this.documentLoader,
3562
- tracerProvider: options.tracerProvider ?? this.tracerProvider
3563
- });
3564
- } catch (error) {
3565
- if (error instanceof FetchError) {
3566
- getLogger([
3567
- "fedify",
3568
- "federation",
3569
- "actor"
3570
- ]).warn("Failed to fetch the key owner {keyOwner} of {keyId} while verifying the request signature; treating the request as unauthenticated: {error}", {
3571
- keyId: key.id?.href,
3572
- keyOwner: error.url.href,
3573
- error
3574
- });
3575
- return this.#signedKeyOwner = null;
3582
+ const normalizedOptions = {
3583
+ documentLoader: options.documentLoader ?? this.documentLoader,
3584
+ contextLoader: options.contextLoader ?? this.contextLoader,
3585
+ tracerProvider: options.tracerProvider ?? this.tracerProvider
3586
+ };
3587
+ const check = this.#getSignatureCheck(normalizedOptions.documentLoader, normalizedOptions.contextLoader);
3588
+ if (check.owner != null) return check.owner;
3589
+ const promise = this.getSignedKey(normalizedOptions).then(async (key) => {
3590
+ if (key == null) {
3591
+ if (check.owner === promise) check.owner = void 0;
3592
+ return null;
3576
3593
  }
3594
+ try {
3595
+ const owner = await getKeyOwner(key, normalizedOptions);
3596
+ if (owner != null) this.#signedKeyOwner ??= owner;
3597
+ return owner;
3598
+ } catch (error) {
3599
+ if (error instanceof FetchError) {
3600
+ getLogger([
3601
+ "fedify",
3602
+ "federation",
3603
+ "actor"
3604
+ ]).warn("Failed to fetch the key owner {keyOwner} of {keyId} while verifying the request signature; treating the request as unauthenticated: {error}", {
3605
+ keyId: key.id?.href,
3606
+ keyOwner: error.url.href,
3607
+ error
3608
+ });
3609
+ return null;
3610
+ }
3611
+ throw error;
3612
+ }
3613
+ }).catch((error) => {
3614
+ if (check.owner === promise) check.owner = void 0;
3577
3615
  throw error;
3578
- }
3616
+ });
3617
+ check.owner = promise;
3618
+ return await promise;
3579
3619
  }
3580
3620
  };
3581
3621
  function forwardActivity(ctx, loggerCategory, forwarder, recipients, options) {
@@ -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-DrUG4fPS.cjs");
6
- const require_types = require("./types-DRPjmHVV.cjs");
7
- const require_proof = require("./proof-DhaTTn-P.cjs");
8
- const require_kv_cache = require("./kv-cache-Bk3V1Tiu.cjs");
5
+ const require_http = require("./http-D8I7LBfx.cjs");
6
+ const require_types = require("./types-DOvyymM4.cjs");
7
+ const require_proof = require("./proof-qiuGJCD7.cjs");
8
+ const require_kv_cache = require("./kv-cache-DC9LcfoC.cjs");
9
9
  let _logtape_logtape = require("@logtape/logtape");
10
10
  let _fedify_vocab = require("@fedify/vocab");
11
11
  let _opentelemetry_api = require("@opentelemetry/api");
@@ -18,6 +18,7 @@ let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conve
18
18
  let _fedify_vocab_runtime_jsonld = require("@fedify/vocab-runtime/jsonld");
19
19
  _fedify_vocab_runtime_jsonld = require_chunk.__toESM(_fedify_vocab_runtime_jsonld);
20
20
  let _fedify_webfinger = require("@fedify/webfinger");
21
+ let _fedify_vocab_runtime_internal = require("@fedify/vocab-runtime/internal");
21
22
  let node_url = require("node:url");
22
23
  //#region src/federation/activity-listener.ts
23
24
  var ActivityListenerSet = class {
@@ -213,7 +214,7 @@ var FederationBuilderImpl = class {
213
214
  this.collectionTypeIds = {};
214
215
  }
215
216
  async build(options) {
216
- const { FederationImpl } = await Promise.resolve().then(() => require("./middleware-B4-Rzdi3.cjs"));
217
+ const { FederationImpl } = await Promise.resolve().then(() => require("./middleware-DmcY2TWS.cjs"));
217
218
  const f = new FederationImpl(options);
218
219
  const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
219
220
  f.router = this.router.clone();
@@ -2738,9 +2739,10 @@ function extractInboxes({ recipients, preferSharedInbox, excludeBaseUris }) {
2738
2739
  *
2739
2740
  * @param parameters The parameters for sending the activity.
2740
2741
  * See also {@link SendActivityParameters}.
2742
+ * @param deps The dependencies, for tests.
2741
2743
  * @throws {Error} If the activity fails to send.
2742
2744
  */
2743
- function sendActivity(options) {
2745
+ function sendActivity(options, deps = {}) {
2744
2746
  const tracerProvider = options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider();
2745
2747
  return tracerProvider.getTracer(require_http.name, require_http.version).startActiveSpan("activitypub.send_activity", {
2746
2748
  kind: _opentelemetry_api.SpanKind.CLIENT,
@@ -2752,7 +2754,7 @@ function sendActivity(options) {
2752
2754
  await sendActivityInternal({
2753
2755
  ...options,
2754
2756
  tracerProvider
2755
- }, span);
2757
+ }, span, deps);
2756
2758
  } catch (e) {
2757
2759
  span.setStatus({
2758
2760
  code: _opentelemetry_api.SpanStatusCode.ERROR,
@@ -2786,21 +2788,25 @@ async function readLimitedResponseBody(response, maxBytes) {
2786
2788
  totalBytes += value.length;
2787
2789
  }
2788
2790
  } finally {
2791
+ if (truncated) reader.cancel().catch(() => {});
2789
2792
  reader.releaseLock();
2790
2793
  }
2791
2794
  let result = chunks.join("");
2792
2795
  if (truncated) result += "… (truncated)";
2793
2796
  return result;
2794
2797
  }
2795
- async function sendActivityInternal({ activity, activityId, keys, inbox, allowPrivateAddress, headers, specDeterminer, tracerProvider }, span) {
2798
+ async function sendActivityInternal({ activity, activityId, keys, inbox, allowPrivateAddress, headers, specDeterminer, tracerProvider }, span, deps) {
2796
2799
  const logger = (0, _logtape_logtape.getLogger)([
2797
2800
  "fedify",
2798
2801
  "federation",
2799
2802
  "outbox"
2800
2803
  ]);
2801
- async function validateUrl(url) {
2802
- if (!allowPrivateAddress) try {
2803
- await (0, _fedify_vocab_runtime.validatePublicUrl)(url);
2804
+ const openUnvalidatedHop = deps.openHop ?? (allowPrivateAddress ? _fedify_vocab_runtime_internal.openDirectFetch : (url) => (0, _fedify_vocab_runtime_internal.openPublicFetch)(url));
2805
+ let opened = false;
2806
+ async function openHop(url) {
2807
+ let session;
2808
+ try {
2809
+ session = await openUnvalidatedHop(url);
2804
2810
  } catch (error) {
2805
2811
  if (error instanceof _fedify_vocab_runtime.UrlError && error.reason === "dns") {
2806
2812
  logger.error("DNS resolution failed for URL: {url}", {
@@ -2817,8 +2823,9 @@ async function sendActivityInternal({ activity, activityId, keys, inbox, allowPr
2817
2823
  });
2818
2824
  throw error;
2819
2825
  }
2826
+ opened = true;
2827
+ return session;
2820
2828
  }
2821
- await validateUrl(inbox.href);
2822
2829
  headers = new Headers(headers);
2823
2830
  headers.set("Content-Type", "application/activity+json");
2824
2831
  const request = new Request(inbox, {
@@ -2839,36 +2846,30 @@ async function sendActivityInternal({ activity, activityId, keys, inbox, allowPr
2839
2846
  privateKey: pair.privateKey
2840
2847
  }))
2841
2848
  });
2842
- let response;
2849
+ let outcome;
2843
2850
  try {
2844
- response = rsaKey == null ? await fetchWithValidatedRedirects(request, validateUrl) : await require_http.doubleKnock(request, rsaKey, {
2851
+ outcome = rsaKey == null ? await fetchWithValidatedRedirects(request, openHop, (response) => consumeSendActivityResponse(response, inbox)) : await require_http.doubleKnockScoped(request, rsaKey, {
2845
2852
  tracerProvider,
2846
- specDeterminer,
2847
- validateRedirect: validateUrl
2848
- });
2853
+ specDeterminer
2854
+ }, openHop, (response) => consumeSendActivityResponse(response, inbox));
2849
2855
  } catch (error) {
2850
- logger.error("Failed to send activity {activityId} to {inbox}:\n{error}", {
2856
+ if (opened) logger.error("Failed to send activity {activityId} to {inbox}:\n{error}", {
2851
2857
  activityId,
2852
2858
  inbox: inbox.href,
2853
2859
  error
2854
2860
  });
2855
2861
  throw error;
2856
2862
  }
2857
- if (!response.ok) {
2858
- let error;
2859
- try {
2860
- error = await readLimitedResponseBody(response, MAX_ERROR_RESPONSE_BODY_BYTES);
2861
- } catch (_) {
2862
- error = "";
2863
- }
2863
+ if (!outcome.ok) {
2864
+ const { status, statusText, error } = outcome;
2864
2865
  logger.error("Failed to send activity {activityId} to {inbox} ({status} {statusText}):\n{error}", {
2865
2866
  activityId,
2866
2867
  inbox: inbox.href,
2867
- status: response.status,
2868
- statusText: response.statusText,
2868
+ status,
2869
+ statusText,
2869
2870
  error
2870
2871
  });
2871
- throw new SendActivityError(inbox, response.status, `Failed to send activity ${activityId} to ${inbox.href} (${response.status} ${response.statusText}):\n${error}`, error);
2872
+ throw new SendActivityError(inbox, status, `Failed to send activity ${activityId} to ${inbox.href} (${status} ${statusText}):\n${error}`, error);
2872
2873
  }
2873
2874
  span.addEvent("activitypub.activity.sent", {
2874
2875
  "activitypub.activity.json": JSON.stringify(activity),
@@ -2876,10 +2877,74 @@ async function sendActivityInternal({ activity, activityId, keys, inbox, allowPr
2876
2877
  "activitypub.activity.id": activityId ?? ""
2877
2878
  });
2878
2879
  }
2879
- async function fetchWithValidatedRedirects(request, validateUrl) {
2880
+ /**
2881
+ * Starts cancelling the body of a response without waiting for it to settle.
2882
+ * The cancellation of a branch of a tee (e.g., a body cloned by a fetch mock)
2883
+ * settles only after the other branch is cancelled too, per the Streams
2884
+ * standard, so awaiting it could block forever. Failures are only logged.
2885
+ */
2886
+ function cancelResponseBody(response, url) {
2887
+ response.body?.cancel().catch((error) => {
2888
+ (0, _logtape_logtape.getLogger)([
2889
+ "fedify",
2890
+ "federation",
2891
+ "outbox"
2892
+ ]).debug("Failed to cancel the response body from {url}: {error}", {
2893
+ url,
2894
+ error
2895
+ });
2896
+ });
2897
+ }
2898
+ /**
2899
+ * Reads what is needed from the final response of a delivery while its
2900
+ * session is open: the body of a successful response is cancelled, and at
2901
+ * most 1 KiB of an error response body is read.
2902
+ */
2903
+ async function consumeSendActivityResponse(response, inbox) {
2904
+ if (response.ok) {
2905
+ cancelResponseBody(response, response.url || inbox.href);
2906
+ return { ok: true };
2907
+ }
2908
+ let error;
2909
+ try {
2910
+ error = await readLimitedResponseBody(response, MAX_ERROR_RESPONSE_BODY_BYTES);
2911
+ } catch (_) {
2912
+ error = "";
2913
+ }
2914
+ return {
2915
+ ok: false,
2916
+ status: response.status,
2917
+ statusText: response.statusText,
2918
+ error
2919
+ };
2920
+ }
2921
+ /**
2922
+ * Sends a request without signing it, following redirects manually like the
2923
+ * Fetch standard does. Every hop is opened with `openHop()`, which validates
2924
+ * the destination, after the previous hop is closed. The final response is
2925
+ * passed to `consume()`, and its session is closed after that.
2926
+ * This is exported only for tests; it is not exported from any entry point.
2927
+ * @param request The request to send.
2928
+ * @param openHop The function to open a session for each hop.
2929
+ * @param consume The function to consume the final response.
2930
+ * @returns The value returned by `consume`.
2931
+ */
2932
+ async function fetchWithValidatedRedirects(request, openHop, consume) {
2933
+ const logger = (0, _logtape_logtape.getLogger)([
2934
+ "fedify",
2935
+ "federation",
2936
+ "outbox"
2937
+ ]);
2880
2938
  let body = await request.clone().text();
2881
2939
  for (let redirects = 0;; redirects++) {
2882
- const response = await fetch(request, { redirect: "manual" });
2940
+ const session = await openHop(request.url);
2941
+ let response;
2942
+ try {
2943
+ response = await session.fetch(request, { redirect: "manual" });
2944
+ } catch (error) {
2945
+ await closeAfterError(session, logger);
2946
+ throw error;
2947
+ }
2883
2948
  const location = response.headers.get("Location");
2884
2949
  if (![
2885
2950
  301,
@@ -2887,11 +2952,21 @@ async function fetchWithValidatedRedirects(request, validateUrl) {
2887
2952
  303,
2888
2953
  307,
2889
2954
  308
2890
- ].includes(response.status) || location == null) return response;
2891
- await response.body?.cancel();
2955
+ ].includes(response.status) || location == null) try {
2956
+ return await consume(response);
2957
+ } catch (error) {
2958
+ logger.debug("Failed to consume the response from {url}; closing the session: {error}", {
2959
+ url: request.url,
2960
+ error
2961
+ });
2962
+ throw error;
2963
+ } finally {
2964
+ await session.close();
2965
+ }
2966
+ cancelResponseBody(response, request.url);
2967
+ await session.close();
2892
2968
  if (redirects >= 20) throw new TypeError("Too many redirects");
2893
2969
  const url = new URL(location, request.url);
2894
- await validateUrl(url.href);
2895
2970
  const headers = new Headers(request.headers);
2896
2971
  if (url.origin !== new URL(request.url).origin) {
2897
2972
  headers.delete("Authorization");
@@ -2919,6 +2994,16 @@ async function fetchWithValidatedRedirects(request, validateUrl) {
2919
2994
  });
2920
2995
  }
2921
2996
  }
2997
+ async function closeAfterError(session, logger) {
2998
+ try {
2999
+ await session.close();
3000
+ } catch (error) {
3001
+ logger.debug("Failed to close the session for {url}: {error}", {
3002
+ url: session.url.href,
3003
+ error
3004
+ });
3005
+ }
3006
+ }
2922
3007
  /**
2923
3008
  * An error that is thrown when an activity fails to send to a remote inbox.
2924
3009
  * It contains structured information about the failure, including the HTTP
@@ -4907,43 +4992,83 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
4907
4992
  }
4908
4993
  }), values);
4909
4994
  }
4910
- #signedKey = void 0;
4995
+ #signedKey = null;
4996
+ #signedKeyOwner = null;
4997
+ #signatureChecks = /* @__PURE__ */ new WeakMap();
4998
+ #getSignatureCheck(documentLoader, contextLoader) {
4999
+ let checks = this.#signatureChecks.get(documentLoader);
5000
+ if (checks == null) {
5001
+ checks = /* @__PURE__ */ new WeakMap();
5002
+ this.#signatureChecks.set(documentLoader, checks);
5003
+ }
5004
+ let check = checks.get(contextLoader);
5005
+ if (check == null) {
5006
+ check = {};
5007
+ checks.set(contextLoader, check);
5008
+ }
5009
+ return check;
5010
+ }
4911
5011
  async getSignedKey(options = {}) {
4912
5012
  if (this.#signedKey != null) return this.#signedKey;
4913
- return this.#signedKey = await require_http.verifyRequest(this.request, {
5013
+ const documentLoader = options.documentLoader ?? this.documentLoader;
5014
+ const contextLoader = options.contextLoader ?? this.contextLoader;
5015
+ const check = this.#getSignatureCheck(documentLoader, contextLoader);
5016
+ if (check.key != null) return check.key;
5017
+ const promise = require_http.verifyRequest(this.request, {
4914
5018
  ...this,
4915
- contextLoader: options.contextLoader ?? this.contextLoader,
4916
- documentLoader: options.documentLoader ?? this.documentLoader,
5019
+ contextLoader,
5020
+ documentLoader,
4917
5021
  timeWindow: this.federation.signatureTimeWindow,
4918
5022
  tracerProvider: options.tracerProvider ?? this.tracerProvider
5023
+ }).then((key) => {
5024
+ if (key != null) this.#signedKey ??= key;
5025
+ return key;
5026
+ }).catch((error) => {
5027
+ if (check.key === promise) check.key = void 0;
5028
+ throw error;
4919
5029
  });
5030
+ check.key = promise;
5031
+ return await promise;
4920
5032
  }
4921
- #signedKeyOwner = void 0;
4922
5033
  async getSignedKeyOwner(options = {}) {
4923
5034
  if (this.#signedKeyOwner != null) return this.#signedKeyOwner;
4924
- const key = await this.getSignedKey(options);
4925
- if (key == null) return this.#signedKeyOwner = null;
4926
- try {
4927
- return this.#signedKeyOwner = await require_proof.getKeyOwner(key, {
4928
- contextLoader: options.contextLoader ?? this.contextLoader,
4929
- documentLoader: options.documentLoader ?? this.documentLoader,
4930
- tracerProvider: options.tracerProvider ?? this.tracerProvider
4931
- });
4932
- } catch (error) {
4933
- if (error instanceof _fedify_vocab_runtime.FetchError) {
4934
- (0, _logtape_logtape.getLogger)([
4935
- "fedify",
4936
- "federation",
4937
- "actor"
4938
- ]).warn("Failed to fetch the key owner {keyOwner} of {keyId} while verifying the request signature; treating the request as unauthenticated: {error}", {
4939
- keyId: key.id?.href,
4940
- keyOwner: error.url.href,
4941
- error
4942
- });
4943
- return this.#signedKeyOwner = null;
5035
+ const normalizedOptions = {
5036
+ documentLoader: options.documentLoader ?? this.documentLoader,
5037
+ contextLoader: options.contextLoader ?? this.contextLoader,
5038
+ tracerProvider: options.tracerProvider ?? this.tracerProvider
5039
+ };
5040
+ const check = this.#getSignatureCheck(normalizedOptions.documentLoader, normalizedOptions.contextLoader);
5041
+ if (check.owner != null) return check.owner;
5042
+ const promise = this.getSignedKey(normalizedOptions).then(async (key) => {
5043
+ if (key == null) {
5044
+ if (check.owner === promise) check.owner = void 0;
5045
+ return null;
4944
5046
  }
5047
+ try {
5048
+ const owner = await require_proof.getKeyOwner(key, normalizedOptions);
5049
+ if (owner != null) this.#signedKeyOwner ??= owner;
5050
+ return owner;
5051
+ } catch (error) {
5052
+ if (error instanceof _fedify_vocab_runtime.FetchError) {
5053
+ (0, _logtape_logtape.getLogger)([
5054
+ "fedify",
5055
+ "federation",
5056
+ "actor"
5057
+ ]).warn("Failed to fetch the key owner {keyOwner} of {keyId} while verifying the request signature; treating the request as unauthenticated: {error}", {
5058
+ keyId: key.id?.href,
5059
+ keyOwner: error.url.href,
5060
+ error
5061
+ });
5062
+ return null;
5063
+ }
5064
+ throw error;
5065
+ }
5066
+ }).catch((error) => {
5067
+ if (check.owner === promise) check.owner = void 0;
4945
5068
  throw error;
4946
- }
5069
+ });
5070
+ check.owner = promise;
5071
+ return await promise;
4947
5072
  }
4948
5073
  };
4949
5074
  function forwardActivity(ctx, loggerCategory, forwarder, recipients, options) {
@@ -1,4 +1,4 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
- const require_middleware = require("./middleware-BWOwEGpy.cjs");
3
+ const require_middleware = require("./middleware-DNueR0GD.cjs");
4
4
  exports.FederationImpl = require_middleware.FederationImpl;