@fedify/fedify 2.5.0-dev.2448 → 2.5.0-dev.2492

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 (126) hide show
  1. package/README.md +3 -0
  2. package/dist/{builder-DbC8bn6k.mjs → builder-DphK9v7f.mjs} +2 -2
  3. package/dist/{client-D-XP_Ccb.d.cts → client-3InDZbba.d.cts} +0 -1
  4. package/dist/{client-D-XP_Ccb.d.ts → client-3InDZbba.d.ts} +0 -1
  5. package/dist/{client-CEnNz8O7.mjs → client-5SIFzW3q.mjs} +113 -64
  6. package/dist/compat/mod.d.cts +1 -1
  7. package/dist/compat/mod.d.ts +1 -1
  8. package/dist/compat/transformers.test.mjs +1 -1
  9. package/dist/{compound-proof-heeXW2_A.mjs → compound-proof-cYtvlTgF.mjs} +2 -2
  10. package/dist/{context-GlHrzdSP.d.ts → context-DQJE9YfH.d.ts} +29 -3
  11. package/dist/{context-BazwQrHn.d.cts → context-DSuL3YWx.d.cts} +29 -3
  12. package/dist/{deno-Dpewt_jJ.mjs → deno-iOAA7iRN.mjs} +1 -1
  13. package/dist/docloader-DCZkiTeF.mjs +136 -0
  14. package/dist/{exporter-DThFfe7P.mjs → exporter-DVG68ozH.mjs} +36 -30
  15. package/dist/federation/builder.test.mjs +1 -1
  16. package/dist/federation/compound-fanout.test.mjs +6 -3
  17. package/dist/federation/handler-compound-proof.test.mjs +3 -3
  18. package/dist/federation/handler.test.mjs +5 -5
  19. package/dist/federation/hashlink-media.test.mjs +2 -2
  20. package/dist/federation/idempotency.test.mjs +2 -2
  21. package/dist/federation/inbox-actorless.test.mjs +5 -5
  22. package/dist/federation/inbox-report.test.mjs +5 -5
  23. package/dist/federation/inbox-view.test.mjs +1 -1
  24. package/dist/federation/inbox.test.mjs +1 -1
  25. package/dist/federation/metrics.test.mjs +1 -1
  26. package/dist/federation/middleware.test.mjs +468 -33
  27. package/dist/federation/mod.cjs +1 -1
  28. package/dist/federation/mod.d.cts +3 -3
  29. package/dist/federation/mod.d.ts +3 -3
  30. package/dist/federation/mod.js +1 -1
  31. package/dist/federation/mq.test.mjs +1 -1
  32. package/dist/federation/outgoing-proof.test.mjs +9 -6
  33. package/dist/federation/portable-actor.test.mjs +3 -3
  34. package/dist/federation/portable-chain.test.mjs +3 -3
  35. package/dist/federation/portable-collection.test.mjs +4 -4
  36. package/dist/federation/portable-delivery.test.mjs +2 -2
  37. package/dist/federation/portable-gateway-keys.test.mjs +11 -5
  38. package/dist/federation/portable-inbox.test.mjs +4 -4
  39. package/dist/federation/portable-parse-uri.test.mjs +1 -1
  40. package/dist/federation/portable-route-activity.test.mjs +2 -2
  41. package/dist/federation/portable.test.mjs +3 -3
  42. package/dist/federation/send.test.mjs +502 -93
  43. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  44. package/dist/federation/tasks/tasks.test.mjs +2 -2
  45. package/dist/federation/temporal.test.mjs +1 -1
  46. package/dist/federation/webfinger.test.mjs +1 -1
  47. package/dist/{federation-D8YexkP1.cjs → federation-D0AZw_aw.cjs} +246 -108
  48. package/dist/{http-BZkyQIkb.mjs → http-BoJCMnjY.mjs} +204 -106
  49. package/dist/{http-BBr-VLil.js → http-ChXUU_3c.js} +178 -109
  50. package/dist/{http-CAl7Num5.cjs → http-kv1LZw-r.cjs} +179 -110
  51. package/dist/{inbox-wDuMKaFW.mjs → inbox-BjihTKLo.mjs} +2 -2
  52. package/dist/{inbox-view-NE9wXdLo.mjs → inbox-view-D-skypg-.mjs} +1 -1
  53. package/dist/{key-nYGY4QB9.d.cts → key-DbLNT-vA.d.ts} +1 -2
  54. package/dist/{key-DhsBxxZN.d.ts → key-DlHT4R6k.d.cts} +1 -2
  55. package/dist/{kv-cache-CTQSfkmp.mjs → kv-cache-Cmsyke4d.mjs} +1 -1
  56. package/dist/{kv-cache-U7uz5nx4.js → kv-cache-DQcte7ES.js} +73 -28
  57. package/dist/{kv-cache-CjgCOz3i.cjs → kv-cache-Ddh5sMxH.cjs} +72 -27
  58. package/dist/{ld-Cel1RRZ5.d.cts → ld-CWVpo_NM.d.cts} +1 -1
  59. package/dist/{ld-CQFuiah9.mjs → ld-CdvcOEXQ.mjs} +2 -2
  60. package/dist/{ld-CcuZ4NZa.d.ts → ld-Inagww4R.d.ts} +1 -1
  61. package/dist/{metrics-B-QxRSSR.mjs → metrics-CpxvUrBz.mjs} +1 -1
  62. package/dist/{middleware-P-AZEQMn.js → middleware-BHb5toqA.js} +247 -109
  63. package/dist/{middleware-BdlNa8BD.mjs → middleware-BKs2DLUF.mjs} +1 -1
  64. package/dist/{middleware-CuVa0UuS.mjs → middleware-CoBLrTKX.mjs} +89 -49
  65. package/dist/{mod-DvWOv2Ho.d.cts → mod-6TQ4UQZb.d.cts} +1 -1
  66. package/dist/{mod-BHQE-7WQ.d.ts → mod-B4kBw1-8.d.ts} +1 -1
  67. package/dist/{mod-DkhfZC1u.d.ts → mod-B7j30IQf.d.ts} +2 -9
  68. package/dist/{mod-B2783_3v.d.cts → mod-C1nT74IG.d.cts} +1 -1
  69. package/dist/{mod-DjZfJj9R.d.ts → mod-CIeBgsOC.d.ts} +1 -1
  70. package/dist/{mod-B5-wdzmJ.d.cts → mod-YvA4Rk8J.d.cts} +2 -9
  71. package/dist/mod.cjs +5 -5
  72. package/dist/mod.d.cts +7 -7
  73. package/dist/mod.d.ts +7 -7
  74. package/dist/mod.js +5 -5
  75. package/dist/nodeinfo/client.test.mjs +337 -19
  76. package/dist/nodeinfo/handler.test.mjs +1 -1
  77. package/dist/nodeinfo/mod.cjs +1 -1
  78. package/dist/nodeinfo/mod.d.cts +1 -1
  79. package/dist/nodeinfo/mod.d.ts +1 -1
  80. package/dist/nodeinfo/mod.js +1 -1
  81. package/dist/otel/exporter.test.mjs +257 -1
  82. package/dist/otel/mod.cjs +36 -30
  83. package/dist/otel/mod.d.cts +13 -0
  84. package/dist/otel/mod.d.ts +13 -0
  85. package/dist/otel/mod.js +36 -30
  86. package/dist/{owner-ccKkQOAf.mjs → owner-DTnVpUKA.mjs} +3 -3
  87. package/dist/{portable-collection-CndnpXfo.mjs → portable-collection-CJeYTL61.mjs} +2 -2
  88. package/dist/{portable-collection-TC-kvkVI.cjs → portable-collection-DciwBIia.cjs} +1 -1
  89. package/dist/{portable-collection-DUlQ3s2M.js → portable-collection-TmtpzD4E.js} +1 -1
  90. package/dist/{portable-key-D5tFOoV-.mjs → portable-key-DcYok4ki.mjs} +1 -1
  91. package/dist/{proof-DPoF06nz.mjs → proof-GH7Poos_.mjs} +3 -3
  92. package/dist/{send-CF44O1Df.mjs → send-BgGJh6pv.mjs} +174 -76
  93. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  94. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  95. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  96. package/dist/sig/compound-proof.test.mjs +2 -2
  97. package/dist/sig/http.test.mjs +553 -2
  98. package/dist/sig/key.test.mjs +1 -1
  99. package/dist/sig/ld.test.mjs +1 -1
  100. package/dist/sig/mitra-receive-interop.test.mjs +4 -4
  101. package/dist/sig/mitra-send-interop.test.mjs +3 -3
  102. package/dist/sig/mod.cjs +2 -2
  103. package/dist/sig/mod.d.cts +3 -3
  104. package/dist/sig/mod.d.ts +3 -3
  105. package/dist/sig/mod.js +2 -2
  106. package/dist/sig/owner.test.mjs +1 -1
  107. package/dist/sig/portable-collection.test.mjs +1 -1
  108. package/dist/sig/portable-interop.test.mjs +3 -3
  109. package/dist/sig/portable-key.test.mjs +5 -5
  110. package/dist/sig/proof.test.mjs +4 -4
  111. package/dist/sig/tootik-request-interop.test.mjs +4 -4
  112. package/dist/sig/tootik-send-interop.test.mjs +3 -3
  113. package/dist/sig/verification.test.mjs +3 -3
  114. package/dist/{temporal-XlZ4Wb94.mjs → temporal-gJINzcL4.mjs} +1 -1
  115. package/dist/testing/kv.test.mjs +1 -1
  116. package/dist/testing/mod.d.mts +26 -0
  117. package/dist/{types-_S2_MeLF.js → types-DUjZb4i6.js} +112 -63
  118. package/dist/{types-poIao40f.cjs → types-hKCCjii0.cjs} +111 -62
  119. package/dist/utils/docloader.test.mjs +344 -36
  120. package/dist/utils/kv-cache.test.mjs +1 -1
  121. package/dist/utils/mod.cjs +1 -1
  122. package/dist/utils/mod.d.cts +1 -1
  123. package/dist/utils/mod.d.ts +1 -1
  124. package/dist/utils/mod.js +1 -1
  125. package/package.json +8 -8
  126. package/dist/docloader-CpsDyqVN.mjs +0 -91
@@ -17,7 +17,7 @@ json_canon = require_rolldown_runtime.__toESM(json_canon, 1);
17
17
  let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
18
18
  //#region deno.json
19
19
  var name = "@fedify/fedify";
20
- var version = "2.5.0-dev.2448+756933d0";
20
+ var version = "2.5.0-dev.2492+4648c04e";
21
21
  //#endregion
22
22
  //#region src/sig/portable-key-id.ts
23
23
  const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
@@ -10165,10 +10165,10 @@ async function createRedirectRequest(request, location, body, validateRedirect)
10165
10165
  cache: request.cache
10166
10166
  });
10167
10167
  }
10168
- async function fetchDoubleKnockRequest(request, signedRequest, signal) {
10168
+ async function fetchDoubleKnockRequest(session, request, signedRequest, signal) {
10169
10169
  const maxAttempts = request.method === "GET" || request.method === "HEAD" ? 2 : 1;
10170
10170
  for (let attempt = 1;; attempt++) try {
10171
- return await fetch(signedRequest, {
10171
+ return await session.fetch(signedRequest, {
10172
10172
  redirect: "manual",
10173
10173
  signal
10174
10174
  });
@@ -10219,125 +10219,194 @@ function getAbortReason(signal) {
10219
10219
  return signal.reason ?? new DOMException("The operation was aborted.", "AbortError");
10220
10220
  }
10221
10221
  /**
10222
- * Performs a double-knock request to the given URL. For the details of
10223
- * double-knocking, see
10224
- * <https://swicg.github.io/activitypub-http-signature/#how-to-upgrade-supported-versions>.
10222
+ * Performs a double-knock request like {@link doubleKnock}, opening a session
10223
+ * for every hop with the given function, and passes the final response to
10224
+ * the given function. The final session is closed after `consume` settles,
10225
+ * so `consume` must read or cancel the response body before it returns.
10226
+ * This is not exported from any entry point.
10225
10227
  * @param request The request to send.
10226
10228
  * @param identity The identity to use for signing the request.
10227
10229
  * @param options The options for double-knock requests.
10228
- * @returns The response to the request.
10229
- * @since 1.6.0
10230
+ * @param openHop The function to open a session for each hop. The session of
10231
+ * a hop is closed before the next hop is opened.
10232
+ * @param consume The function to consume the final response.
10233
+ * @returns The value returned by `consume`.
10230
10234
  */
10231
- async function doubleKnock(request, identity, options = {}) {
10232
- return await doubleKnockInternal(request, identity, options);
10235
+ async function doubleKnockScoped(request, identity, options, openHop, consume) {
10236
+ const { response, session } = await doubleKnockCore(request, identity, options, openHop);
10237
+ try {
10238
+ return await consume(response);
10239
+ } catch (error) {
10240
+ (0, _logtape_logtape.getLogger)([
10241
+ "fedify",
10242
+ "sig",
10243
+ "http"
10244
+ ]).debug("Failed to consume the response from {url}; closing the session: {error}", {
10245
+ url: request.url,
10246
+ error
10247
+ });
10248
+ throw error;
10249
+ } finally {
10250
+ await session.close();
10251
+ }
10252
+ }
10253
+ function isRedirectResponse(response) {
10254
+ return response.status >= 300 && response.status < 400 && response.headers.has("Location");
10255
+ }
10256
+ /**
10257
+ * Starts cancelling the body of a response that is not passed to the caller,
10258
+ * without waiting for it to settle. The cancellation of a branch of a tee
10259
+ * (e.g., a body cloned by a fetch mock) settles only after the other branch
10260
+ * is cancelled too, per the Streams standard, so awaiting it could block
10261
+ * forever. Failures are only logged.
10262
+ */
10263
+ function cancelResponseBody(response, url) {
10264
+ response.body?.cancel().catch((error) => {
10265
+ (0, _logtape_logtape.getLogger)([
10266
+ "fedify",
10267
+ "sig",
10268
+ "http"
10269
+ ]).debug("Failed to cancel the response body from {url}: {error}", {
10270
+ url,
10271
+ error
10272
+ });
10273
+ });
10233
10274
  }
10234
- async function doubleKnockInternal(request, identity, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
10275
+ async function doubleKnockCore(request, identity, options, openHop) {
10235
10276
  const { specDeterminer, log, tracerProvider, signal } = options;
10236
10277
  signal?.throwIfAborted();
10237
10278
  const maximumRedirection = options.maxRedirection ?? DEFAULT_MAX_REDIRECTION;
10238
- visited.add(request.url);
10239
- const origin = new URL(request.url).origin;
10240
- const firstTrySpec = specDeterminer == null ? "rfc9421" : await specDeterminer.determineSpec(origin);
10241
- signal?.throwIfAborted();
10279
+ const visited = /* @__PURE__ */ new Set();
10242
10280
  const body = options.body !== void 0 ? options.body : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
10243
- let signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
10244
- spec: firstTrySpec,
10245
- tracerProvider,
10246
- body
10247
- });
10248
- signal?.throwIfAborted();
10249
- log?.(signedRequest);
10250
- let response = await fetchDoubleKnockRequest(request, signedRequest, signal);
10251
- if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
10252
- if (redirected >= maximumRedirection) throw new _fedify_vocab_runtime.FetchError(request.url, `Too many redirections (${redirected + 1})`);
10253
- const redirectRequest = await createRedirectRequest(request, response.headers.get("Location"), body, options.validateRedirect);
10254
- if (visited.has(redirectRequest.url)) throw new _fedify_vocab_runtime.FetchError(request.url, `Redirect loop detected: ${redirectRequest.url}`);
10255
- return doubleKnockInternal(redirectRequest, identity, {
10256
- ...options,
10257
- body
10258
- }, redirected + 1, visited);
10259
- } else if (response.status === 400 || response.status === 401 || response.status > 401) {
10260
- const logger = (0, _logtape_logtape.getLogger)([
10261
- "fedify",
10262
- "sig",
10263
- "http"
10264
- ]);
10265
- const acceptSigHeader = response.headers.get("Accept-Signature");
10266
- if (acceptSigHeader != null) {
10267
- const entries = validateAcceptSignature(parseAcceptSignature(acceptSigHeader));
10268
- const localKeyId = identity.keyId.href;
10269
- const localAlg = "rsa-v1_5-sha256";
10270
- let fulfilled = false;
10271
- let challengeRequest;
10272
- for (const entry of entries) {
10273
- signal?.throwIfAborted();
10274
- const rfc9421 = fulfillAcceptSignature(entry, localKeyId, localAlg);
10275
- if (rfc9421 == null) continue;
10276
- logger.debug("Received Accept-Signature challenge; accumulating label {label} and components {components}.", {
10277
- label: rfc9421.label,
10278
- components: rfc9421.components
10279
- });
10280
- try {
10281
- challengeRequest = await signRequest(challengeRequest ?? request, identity.privateKey, identity.keyId, {
10282
- spec: "rfc9421",
10283
- tracerProvider,
10284
- body,
10285
- rfc9421
10281
+ let session = await openHop(request.url);
10282
+ try {
10283
+ for (let redirected = 0;; redirected++) {
10284
+ visited.add(request.url);
10285
+ const origin = new URL(request.url).origin;
10286
+ const firstTrySpec = specDeterminer == null ? "rfc9421" : await specDeterminer.determineSpec(origin);
10287
+ signal?.throwIfAborted();
10288
+ let signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
10289
+ spec: firstTrySpec,
10290
+ tracerProvider,
10291
+ body
10292
+ });
10293
+ signal?.throwIfAborted();
10294
+ log?.(signedRequest);
10295
+ let response = await fetchDoubleKnockRequest(session, request, signedRequest, signal);
10296
+ const firstRedirected = isRedirectResponse(response);
10297
+ if (!firstRedirected && (response.status === 400 || response.status === 401 || response.status > 401)) {
10298
+ const logger = (0, _logtape_logtape.getLogger)([
10299
+ "fedify",
10300
+ "sig",
10301
+ "http"
10302
+ ]);
10303
+ const acceptSigHeader = response.headers.get("Accept-Signature");
10304
+ if (acceptSigHeader != null) {
10305
+ const entries = validateAcceptSignature(parseAcceptSignature(acceptSigHeader));
10306
+ const localKeyId = identity.keyId.href;
10307
+ const localAlg = "rsa-v1_5-sha256";
10308
+ let fulfilled = false;
10309
+ let challengeRequest;
10310
+ for (const entry of entries) {
10311
+ signal?.throwIfAborted();
10312
+ const rfc9421 = fulfillAcceptSignature(entry, localKeyId, localAlg);
10313
+ if (rfc9421 == null) continue;
10314
+ logger.debug("Received Accept-Signature challenge; accumulating label {label} and components {components}.", {
10315
+ label: rfc9421.label,
10316
+ components: rfc9421.components
10317
+ });
10318
+ try {
10319
+ challengeRequest = await signRequest(challengeRequest ?? request, identity.privateKey, identity.keyId, {
10320
+ spec: "rfc9421",
10321
+ tracerProvider,
10322
+ body,
10323
+ rfc9421
10324
+ });
10325
+ fulfilled = true;
10326
+ } catch (error) {
10327
+ logger.debug("Failed to fulfill Accept-Signature challenge entry {label}: {error}", {
10328
+ label: entry.label,
10329
+ error
10330
+ });
10331
+ }
10332
+ }
10333
+ if (fulfilled && challengeRequest != null) {
10334
+ signal?.throwIfAborted();
10335
+ cancelResponseBody(response, request.url);
10336
+ signedRequest = challengeRequest;
10337
+ log?.(signedRequest);
10338
+ response = await session.fetch(signedRequest, {
10339
+ redirect: "manual",
10340
+ signal
10341
+ });
10342
+ }
10343
+ if (fulfilled && !isRedirectResponse(response)) {
10344
+ if (response.status < 300) {
10345
+ await specDeterminer?.rememberSpec(origin, "rfc9421");
10346
+ return {
10347
+ response,
10348
+ session
10349
+ };
10350
+ }
10351
+ if (response.status !== 400 && response.status !== 401) return {
10352
+ response,
10353
+ session
10354
+ };
10355
+ }
10356
+ }
10357
+ if (!isRedirectResponse(response)) {
10358
+ const spec = firstTrySpec === "draft-cavage-http-signatures-12" ? "rfc9421" : "draft-cavage-http-signatures-12";
10359
+ (0, _logtape_logtape.getLogger)([
10360
+ "fedify",
10361
+ "sig",
10362
+ "http"
10363
+ ]).debug("Failed to verify with the spec {spec} ({status} {statusText}); retrying with spec {secondSpec}... (double-knocking)", {
10364
+ spec: firstTrySpec,
10365
+ secondSpec: spec,
10366
+ status: response.status,
10367
+ statusText: response.statusText
10286
10368
  });
10287
- fulfilled = true;
10288
- } catch (error) {
10289
- logger.debug("Failed to fulfill Accept-Signature challenge entry {label}: {error}", {
10290
- label: entry.label,
10291
- error
10369
+ cancelResponseBody(response, request.url);
10370
+ signal?.throwIfAborted();
10371
+ signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
10372
+ spec,
10373
+ tracerProvider,
10374
+ body
10292
10375
  });
10376
+ signal?.throwIfAborted();
10377
+ log?.(signedRequest);
10378
+ response = await fetchDoubleKnockRequest(session, request, signedRequest, signal);
10379
+ if (!isRedirectResponse(response) && response.status !== 400 && response.status !== 401) await specDeterminer?.rememberSpec(origin, spec);
10293
10380
  }
10294
- }
10295
- if (fulfilled && challengeRequest != null) {
10296
- signal?.throwIfAborted();
10297
- signedRequest = challengeRequest;
10298
- log?.(signedRequest);
10299
- response = await fetch(signedRequest, {
10300
- redirect: "manual",
10301
- signal
10302
- });
10303
- if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) return doubleKnock(await createRedirectRequest(request, response.headers.get("Location"), body, options.validateRedirect), identity, {
10304
- ...options,
10305
- body
10306
- });
10307
- }
10308
- if (fulfilled && response.status < 300) {
10309
- await specDeterminer?.rememberSpec(origin, "rfc9421");
10310
- return response;
10311
- }
10312
- if (fulfilled && response.status !== 400 && response.status !== 401) return response;
10313
- }
10314
- const spec = firstTrySpec === "draft-cavage-http-signatures-12" ? "rfc9421" : "draft-cavage-http-signatures-12";
10315
- logger.debug("Failed to verify with the spec {spec} ({status} {statusText}); retrying with spec {secondSpec}... (double-knocking)", {
10316
- spec: firstTrySpec,
10317
- secondSpec: spec,
10318
- status: response.status,
10319
- statusText: response.statusText
10320
- });
10321
- signal?.throwIfAborted();
10322
- signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
10323
- spec,
10324
- tracerProvider,
10325
- body
10326
- });
10327
- signal?.throwIfAborted();
10328
- log?.(signedRequest);
10329
- response = await fetchDoubleKnockRequest(request, signedRequest, signal);
10330
- if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
10381
+ } else if (!firstRedirected) await specDeterminer?.rememberSpec(origin, firstTrySpec);
10382
+ if (!isRedirectResponse(response)) return {
10383
+ response,
10384
+ session
10385
+ };
10386
+ cancelResponseBody(response, request.url);
10387
+ await session.close();
10331
10388
  if (redirected >= maximumRedirection) throw new _fedify_vocab_runtime.FetchError(request.url, `Too many redirections (${redirected + 1})`);
10332
- const redirectRequest = await createRedirectRequest(request, response.headers.get("Location"), body, options.validateRedirect);
10389
+ const location = response.headers.get("Location");
10390
+ const redirectRequest = await createRedirectRequest(request, location, body, options.validateRedirect);
10333
10391
  if (visited.has(redirectRequest.url)) throw new _fedify_vocab_runtime.FetchError(request.url, `Redirect loop detected: ${redirectRequest.url}`);
10334
- return doubleKnockInternal(redirectRequest, identity, {
10335
- ...options,
10336
- body
10337
- }, redirected + 1, visited);
10338
- } else if (response.status !== 400 && response.status !== 401) await specDeterminer?.rememberSpec(origin, spec);
10339
- } else await specDeterminer?.rememberSpec(origin, firstTrySpec);
10340
- return response;
10392
+ request = redirectRequest;
10393
+ session = await openHop(request.url);
10394
+ }
10395
+ } catch (error) {
10396
+ try {
10397
+ await session.close();
10398
+ } catch (closeError) {
10399
+ (0, _logtape_logtape.getLogger)([
10400
+ "fedify",
10401
+ "sig",
10402
+ "http"
10403
+ ]).debug("Failed to close the session for {url}: {error}", {
10404
+ url: request.url,
10405
+ error: closeError
10406
+ });
10407
+ }
10408
+ throw error;
10409
+ }
10341
10410
  }
10342
10411
  /**
10343
10412
  * Performs a timing-safe equality comparison between two `Uint8Array` values.
@@ -10433,10 +10502,10 @@ Object.defineProperty(exports, "detachSignature", {
10433
10502
  return detachSignature;
10434
10503
  }
10435
10504
  });
10436
- Object.defineProperty(exports, "doubleKnock", {
10505
+ Object.defineProperty(exports, "doubleKnockScoped", {
10437
10506
  enumerable: true,
10438
10507
  get: function() {
10439
- return doubleKnock;
10508
+ return doubleKnockScoped;
10440
10509
  }
10441
10510
  });
10442
10511
  Object.defineProperty(exports, "exportJwk", {
@@ -3,8 +3,8 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as getAuthenticationOrigin } from "./portable-key-id-Ca30-hOa.mjs";
5
5
  import { o as serializeAuthentication, r as getEffectivePrincipals } from "./inbox-authentication-DflPYlLL.mjs";
6
- import { n as version, t as name } from "./deno-Dpewt_jJ.mjs";
7
- import { g as recordInboxActivity, n as getDurationMs, r as getFederationMetrics } from "./metrics-B-QxRSSR.mjs";
6
+ import { n as version, t as name } from "./deno-iOAA7iRN.mjs";
7
+ import { g as recordInboxActivity, n as getDurationMs, r as getFederationMetrics } from "./metrics-CpxvUrBz.mjs";
8
8
  import { getLogger } from "@logtape/logtape";
9
9
  import { getTypeId } from "@fedify/vocab";
10
10
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
@@ -1,7 +1,7 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { c as getNormalizationContextLoader, p as markEffectiveContextLoader, s as detachSignature, y as wrapContextLoaderForJsonLd } from "./ld-CQFuiah9.mjs";
4
+ import { c as getNormalizationContextLoader, p as markEffectiveContextLoader, s as detachSignature, y as wrapContextLoaderForJsonLd } from "./ld-CdvcOEXQ.mjs";
5
5
  import { preloadedContexts } from "@fedify/vocab-runtime";
6
6
  import { encodeHex } from "byte-encodings/hex";
7
7
  import jsonld from "@fedify/vocab-runtime/jsonld";
@@ -1,8 +1,7 @@
1
1
  /// <reference lib="esnext.temporal" />
2
2
  import { Actor, CryptographicKey, DataIntegrityProof, Multikey, Object as Object$1 } from "@fedify/vocab";
3
- import { DocumentLoader, PortableObjectVerifier } from "@fedify/vocab-runtime";
4
3
  import { MeterProvider, TracerProvider } from "@opentelemetry/api";
5
-
4
+ import { DocumentLoader, PortableObjectVerifier } from "@fedify/vocab-runtime";
6
5
  //#region src/sig/proof.d.ts
7
6
  /**
8
7
  * Checks if the given JSON-LD document has a DataIntegrityProof-like object,
@@ -1,8 +1,7 @@
1
1
  /// <reference lib="esnext.temporal" />
2
2
  import { Actor, CryptographicKey, DataIntegrityProof, Multikey, Object as Object$1 } from "@fedify/vocab";
3
- import { MeterProvider, TracerProvider } from "@opentelemetry/api";
4
3
  import { DocumentLoader, PortableObjectVerifier } from "@fedify/vocab-runtime";
5
-
4
+ import { MeterProvider, TracerProvider } from "@opentelemetry/api";
6
5
  //#region src/sig/proof.d.ts
7
6
  /**
8
7
  * Checks if the given JSON-LD document has a DataIntegrityProof-like object,
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { p as recordDocumentCache } from "./metrics-B-QxRSSR.mjs";
4
+ import { p as recordDocumentCache } from "./metrics-CpxvUrBz.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 "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { X as validateCryptoKey, dt as recordDocumentCache, t as doubleKnock } from "./http-BBr-VLil.js";
3
+ import { X as validateCryptoKey, dt as recordDocumentCache, t as doubleKnockScoped } from "./http-ChXUU_3c.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
- import { FetchError, UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, resolveDocumentLoaderTimeout, validatePublicUrl, withDocumentLoaderTimeout } from "@fedify/vocab-runtime";
5
+ import { FetchError, UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, resolveDocumentLoaderTimeout, withDocumentLoaderTimeout } 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",
@@ -27,13 +28,35 @@ const DEFAULT_MAX_REDIRECTION = 20;
27
28
  * @throws {RangeError} If the `timeout` option is invalid.
28
29
  * @since 0.4.0
29
30
  */
30
- function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedirection, userAgent, specDeterminer, timeout, tracerProvider } = {}) {
31
+ function getAuthenticatedDocumentLoader(identity, options = {}) {
32
+ return createAuthenticatedDocumentLoader(identity, options);
33
+ }
34
+ /**
35
+ * The placeholder that the alternate document callback of
36
+ * `getRemoteDocument()` returns, so that the alternate document is loaded
37
+ * after the current hop is closed.
38
+ */
39
+ const ALTERNATE_PLACEHOLDER = Object.freeze({
40
+ contextUrl: null,
41
+ document: null,
42
+ documentUrl: "about:blank"
43
+ });
44
+ /**
45
+ * The implementation of {@link getAuthenticatedDocumentLoader}. This is not
46
+ * exported from any entry point.
47
+ * @param identity The identity to get the document loader for.
48
+ * @param options The options for the document loader.
49
+ * @param deps The dependencies, for tests.
50
+ * @returns The authenticated document loader.
51
+ * @throws {TypeError} If the key is invalid or unsupported.
52
+ */
53
+ function createAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedirection, userAgent, specDeterminer, timeout, tracerProvider } = {}, deps = {}) {
31
54
  validateCryptoKey(identity.privateKey);
32
55
  const resolvedTimeout = resolveDocumentLoaderTimeout(timeout);
33
56
  async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
34
57
  options?.signal?.throwIfAborted();
35
58
  let currentUrl = new URL(url).href;
36
- await validateUrl(currentUrl, options);
59
+ validateUrl(currentUrl);
37
60
  visited.add(currentUrl);
38
61
  const originalRequest = createActivityPubRequest(currentUrl, { userAgent });
39
62
  function follow(nextUrl) {
@@ -42,46 +65,68 @@ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedi
42
65
  if (visited.has(nextUrl)) throw new FetchError(currentUrl, `Redirect loop detected: ${nextUrl}`);
43
66
  redirected++;
44
67
  }
45
- const response = await doubleKnock(originalRequest, identity, {
68
+ const result = await doubleKnockScoped(originalRequest, identity, {
46
69
  maxRedirection,
47
70
  specDeterminer,
48
71
  log: curry(logRequest)(logger$1),
49
72
  tracerProvider,
50
73
  signal: options?.signal,
51
- validateRedirect: async (nextUrl) => {
74
+ validateRedirect: (nextUrl) => {
52
75
  follow(nextUrl);
53
- await validateUrl(nextUrl, options);
76
+ validateUrl(nextUrl);
54
77
  visited.add(nextUrl);
55
78
  currentUrl = nextUrl;
56
79
  }
80
+ }, (hopUrl) => openHop(hopUrl, options), async (response) => {
81
+ options?.signal?.throwIfAborted();
82
+ let alternateUrl;
83
+ const document = await getRemoteDocument(currentUrl, response, (url) => {
84
+ alternateUrl = url;
85
+ return Promise.resolve(ALTERNATE_PLACEHOLDER);
86
+ }, options);
87
+ if (alternateUrl == null) return { document };
88
+ if (!response.bodyUsed && response.body?.locked !== true) response.body?.cancel().catch((error) => {
89
+ logger$1.debug("Failed to cancel the response body from {url}: {error}", {
90
+ url: currentUrl,
91
+ error
92
+ });
93
+ });
94
+ return { alternateUrl };
57
95
  });
58
96
  options?.signal?.throwIfAborted();
59
- return getRemoteDocument(currentUrl, response, (alternateUrl) => {
60
- follow(alternateUrl);
61
- return load(alternateUrl, options, redirected, visited);
62
- }, options);
97
+ if ("document" in result) return result.document;
98
+ follow(result.alternateUrl);
99
+ return load(result.alternateUrl, options, redirected, visited);
100
+ }
101
+ function openUnvalidatedHop(url, signal) {
102
+ if (deps.openHop != null) return deps.openHop(url);
103
+ return allowPrivateAddress ? openDirectFetch(url) : openPublicFetch(url, { signal });
104
+ }
105
+ async function openHop(url, options) {
106
+ try {
107
+ const session = await openUnvalidatedHop(url, options?.signal);
108
+ if (options?.signal?.aborted) {
109
+ await session.close();
110
+ options.signal.throwIfAborted();
111
+ }
112
+ return session;
113
+ } catch (error) {
114
+ if (error instanceof UrlError) if (error.reason === "dns") logger$1.debug("DNS lookup failed for {url}", {
115
+ url,
116
+ error
117
+ });
118
+ else logger$1[options?.suppressError ? "warn" : "error"]("Disallowed private URL: {url}", {
119
+ url,
120
+ error
121
+ });
122
+ throw error;
123
+ }
63
124
  }
64
- async function validateUrl(url, options) {
125
+ function validateUrl(url) {
65
126
  const parsedUrl = new URL(url);
66
127
  const protocol = parsedUrl.protocol;
67
128
  if (protocol !== "http:" && protocol !== "https:") throw new UrlError(`Unsupported protocol: ${protocol}`);
68
129
  if (parsedUrl.username !== "" || parsedUrl.password !== "") throw new UrlError("URLs with credentials are not allowed");
69
- if (!allowPrivateAddress) {
70
- try {
71
- await validatePublicUrl(url);
72
- } catch (error) {
73
- if (error instanceof UrlError) if (error.reason === "dns") logger$1.debug("DNS lookup failed for {url}", {
74
- url,
75
- error
76
- });
77
- else logger$1[options?.suppressError ? "warn" : "error"]("Disallowed private URL: {url}", {
78
- url,
79
- error
80
- });
81
- throw error;
82
- }
83
- options?.signal?.throwIfAborted();
84
- }
85
130
  }
86
131
  return withDocumentLoaderTimeout((url, options) => load(url, options), resolvedTimeout);
87
132
  }