@fedify/fedify 2.2.3-dev.1098 → 2.2.3

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 (60) hide show
  1. package/dist/{builder-mqtih91o.mjs → builder-CaVN56-q.mjs} +2 -2
  2. package/dist/compat/mod.d.cts +1 -1
  3. package/dist/compat/mod.d.ts +1 -1
  4. package/dist/compat/transformers.test.mjs +1 -1
  5. package/dist/{context-BPMgyX7m.d.ts → context-BU-1O90h.d.ts} +48 -6
  6. package/dist/{context-DwkhwUX9.d.cts → context-DVA8wHZ0.d.cts} +48 -6
  7. package/dist/{deno-CziVFvS6.mjs → deno-DMg4SgCb.mjs} +1 -1
  8. package/dist/{docloader-fI9DeYyB.mjs → docloader-Da15YRxG.mjs} +2 -2
  9. package/dist/federation/builder.test.mjs +1 -1
  10. package/dist/federation/handler.test.mjs +1363 -43
  11. package/dist/federation/idempotency.test.mjs +2 -2
  12. package/dist/federation/middleware.test.mjs +1584 -80
  13. package/dist/federation/mod.cjs +1 -1
  14. package/dist/federation/mod.d.cts +2 -2
  15. package/dist/federation/mod.d.ts +2 -2
  16. package/dist/federation/mod.js +1 -1
  17. package/dist/federation/retry.test.mjs +1 -1
  18. package/dist/federation/send.test.mjs +3 -3
  19. package/dist/federation/temporal.test.d.mts +2 -0
  20. package/dist/federation/temporal.test.mjs +71 -0
  21. package/dist/federation/webfinger.test.mjs +1 -1
  22. package/dist/{http-D8qsXrUS.js → http-BPPaA2uz.js} +1 -1
  23. package/dist/{http-BDCGf4Ac.mjs → http-C_edJspG.mjs} +2 -2
  24. package/dist/{http-kPc328Pc.cjs → http-Cl0Q2bUO.cjs} +1 -1
  25. package/dist/{key-D3TgMhcs.mjs → key-BAQuZEU1.mjs} +1 -1
  26. package/dist/{kv-cache-D_eVhctK.js → kv-cache-C4DGZ_t4.js} +1 -1
  27. package/dist/{kv-cache-zxW74Wfd.cjs → kv-cache-DmGi6uC-.cjs} +1 -1
  28. package/dist/ld-tusP_XxG.mjs +573 -0
  29. package/dist/{middleware-xR9KxICq.cjs → middleware-0V-9qj7m.cjs} +399 -73
  30. package/dist/{middleware-gXlDLkok.js → middleware-Ar1QOOPG.js} +396 -71
  31. package/dist/{middleware-2gmMVy8b.mjs → middleware-D9k0Knum.mjs} +314 -78
  32. package/dist/{middleware-BuOXw_hM.cjs → middleware-OQPBzyvx.cjs} +1 -1
  33. package/dist/{middleware-CfaiRKQ9.mjs → middleware-madKLp2f.mjs} +1 -1
  34. package/dist/{mod-CNAHY39V.d.ts → mod-BVt6iTmH.d.ts} +1 -1
  35. package/dist/{mod-Bi6WOdti.d.cts → mod-q-NFLW6B.d.cts} +1 -1
  36. package/dist/mod.cjs +4 -4
  37. package/dist/mod.d.cts +2 -2
  38. package/dist/mod.d.ts +2 -2
  39. package/dist/mod.js +4 -4
  40. package/dist/nodeinfo/handler.test.mjs +1 -1
  41. package/dist/{owner-DBSV2TSl.mjs → owner-DRHNR5YO.mjs} +2 -2
  42. package/dist/{proof-tz91vdtN.mjs → proof-DLhLRv3m.mjs} +2 -2
  43. package/dist/{proof-CZDkoeWG.cjs → proof-DfrItHmh.cjs} +351 -3
  44. package/dist/{proof-z93OkIov.js → proof-SQ4cQs3A.js} +298 -4
  45. package/dist/{send-CNjG31rJ.mjs → send-C7tim5U9.mjs} +2 -2
  46. package/dist/sig/http.test.mjs +2 -2
  47. package/dist/sig/key.test.mjs +1 -1
  48. package/dist/sig/ld.test.mjs +558 -2
  49. package/dist/sig/mod.cjs +2 -2
  50. package/dist/sig/mod.js +2 -2
  51. package/dist/sig/owner.test.mjs +1 -1
  52. package/dist/sig/proof.test.mjs +1 -1
  53. package/dist/temporal-LL61Ddf2.mjs +95 -0
  54. package/dist/testing/mod.d.mts +48 -6
  55. package/dist/utils/docloader.test.mjs +2 -2
  56. package/dist/utils/mod.cjs +1 -1
  57. package/dist/utils/mod.js +1 -1
  58. package/package.json +5 -5
  59. package/dist/ld-D_u8mdpv.mjs +0 -279
  60. /package/dist/{retry-bMXBL97A.mjs → retry-v_sGLH1d.mjs} +0 -0
@@ -11,17 +11,17 @@ import { t as assertThrows } from "../assert_throws-4NwKEy2q.mjs";
11
11
  import { t as assertNotEquals } from "../assert_not_equals--wG9hV7u.mjs";
12
12
  import { t as assertStrictEquals } from "../assert_strict_equals-Dmjbg-bA.mjs";
13
13
  import { t as assert } from "../assert-DikXweDx.mjs";
14
- import { l as verifyRequest, s as signRequest } from "../http-BDCGf4Ac.mjs";
14
+ import { l as verifyRequest, s as signRequest } from "../http-C_edJspG.mjs";
15
15
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-DGu1NFwu.mjs";
16
- import { t as getAuthenticatedDocumentLoader } from "../docloader-fI9DeYyB.mjs";
17
- import { a as signJsonLd, o as verifyJsonLd, r as detachSignature } from "../ld-D_u8mdpv.mjs";
18
- import { t as doesActorOwnKey } from "../owner-DBSV2TSl.mjs";
19
- import { i as verifyObject, r as signObject } from "../proof-tz91vdtN.mjs";
16
+ import { t as getAuthenticatedDocumentLoader } from "../docloader-Da15YRxG.mjs";
17
+ import { a as compactJsonLd, h as verifyJsonLd, p as signJsonLd, s as detachSignature } from "../ld-tusP_XxG.mjs";
18
+ import { t as doesActorOwnKey } from "../owner-DRHNR5YO.mjs";
19
+ import { i as verifyObject, r as signObject } from "../proof-DLhLRv3m.mjs";
20
20
  import { t as MemoryKvStore } from "../kv-rV3vodCc.mjs";
21
- import { i as KvSpecDeterminer, n as FederationImpl, o as createFederation, r as InboxContextImpl, t as ContextImpl } from "../middleware-2gmMVy8b.mjs";
21
+ import { i as KvSpecDeterminer, n as FederationImpl, o as createFederation, r as InboxContextImpl, t as ContextImpl } from "../middleware-D9k0Knum.mjs";
22
22
  import { createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
23
23
  import * as vocab from "@fedify/vocab";
24
- import { getTypeId, lookupObject } from "@fedify/vocab";
24
+ import { Create, Offer, Person, getTypeId, lookupObject } from "@fedify/vocab";
25
25
  import { FetchError, getDocumentLoader } from "@fedify/vocab-runtime";
26
26
  import { configure, reset } from "@logtape/logtape";
27
27
  import serialize from "json-canon";
@@ -1733,6 +1733,54 @@ test("Federation.setOutboxListeners()", async (t) => {
1733
1733
  assertEquals(enqueued[0].actorIds, ["https://remote.example/users/alice"]);
1734
1734
  });
1735
1735
  });
1736
+ test("Federation.fetch() preserves original LD-signed payload for InboxContextImpl.activity", async () => {
1737
+ const remoteContextUrl = "https://remote.example/contexts/ext";
1738
+ const sourceContextLoader = async (resource) => {
1739
+ const url = new URL(resource).href;
1740
+ if (url === remoteContextUrl) return {
1741
+ contextUrl: null,
1742
+ documentUrl: url,
1743
+ document: { "@context": { ext: "https://example.com/ext" } }
1744
+ };
1745
+ return await mockDocumentLoader(url);
1746
+ };
1747
+ const federation = createFederation({
1748
+ kv: new MemoryKvStore(),
1749
+ documentLoaderFactory: () => mockDocumentLoader,
1750
+ contextLoaderFactory: () => sourceContextLoader
1751
+ });
1752
+ federation.setActorDispatcher("/users/{identifier}", (_ctx, identifier) => identifier === "someone" ? new Person({}) : null);
1753
+ let receivedRaw = null;
1754
+ let receivedTyped = null;
1755
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, (ctx, activity) => {
1756
+ receivedRaw = ctx.activity;
1757
+ receivedTyped = activity;
1758
+ });
1759
+ const signed = await signJsonLd({
1760
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1761
+ id: "https://example.com/activities/preserve-raw",
1762
+ type: "Create",
1763
+ actor: "https://example.com/person2",
1764
+ ext: "preserve-me",
1765
+ object: {
1766
+ id: "https://example.com/notes/preserve-raw",
1767
+ type: "Note",
1768
+ attributedTo: "https://example.com/person2",
1769
+ content: "Hello, world!"
1770
+ }
1771
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: sourceContextLoader });
1772
+ const response = await federation.fetch(new Request("https://example.com/inbox", {
1773
+ method: "POST",
1774
+ headers: { "Content-Type": "application/activity+json" },
1775
+ body: JSON.stringify(signed)
1776
+ }), { contextData: void 0 });
1777
+ assertEquals([response.status, await response.text()], [202, ""]);
1778
+ assertEquals(receivedRaw, signed);
1779
+ assertNotEquals(receivedRaw, await compactJsonLd(signed, sourceContextLoader));
1780
+ const delivered = receivedTyped;
1781
+ assert(delivered != null);
1782
+ assertEquals(delivered.id?.href, "https://example.com/activities/preserve-raw");
1783
+ });
1736
1784
  test("Federation.setInboxDispatcher()", async (t) => {
1737
1785
  const kv = new MemoryKvStore();
1738
1786
  await t.step("path match", () => {
@@ -1927,132 +1975,1559 @@ test("FederationImpl.processQueuedTask()", async (t) => {
1927
1975
  const queuedMessages = [];
1928
1976
  const federation = new FederationImpl({
1929
1977
  kv,
1930
- queue: {
1931
- nativeRetrial: true,
1932
- enqueue(message, _options) {
1933
- queuedMessages.push(message);
1934
- return Promise.resolve();
1935
- },
1936
- listen(_handler, _options) {
1937
- return Promise.resolve();
1978
+ queue: {
1979
+ nativeRetrial: true,
1980
+ enqueue(message, _options) {
1981
+ queuedMessages.push(message);
1982
+ return Promise.resolve();
1983
+ },
1984
+ listen(_handler, _options) {
1985
+ return Promise.resolve();
1986
+ }
1987
+ }
1988
+ });
1989
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(vocab.Create, () => {
1990
+ throw new Error("Intended error for testing");
1991
+ });
1992
+ await assertRejects(() => federation.processQueuedTask(void 0, {
1993
+ type: "outbox",
1994
+ id: crypto.randomUUID(),
1995
+ baseUrl: "https://example.com",
1996
+ keys: [],
1997
+ activity: {
1998
+ "@context": "https://www.w3.org/ns/activitystreams",
1999
+ type: "Create",
2000
+ actor: "https://example.com/users/alice",
2001
+ object: {
2002
+ type: "Note",
2003
+ content: "test"
2004
+ }
2005
+ },
2006
+ activityType: "https://www.w3.org/ns/activitystreams#Create",
2007
+ inbox: "https://invalid-domain-that-does-not-exist.example/inbox",
2008
+ sharedInbox: false,
2009
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2010
+ attempt: 0,
2011
+ headers: {},
2012
+ traceContext: {}
2013
+ }), Error);
2014
+ assertEquals(queuedMessages, []);
2015
+ await assertRejects(() => federation.processQueuedTask(void 0, {
2016
+ type: "inbox",
2017
+ id: crypto.randomUUID(),
2018
+ baseUrl: "https://example.com",
2019
+ activity: {
2020
+ "@context": "https://www.w3.org/ns/activitystreams",
2021
+ type: "Create",
2022
+ actor: "https://remote.example/users/alice",
2023
+ object: {
2024
+ type: "Note",
2025
+ content: "Hello world"
2026
+ }
2027
+ },
2028
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2029
+ attempt: 0,
2030
+ identifier: null,
2031
+ traceContext: {}
2032
+ }), Error);
2033
+ assertEquals(queuedMessages, []);
2034
+ });
2035
+ await t.step("with MessageQueue having no nativeRetrial", async () => {
2036
+ const kv = new MemoryKvStore();
2037
+ let queuedMessages = [];
2038
+ const federation = new FederationImpl({
2039
+ kv,
2040
+ queue: {
2041
+ enqueue(message, _options) {
2042
+ queuedMessages.push(message);
2043
+ return Promise.resolve();
2044
+ },
2045
+ listen(_handler, _options) {
2046
+ return Promise.resolve();
2047
+ }
2048
+ }
2049
+ });
2050
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(vocab.Create, () => {
2051
+ throw new Error("Intended error for testing");
2052
+ });
2053
+ const outboxMessage = {
2054
+ type: "outbox",
2055
+ id: crypto.randomUUID(),
2056
+ baseUrl: "https://example.com",
2057
+ keys: [],
2058
+ activity: {
2059
+ "@context": "https://www.w3.org/ns/activitystreams",
2060
+ type: "Create",
2061
+ actor: "https://example.com/users/alice",
2062
+ object: {
2063
+ type: "Note",
2064
+ content: "test"
2065
+ }
2066
+ },
2067
+ activityType: "https://www.w3.org/ns/activitystreams#Create",
2068
+ inbox: "https://invalid-domain-that-does-not-exist.example/inbox",
2069
+ sharedInbox: false,
2070
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2071
+ attempt: 0,
2072
+ headers: {},
2073
+ traceContext: {}
2074
+ };
2075
+ await federation.processQueuedTask(void 0, outboxMessage);
2076
+ assertEquals(queuedMessages, [{
2077
+ ...outboxMessage,
2078
+ attempt: 1
2079
+ }]);
2080
+ queuedMessages = [];
2081
+ const inboxMessage = {
2082
+ type: "inbox",
2083
+ id: crypto.randomUUID(),
2084
+ baseUrl: "https://example.com",
2085
+ activity: {
2086
+ "@context": "https://www.w3.org/ns/activitystreams",
2087
+ type: "Create",
2088
+ actor: "https://remote.example/users/alice",
2089
+ object: {
2090
+ type: "Note",
2091
+ content: "Hello world"
2092
+ }
2093
+ },
2094
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2095
+ attempt: 0,
2096
+ identifier: null,
2097
+ traceContext: {}
2098
+ };
2099
+ await federation.processQueuedTask(void 0, inboxMessage);
2100
+ assertEquals(queuedMessages, [{
2101
+ ...inboxMessage,
2102
+ attempt: 1
2103
+ }]);
2104
+ });
2105
+ await t.step("with restrictive context loader and normalized LD-signed inbox activity", async () => {
2106
+ const remoteContextUrl = "https://remote.example/contexts/ext";
2107
+ const sourceContextLoader = async (resource) => {
2108
+ const url = new URL(resource).href;
2109
+ if (url === remoteContextUrl) return {
2110
+ contextUrl: null,
2111
+ documentUrl: url,
2112
+ document: { "@context": { ext: "https://example.com/ext" } }
2113
+ };
2114
+ return await mockDocumentLoader(url);
2115
+ };
2116
+ const restrictiveContextLoader = async (resource) => {
2117
+ const url = new URL(resource).href;
2118
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2119
+ throw new Error(`Unexpected context: ${url}`);
2120
+ };
2121
+ const kv = new MemoryKvStore();
2122
+ let receivedCount = 0;
2123
+ let received = null;
2124
+ let receivedRaw = null;
2125
+ const federation = new FederationImpl({
2126
+ kv,
2127
+ contextLoaderFactory: () => restrictiveContextLoader
2128
+ });
2129
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, (ctx, activity) => {
2130
+ receivedCount++;
2131
+ receivedRaw = ctx.activity;
2132
+ received = activity;
2133
+ });
2134
+ const signed = await signJsonLd({
2135
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
2136
+ id: "https://remote.example/activities/1",
2137
+ type: "Create",
2138
+ actor: "https://remote.example/users/alice",
2139
+ ext: "preserve-me",
2140
+ object: {
2141
+ id: "https://remote.example/notes/1",
2142
+ type: "Note",
2143
+ attributedTo: "https://remote.example/users/alice",
2144
+ content: "Hello, world!"
2145
+ }
2146
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: sourceContextLoader });
2147
+ const normalizedActivity = await compactJsonLd(signed, sourceContextLoader);
2148
+ const messageId = crypto.randomUUID();
2149
+ await federation.processQueuedTask(void 0, {
2150
+ type: "inbox",
2151
+ id: messageId,
2152
+ baseUrl: "https://example.com",
2153
+ activity: signed,
2154
+ normalizedActivity,
2155
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2156
+ attempt: 0,
2157
+ identifier: null,
2158
+ traceContext: {}
2159
+ });
2160
+ const delivered = received;
2161
+ assert(delivered != null);
2162
+ const deliveredCreate = delivered;
2163
+ assertInstanceOf(deliveredCreate, Create);
2164
+ assertEquals(deliveredCreate.id?.href, "https://remote.example/activities/1");
2165
+ assertEquals(receivedRaw, signed);
2166
+ await federation.processQueuedTask(void 0, {
2167
+ type: "inbox",
2168
+ id: messageId,
2169
+ baseUrl: "https://example.com",
2170
+ activity: signed,
2171
+ normalizedActivity,
2172
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2173
+ attempt: 0,
2174
+ identifier: null,
2175
+ traceContext: {}
2176
+ });
2177
+ assertEquals(receivedCount, 1);
2178
+ });
2179
+ await t.step("cached normalizedActivity is rechecked for unsafe JSON-LD keywords", async () => {
2180
+ const queuedMessages = [];
2181
+ const queue = {
2182
+ enqueue(message, _options) {
2183
+ queuedMessages.push(message);
2184
+ return Promise.resolve();
2185
+ },
2186
+ listen(_handler, _options) {
2187
+ return Promise.resolve();
2188
+ }
2189
+ };
2190
+ const kv = new MemoryKvStore();
2191
+ let receivedCount = 0;
2192
+ let errorCount = 0;
2193
+ const federation = new FederationImpl({
2194
+ kv,
2195
+ queue
2196
+ });
2197
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2198
+ receivedCount++;
2199
+ }).onError(() => {
2200
+ errorCount++;
2201
+ });
2202
+ const signed = await signJsonLd({
2203
+ "@context": "https://www.w3.org/ns/activitystreams",
2204
+ id: "https://remote.example/activities/unsafe-normalized-cache",
2205
+ type: "Create",
2206
+ actor: "https://remote.example/users/alice",
2207
+ object: {
2208
+ id: "https://remote.example/notes/unsafe-normalized-cache",
2209
+ type: "Note",
2210
+ attributedTo: "https://remote.example/users/alice",
2211
+ content: "Hello from unsafe normalized cache"
2212
+ }
2213
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
2214
+ const normalizedActivity = await compactJsonLd(signed, mockDocumentLoader);
2215
+ const tamperedNormalizedActivity = {
2216
+ ...normalizedActivity,
2217
+ signature: {
2218
+ ...normalizedActivity.signature,
2219
+ "@included": [{
2220
+ id: "https://remote.example/activities/inside-signature",
2221
+ type: "Undo"
2222
+ }]
2223
+ }
2224
+ };
2225
+ await federation.processQueuedTask(void 0, {
2226
+ type: "inbox",
2227
+ id: crypto.randomUUID(),
2228
+ baseUrl: "https://example.com",
2229
+ activity: signed,
2230
+ normalizedActivity: tamperedNormalizedActivity,
2231
+ ldSignatureVerified: false,
2232
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2233
+ attempt: 0,
2234
+ identifier: null,
2235
+ traceContext: {}
2236
+ });
2237
+ assertEquals(receivedCount, 0);
2238
+ assertEquals(errorCount, 1);
2239
+ assertEquals(queuedMessages, []);
2240
+ });
2241
+ await t.step("old queued LDS inbox messages without normalizedActivity still work", async () => {
2242
+ const restrictiveContextLoader = async (resource) => {
2243
+ const url = new URL(resource).href;
2244
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2245
+ throw new Error(`Unexpected context: ${url}`);
2246
+ };
2247
+ const kv = new MemoryKvStore();
2248
+ let received = null;
2249
+ const federation = new FederationImpl({
2250
+ kv,
2251
+ contextLoaderFactory: () => restrictiveContextLoader
2252
+ });
2253
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, (_ctx, activity) => {
2254
+ received = activity;
2255
+ });
2256
+ const compacted = await compactJsonLd(await signJsonLd({
2257
+ "@context": "https://www.w3.org/ns/activitystreams",
2258
+ id: "https://remote.example/activities/legacy",
2259
+ type: "Create",
2260
+ actor: "https://remote.example/users/alice",
2261
+ object: {
2262
+ id: "https://remote.example/notes/legacy",
2263
+ type: "Note",
2264
+ attributedTo: "https://remote.example/users/alice",
2265
+ content: "Hello from legacy queue"
2266
+ }
2267
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader }), restrictiveContextLoader);
2268
+ await federation.processQueuedTask(void 0, {
2269
+ type: "inbox",
2270
+ id: crypto.randomUUID(),
2271
+ baseUrl: "https://example.com",
2272
+ activity: compacted,
2273
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2274
+ attempt: 0,
2275
+ identifier: null,
2276
+ traceContext: {}
2277
+ });
2278
+ assert(received != null);
2279
+ assertEquals(received.id?.href, "https://remote.example/activities/legacy");
2280
+ });
2281
+ await t.step("queued signature-bearing non-LDS inbox messages keep parse-time normalization contexts", async () => {
2282
+ const signingContextLoader = async (resource) => {
2283
+ const url = new URL(resource).href;
2284
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1" || url === "https://w3id.org/security/v1" || url === "https://w3id.org/security/data-integrity/v1") return await mockDocumentLoader(url);
2285
+ throw new Error(`Unexpected context: ${url}`);
2286
+ };
2287
+ const processingContextLoader = async (resource) => {
2288
+ const url = new URL(resource).href;
2289
+ if (url === "https://w3id.org/identity/v1" || url === "https://w3id.org/security/v1" || url === "https://w3id.org/security/data-integrity/v1") throw new Error("queued non-LDS signed payloads should parse with the normalization loader's built-in signature contexts");
2290
+ return await signingContextLoader(resource);
2291
+ };
2292
+ const kv = new MemoryKvStore();
2293
+ let received = null;
2294
+ let receivedRaw = null;
2295
+ const federation = new FederationImpl({
2296
+ kv,
2297
+ contextLoaderFactory: () => processingContextLoader
2298
+ });
2299
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, (ctx, activity) => {
2300
+ receivedRaw = ctx.activity;
2301
+ received = activity;
2302
+ });
2303
+ const signed = await signJsonLd({
2304
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
2305
+ id: "https://remote.example/activities/non-lds-queued-signature",
2306
+ type: "Create",
2307
+ actor: "https://remote.example/users/alice",
2308
+ object: {
2309
+ id: "https://remote.example/notes/non-lds-queued-signature",
2310
+ type: "Note",
2311
+ attributedTo: "https://remote.example/users/alice",
2312
+ content: "Hello from non-LDS queued signature"
2313
+ }
2314
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: signingContextLoader });
2315
+ const signedPayload = signed;
2316
+ assert(Array.isArray(signedPayload["@context"]) && signedPayload["@context"].includes("https://w3id.org/security/v1"));
2317
+ await federation.processQueuedTask(void 0, {
2318
+ type: "inbox",
2319
+ id: crypto.randomUUID(),
2320
+ baseUrl: "https://example.com",
2321
+ activity: signed,
2322
+ ldSignatureVerified: false,
2323
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2324
+ attempt: 0,
2325
+ identifier: null,
2326
+ traceContext: {}
2327
+ });
2328
+ if (received == null) throw new Error("Inbox activity not delivered.");
2329
+ assertEquals(received.id?.href, "https://remote.example/activities/non-lds-queued-signature");
2330
+ assertEquals(receivedRaw, signed);
2331
+ });
2332
+ await t.step("queued signature-bearing non-LDS inbox messages reuse normalizedActivity for custom contexts", async () => {
2333
+ const remoteContextUrl = "https://remote.example/contexts/ext";
2334
+ const sourceContextLoader = async (resource) => {
2335
+ const url = new URL(resource).href;
2336
+ if (url === remoteContextUrl) return {
2337
+ contextUrl: null,
2338
+ documentUrl: url,
2339
+ document: { "@context": { ext: "https://example.com/ext" } }
2340
+ };
2341
+ return await mockDocumentLoader(url);
2342
+ };
2343
+ const restrictiveContextLoader = async (resource) => {
2344
+ const url = new URL(resource).href;
2345
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2346
+ throw new Error(`Unexpected context: ${url}`);
2347
+ };
2348
+ const kv = new MemoryKvStore();
2349
+ let received = null;
2350
+ let receivedRaw = null;
2351
+ const federation = new FederationImpl({
2352
+ kv,
2353
+ contextLoaderFactory: () => restrictiveContextLoader
2354
+ });
2355
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, (ctx, activity) => {
2356
+ receivedRaw = ctx.activity;
2357
+ received = activity;
2358
+ });
2359
+ const unsignedBody = {
2360
+ "@context": [
2361
+ remoteContextUrl,
2362
+ "https://www.w3.org/ns/activitystreams",
2363
+ "https://w3id.org/security/v1"
2364
+ ],
2365
+ id: "https://remote.example/activities/non-lds-queued-custom-context",
2366
+ type: "Create",
2367
+ actor: "https://remote.example/users/alice",
2368
+ ext: "preserve-me",
2369
+ object: {
2370
+ id: "https://remote.example/notes/non-lds-queued-custom-context",
2371
+ type: "Note",
2372
+ attributedTo: "https://remote.example/users/alice",
2373
+ content: "Hello from non-LDS queued custom context"
2374
+ },
2375
+ signature: {
2376
+ type: "RsaSignature2017",
2377
+ creator: "not a url",
2378
+ created: "2024-09-12T16:50:46Z",
2379
+ signatureValue: "Zm9v"
2380
+ }
2381
+ };
2382
+ const normalizedActivity = await compactJsonLd(unsignedBody, sourceContextLoader);
2383
+ await federation.processQueuedTask(void 0, {
2384
+ type: "inbox",
2385
+ id: crypto.randomUUID(),
2386
+ baseUrl: "https://example.com",
2387
+ activity: unsignedBody,
2388
+ normalizedActivity,
2389
+ ldSignatureVerified: false,
2390
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2391
+ attempt: 0,
2392
+ identifier: null,
2393
+ traceContext: {}
2394
+ });
2395
+ if (received == null) throw new Error("Inbox activity not delivered.");
2396
+ assertEquals(received.id?.href, "https://remote.example/activities/non-lds-queued-custom-context");
2397
+ assertEquals(receivedRaw, unsignedBody);
2398
+ });
2399
+ await t.step("legacy raw LDS inbox messages without normalizedActivity retry through worker error handling", async () => {
2400
+ const remoteContextUrl = "https://remote.example/contexts/ext";
2401
+ const restrictiveContextLoader = async (resource) => {
2402
+ const url = new URL(resource).href;
2403
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2404
+ throw new Error(`Unexpected context: ${url}`);
2405
+ };
2406
+ const queue = {
2407
+ enqueue(message, _options) {
2408
+ queuedMessages.push(message);
2409
+ return Promise.resolve();
2410
+ },
2411
+ listen(_handler, _options) {
2412
+ return Promise.resolve();
2413
+ }
2414
+ };
2415
+ const kv = new MemoryKvStore();
2416
+ const queuedMessages = [];
2417
+ let errorCount = 0;
2418
+ const federation = new FederationImpl({
2419
+ kv,
2420
+ queue,
2421
+ contextLoaderFactory: () => restrictiveContextLoader
2422
+ });
2423
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2424
+ throw new Error("listener should not run");
2425
+ }).onError(() => {
2426
+ errorCount++;
2427
+ });
2428
+ const signed = await signJsonLd({
2429
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
2430
+ id: "https://remote.example/activities/legacy-raw",
2431
+ type: "Create",
2432
+ actor: "https://remote.example/users/alice",
2433
+ ext: "preserve-me",
2434
+ object: {
2435
+ id: "https://remote.example/notes/legacy-raw",
2436
+ type: "Note",
2437
+ attributedTo: "https://remote.example/users/alice",
2438
+ content: "Hello from raw legacy queue"
2439
+ }
2440
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
2441
+ const url = new URL(resource).href;
2442
+ if (url === remoteContextUrl) return {
2443
+ contextUrl: null,
2444
+ documentUrl: url,
2445
+ document: { "@context": { ext: "https://example.com/ext" } }
2446
+ };
2447
+ return await mockDocumentLoader(url);
2448
+ } });
2449
+ const inboxMessage = {
2450
+ type: "inbox",
2451
+ id: crypto.randomUUID(),
2452
+ baseUrl: "https://example.com",
2453
+ activity: signed,
2454
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2455
+ attempt: 0,
2456
+ identifier: null,
2457
+ traceContext: {}
2458
+ };
2459
+ await federation.processQueuedTask(void 0, inboxMessage);
2460
+ assertEquals(errorCount, 1);
2461
+ assertEquals(queuedMessages.length, 1);
2462
+ const retried = queuedMessages[0];
2463
+ assertEquals(retried.attempt, 1);
2464
+ assertEquals(retried.activity, inboxMessage.activity);
2465
+ });
2466
+ await t.step("without inbox queue retriable inbox parse failures bubble to caller", async () => {
2467
+ const remoteContextUrl = "https://remote.example/contexts/ext";
2468
+ const sourceContextLoader = async (resource) => {
2469
+ const url = new URL(resource).href;
2470
+ if (url === remoteContextUrl) return {
2471
+ contextUrl: null,
2472
+ documentUrl: url,
2473
+ document: { "@context": { ext: "https://example.com/ext" } }
2474
+ };
2475
+ return await mockDocumentLoader(url);
2476
+ };
2477
+ let errorCount = 0;
2478
+ const federation = new FederationImpl({
2479
+ kv: new MemoryKvStore(),
2480
+ contextLoaderFactory: () => async (resource) => {
2481
+ const url = new URL(resource).href;
2482
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2483
+ if (url === remoteContextUrl) throw new Error(`Transient remote context failure: ${url}`);
2484
+ throw new Error(`Unexpected context: ${url}`);
2485
+ }
2486
+ });
2487
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2488
+ throw new Error("listener should not run");
2489
+ }).onError(() => {
2490
+ errorCount++;
2491
+ });
2492
+ const signed = await signJsonLd({
2493
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
2494
+ id: "https://remote.example/activities/manual-retry",
2495
+ type: "Create",
2496
+ actor: "https://remote.example/users/alice",
2497
+ ext: "preserve-me",
2498
+ object: {
2499
+ id: "https://remote.example/notes/manual-retry",
2500
+ type: "Note",
2501
+ attributedTo: "https://remote.example/users/alice",
2502
+ content: "Hello from manual retry queue"
2503
+ }
2504
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: sourceContextLoader });
2505
+ await assertRejects(() => federation.processQueuedTask(void 0, {
2506
+ type: "inbox",
2507
+ id: crypto.randomUUID(),
2508
+ baseUrl: "https://example.com",
2509
+ activity: signed,
2510
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2511
+ attempt: 0,
2512
+ identifier: null,
2513
+ traceContext: {}
2514
+ }), Error);
2515
+ assertEquals(errorCount, 1);
2516
+ });
2517
+ await t.step("legacy raw LDS inbox messages with transient InvalidUrl failures retry", async () => {
2518
+ const remoteContextUrl = "https://remote.example/contexts/ext";
2519
+ const queue = {
2520
+ enqueue(message, _options) {
2521
+ queuedMessages.push(message);
2522
+ return Promise.resolve();
2523
+ },
2524
+ listen(_handler, _options) {
2525
+ return Promise.resolve();
2526
+ }
2527
+ };
2528
+ const kv = new MemoryKvStore();
2529
+ const queuedMessages = [];
2530
+ let errorCount = 0;
2531
+ const federation = new FederationImpl({
2532
+ kv,
2533
+ queue,
2534
+ contextLoaderFactory: () => async (resource) => {
2535
+ const url = new URL(resource).href;
2536
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2537
+ if (url === remoteContextUrl) {
2538
+ const error = /* @__PURE__ */ new Error(`Transient remote context failure: ${url}`);
2539
+ error.name = "jsonld.InvalidUrl";
2540
+ error.details = {
2541
+ code: "loading remote context failed",
2542
+ url
2543
+ };
2544
+ throw error;
2545
+ }
2546
+ throw new Error(`Unexpected context: ${url}`);
2547
+ }
2548
+ });
2549
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2550
+ throw new Error("listener should not run");
2551
+ }).onError(() => {
2552
+ errorCount++;
2553
+ });
2554
+ const signed = await signJsonLd({
2555
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
2556
+ id: "https://remote.example/activities/legacy-invalid-context",
2557
+ type: "Create",
2558
+ actor: "https://remote.example/users/alice",
2559
+ ext: "preserve-me",
2560
+ object: {
2561
+ id: "https://remote.example/notes/legacy-invalid-context",
2562
+ type: "Note",
2563
+ attributedTo: "https://remote.example/users/alice",
2564
+ content: "Hello from invalid legacy queue"
2565
+ }
2566
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
2567
+ const url = new URL(resource).href;
2568
+ if (url === remoteContextUrl) return {
2569
+ contextUrl: null,
2570
+ documentUrl: url,
2571
+ document: { "@context": { ext: "https://example.com/ext" } }
2572
+ };
2573
+ return await mockDocumentLoader(url);
2574
+ } });
2575
+ const inboxMessage = {
2576
+ type: "inbox",
2577
+ id: crypto.randomUUID(),
2578
+ baseUrl: "https://example.com",
2579
+ activity: signed,
2580
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2581
+ attempt: 0,
2582
+ identifier: null,
2583
+ traceContext: {}
2584
+ };
2585
+ await federation.processQueuedTask(void 0, inboxMessage);
2586
+ assertEquals(errorCount, 1);
2587
+ assertEquals(queuedMessages.length, 1);
2588
+ const retried = queuedMessages[0];
2589
+ assertEquals(retried.attempt, 1);
2590
+ assertEquals(retried.activity, inboxMessage.activity);
2591
+ });
2592
+ await t.step("legacy raw LDS inbox messages with opaque context ids retry", async () => {
2593
+ const queue = {
2594
+ enqueue(message, _options) {
2595
+ queuedMessages.push(message);
2596
+ return Promise.resolve();
2597
+ },
2598
+ listen(_handler, _options) {
2599
+ return Promise.resolve();
2600
+ }
2601
+ };
2602
+ const kv = new MemoryKvStore();
2603
+ const queuedMessages = [];
2604
+ let errorCount = 0;
2605
+ const federation = new FederationImpl({
2606
+ kv,
2607
+ queue,
2608
+ contextLoaderFactory: () => async (resource) => {
2609
+ if (resource === "app-context") {
2610
+ const error = /* @__PURE__ */ new Error(`Opaque context backend is unavailable: ${resource}`);
2611
+ error.name = "jsonld.InvalidUrl";
2612
+ error.details = {
2613
+ code: "loading remote context failed",
2614
+ url: resource
2615
+ };
2616
+ throw error;
2617
+ }
2618
+ const url = new URL(resource).href;
2619
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2620
+ throw new Error(`Unexpected context: ${resource}`);
2621
+ }
2622
+ });
2623
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2624
+ throw new Error("listener should not run");
2625
+ }).onError(() => {
2626
+ errorCount++;
2627
+ });
2628
+ const signed = await signJsonLd({
2629
+ "@context": "https://www.w3.org/ns/activitystreams",
2630
+ id: "https://remote.example/activities/legacy-malformed-context",
2631
+ type: "Create",
2632
+ actor: "https://remote.example/users/alice",
2633
+ object: {
2634
+ id: "https://remote.example/notes/legacy-malformed-context",
2635
+ type: "Note",
2636
+ attributedTo: "https://remote.example/users/alice",
2637
+ content: "Hello from malformed legacy queue"
2638
+ }
2639
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
2640
+ const inboxMessage = {
2641
+ type: "inbox",
2642
+ id: crypto.randomUUID(),
2643
+ baseUrl: "https://example.com",
2644
+ activity: {
2645
+ ...signed,
2646
+ "@context": ["app-context", "https://www.w3.org/ns/activitystreams"]
2647
+ },
2648
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2649
+ attempt: 0,
2650
+ identifier: null,
2651
+ traceContext: {}
2652
+ };
2653
+ await federation.processQueuedTask(void 0, inboxMessage);
2654
+ assertEquals(errorCount, 1);
2655
+ assertEquals(queuedMessages, [{
2656
+ ...inboxMessage,
2657
+ attempt: 1
2658
+ }]);
2659
+ });
2660
+ await t.step("legacy raw LDS inbox messages with Invalid URL TypeErrors retry", async () => {
2661
+ const queue = {
2662
+ enqueue(message, _options) {
2663
+ queuedMessages.push(message);
2664
+ return Promise.resolve();
2665
+ },
2666
+ listen(_handler, _options) {
2667
+ return Promise.resolve();
2668
+ }
2669
+ };
2670
+ const kv = new MemoryKvStore();
2671
+ const queuedMessages = [];
2672
+ let errorCount = 0;
2673
+ const federation = new FederationImpl({
2674
+ kv,
2675
+ queue,
2676
+ contextLoaderFactory: () => async (resource) => {
2677
+ if (resource === "app:context") throw new TypeError(`Invalid URL: ${resource}`);
2678
+ const url = new URL(resource).href;
2679
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2680
+ throw new Error(`Unexpected context: ${resource}`);
2681
+ }
2682
+ });
2683
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2684
+ throw new Error("listener should not run");
2685
+ }).onError(() => {
2686
+ errorCount++;
2687
+ });
2688
+ const signed = await signJsonLd({
2689
+ "@context": "https://www.w3.org/ns/activitystreams",
2690
+ id: "https://remote.example/activities/legacy-typeerror-invalid-url",
2691
+ type: "Create",
2692
+ actor: "https://remote.example/users/alice",
2693
+ object: {
2694
+ id: "https://remote.example/notes/legacy-typeerror-invalid-url",
2695
+ type: "Note",
2696
+ attributedTo: "https://remote.example/users/alice",
2697
+ content: "Hello from invalid-url typeerror queue"
2698
+ }
2699
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
2700
+ const inboxMessage = {
2701
+ type: "inbox",
2702
+ id: crypto.randomUUID(),
2703
+ baseUrl: "https://example.com",
2704
+ activity: {
2705
+ ...signed,
2706
+ "@context": ["app:context", "https://www.w3.org/ns/activitystreams"]
2707
+ },
2708
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2709
+ attempt: 0,
2710
+ identifier: null,
2711
+ traceContext: {}
2712
+ };
2713
+ await federation.processQueuedTask(void 0, inboxMessage);
2714
+ assertEquals(errorCount, 1);
2715
+ assertEquals(queuedMessages.length, 1);
2716
+ const retried = queuedMessages[0];
2717
+ assertEquals(retried.attempt, 1);
2718
+ assertEquals(retried.activity, inboxMessage.activity);
2719
+ });
2720
+ await t.step("legacy raw LDS inbox messages with malformed absolute context refs do not retry", async () => {
2721
+ const queue = {
2722
+ enqueue(message, _options) {
2723
+ queuedMessages.push(message);
2724
+ return Promise.resolve();
2725
+ },
2726
+ listen(_handler, _options) {
2727
+ return Promise.resolve();
2728
+ }
2729
+ };
2730
+ const kv = new MemoryKvStore();
2731
+ const queuedMessages = [];
2732
+ let errorCount = 0;
2733
+ const federation = new FederationImpl({
2734
+ kv,
2735
+ queue,
2736
+ contextLoaderFactory: () => async (resource) => {
2737
+ if (resource === "http:/[") throw new TypeError(`Invalid URL: ${resource}`);
2738
+ const url = new URL(resource).href;
2739
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2740
+ throw new Error(`Unexpected context: ${resource}`);
2741
+ }
2742
+ });
2743
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2744
+ throw new Error("listener should not run");
2745
+ }).onError(() => {
2746
+ errorCount++;
2747
+ });
2748
+ const signed = await signJsonLd({
2749
+ "@context": "https://www.w3.org/ns/activitystreams",
2750
+ id: "https://remote.example/activities/legacy-malformed-absolute-context",
2751
+ type: "Create",
2752
+ actor: "https://remote.example/users/alice",
2753
+ object: {
2754
+ id: "https://remote.example/notes/legacy-malformed-absolute-context",
2755
+ type: "Note",
2756
+ attributedTo: "https://remote.example/users/alice",
2757
+ content: "Hello from malformed absolute context queue"
2758
+ }
2759
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
2760
+ const inboxMessage = {
2761
+ type: "inbox",
2762
+ id: crypto.randomUUID(),
2763
+ baseUrl: "https://example.com",
2764
+ activity: {
2765
+ ...signed,
2766
+ "@context": ["http:/[", "https://www.w3.org/ns/activitystreams"]
2767
+ },
2768
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2769
+ attempt: 0,
2770
+ identifier: null,
2771
+ traceContext: {}
2772
+ };
2773
+ await federation.processQueuedTask(void 0, inboxMessage);
2774
+ assertEquals(errorCount, 1);
2775
+ assertEquals(queuedMessages, []);
2776
+ });
2777
+ await t.step("malformed IRI fields are permanent queued inbox parse errors", async () => {
2778
+ const queuedMessages = [];
2779
+ const queue = {
2780
+ enqueue(message, _options) {
2781
+ queuedMessages.push(message);
2782
+ return Promise.resolve();
2783
+ },
2784
+ listen(_handler, _options) {
2785
+ return Promise.resolve();
2786
+ }
2787
+ };
2788
+ const kv = new MemoryKvStore();
2789
+ let errorCount = 0;
2790
+ const federation = new FederationImpl({
2791
+ kv,
2792
+ queue
2793
+ });
2794
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2795
+ throw new Error("listener should not run");
2796
+ }).onError(() => {
2797
+ errorCount++;
2798
+ });
2799
+ await federation.processQueuedTask(void 0, {
2800
+ type: "inbox",
2801
+ id: crypto.randomUUID(),
2802
+ baseUrl: "https://example.com",
2803
+ activity: {
2804
+ "@context": "https://www.w3.org/ns/activitystreams",
2805
+ id: "http://[",
2806
+ type: "Create",
2807
+ actor: "https://remote.example/users/alice",
2808
+ object: {
2809
+ id: "https://remote.example/notes/invalid-iri",
2810
+ type: "Note",
2811
+ attributedTo: "https://remote.example/users/alice",
2812
+ content: "Hello from invalid IRI queue"
2813
+ }
2814
+ },
2815
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2816
+ attempt: 0,
2817
+ identifier: null,
2818
+ traceContext: {}
2819
+ });
2820
+ assertEquals(errorCount, 1);
2821
+ assertEquals(queuedMessages, []);
2822
+ });
2823
+ await t.step("legacy raw LDS inbox messages with network-path context ids retry", async () => {
2824
+ const queue = {
2825
+ enqueue(message, _options) {
2826
+ queuedMessages.push(message);
2827
+ return Promise.resolve();
2828
+ },
2829
+ listen(_handler, _options) {
2830
+ return Promise.resolve();
2831
+ }
2832
+ };
2833
+ const kv = new MemoryKvStore();
2834
+ const queuedMessages = [];
2835
+ let errorCount = 0;
2836
+ const federation = new FederationImpl({
2837
+ kv,
2838
+ queue,
2839
+ contextLoaderFactory: () => async (resource) => {
2840
+ if (resource === "//cdn.example/ctx") {
2841
+ const error = /* @__PURE__ */ new Error(`Network-path context backend is unavailable: ${resource}`);
2842
+ error.name = "jsonld.InvalidUrl";
2843
+ error.details = {
2844
+ code: "loading remote context failed",
2845
+ url: resource
2846
+ };
2847
+ throw error;
2848
+ }
2849
+ const url = new URL(resource).href;
2850
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2851
+ throw new Error(`Unexpected context: ${resource}`);
2852
+ }
2853
+ });
2854
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2855
+ throw new Error("listener should not run");
2856
+ }).onError(() => {
2857
+ errorCount++;
2858
+ });
2859
+ const signed = await signJsonLd({
2860
+ "@context": "https://www.w3.org/ns/activitystreams",
2861
+ id: "https://remote.example/activities/legacy-network-path-context",
2862
+ type: "Create",
2863
+ actor: "https://remote.example/users/alice",
2864
+ object: {
2865
+ id: "https://remote.example/notes/legacy-network-path-context",
2866
+ type: "Note",
2867
+ attributedTo: "https://remote.example/users/alice",
2868
+ content: "Hello from network-path legacy queue"
2869
+ }
2870
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
2871
+ const inboxMessage = {
2872
+ type: "inbox",
2873
+ id: crypto.randomUUID(),
2874
+ baseUrl: "https://example.com",
2875
+ activity: {
2876
+ ...signed,
2877
+ "@context": ["//cdn.example/ctx", "https://www.w3.org/ns/activitystreams"]
2878
+ },
2879
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2880
+ attempt: 0,
2881
+ identifier: null,
2882
+ traceContext: {}
2883
+ };
2884
+ await federation.processQueuedTask(void 0, inboxMessage);
2885
+ assertEquals(errorCount, 1);
2886
+ assertEquals(queuedMessages, [{
2887
+ ...inboxMessage,
2888
+ attempt: 1
2889
+ }]);
2890
+ });
2891
+ await t.step("legacy raw LDS inbox messages with malformed network-path refs do not retry", async () => {
2892
+ const queue = {
2893
+ enqueue(message, _options) {
2894
+ queuedMessages.push(message);
2895
+ return Promise.resolve();
2896
+ },
2897
+ listen(_handler, _options) {
2898
+ return Promise.resolve();
2899
+ }
2900
+ };
2901
+ const kv = new MemoryKvStore();
2902
+ const queuedMessages = [];
2903
+ let errorCount = 0;
2904
+ const federation = new FederationImpl({
2905
+ kv,
2906
+ queue,
2907
+ contextLoaderFactory: () => async (resource) => {
2908
+ if (resource === "//[") {
2909
+ const error = /* @__PURE__ */ new Error(`Malformed network-path context: ${resource}`);
2910
+ error.name = "jsonld.InvalidUrl";
2911
+ error.details = {
2912
+ code: "loading remote context failed",
2913
+ url: resource
2914
+ };
2915
+ throw error;
2916
+ }
2917
+ const url = new URL(resource).href;
2918
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2919
+ throw new Error(`Unexpected context: ${resource}`);
2920
+ }
2921
+ });
2922
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2923
+ throw new Error("listener should not run");
2924
+ }).onError(() => {
2925
+ errorCount++;
2926
+ });
2927
+ const signed = await signJsonLd({
2928
+ "@context": "https://www.w3.org/ns/activitystreams",
2929
+ id: "https://remote.example/activities/legacy-malformed-network-path-context",
2930
+ type: "Create",
2931
+ actor: "https://remote.example/users/alice",
2932
+ object: {
2933
+ id: "https://remote.example/notes/legacy-malformed-network-path-context",
2934
+ type: "Note",
2935
+ attributedTo: "https://remote.example/users/alice",
2936
+ content: "Hello from malformed network-path legacy queue"
2937
+ }
2938
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
2939
+ const inboxMessage = {
2940
+ type: "inbox",
2941
+ id: crypto.randomUUID(),
2942
+ baseUrl: "https://example.com",
2943
+ activity: {
2944
+ ...signed,
2945
+ "@context": ["//[", "https://www.w3.org/ns/activitystreams"]
2946
+ },
2947
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2948
+ attempt: 0,
2949
+ identifier: null,
2950
+ traceContext: {}
2951
+ };
2952
+ await federation.processQueuedTask(void 0, inboxMessage);
2953
+ assertEquals(errorCount, 1);
2954
+ assertEquals(queuedMessages, []);
2955
+ });
2956
+ await t.step("legacy raw LDS inbox messages with malformed context URLs do not retry", async () => {
2957
+ const queue = {
2958
+ enqueue(message, _options) {
2959
+ queuedMessages.push(message);
2960
+ return Promise.resolve();
2961
+ },
2962
+ listen(_handler, _options) {
2963
+ return Promise.resolve();
2964
+ }
2965
+ };
2966
+ const kv = new MemoryKvStore();
2967
+ const queuedMessages = [];
2968
+ let errorCount = 0;
2969
+ const federation = new FederationImpl({
2970
+ kv,
2971
+ queue,
2972
+ contextLoaderFactory: () => async (resource) => {
2973
+ if (resource === "not a url") {
2974
+ const error = /* @__PURE__ */ new Error(`Invalid remote context URL: ${resource}`);
2975
+ error.name = "jsonld.InvalidUrl";
2976
+ error.details = {
2977
+ code: "loading remote context failed",
2978
+ url: resource
2979
+ };
2980
+ throw error;
2981
+ }
2982
+ const url = new URL(resource).href;
2983
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2984
+ throw new Error(`Unexpected context: ${resource}`);
2985
+ }
2986
+ });
2987
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2988
+ throw new Error("listener should not run");
2989
+ }).onError(() => {
2990
+ errorCount++;
2991
+ });
2992
+ const signed = await signJsonLd({
2993
+ "@context": "https://www.w3.org/ns/activitystreams",
2994
+ id: "https://remote.example/activities/legacy-malformed-context",
2995
+ type: "Create",
2996
+ actor: "https://remote.example/users/alice",
2997
+ object: {
2998
+ id: "https://remote.example/notes/legacy-malformed-context",
2999
+ type: "Note",
3000
+ attributedTo: "https://remote.example/users/alice",
3001
+ content: "Hello from malformed legacy queue"
3002
+ }
3003
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
3004
+ const inboxMessage = {
3005
+ type: "inbox",
3006
+ id: crypto.randomUUID(),
3007
+ baseUrl: "https://example.com",
3008
+ activity: {
3009
+ ...signed,
3010
+ "@context": ["not a url", "https://www.w3.org/ns/activitystreams"]
3011
+ },
3012
+ started: (/* @__PURE__ */ new Date()).toISOString(),
3013
+ attempt: 0,
3014
+ identifier: null,
3015
+ traceContext: {}
3016
+ };
3017
+ await federation.processQueuedTask(void 0, inboxMessage);
3018
+ assertEquals(errorCount, 1);
3019
+ assertEquals(queuedMessages, []);
3020
+ });
3021
+ await t.step("legacy raw LDS inbox messages with invalid percent escapes do not retry", async () => {
3022
+ const queue = {
3023
+ enqueue(message, _options) {
3024
+ queuedMessages.push(message);
3025
+ return Promise.resolve();
3026
+ },
3027
+ listen(_handler, _options) {
3028
+ return Promise.resolve();
3029
+ }
3030
+ };
3031
+ const kv = new MemoryKvStore();
3032
+ const queuedMessages = [];
3033
+ let errorCount = 0;
3034
+ const federation = new FederationImpl({
3035
+ kv,
3036
+ queue,
3037
+ contextLoaderFactory: () => async (resource) => {
3038
+ if (resource === "foo%zz") {
3039
+ const error = /* @__PURE__ */ new Error(`Invalid remote context URL: ${resource}`);
3040
+ error.name = "jsonld.InvalidUrl";
3041
+ error.details = {
3042
+ code: "loading remote context failed",
3043
+ url: resource
3044
+ };
3045
+ throw error;
1938
3046
  }
3047
+ const url = new URL(resource).href;
3048
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
3049
+ throw new Error(`Unexpected context: ${resource}`);
1939
3050
  }
1940
3051
  });
1941
- federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(vocab.Create, () => {
1942
- throw new Error("Intended error for testing");
3052
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
3053
+ throw new Error("listener should not run");
3054
+ }).onError(() => {
3055
+ errorCount++;
1943
3056
  });
1944
- await assertRejects(() => federation.processQueuedTask(void 0, {
1945
- type: "outbox",
3057
+ const signed = await signJsonLd({
3058
+ "@context": "https://www.w3.org/ns/activitystreams",
3059
+ id: "https://remote.example/activities/legacy-malformed-percent-context",
3060
+ type: "Create",
3061
+ actor: "https://remote.example/users/alice",
3062
+ object: {
3063
+ id: "https://remote.example/notes/legacy-malformed-percent-context",
3064
+ type: "Note",
3065
+ attributedTo: "https://remote.example/users/alice",
3066
+ content: "Hello from malformed percent legacy queue"
3067
+ }
3068
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
3069
+ const inboxMessage = {
3070
+ type: "inbox",
1946
3071
  id: crypto.randomUUID(),
1947
3072
  baseUrl: "https://example.com",
1948
- keys: [],
1949
3073
  activity: {
1950
- "@context": "https://www.w3.org/ns/activitystreams",
1951
- type: "Create",
1952
- actor: "https://example.com/users/alice",
1953
- object: {
1954
- type: "Note",
1955
- content: "test"
1956
- }
3074
+ ...signed,
3075
+ "@context": ["foo%zz", "https://www.w3.org/ns/activitystreams"]
1957
3076
  },
1958
- activityType: "https://www.w3.org/ns/activitystreams#Create",
1959
- inbox: "https://invalid-domain-that-does-not-exist.example/inbox",
1960
- sharedInbox: false,
1961
3077
  started: (/* @__PURE__ */ new Date()).toISOString(),
1962
3078
  attempt: 0,
1963
- headers: {},
3079
+ identifier: null,
1964
3080
  traceContext: {}
1965
- }), Error);
3081
+ };
3082
+ await federation.processQueuedTask(void 0, inboxMessage);
3083
+ assertEquals(errorCount, 1);
1966
3084
  assertEquals(queuedMessages, []);
1967
- await assertRejects(() => federation.processQueuedTask(void 0, {
3085
+ });
3086
+ await t.step("legacy raw LDS inbox messages with invalid remote contexts do not retry", async () => {
3087
+ const remoteContextUrl = "https://remote.example/contexts/ext";
3088
+ const queue = {
3089
+ enqueue(message, _options) {
3090
+ queuedMessages.push(message);
3091
+ return Promise.resolve();
3092
+ },
3093
+ listen(_handler, _options) {
3094
+ return Promise.resolve();
3095
+ }
3096
+ };
3097
+ const kv = new MemoryKvStore();
3098
+ const queuedMessages = [];
3099
+ let errorCount = 0;
3100
+ const federation = new FederationImpl({
3101
+ kv,
3102
+ queue,
3103
+ contextLoaderFactory: () => async (resource) => {
3104
+ const url = new URL(resource).href;
3105
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
3106
+ if (url === remoteContextUrl) return {
3107
+ contextUrl: null,
3108
+ documentUrl: url,
3109
+ document: [
3110
+ "not",
3111
+ "an",
3112
+ "object"
3113
+ ]
3114
+ };
3115
+ throw new Error(`Unexpected context: ${url}`);
3116
+ }
3117
+ });
3118
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
3119
+ throw new Error("listener should not run");
3120
+ }).onError(() => {
3121
+ errorCount++;
3122
+ });
3123
+ const signed = await signJsonLd({
3124
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
3125
+ id: "https://remote.example/activities/legacy-invalid-remote-context",
3126
+ type: "Create",
3127
+ actor: "https://remote.example/users/alice",
3128
+ ext: "preserve-me",
3129
+ object: {
3130
+ id: "https://remote.example/notes/legacy-invalid-remote-context",
3131
+ type: "Note",
3132
+ attributedTo: "https://remote.example/users/alice",
3133
+ content: "Hello from invalid remote context queue"
3134
+ }
3135
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
3136
+ const url = new URL(resource).href;
3137
+ if (url === remoteContextUrl) return {
3138
+ contextUrl: null,
3139
+ documentUrl: url,
3140
+ document: { "@context": { ext: "https://example.com/ext" } }
3141
+ };
3142
+ return await mockDocumentLoader(url);
3143
+ } });
3144
+ const inboxMessage = {
1968
3145
  type: "inbox",
1969
3146
  id: crypto.randomUUID(),
1970
3147
  baseUrl: "https://example.com",
1971
- activity: {
1972
- "@context": "https://www.w3.org/ns/activitystreams",
1973
- type: "Create",
1974
- actor: "https://remote.example/users/alice",
1975
- object: {
1976
- type: "Note",
1977
- content: "Hello world"
1978
- }
1979
- },
3148
+ activity: signed,
1980
3149
  started: (/* @__PURE__ */ new Date()).toISOString(),
1981
3150
  attempt: 0,
1982
3151
  identifier: null,
1983
3152
  traceContext: {}
1984
- }), Error);
3153
+ };
3154
+ await federation.processQueuedTask(void 0, inboxMessage);
3155
+ assertEquals(errorCount, 1);
1985
3156
  assertEquals(queuedMessages, []);
1986
3157
  });
1987
- await t.step("with MessageQueue having no nativeRetrial", async () => {
3158
+ await t.step("legacy raw LDS inbox messages with string remote contexts retry", async () => {
3159
+ const remoteContextUrl = "https://remote.example/contexts/ext";
3160
+ const queue = {
3161
+ enqueue(message, _options) {
3162
+ queuedMessages.push(message);
3163
+ return Promise.resolve();
3164
+ },
3165
+ listen(_handler, _options) {
3166
+ return Promise.resolve();
3167
+ }
3168
+ };
1988
3169
  const kv = new MemoryKvStore();
1989
- let queuedMessages = [];
3170
+ const queuedMessages = [];
3171
+ let errorCount = 0;
1990
3172
  const federation = new FederationImpl({
1991
3173
  kv,
1992
- queue: {
1993
- enqueue(message, _options) {
1994
- queuedMessages.push(message);
1995
- return Promise.resolve();
1996
- },
1997
- listen(_handler, _options) {
1998
- return Promise.resolve();
1999
- }
3174
+ queue,
3175
+ contextLoaderFactory: () => async (resource) => {
3176
+ const url = new URL(resource).href;
3177
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
3178
+ if (url === remoteContextUrl) return {
3179
+ contextUrl: null,
3180
+ documentUrl: url,
3181
+ document: "{not valid json"
3182
+ };
3183
+ throw new Error(`Unexpected context: ${url}`);
2000
3184
  }
2001
3185
  });
2002
- federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(vocab.Create, () => {
2003
- throw new Error("Intended error for testing");
3186
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
3187
+ throw new Error("listener should not run");
3188
+ }).onError(() => {
3189
+ errorCount++;
2004
3190
  });
2005
- const outboxMessage = {
2006
- type: "outbox",
3191
+ const signed = await signJsonLd({
3192
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
3193
+ id: "https://remote.example/activities/legacy-string-remote-context",
3194
+ type: "Create",
3195
+ actor: "https://remote.example/users/alice",
3196
+ ext: "preserve-me",
3197
+ object: {
3198
+ id: "https://remote.example/notes/legacy-string-remote-context",
3199
+ type: "Note",
3200
+ attributedTo: "https://remote.example/users/alice",
3201
+ content: "Hello from string remote context queue"
3202
+ }
3203
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
3204
+ const url = new URL(resource).href;
3205
+ if (url === remoteContextUrl) return {
3206
+ contextUrl: null,
3207
+ documentUrl: url,
3208
+ document: { "@context": { ext: "https://example.com/ext" } }
3209
+ };
3210
+ return await mockDocumentLoader(url);
3211
+ } });
3212
+ const inboxMessage = {
3213
+ type: "inbox",
2007
3214
  id: crypto.randomUUID(),
2008
3215
  baseUrl: "https://example.com",
2009
- keys: [],
2010
- activity: {
2011
- "@context": "https://www.w3.org/ns/activitystreams",
2012
- type: "Create",
2013
- actor: "https://example.com/users/alice",
2014
- object: {
2015
- type: "Note",
2016
- content: "test"
2017
- }
3216
+ activity: signed,
3217
+ started: (/* @__PURE__ */ new Date()).toISOString(),
3218
+ attempt: 0,
3219
+ identifier: null,
3220
+ traceContext: {}
3221
+ };
3222
+ await federation.processQueuedTask(void 0, inboxMessage);
3223
+ assertEquals(errorCount, 1);
3224
+ assertEquals(queuedMessages, [{
3225
+ ...inboxMessage,
3226
+ attempt: 1
3227
+ }]);
3228
+ });
3229
+ await t.step("legacy raw LDS inbox messages with loader TypeErrors retry", async () => {
3230
+ const remoteContextUrl = "https://remote.example/contexts/ext";
3231
+ const queue = {
3232
+ enqueue(message, _options) {
3233
+ queuedMessages.push(message);
3234
+ return Promise.resolve();
2018
3235
  },
2019
- activityType: "https://www.w3.org/ns/activitystreams#Create",
2020
- inbox: "https://invalid-domain-that-does-not-exist.example/inbox",
2021
- sharedInbox: false,
3236
+ listen(_handler, _options) {
3237
+ return Promise.resolve();
3238
+ }
3239
+ };
3240
+ const kv = new MemoryKvStore();
3241
+ const queuedMessages = [];
3242
+ let errorCount = 0;
3243
+ const federation = new FederationImpl({
3244
+ kv,
3245
+ queue,
3246
+ contextLoaderFactory: () => async (resource) => {
3247
+ const url = new URL(resource).href;
3248
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
3249
+ if (url === remoteContextUrl) throw new TypeError(`Cannot initialize remote context loader: ${url}`);
3250
+ throw new Error(`Unexpected context: ${url}`);
3251
+ }
3252
+ });
3253
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
3254
+ throw new Error("listener should not run");
3255
+ }).onError(() => {
3256
+ errorCount++;
3257
+ });
3258
+ const signed = await signJsonLd({
3259
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
3260
+ id: "https://remote.example/activities/legacy-typeerror-context",
3261
+ type: "Create",
3262
+ actor: "https://remote.example/users/alice",
3263
+ ext: "preserve-me",
3264
+ object: {
3265
+ id: "https://remote.example/notes/legacy-typeerror-context",
3266
+ type: "Note",
3267
+ attributedTo: "https://remote.example/users/alice",
3268
+ content: "Hello from typeerror legacy queue"
3269
+ }
3270
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
3271
+ const url = new URL(resource).href;
3272
+ if (url === remoteContextUrl) return {
3273
+ contextUrl: null,
3274
+ documentUrl: url,
3275
+ document: { "@context": { ext: "https://example.com/ext" } }
3276
+ };
3277
+ return await mockDocumentLoader(url);
3278
+ } });
3279
+ const inboxMessage = {
3280
+ type: "inbox",
3281
+ id: crypto.randomUUID(),
3282
+ baseUrl: "https://example.com",
3283
+ activity: signed,
2022
3284
  started: (/* @__PURE__ */ new Date()).toISOString(),
2023
3285
  attempt: 0,
2024
- headers: {},
3286
+ identifier: null,
2025
3287
  traceContext: {}
2026
3288
  };
2027
- await federation.processQueuedTask(void 0, outboxMessage);
3289
+ await federation.processQueuedTask(void 0, inboxMessage);
3290
+ assertEquals(errorCount, 1);
2028
3291
  assertEquals(queuedMessages, [{
2029
- ...outboxMessage,
3292
+ ...inboxMessage,
2030
3293
  attempt: 1
2031
3294
  }]);
2032
- queuedMessages = [];
3295
+ });
3296
+ await t.step("legacy raw LDS inbox messages with syntax errors in remote contexts retry", async () => {
3297
+ const remoteContextUrl = "https://remote.example/contexts/ext";
3298
+ const queue = {
3299
+ enqueue(message, _options) {
3300
+ queuedMessages.push(message);
3301
+ return Promise.resolve();
3302
+ },
3303
+ listen(_handler, _options) {
3304
+ return Promise.resolve();
3305
+ }
3306
+ };
3307
+ const kv = new MemoryKvStore();
3308
+ const queuedMessages = [];
3309
+ let errorCount = 0;
3310
+ const federation = new FederationImpl({
3311
+ kv,
3312
+ queue,
3313
+ contextLoaderFactory: () => async (resource) => {
3314
+ const url = new URL(resource).href;
3315
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
3316
+ if (url === remoteContextUrl) {
3317
+ const error = /* @__PURE__ */ new Error(`Transient syntax failure: ${url}`);
3318
+ error.name = "jsonld.SyntaxError";
3319
+ error.details = { code: "loading remote context failed" };
3320
+ throw error;
3321
+ }
3322
+ throw new Error(`Unexpected context: ${url}`);
3323
+ }
3324
+ });
3325
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
3326
+ throw new Error("listener should not run");
3327
+ }).onError(() => {
3328
+ errorCount++;
3329
+ });
3330
+ const signed = await signJsonLd({
3331
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
3332
+ id: "https://remote.example/activities/legacy-syntax-context",
3333
+ type: "Create",
3334
+ actor: "https://remote.example/users/alice",
3335
+ ext: "preserve-me",
3336
+ object: {
3337
+ id: "https://remote.example/notes/legacy-syntax-context",
3338
+ type: "Note",
3339
+ attributedTo: "https://remote.example/users/alice",
3340
+ content: "Hello from syntax legacy queue"
3341
+ }
3342
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
3343
+ const url = new URL(resource).href;
3344
+ if (url === remoteContextUrl) return {
3345
+ contextUrl: null,
3346
+ documentUrl: url,
3347
+ document: { "@context": { ext: "https://example.com/ext" } }
3348
+ };
3349
+ return await mockDocumentLoader(url);
3350
+ } });
3351
+ const inboxMessage = {
3352
+ type: "inbox",
3353
+ id: crypto.randomUUID(),
3354
+ baseUrl: "https://example.com",
3355
+ activity: signed,
3356
+ started: (/* @__PURE__ */ new Date()).toISOString(),
3357
+ attempt: 0,
3358
+ identifier: null,
3359
+ traceContext: {}
3360
+ };
3361
+ await federation.processQueuedTask(void 0, inboxMessage);
3362
+ assertEquals(errorCount, 1);
3363
+ assertEquals(queuedMessages, [{
3364
+ ...inboxMessage,
3365
+ attempt: 1
3366
+ }]);
3367
+ });
3368
+ await t.step("legacy raw LDS inbox messages with loader RangeErrors retry", async () => {
3369
+ const remoteContextUrl = "https://remote.example/contexts/ext";
3370
+ const queue = {
3371
+ enqueue(message, _options) {
3372
+ queuedMessages.push(message);
3373
+ return Promise.resolve();
3374
+ },
3375
+ listen(_handler, _options) {
3376
+ return Promise.resolve();
3377
+ }
3378
+ };
3379
+ const kv = new MemoryKvStore();
3380
+ const queuedMessages = [];
3381
+ let errorCount = 0;
3382
+ const federation = new FederationImpl({
3383
+ kv,
3384
+ queue,
3385
+ contextLoaderFactory: () => async (resource) => {
3386
+ const url = new URL(resource).href;
3387
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
3388
+ if (url === remoteContextUrl) throw new RangeError(`Temporary remote context cache window exceeded: ${url}`);
3389
+ throw new Error(`Unexpected context: ${url}`);
3390
+ }
3391
+ });
3392
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
3393
+ throw new Error("listener should not run");
3394
+ }).onError(() => {
3395
+ errorCount++;
3396
+ });
3397
+ const signed = await signJsonLd({
3398
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
3399
+ id: "https://remote.example/activities/legacy-rangeerror-context",
3400
+ type: "Create",
3401
+ actor: "https://remote.example/users/alice",
3402
+ ext: "preserve-me",
3403
+ object: {
3404
+ id: "https://remote.example/notes/legacy-rangeerror-context",
3405
+ type: "Note",
3406
+ attributedTo: "https://remote.example/users/alice",
3407
+ content: "Hello from rangeerror legacy queue"
3408
+ }
3409
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
3410
+ const url = new URL(resource).href;
3411
+ if (url === remoteContextUrl) return {
3412
+ contextUrl: null,
3413
+ documentUrl: url,
3414
+ document: { "@context": { ext: "https://example.com/ext" } }
3415
+ };
3416
+ return await mockDocumentLoader(url);
3417
+ } });
2033
3418
  const inboxMessage = {
3419
+ type: "inbox",
3420
+ id: crypto.randomUUID(),
3421
+ baseUrl: "https://example.com",
3422
+ activity: signed,
3423
+ started: (/* @__PURE__ */ new Date()).toISOString(),
3424
+ attempt: 0,
3425
+ identifier: null,
3426
+ traceContext: {}
3427
+ };
3428
+ await federation.processQueuedTask(void 0, inboxMessage);
3429
+ assertEquals(errorCount, 1);
3430
+ assertEquals(queuedMessages, [{
3431
+ ...inboxMessage,
3432
+ attempt: 1
3433
+ }]);
3434
+ });
3435
+ await t.step("permanent queued inbox parse errors do not re-enqueue poison messages", async () => {
3436
+ const queuedMessages = [];
3437
+ const queue = {
3438
+ enqueue(message, _options) {
3439
+ queuedMessages.push(message);
3440
+ return Promise.resolve();
3441
+ },
3442
+ listen(_handler, _options) {
3443
+ return Promise.resolve();
3444
+ }
3445
+ };
3446
+ const kv = new MemoryKvStore();
3447
+ let errorCount = 0;
3448
+ const federation = new FederationImpl({
3449
+ kv,
3450
+ queue
3451
+ });
3452
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
3453
+ throw new Error("listener should not run");
3454
+ }).onError(() => {
3455
+ errorCount++;
3456
+ });
3457
+ await federation.processQueuedTask(void 0, {
2034
3458
  type: "inbox",
2035
3459
  id: crypto.randomUUID(),
2036
3460
  baseUrl: "https://example.com",
2037
3461
  activity: {
2038
3462
  "@context": "https://www.w3.org/ns/activitystreams",
3463
+ id: "https://remote.example/objects/not-an-activity",
3464
+ type: "Note",
3465
+ attributedTo: "https://remote.example/users/alice",
3466
+ content: "Not an activity"
3467
+ },
3468
+ started: (/* @__PURE__ */ new Date()).toISOString(),
3469
+ attempt: 0,
3470
+ identifier: null,
3471
+ traceContext: {}
3472
+ });
3473
+ assertEquals(errorCount, 1);
3474
+ assertEquals(queuedMessages, []);
3475
+ });
3476
+ await t.step("malformed Temporal fields are permanent queued inbox parse errors", async () => {
3477
+ const queuedMessages = [];
3478
+ const queue = {
3479
+ enqueue(message, _options) {
3480
+ queuedMessages.push(message);
3481
+ return Promise.resolve();
3482
+ },
3483
+ listen(_handler, _options) {
3484
+ return Promise.resolve();
3485
+ }
3486
+ };
3487
+ const kv = new MemoryKvStore();
3488
+ let errorCount = 0;
3489
+ const federation = new FederationImpl({
3490
+ kv,
3491
+ queue,
3492
+ documentLoaderFactory: () => mockDocumentLoader,
3493
+ contextLoaderFactory: () => mockDocumentLoader
3494
+ });
3495
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
3496
+ throw new Error("listener should not run");
3497
+ }).onError(() => {
3498
+ errorCount++;
3499
+ });
3500
+ await federation.processQueuedTask(void 0, {
3501
+ type: "inbox",
3502
+ id: crypto.randomUUID(),
3503
+ baseUrl: "https://example.com",
3504
+ activity: {
3505
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
3506
+ id: "https://remote.example/activities/invalid-proof-created",
2039
3507
  type: "Create",
2040
3508
  actor: "https://remote.example/users/alice",
2041
3509
  object: {
3510
+ id: "https://remote.example/notes/invalid-proof-created",
2042
3511
  type: "Note",
2043
- content: "Hello world"
3512
+ attributedTo: "https://remote.example/users/alice",
3513
+ content: "Hello, world!"
3514
+ },
3515
+ proof: {
3516
+ type: "DataIntegrityProof",
3517
+ cryptosuite: "eddsa-jcs-2022",
3518
+ verificationMethod: "https://remote.example/users/alice#ed25519-key",
3519
+ proofPurpose: "assertionMethod",
3520
+ created: { "@value": "not-a-date" },
3521
+ proofValue: "zLaewdp4H9kqtwyrLatK4cjY5oRHwVcw4gibPSUDYDMhi4M49v8pcYk3ZB6D69dNpAPbUmY8ocuJ3m9KhKJEEg7z"
2044
3522
  }
2045
3523
  },
2046
3524
  started: (/* @__PURE__ */ new Date()).toISOString(),
2047
3525
  attempt: 0,
2048
3526
  identifier: null,
2049
3527
  traceContext: {}
2050
- };
2051
- await federation.processQueuedTask(void 0, inboxMessage);
2052
- assertEquals(queuedMessages, [{
2053
- ...inboxMessage,
2054
- attempt: 1
2055
- }]);
3528
+ });
3529
+ assertEquals(errorCount, 1);
3530
+ assertEquals(queuedMessages, []);
2056
3531
  });
2057
3532
  });
2058
3533
  test("FederationImpl.processQueuedTask() permanent failure", async (t) => {
@@ -2842,6 +4317,35 @@ test({
2842
4317
  ]);
2843
4318
  }
2844
4319
  });
4320
+ test("ContextImpl.routeActivity() marks queued signed activities as non-LDS", async () => {
4321
+ let queuedMessage = null;
4322
+ const federation = new FederationImpl({
4323
+ kv: new MemoryKvStore(),
4324
+ queue: {
4325
+ enqueue(message) {
4326
+ queuedMessage = message;
4327
+ return Promise.resolve();
4328
+ },
4329
+ async listen() {}
4330
+ }
4331
+ });
4332
+ federation.setInboxListeners("/u/{identifier}/i", "/i").on(Offer, () => {
4333
+ throw new Error("listener should not run for queued routeActivity");
4334
+ });
4335
+ const ctx = new ContextImpl({
4336
+ url: new URL("https://example.com/"),
4337
+ federation,
4338
+ data: void 0,
4339
+ documentLoader: mockDocumentLoader,
4340
+ contextLoader: documentLoader
4341
+ });
4342
+ const signedOffer = await signObject(new Offer({ actor: new URL("https://example.com/person2") }), ed25519PrivateKey, ed25519Multikey.id);
4343
+ assert(await ctx.routeActivity(null, signedOffer));
4344
+ if (queuedMessage == null) throw new Error("Inbox message not queued.");
4345
+ const inboxMessage = queuedMessage;
4346
+ assertEquals(inboxMessage.ldSignatureVerified, false);
4347
+ assertEquals(inboxMessage.normalizedActivity, void 0);
4348
+ });
2845
4349
  test("ContextImpl.getCollectionUri()", () => {
2846
4350
  const federation = new FederationImpl({ kv: new MemoryKvStore() });
2847
4351
  const base = "https://example.com";