@fedify/fedify 2.1.24 → 2.1.25

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 (69) hide show
  1. package/dist/{builder-BatYe9a2.mjs → builder-DsBAef6b.mjs} +3 -3
  2. package/dist/{client-DNiBssfx.mjs → client-DM72NZuk.mjs} +7 -3
  3. package/dist/compat/transformers.test.mjs +1 -1
  4. package/dist/{deno-BXkm_rws.mjs → deno-9AaebKOA.mjs} +1 -1
  5. package/dist/docloader-DX96uPUj.mjs +78 -0
  6. package/dist/federation/builder.test.mjs +2 -2
  7. package/dist/federation/collection.test.mjs +1 -1
  8. package/dist/federation/handler.test.mjs +7 -7
  9. package/dist/federation/idempotency.test.mjs +3 -3
  10. package/dist/federation/inbox.test.mjs +1 -1
  11. package/dist/federation/kv.test.mjs +1 -1
  12. package/dist/federation/middleware.test.mjs +134 -7
  13. package/dist/federation/mod.cjs +1 -1
  14. package/dist/federation/mod.js +1 -1
  15. package/dist/federation/mq.test.mjs +98 -43
  16. package/dist/federation/negotiation.test.mjs +1 -1
  17. package/dist/federation/router.test.mjs +1 -1
  18. package/dist/federation/send.test.mjs +68 -5
  19. package/dist/federation/temporal.test.mjs +2 -2
  20. package/dist/federation/webfinger.test.mjs +2 -2
  21. package/dist/{http-UyebnM2c.mjs → http-B5qAeoJd.mjs} +7 -3
  22. package/dist/{http-7FmLhLDv.cjs → http-BJEMpnnY.cjs} +6 -2
  23. package/dist/{http-Chr54tTL.js → http-CdJ6n4rA.js} +6 -2
  24. package/dist/{inbox-ByQ2iD14.mjs → inbox-BSyjaFbo.mjs} +1 -1
  25. package/dist/{key-A8SuFznD.mjs → key-x9f-H7eO.mjs} +1 -1
  26. package/dist/{kv-cache-DJhBDA9T.cjs → kv-cache-BDp2c_QE.cjs} +34 -8
  27. package/dist/{kv-cache-J6hmiAEd.js → kv-cache-Dx3QwNBI.js} +35 -9
  28. package/dist/{ld-BSvHU-S0.mjs → ld-cfUwoi0C.mjs} +3 -3
  29. package/dist/{middleware-Bddj287b.cjs → middleware-BXJpAXhO.cjs} +1 -1
  30. package/dist/{middleware-Do50b12a.js → middleware-CP35ElZv.js} +13 -4
  31. package/dist/{middleware-DvxNTKpl.mjs → middleware-DDT9ljTQ.mjs} +1 -1
  32. package/dist/{middleware-Dy18K1xP.mjs → middleware-DHHRwSZH.mjs} +12 -12
  33. package/dist/{middleware-CCYqHlzn.cjs → middleware-XXuSt_6b.cjs} +14 -5
  34. package/dist/{mod-DniKVJ_C.d.cts → mod-DkE98l_7.d.cts} +2 -0
  35. package/dist/{mod-CMK_S-eX.d.ts → mod-DqkY4FDr.d.ts} +2 -0
  36. package/dist/mod.cjs +5 -5
  37. package/dist/mod.d.cts +1 -1
  38. package/dist/mod.d.ts +1 -1
  39. package/dist/mod.js +5 -5
  40. package/dist/nodeinfo/client.test.mjs +50 -2
  41. package/dist/nodeinfo/handler.test.mjs +2 -2
  42. package/dist/nodeinfo/mod.cjs +1 -1
  43. package/dist/nodeinfo/mod.js +1 -1
  44. package/dist/nodeinfo/types.test.mjs +1 -1
  45. package/dist/otel/exporter.test.mjs +1 -1
  46. package/dist/{owner-CaaCVHsG.mjs → owner-DklgvZXn.mjs} +2 -2
  47. package/dist/{proof-_AyA4r7z.mjs → proof-CI4rsVLu.mjs} +2 -2
  48. package/dist/{proof-BrOQrPQf.js → proof-Dsa_ZC-M.js} +2 -2
  49. package/dist/{proof-CaCUMcvk.cjs → proof-DzFzyfIc.cjs} +2 -2
  50. package/dist/{send-B1CRgb14.mjs → send-DJmW6Z-I.mjs} +12 -3
  51. package/dist/sig/http.test.mjs +43 -3
  52. package/dist/sig/key.test.mjs +2 -2
  53. package/dist/sig/ld.test.mjs +5 -2
  54. package/dist/sig/mod.cjs +2 -2
  55. package/dist/sig/mod.js +2 -2
  56. package/dist/sig/owner.test.mjs +2 -2
  57. package/dist/sig/proof.test.mjs +2 -2
  58. package/dist/{std__assert-Duiq_YC9.mjs → std__assert-BUOJXi-Z.mjs} +1 -26
  59. package/dist/{temporal-Pmk5ouB1.mjs → temporal-BanFifxu.mjs} +1 -1
  60. package/dist/{types-DWs-qZaY.cjs → types-DRPjmHVV.cjs} +7 -3
  61. package/dist/{types-sXITXcWa.js → types-miAYfiGO.js} +7 -3
  62. package/dist/utils/docloader.test.mjs +299 -3
  63. package/dist/utils/mod.cjs +1 -1
  64. package/dist/utils/mod.d.cts +1 -1
  65. package/dist/utils/mod.d.ts +1 -1
  66. package/dist/utils/mod.js +1 -1
  67. package/dist/vocab/cjs.test.mjs +1 -1
  68. package/package.json +6 -6
  69. package/dist/docloader-DdEK2tXP.mjs +0 -52
@@ -2,8 +2,8 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { n as RouterError, t as Router } from "./router-CrMLXoOr.mjs";
5
- import { n as version, t as name } from "./deno-BXkm_rws.mjs";
6
- import { t as InboxListenerSet } from "./inbox-ByQ2iD14.mjs";
5
+ import { n as version, t as name } from "./deno-9AaebKOA.mjs";
6
+ import { t as InboxListenerSet } from "./inbox-BSyjaFbo.mjs";
7
7
  import { getTypeId } from "@fedify/vocab";
8
8
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
9
9
  import { getLogger } from "@logtape/logtape";
@@ -43,7 +43,7 @@ var FederationBuilderImpl = class {
43
43
  this.collectionTypeIds = {};
44
44
  }
45
45
  async build(options) {
46
- const { FederationImpl } = await import("./middleware-DvxNTKpl.mjs");
46
+ const { FederationImpl } = await import("./middleware-DDT9ljTQ.mjs");
47
47
  const f = new FederationImpl(options);
48
48
  const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
49
49
  f.router = this.router.clone();
@@ -93,8 +93,8 @@ const DEFAULT_MAX_REDIRECTION = 5;
93
93
  * bounce the request to an internal address.
94
94
  * @param url The URL to fetch.
95
95
  * @param headers The request headers.
96
- * @returns The final response, or `null` if the URL is disallowed or there
97
- * are too many redirects.
96
+ * @returns The final response, or `null` if the URL is disallowed, its
97
+ * hostname cannot be resolved, or there are too many redirects.
98
98
  */
99
99
  async function fetchPublicUrl(url, headers) {
100
100
  let current = url instanceof URL ? url : new URL(url);
@@ -104,7 +104,11 @@ async function fetchPublicUrl(url, headers) {
104
104
  await validatePublicUrl(current.href);
105
105
  } catch (e) {
106
106
  if (e instanceof UrlError) {
107
- logger.error("Refused to fetch a private or invalid URL {url}: {error}", {
107
+ if (e.reason === "dns") logger.error("DNS lookup failed for {url}: {error}", {
108
+ url: current.href,
109
+ error: e
110
+ });
111
+ else logger.error("Refused to fetch a private or invalid URL {url}: {error}", {
108
112
  url: current.href,
109
113
  error: e
110
114
  });
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
5
5
  import { n as assertInstanceOf, t as assert } from "../assert-BhQd8QHP.mjs";
6
6
  import { t as MemoryKvStore } from "../kv-BrZHNugx.mjs";
7
- import { _ as autoIdAssigner, g as actorDehydrator, n as FederationImpl } from "../middleware-Dy18K1xP.mjs";
7
+ import { _ as autoIdAssigner, g as actorDehydrator, n as FederationImpl } from "../middleware-DHHRwSZH.mjs";
8
8
  import { test } from "@fedify/fixture";
9
9
  import { Follow, Person } from "@fedify/vocab";
10
10
  //#region src/compat/transformers.test.ts
@@ -3,6 +3,6 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  //#region deno.json
5
5
  var name = "@fedify/fedify";
6
- var version = "2.1.24";
6
+ var version = "2.1.25";
7
7
  //#endregion
8
8
  export { version as n, name as t };
@@ -0,0 +1,78 @@
1
+ import "@js-temporal/polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { s as validateCryptoKey } from "./key-x9f-H7eO.mjs";
5
+ import { n as doubleKnock } from "./http-B5qAeoJd.mjs";
6
+ import { curry } from "es-toolkit";
7
+ import { FetchError, UrlError, createActivityPubRequest, getRemoteDocument, logRequest, validatePublicUrl } from "@fedify/vocab-runtime";
8
+ import { getLogger } from "@logtape/logtape";
9
+ //#region src/utils/docloader.ts
10
+ const logger = getLogger([
11
+ "fedify",
12
+ "utils",
13
+ "docloader"
14
+ ]);
15
+ const DEFAULT_MAX_REDIRECTION = 20;
16
+ /**
17
+ * Gets an authenticated {@link DocumentLoader} for the given identity.
18
+ * Note that an authenticated document loader intentionally does not cache
19
+ * the fetched documents.
20
+ * At most 20 HTTP redirects and alternate document links are followed in total
21
+ * per call. Revisiting a URL within that chain throws a {@link FetchError}.
22
+ * @param identity The identity to get the document loader for.
23
+ * The actor's key pair.
24
+ * @param options The options for the document loader.
25
+ * @returns The authenticated document loader.
26
+ * @throws {TypeError} If the key is invalid or unsupported.
27
+ * @since 0.4.0
28
+ */
29
+ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, userAgent, specDeterminer, tracerProvider } = {}) {
30
+ validateCryptoKey(identity.privateKey);
31
+ async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
32
+ options?.signal?.throwIfAborted();
33
+ let currentUrl = new URL(url).href;
34
+ await validateUrl(currentUrl);
35
+ visited.add(currentUrl);
36
+ const originalRequest = createActivityPubRequest(currentUrl, { userAgent });
37
+ function follow(nextUrl) {
38
+ options?.signal?.throwIfAborted();
39
+ if (redirected >= DEFAULT_MAX_REDIRECTION) throw new FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
40
+ if (visited.has(nextUrl)) throw new FetchError(currentUrl, `Redirect loop detected: ${nextUrl}`);
41
+ redirected++;
42
+ }
43
+ const response = await doubleKnock(originalRequest, identity, {
44
+ specDeterminer,
45
+ log: curry(logRequest)(logger),
46
+ tracerProvider,
47
+ signal: options?.signal,
48
+ validateRedirect: async (nextUrl) => {
49
+ follow(nextUrl);
50
+ await validateUrl(nextUrl);
51
+ visited.add(nextUrl);
52
+ currentUrl = nextUrl;
53
+ }
54
+ });
55
+ return getRemoteDocument(currentUrl, response, (alternateUrl) => {
56
+ follow(alternateUrl);
57
+ return load(alternateUrl, options, redirected, visited);
58
+ });
59
+ }
60
+ async function validateUrl(url) {
61
+ if (!allowPrivateAddress) try {
62
+ await validatePublicUrl(url);
63
+ } catch (error) {
64
+ if (error instanceof UrlError) if (error.reason === "dns") logger.debug("DNS lookup failed for {url}", {
65
+ url,
66
+ error
67
+ });
68
+ else logger.error("Disallowed private URL: {url}", {
69
+ url,
70
+ error
71
+ });
72
+ throw error;
73
+ }
74
+ }
75
+ return (url, options) => load(url, options);
76
+ }
77
+ //#endregion
78
+ export { getAuthenticatedDocumentLoader as t };
@@ -2,10 +2,10 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
5
- import { a as assertExists } from "../std__assert-Duiq_YC9.mjs";
5
+ import { i as assertExists } from "../std__assert-BUOJXi-Z.mjs";
6
6
  import { t as assertThrows } from "../assert_throws-4NwKEy2q.mjs";
7
7
  import { t as MemoryKvStore } from "../kv-BrZHNugx.mjs";
8
- import { n as createFederationBuilder } from "../builder-BatYe9a2.mjs";
8
+ import { n as createFederationBuilder } from "../builder-DsBAef6b.mjs";
9
9
  import { test } from "@fedify/fixture";
10
10
  import { Activity, Note, Person } from "@fedify/vocab";
11
11
  //#region src/federation/builder.test.ts
@@ -2,7 +2,7 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
5
- import "../std__assert-Duiq_YC9.mjs";
5
+ import "../std__assert-BUOJXi-Z.mjs";
6
6
  import { n as digest, t as buildCollectionSynchronizationHeader } from "../collection-ChgDTHLz.mjs";
7
7
  import { test } from "@fedify/fixture";
8
8
  import { decodeHex } from "byte-encodings/hex";
@@ -3,18 +3,18 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { n as createRequestContext, t as createInboxContext } from "../context-Juj6bdHC.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
6
- import "../std__assert-Duiq_YC9.mjs";
6
+ import "../std__assert-BUOJXi-Z.mjs";
7
7
  import { t as assertRejects } from "../assert_rejects-B-qJtC9Z.mjs";
8
8
  import { t as assert } from "../assert-BhQd8QHP.mjs";
9
9
  import { r as parseAcceptSignature } from "../accept-BavzedPB.mjs";
10
- import { a as generateCryptoKeyPair } from "../key-A8SuFznD.mjs";
11
- import { s as signRequest } from "../http-UyebnM2c.mjs";
10
+ import { a as generateCryptoKeyPair } from "../key-x9f-H7eO.mjs";
11
+ import { s as signRequest } from "../http-B5qAeoJd.mjs";
12
12
  import { a as rsaPrivateKey3, c as rsaPublicKey3, s as rsaPublicKey2 } from "../keys-CtZLJq76.mjs";
13
- import { a as compactJsonLd, f as signJsonLd } from "../ld-BSvHU-S0.mjs";
14
- import { n as signObject } from "../proof-_AyA4r7z.mjs";
13
+ import { a as compactJsonLd, f as signJsonLd } from "../ld-cfUwoi0C.mjs";
14
+ import { n as signObject } from "../proof-CI4rsVLu.mjs";
15
15
  import { t as MemoryKvStore } from "../kv-BrZHNugx.mjs";
16
- import { a as createFederation, c as handleCollection, d as handleObject, f as respondWithObject, l as handleCustomCollection, p as respondWithObjectIfAcceptable, s as handleActor, u as handleInbox } from "../middleware-Dy18K1xP.mjs";
17
- import { t as InboxListenerSet } from "../inbox-ByQ2iD14.mjs";
16
+ import { a as createFederation, c as handleCollection, d as handleObject, f as respondWithObject, l as handleCustomCollection, p as respondWithObjectIfAcceptable, s as handleActor, u as handleInbox } from "../middleware-DHHRwSZH.mjs";
17
+ import { t as InboxListenerSet } from "../inbox-BSyjaFbo.mjs";
18
18
  import { createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
19
19
  import { Create, CryptographicKey, Multikey, Note, Person } from "@fedify/vocab";
20
20
  import { FetchError } from "@fedify/vocab-runtime";
@@ -2,11 +2,11 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
5
- import "../std__assert-Duiq_YC9.mjs";
5
+ import "../std__assert-BUOJXi-Z.mjs";
6
6
  import { n as ed25519PrivateKey, r as ed25519PublicKey, t as ed25519Multikey } from "../keys-CtZLJq76.mjs";
7
- import { n as signObject } from "../proof-_AyA4r7z.mjs";
7
+ import { n as signObject } from "../proof-CI4rsVLu.mjs";
8
8
  import { t as MemoryKvStore } from "../kv-BrZHNugx.mjs";
9
- import { a as createFederation } from "../middleware-Dy18K1xP.mjs";
9
+ import { a as createFederation } from "../middleware-DHHRwSZH.mjs";
10
10
  import { mockDocumentLoader, test } from "@fedify/fixture";
11
11
  import { Create, Follow, Person } from "@fedify/vocab";
12
12
  //#region src/federation/idempotency.test.ts
@@ -3,7 +3,7 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
5
5
  import { t as assertThrows } from "../assert_throws-4NwKEy2q.mjs";
6
- import { t as InboxListenerSet } from "../inbox-ByQ2iD14.mjs";
6
+ import { t as InboxListenerSet } from "../inbox-BSyjaFbo.mjs";
7
7
  import { test } from "@fedify/fixture";
8
8
  import { Activity, Create, Invite, Offer, Update } from "@fedify/vocab";
9
9
  //#region src/federation/inbox.test.ts
@@ -2,7 +2,7 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
5
- import "../std__assert-Duiq_YC9.mjs";
5
+ import "../std__assert-BUOJXi-Z.mjs";
6
6
  import { t as MemoryKvStore } from "../kv-BrZHNugx.mjs";
7
7
  import { test } from "@fedify/fixture";
8
8
  //#region src/federation/kv.test.ts
@@ -4,23 +4,24 @@ globalThis.addEventListener = () => {};
4
4
  import { t as esm_default } from "../esm-DVILvP5e.mjs";
5
5
  import { n as RouterError } from "../router-CrMLXoOr.mjs";
6
6
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
7
- import { n as assertStrictEquals } from "../std__assert-Duiq_YC9.mjs";
7
+ import { n as assertStrictEquals } from "../std__assert-BUOJXi-Z.mjs";
8
8
  import { n as assertFalse, t as assertRejects } from "../assert_rejects-B-qJtC9Z.mjs";
9
9
  import { n as assertInstanceOf, t as assert } from "../assert-BhQd8QHP.mjs";
10
10
  import { t as assertThrows } from "../assert_throws-4NwKEy2q.mjs";
11
11
  import { t as assertNotEquals } from "../assert_not_equals--wG9hV7u.mjs";
12
- import { l as verifyRequest, s as signRequest } from "../http-UyebnM2c.mjs";
12
+ import { l as verifyRequest, s as signRequest } from "../http-B5qAeoJd.mjs";
13
13
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-CtZLJq76.mjs";
14
- import { t as getAuthenticatedDocumentLoader } from "../docloader-DdEK2tXP.mjs";
15
- import { a as compactJsonLd, f as signJsonLd, m as verifyJsonLd, s as detachSignature } from "../ld-BSvHU-S0.mjs";
16
- import { t as doesActorOwnKey } from "../owner-CaaCVHsG.mjs";
17
- import { n as signObject, r as verifyObject } from "../proof-_AyA4r7z.mjs";
14
+ import { t as getAuthenticatedDocumentLoader } from "../docloader-DX96uPUj.mjs";
15
+ import { a as compactJsonLd, f as signJsonLd, m as verifyJsonLd, s as detachSignature } from "../ld-cfUwoi0C.mjs";
16
+ import { t as doesActorOwnKey } from "../owner-DklgvZXn.mjs";
17
+ import { n as signObject, r as verifyObject } from "../proof-CI4rsVLu.mjs";
18
18
  import { t as MemoryKvStore } from "../kv-BrZHNugx.mjs";
19
- import { a as createFederation, i as KvSpecDeterminer, n as FederationImpl, r as InboxContextImpl, t as ContextImpl } from "../middleware-Dy18K1xP.mjs";
19
+ import { a as createFederation, i as KvSpecDeterminer, n as FederationImpl, r as InboxContextImpl, t as ContextImpl } from "../middleware-DHHRwSZH.mjs";
20
20
  import { mockDocumentLoader, test } from "@fedify/fixture";
21
21
  import * as vocab from "@fedify/vocab";
22
22
  import { Create, Offer, Person, getTypeId, lookupObject } from "@fedify/vocab";
23
23
  import { FetchError, UrlError, getDocumentLoader } from "@fedify/vocab-runtime";
24
+ import dns from "node:dns/promises";
24
25
  //#region ../fixture/src/fixtures/example.com/create.json
25
26
  var id$2 = "https://example.com/create";
26
27
  var create_default = {
@@ -717,6 +718,31 @@ test({
717
718
  esm_default.hardReset();
718
719
  }
719
720
  });
721
+ test("Federation.fetch() [rfc9421] multiple POST signatures", async (t) => {
722
+ for (const validSecond of [true, false]) await t.step(validSecond ? "valid second signature" : "both signatures invalid", async () => {
723
+ const inbox = [];
724
+ const federation = createFederation({
725
+ kv: new MemoryKvStore(),
726
+ documentLoaderFactory: () => mockDocumentLoader,
727
+ contextLoaderFactory: () => mockDocumentLoader
728
+ });
729
+ federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => new vocab.Person({ id: ctx.getActorUri(identifier) }));
730
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(vocab.Create, (_ctx, activity) => {
731
+ inbox.push(activity.id.href);
732
+ });
733
+ const signed = await signRequest(new Request("https://example.com/inbox", {
734
+ method: "POST",
735
+ headers: { "Content-Type": "application/activity+json" },
736
+ body: JSON.stringify(create_default)
737
+ }), rsaPrivateKey2, rsaPublicKey2.id, { spec: "rfc9421" });
738
+ const input = signed.headers.get("Signature-Input");
739
+ const signature = signed.headers.get("Signature");
740
+ signed.headers.set("Signature-Input", `${input}, ${input.replace(/^sig1=/, "sig2=")}`);
741
+ signed.headers.set("Signature", `sig1=:AAAAAA==:, ${validSecond ? signature.replace(/^sig1=/, "sig2=") : "sig2=:AAAAAA==:"}`);
742
+ assertEquals((await federation.fetch(signed, { contextData: void 0 })).status, validSecond ? 202 : 401);
743
+ assertEquals(inbox, validSecond ? [id$2] : []);
744
+ });
745
+ });
720
746
  test("Federation.fetch()", async (t) => {
721
747
  esm_default.spyGlobal();
722
748
  esm_default.get("https://example.com/key2", {
@@ -1426,6 +1452,7 @@ test("FederationImpl.processQueuedTask()", async (t) => {
1426
1452
  const kv = new MemoryKvStore();
1427
1453
  const queuedMessages = [];
1428
1454
  const federation = new FederationImpl({
1455
+ allowPrivateAddress: true,
1429
1456
  kv,
1430
1457
  queue: {
1431
1458
  nativeRetrial: true,
@@ -1488,6 +1515,7 @@ test("FederationImpl.processQueuedTask()", async (t) => {
1488
1515
  const kv = new MemoryKvStore();
1489
1516
  let queuedMessages = [];
1490
1517
  const federation = new FederationImpl({
1518
+ allowPrivateAddress: true,
1491
1519
  kv,
1492
1520
  queue: {
1493
1521
  enqueue(message, _options) {
@@ -2982,6 +3010,105 @@ test("FederationImpl.processQueuedTask()", async (t) => {
2982
3010
  assertEquals(queuedMessages, []);
2983
3011
  });
2984
3012
  });
3013
+ test({
3014
+ name: "FederationImpl.processQueuedTask() retries DNS failures",
3015
+ ignore: "Deno" in globalThis && (await Deno.permissions.query({ name: "net" })).state !== "granted",
3016
+ async fn(t) {
3017
+ for (const redirected of [false, true]) await t.step(`redirected: ${redirected}`, async () => {
3018
+ const destination = "https://delivery.invalid/inbox";
3019
+ const publicInbox = "https://8.8.8.8/inbox";
3020
+ const queuedMessages = [];
3021
+ const delays = [];
3022
+ const errors = [];
3023
+ const activities = [];
3024
+ const attempts = [];
3025
+ const delay = Temporal.Duration.from({ seconds: 5 });
3026
+ const federation = new FederationImpl({
3027
+ kv: new MemoryKvStore(),
3028
+ queue: {
3029
+ enqueue(message, options) {
3030
+ queuedMessages.push(message);
3031
+ delays.push(options?.delay);
3032
+ return Promise.resolve();
3033
+ },
3034
+ listen() {
3035
+ return Promise.resolve();
3036
+ }
3037
+ },
3038
+ documentLoaderFactory: () => mockDocumentLoader,
3039
+ contextLoaderFactory: () => mockDocumentLoader,
3040
+ onOutboxError(error, activity) {
3041
+ errors.push(error);
3042
+ activities.push(activity);
3043
+ },
3044
+ outboxRetryPolicy(options) {
3045
+ attempts.push(options.attempts);
3046
+ return delay;
3047
+ }
3048
+ });
3049
+ const message = {
3050
+ type: "outbox",
3051
+ id: crypto.randomUUID(),
3052
+ baseUrl: "https://example.com",
3053
+ keys: [],
3054
+ activity: {
3055
+ "@context": "https://www.w3.org/ns/activitystreams",
3056
+ type: "Create",
3057
+ id: "https://example.com/activities/dns-failure",
3058
+ actor: "https://example.com/users/alice",
3059
+ object: {
3060
+ type: "Note",
3061
+ content: "test"
3062
+ }
3063
+ },
3064
+ activityType: "https://www.w3.org/ns/activitystreams#Create",
3065
+ inbox: redirected ? publicInbox : destination,
3066
+ sharedInbox: false,
3067
+ started: (/* @__PURE__ */ new Date()).toISOString(),
3068
+ attempt: 0,
3069
+ headers: {},
3070
+ traceContext: {}
3071
+ };
3072
+ const originalLookup = dns.lookup;
3073
+ const resolverError = /* @__PURE__ */ new Error("Resolver unavailable");
3074
+ const lookups = [];
3075
+ dns.lookup = ((hostname) => {
3076
+ lookups.push(hostname);
3077
+ return Promise.reject(resolverError);
3078
+ });
3079
+ try {
3080
+ esm_default.mockGlobal().catch(202);
3081
+ if (redirected) esm_default.route(publicInbox, {
3082
+ status: 307,
3083
+ headers: { Location: destination }
3084
+ });
3085
+ await federation.processQueuedTask(void 0, message);
3086
+ assertEquals(errors.length, 1);
3087
+ const error = errors[0];
3088
+ assertInstanceOf(error, FetchError);
3089
+ assertEquals(error.url.href, destination);
3090
+ assertInstanceOf(error.cause, UrlError);
3091
+ assertEquals(error.cause.reason, "dns");
3092
+ assertStrictEquals(error.cause.cause, resolverError);
3093
+ assertEquals(activities.length, 1);
3094
+ assertInstanceOf(activities[0], Create);
3095
+ assertEquals(activities[0].id?.href, "https://example.com/activities/dns-failure");
3096
+ assertEquals(attempts, [0]);
3097
+ assertEquals(queuedMessages, [{
3098
+ ...message,
3099
+ attempt: 1
3100
+ }]);
3101
+ assertEquals(delays, [delay]);
3102
+ assertEquals(lookups, ["delivery.invalid"]);
3103
+ assertEquals(esm_default.callHistory.calls(destination).length, 0);
3104
+ assertEquals(esm_default.callHistory.calls().length, redirected ? 1 : 0);
3105
+ } finally {
3106
+ dns.lookup = originalLookup;
3107
+ esm_default.hardReset();
3108
+ }
3109
+ });
3110
+ }
3111
+ });
2985
3112
  test("FederationImpl.processQueuedTask() permanent failure", async (t) => {
2986
3113
  esm_default.spyGlobal();
2987
3114
  esm_default.post("https://gone.example/inbox", {
@@ -2,7 +2,7 @@ const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
4
  require("../chunk-DDcVe30Y.cjs");
5
- const require_middleware = require("../middleware-CCYqHlzn.cjs");
5
+ const require_middleware = require("../middleware-XXuSt_6b.cjs");
6
6
  let es_toolkit = require("es-toolkit");
7
7
  //#region src/federation/kv.ts
8
8
  /**
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { a as createExponentialBackoffPolicy, c as buildCollectionSynchronizationHeader, d as Router, f as RouterError, i as SendActivityError, l as digest, o as respondWithObject, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as createFederationBuilder } from "../middleware-Do50b12a.js";
3
+ import { a as createExponentialBackoffPolicy, c as buildCollectionSynchronizationHeader, d as Router, f as RouterError, i as SendActivityError, l as digest, o as respondWithObject, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as createFederationBuilder } from "../middleware-CP35ElZv.js";
4
4
  import { isEqual } from "es-toolkit";
5
5
  //#region src/federation/kv.ts
6
6
  /**
@@ -2,7 +2,7 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
5
- import { i as assertGreaterOrEqual, r as assertGreater } from "../std__assert-Duiq_YC9.mjs";
5
+ import { r as assertGreaterOrEqual } from "../std__assert-BUOJXi-Z.mjs";
6
6
  import { n as assertFalse } from "../assert_rejects-B-qJtC9Z.mjs";
7
7
  import { t as assert } from "../assert-BhQd8QHP.mjs";
8
8
  import { test } from "@fedify/fixture";
@@ -248,50 +248,105 @@ test("InProcessMessageQueue", async (t) => {
248
248
  const listening = mq.listen((message) => {
249
249
  messages.push(message);
250
250
  }, controller);
251
- await t.step("enqueue()", async () => {
252
- await mq.enqueue("Hello, world!");
253
- });
254
- await waitFor(() => messages.length > 0, 15e3);
255
- await t.step("listen()", () => {
256
- assertEquals(messages, ["Hello, world!"]);
257
- });
258
- let started = 0;
259
- await t.step("enqueue() with delay", async () => {
260
- started = Date.now();
261
- await mq.enqueue("Delayed message", { delay: Temporal.Duration.from({ seconds: 3 }) });
262
- assertEquals(messages, ["Hello, world!"]);
263
- });
264
- await waitFor(() => messages.length > 1, 15e3);
265
- await t.step("listen() with delay", () => {
266
- assertEquals(messages, ["Hello, world!", "Delayed message"]);
267
- assertGreater(Date.now() - started, 3e3);
268
- });
269
- while (messages.length > 0) messages.pop();
270
- await t.step("enqueueMany()", async () => {
271
- const testMessages = Array.from({ length: 5 }, (_, i) => `Batch message ${i}!`);
272
- await mq.enqueueMany(testMessages);
273
- });
274
- await waitFor(() => messages.length >= 5, 15e3);
275
- await t.step("listen() [multiple]", () => {
276
- assertEquals(messages.length, 5);
277
- for (let i = 0; i < 5; i++) assertEquals(messages[i], `Batch message ${i}!`);
278
- });
279
- while (messages.length > 0) messages.pop();
280
- started = 0;
281
- await t.step("enqueueMany() with delay", async () => {
282
- started = Date.now();
283
- const testMessages = Array.from({ length: 3 }, (_, i) => `Delayed batch ${i}!`);
284
- await mq.enqueueMany(testMessages, { delay: Temporal.Duration.from({ seconds: 2 }) });
285
- assertEquals(messages.length, 0);
251
+ try {
252
+ await t.step("enqueue()", async () => {
253
+ await mq.enqueue("Hello, world!");
254
+ });
255
+ await waitFor(() => messages.length > 0, 15e3);
256
+ await t.step("listen()", () => {
257
+ assertEquals(messages, ["Hello, world!"]);
258
+ });
259
+ await t.step("enqueue() with delay", async () => {
260
+ const { timers, enqueued } = captureTimers(() => mq.enqueue("Delayed message", { delay: Temporal.Duration.from({ seconds: 3 }) }));
261
+ await enqueued;
262
+ assertEquals(timers.length, 1);
263
+ assertEquals(timers[0].delay, 3e3);
264
+ await mq.enqueue("Single delay marker");
265
+ await waitFor(() => messages.includes("Single delay marker"), 15e3);
266
+ assertEquals(messages, ["Hello, world!", "Single delay marker"]);
267
+ timers[0].callback();
268
+ });
269
+ await waitFor(() => messages.length >= 3, 15e3);
270
+ await t.step("listen() with delay", () => {
271
+ assertEquals(messages, [
272
+ "Hello, world!",
273
+ "Single delay marker",
274
+ "Delayed message"
275
+ ]);
276
+ });
277
+ while (messages.length > 0) messages.pop();
278
+ await t.step("enqueueMany()", async () => {
279
+ const testMessages = Array.from({ length: 5 }, (_, i) => `Batch message ${i}!`);
280
+ await mq.enqueueMany(testMessages);
281
+ });
282
+ await waitFor(() => messages.length >= 5, 15e3);
283
+ await t.step("listen() [multiple]", () => {
284
+ assertEquals(messages.length, 5);
285
+ for (let i = 0; i < 5; i++) assertEquals(messages[i], `Batch message ${i}!`);
286
+ });
287
+ while (messages.length > 0) messages.pop();
288
+ await t.step("enqueueMany() with delay", async () => {
289
+ const testMessages = Array.from({ length: 3 }, (_, i) => `Delayed batch ${i}!`);
290
+ const { timers, enqueued } = captureTimers(() => mq.enqueueMany(testMessages, { delay: Temporal.Duration.from({ seconds: 2 }) }));
291
+ await enqueued;
292
+ assertEquals(timers.length, 1);
293
+ assertEquals(timers[0].delay, 2e3);
294
+ await mq.enqueue("Batch delay marker");
295
+ await waitFor(() => messages.includes("Batch delay marker"), 15e3);
296
+ assertEquals(messages, ["Batch delay marker"]);
297
+ timers[0].callback();
298
+ });
299
+ await waitFor(() => messages.length >= 4, 15e3);
300
+ await t.step("listen() [delayed multiple]", () => {
301
+ assertEquals(messages.length, 4);
302
+ assertEquals(messages[0], "Batch delay marker");
303
+ for (let i = 0; i < 3; i++) assertEquals(messages[i + 1], `Delayed batch ${i}!`);
304
+ });
305
+ } finally {
306
+ controller.abort();
307
+ await listening;
308
+ }
309
+ });
310
+ function captureTimers(enqueue) {
311
+ const timers = [];
312
+ const original = globalThis.setTimeout;
313
+ globalThis.setTimeout = ((callback, delay) => {
314
+ timers.push({
315
+ callback,
316
+ delay
317
+ });
318
+ return 0;
286
319
  });
287
- await waitFor(() => messages.length >= 3, 15e3);
288
- await t.step("listen() [delayed multiple]", () => {
289
- assertEquals(messages.length, 3);
290
- assertGreater(Date.now() - started, 2e3);
291
- for (let i = 0; i < 3; i++) assertEquals(messages[i], `Delayed batch ${i}!`);
320
+ try {
321
+ return {
322
+ timers,
323
+ enqueued: enqueue()
324
+ };
325
+ } finally {
326
+ globalThis.setTimeout = original;
327
+ }
328
+ }
329
+ test("InProcessMessageQueue real delay", async (t) => {
330
+ for (const batch of [false, true]) await t.step(batch ? "enqueueMany()" : "enqueue()", async () => {
331
+ const mq = new InProcessMessageQueue();
332
+ const messages = [];
333
+ const controller = new AbortController();
334
+ const listening = mq.listen((message) => {
335
+ messages.push(message);
336
+ }, controller);
337
+ try {
338
+ const delayed = batch ? ["Delayed 1", "Delayed 2"] : ["Delayed"];
339
+ const options = { delay: Temporal.Duration.from({ milliseconds: 100 }) };
340
+ if (batch) await mq.enqueueMany(delayed, options);
341
+ else await mq.enqueue(delayed[0], options);
342
+ await mq.enqueue("Immediate");
343
+ await waitFor(() => messages.length >= delayed.length + 1, 15e3);
344
+ assertEquals(messages, ["Immediate", ...delayed]);
345
+ } finally {
346
+ controller.abort();
347
+ await listening;
348
+ }
292
349
  });
293
- controller.abort();
294
- await listening;
295
350
  });
296
351
  test("InProcessMessageQueue orderingKey", async (t) => {
297
352
  const mq = new InProcessMessageQueue();
@@ -1,7 +1,7 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import "../std__assert-Duiq_YC9.mjs";
4
+ import "../std__assert-BUOJXi-Z.mjs";
5
5
  import { n as assertFalse } from "../assert_rejects-B-qJtC9Z.mjs";
6
6
  import { t as assert } from "../assert-BhQd8QHP.mjs";
7
7
  import { t as acceptsJsonLd } from "../negotiation-BehA2uul.mjs";
@@ -3,7 +3,7 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { n as RouterError, t as Router } from "../router-CrMLXoOr.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
6
- import "../std__assert-Duiq_YC9.mjs";
6
+ import "../std__assert-BUOJXi-Z.mjs";
7
7
  import { n as assertFalse } from "../assert_rejects-B-qJtC9Z.mjs";
8
8
  import { t as assert } from "../assert-BhQd8QHP.mjs";
9
9
  import { t as assertThrows } from "../assert_throws-4NwKEy2q.mjs";