@fedify/fedify 2.0.17 → 2.0.19

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 (62) hide show
  1. package/dist/{builder-Dgk0oJse.mjs → builder-DpMJ796G.mjs} +3 -3
  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-BzRJ49gL.d.cts → context-CJZHWLmz.d.cts} +48 -6
  6. package/dist/{context-CdJ9wAQa.d.ts → context-DnsFj3yK.d.ts} +48 -6
  7. package/dist/{deno-Cb0U4mYY.mjs → deno-OvlgTe2z.mjs} +1 -1
  8. package/dist/{docloader-BtgBOjR2.mjs → docloader-LahMFnq1.mjs} +2 -2
  9. package/dist/federation/builder.test.mjs +1 -1
  10. package/dist/federation/handler.test.mjs +1358 -41
  11. package/dist/federation/idempotency.test.mjs +2 -2
  12. package/dist/federation/inbox.test.mjs +1 -1
  13. package/dist/federation/middleware.test.mjs +1588 -84
  14. package/dist/federation/mod.cjs +1 -1
  15. package/dist/federation/mod.d.cts +2 -2
  16. package/dist/federation/mod.d.ts +2 -2
  17. package/dist/federation/mod.js +1 -1
  18. package/dist/federation/retry.test.mjs +1 -1
  19. package/dist/federation/send.test.mjs +3 -3
  20. package/dist/federation/temporal.test.d.mts +2 -0
  21. package/dist/federation/temporal.test.mjs +71 -0
  22. package/dist/federation/webfinger.test.mjs +1 -1
  23. package/dist/{http-CkpjqreM.mjs → http-BGqCzFFW.mjs} +2 -2
  24. package/dist/{http-C8fyMUjD.cjs → http-B_rBGJzj.cjs} +1 -1
  25. package/dist/{http-DOir4i5c.js → http-GMhBzdUg.js} +1 -1
  26. package/dist/{inbox-VNZAN2rq.mjs → inbox-B0rurFqy.mjs} +7 -4
  27. package/dist/{key-jLdTQH2f.mjs → key-v_BpXvjh.mjs} +1 -1
  28. package/dist/{kv-cache-D7YHL8kZ.cjs → kv-cache-B-QyKmN6.cjs} +1 -1
  29. package/dist/{kv-cache-BT1ZahAh.js → kv-cache-DIgdPeX1.js} +1 -1
  30. package/dist/ld-DHdnBuQl.mjs +550 -0
  31. package/dist/{middleware-Bua1eJKj.mjs → middleware-B0dEe1fJ.mjs} +309 -76
  32. package/dist/{middleware-ChZir5hz.mjs → middleware-D-GLLKcV.mjs} +1 -1
  33. package/dist/{middleware-BIaxjP-q.cjs → middleware-DQE7csTG.cjs} +399 -73
  34. package/dist/{middleware-o4he3VG2.js → middleware-DwMR-UJ5.js} +396 -71
  35. package/dist/{middleware-BmYsY7pr.cjs → middleware-zX2hBOnb.cjs} +1 -1
  36. package/dist/{mod-CKkpvznG.d.cts → mod-43DJyETz.d.cts} +1 -1
  37. package/dist/{mod-Kzr52ns8.d.ts → mod-COgmn-UO.d.ts} +1 -1
  38. package/dist/mod.cjs +4 -4
  39. package/dist/mod.d.cts +2 -2
  40. package/dist/mod.d.ts +2 -2
  41. package/dist/mod.js +4 -4
  42. package/dist/nodeinfo/handler.test.mjs +1 -1
  43. package/dist/{owner-9JLKNjOK.mjs → owner-B8gYOXYm.mjs} +2 -2
  44. package/dist/{proof-Bds_rOyf.js → proof-D2j9J3Sr.js} +298 -4
  45. package/dist/{proof-CVNf-KlO.mjs → proof-DSGV862v.mjs} +2 -2
  46. package/dist/{proof-DWK4MOe5.cjs → proof-RC4Q3Ozn.cjs} +345 -3
  47. package/dist/{send-j6vHPA3l.mjs → send-myUWQlOg.mjs} +2 -2
  48. package/dist/sig/http.test.mjs +2 -2
  49. package/dist/sig/key.test.mjs +1 -1
  50. package/dist/sig/ld.test.mjs +558 -2
  51. package/dist/sig/mod.cjs +2 -2
  52. package/dist/sig/mod.js +2 -2
  53. package/dist/sig/owner.test.mjs +1 -1
  54. package/dist/sig/proof.test.mjs +1 -1
  55. package/dist/temporal-D4MiakbY.mjs +95 -0
  56. package/dist/testing/mod.d.mts +48 -6
  57. package/dist/utils/docloader.test.mjs +2 -2
  58. package/dist/utils/mod.cjs +1 -1
  59. package/dist/utils/mod.js +1 -1
  60. package/package.json +6 -6
  61. package/dist/ld-C48i1Mdp.mjs +0 -256
  62. /package/dist/{retry-Ddbq3AcK.mjs → retry-B_E3V_Dx.mjs} +0 -0
@@ -10,17 +10,17 @@ import { t as assertInstanceOf } from "../assert_instance_of-C4Ri6VuN.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
12
  import { t as assert } from "../assert-ddO5KLpe.mjs";
13
- import { l as verifyRequest, s as signRequest } from "../http-CkpjqreM.mjs";
13
+ import { l as verifyRequest, s as signRequest } from "../http-BGqCzFFW.mjs";
14
14
  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";
15
- import { t as getAuthenticatedDocumentLoader } from "../docloader-BtgBOjR2.mjs";
16
- import { a as signJsonLd, o as verifyJsonLd, r as detachSignature } from "../ld-C48i1Mdp.mjs";
17
- import { t as doesActorOwnKey } from "../owner-9JLKNjOK.mjs";
18
- import { n as signObject, r as verifyObject } from "../proof-CVNf-KlO.mjs";
15
+ import { t as getAuthenticatedDocumentLoader } from "../docloader-LahMFnq1.mjs";
16
+ import { a as compactJsonLd, f as signJsonLd, m as verifyJsonLd, s as detachSignature } from "../ld-DHdnBuQl.mjs";
17
+ import { t as doesActorOwnKey } from "../owner-B8gYOXYm.mjs";
18
+ import { n as signObject, r as verifyObject } from "../proof-DSGV862v.mjs";
19
19
  import { t as MemoryKvStore } from "../kv-BrZHNugx.mjs";
20
- import { a as createFederation, i as KvSpecDeterminer, n as FederationImpl, r as InboxContextImpl, t as ContextImpl } from "../middleware-Bua1eJKj.mjs";
20
+ import { a as createFederation, i as KvSpecDeterminer, n as FederationImpl, r as InboxContextImpl, t as ContextImpl } from "../middleware-B0dEe1fJ.mjs";
21
21
  import { mockDocumentLoader, test } from "@fedify/fixture";
22
22
  import * as vocab from "@fedify/vocab";
23
- import { getTypeId, lookupObject } from "@fedify/vocab";
23
+ import { Create, Offer, Person, getTypeId, lookupObject } from "@fedify/vocab";
24
24
  import { getDocumentLoader } from "@fedify/vocab-runtime";
25
25
  //#region ../fixture/src/fixtures/example.com/create.json
26
26
  var id$2 = "https://example.com/create";
@@ -1061,6 +1061,54 @@ test("Federation.setInboxListeners()", async (t) => {
1061
1061
  });
1062
1062
  esm_default.hardReset();
1063
1063
  });
1064
+ test("Federation.fetch() preserves original LD-signed payload for InboxContextImpl.activity", async () => {
1065
+ const remoteContextUrl = "https://remote.example/contexts/ext";
1066
+ const sourceContextLoader = async (resource) => {
1067
+ const url = new URL(resource).href;
1068
+ if (url === remoteContextUrl) return {
1069
+ contextUrl: null,
1070
+ documentUrl: url,
1071
+ document: { "@context": { ext: "https://example.com/ext" } }
1072
+ };
1073
+ return await mockDocumentLoader(url);
1074
+ };
1075
+ const federation = createFederation({
1076
+ kv: new MemoryKvStore(),
1077
+ documentLoaderFactory: () => mockDocumentLoader,
1078
+ contextLoaderFactory: () => sourceContextLoader
1079
+ });
1080
+ federation.setActorDispatcher("/users/{identifier}", (_ctx, identifier) => identifier === "someone" ? new Person({}) : null);
1081
+ let receivedRaw = null;
1082
+ let receivedTyped = null;
1083
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, (ctx, activity) => {
1084
+ receivedRaw = ctx.activity;
1085
+ receivedTyped = activity;
1086
+ });
1087
+ const signed = await signJsonLd({
1088
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1089
+ id: "https://example.com/activities/preserve-raw",
1090
+ type: "Create",
1091
+ actor: "https://example.com/person2",
1092
+ ext: "preserve-me",
1093
+ object: {
1094
+ id: "https://example.com/notes/preserve-raw",
1095
+ type: "Note",
1096
+ attributedTo: "https://example.com/person2",
1097
+ content: "Hello, world!"
1098
+ }
1099
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: sourceContextLoader });
1100
+ const response = await federation.fetch(new Request("https://example.com/inbox", {
1101
+ method: "POST",
1102
+ headers: { "Content-Type": "application/activity+json" },
1103
+ body: JSON.stringify(signed)
1104
+ }), { contextData: void 0 });
1105
+ assertEquals([response.status, await response.text()], [202, ""]);
1106
+ assertEquals(receivedRaw, signed);
1107
+ assertNotEquals(receivedRaw, await compactJsonLd(signed, sourceContextLoader));
1108
+ const delivered = receivedTyped;
1109
+ assert(delivered != null);
1110
+ assertEquals(delivered.id?.href, "https://example.com/activities/preserve-raw");
1111
+ });
1064
1112
  test("Federation.setInboxDispatcher()", async (t) => {
1065
1113
  const kv = new MemoryKvStore();
1066
1114
  await t.step("path match", () => {
@@ -1172,140 +1220,1567 @@ test("FederationImpl.sendActivity()", async (t) => {
1172
1220
  assertEquals(request?.url, "https://example.com/inbox");
1173
1221
  assertEquals(request?.headers.get("Content-Type"), "application/activity+json");
1174
1222
  });
1175
- esm_default.hardReset();
1176
- });
1177
- test("FederationImpl.processQueuedTask()", async (t) => {
1178
- await t.step("with MessageQueue having nativeRetrial", async () => {
1223
+ esm_default.hardReset();
1224
+ });
1225
+ test("FederationImpl.processQueuedTask()", async (t) => {
1226
+ await t.step("with MessageQueue having nativeRetrial", async () => {
1227
+ const kv = new MemoryKvStore();
1228
+ const queuedMessages = [];
1229
+ const federation = new FederationImpl({
1230
+ kv,
1231
+ queue: {
1232
+ nativeRetrial: true,
1233
+ enqueue(message, _options) {
1234
+ queuedMessages.push(message);
1235
+ return Promise.resolve();
1236
+ },
1237
+ listen(_handler, _options) {
1238
+ return Promise.resolve();
1239
+ }
1240
+ }
1241
+ });
1242
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(vocab.Create, () => {
1243
+ throw new Error("Intended error for testing");
1244
+ });
1245
+ await assertRejects(() => federation.processQueuedTask(void 0, {
1246
+ type: "outbox",
1247
+ id: crypto.randomUUID(),
1248
+ baseUrl: "https://example.com",
1249
+ keys: [],
1250
+ activity: {
1251
+ "@context": "https://www.w3.org/ns/activitystreams",
1252
+ type: "Create",
1253
+ actor: "https://example.com/users/alice",
1254
+ object: {
1255
+ type: "Note",
1256
+ content: "test"
1257
+ }
1258
+ },
1259
+ activityType: "https://www.w3.org/ns/activitystreams#Create",
1260
+ inbox: "https://invalid-domain-that-does-not-exist.example/inbox",
1261
+ sharedInbox: false,
1262
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1263
+ attempt: 0,
1264
+ headers: {},
1265
+ traceContext: {}
1266
+ }), Error);
1267
+ assertEquals(queuedMessages, []);
1268
+ await assertRejects(() => federation.processQueuedTask(void 0, {
1269
+ type: "inbox",
1270
+ id: crypto.randomUUID(),
1271
+ baseUrl: "https://example.com",
1272
+ activity: {
1273
+ "@context": "https://www.w3.org/ns/activitystreams",
1274
+ type: "Create",
1275
+ actor: "https://remote.example/users/alice",
1276
+ object: {
1277
+ type: "Note",
1278
+ content: "Hello world"
1279
+ }
1280
+ },
1281
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1282
+ attempt: 0,
1283
+ identifier: null,
1284
+ traceContext: {}
1285
+ }), Error);
1286
+ assertEquals(queuedMessages, []);
1287
+ });
1288
+ await t.step("with MessageQueue having no nativeRetrial", async () => {
1289
+ const kv = new MemoryKvStore();
1290
+ let queuedMessages = [];
1291
+ const federation = new FederationImpl({
1292
+ kv,
1293
+ queue: {
1294
+ enqueue(message, _options) {
1295
+ queuedMessages.push(message);
1296
+ return Promise.resolve();
1297
+ },
1298
+ listen(_handler, _options) {
1299
+ return Promise.resolve();
1300
+ }
1301
+ }
1302
+ });
1303
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(vocab.Create, () => {
1304
+ throw new Error("Intended error for testing");
1305
+ });
1306
+ const outboxMessage = {
1307
+ type: "outbox",
1308
+ id: crypto.randomUUID(),
1309
+ baseUrl: "https://example.com",
1310
+ keys: [],
1311
+ activity: {
1312
+ "@context": "https://www.w3.org/ns/activitystreams",
1313
+ type: "Create",
1314
+ actor: "https://example.com/users/alice",
1315
+ object: {
1316
+ type: "Note",
1317
+ content: "test"
1318
+ }
1319
+ },
1320
+ activityType: "https://www.w3.org/ns/activitystreams#Create",
1321
+ inbox: "https://invalid-domain-that-does-not-exist.example/inbox",
1322
+ sharedInbox: false,
1323
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1324
+ attempt: 0,
1325
+ headers: {},
1326
+ traceContext: {}
1327
+ };
1328
+ await federation.processQueuedTask(void 0, outboxMessage);
1329
+ assertEquals(queuedMessages, [{
1330
+ ...outboxMessage,
1331
+ attempt: 1
1332
+ }]);
1333
+ queuedMessages = [];
1334
+ const inboxMessage = {
1335
+ type: "inbox",
1336
+ id: crypto.randomUUID(),
1337
+ baseUrl: "https://example.com",
1338
+ activity: {
1339
+ "@context": "https://www.w3.org/ns/activitystreams",
1340
+ type: "Create",
1341
+ actor: "https://remote.example/users/alice",
1342
+ object: {
1343
+ type: "Note",
1344
+ content: "Hello world"
1345
+ }
1346
+ },
1347
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1348
+ attempt: 0,
1349
+ identifier: null,
1350
+ traceContext: {}
1351
+ };
1352
+ await federation.processQueuedTask(void 0, inboxMessage);
1353
+ assertEquals(queuedMessages, [{
1354
+ ...inboxMessage,
1355
+ attempt: 1
1356
+ }]);
1357
+ });
1358
+ await t.step("with restrictive context loader and normalized LD-signed inbox activity", async () => {
1359
+ const remoteContextUrl = "https://remote.example/contexts/ext";
1360
+ const sourceContextLoader = async (resource) => {
1361
+ const url = new URL(resource).href;
1362
+ if (url === remoteContextUrl) return {
1363
+ contextUrl: null,
1364
+ documentUrl: url,
1365
+ document: { "@context": { ext: "https://example.com/ext" } }
1366
+ };
1367
+ return await mockDocumentLoader(url);
1368
+ };
1369
+ const restrictiveContextLoader = async (resource) => {
1370
+ const url = new URL(resource).href;
1371
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
1372
+ throw new Error(`Unexpected context: ${url}`);
1373
+ };
1374
+ const kv = new MemoryKvStore();
1375
+ let receivedCount = 0;
1376
+ let received = null;
1377
+ let receivedRaw = null;
1378
+ const federation = new FederationImpl({
1379
+ kv,
1380
+ contextLoaderFactory: () => restrictiveContextLoader
1381
+ });
1382
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, (ctx, activity) => {
1383
+ receivedCount++;
1384
+ receivedRaw = ctx.activity;
1385
+ received = activity;
1386
+ });
1387
+ const signed = await signJsonLd({
1388
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1389
+ id: "https://remote.example/activities/1",
1390
+ type: "Create",
1391
+ actor: "https://remote.example/users/alice",
1392
+ ext: "preserve-me",
1393
+ object: {
1394
+ id: "https://remote.example/notes/1",
1395
+ type: "Note",
1396
+ attributedTo: "https://remote.example/users/alice",
1397
+ content: "Hello, world!"
1398
+ }
1399
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: sourceContextLoader });
1400
+ const normalizedActivity = await compactJsonLd(signed, sourceContextLoader);
1401
+ const messageId = crypto.randomUUID();
1402
+ await federation.processQueuedTask(void 0, {
1403
+ type: "inbox",
1404
+ id: messageId,
1405
+ baseUrl: "https://example.com",
1406
+ activity: signed,
1407
+ normalizedActivity,
1408
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1409
+ attempt: 0,
1410
+ identifier: null,
1411
+ traceContext: {}
1412
+ });
1413
+ const delivered = received;
1414
+ assert(delivered != null);
1415
+ const deliveredCreate = delivered;
1416
+ assertInstanceOf(deliveredCreate, Create);
1417
+ assertEquals(deliveredCreate.id?.href, "https://remote.example/activities/1");
1418
+ assertEquals(receivedRaw, signed);
1419
+ await federation.processQueuedTask(void 0, {
1420
+ type: "inbox",
1421
+ id: messageId,
1422
+ baseUrl: "https://example.com",
1423
+ activity: signed,
1424
+ normalizedActivity,
1425
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1426
+ attempt: 0,
1427
+ identifier: null,
1428
+ traceContext: {}
1429
+ });
1430
+ assertEquals(receivedCount, 1);
1431
+ });
1432
+ await t.step("cached normalizedActivity is rechecked for unsafe JSON-LD keywords", async () => {
1433
+ const queuedMessages = [];
1434
+ const queue = {
1435
+ enqueue(message, _options) {
1436
+ queuedMessages.push(message);
1437
+ return Promise.resolve();
1438
+ },
1439
+ listen(_handler, _options) {
1440
+ return Promise.resolve();
1441
+ }
1442
+ };
1443
+ const kv = new MemoryKvStore();
1444
+ let receivedCount = 0;
1445
+ let errorCount = 0;
1446
+ const federation = new FederationImpl({
1447
+ kv,
1448
+ queue
1449
+ });
1450
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
1451
+ receivedCount++;
1452
+ }).onError(() => {
1453
+ errorCount++;
1454
+ });
1455
+ const signed = await signJsonLd({
1456
+ "@context": "https://www.w3.org/ns/activitystreams",
1457
+ id: "https://remote.example/activities/unsafe-normalized-cache",
1458
+ type: "Create",
1459
+ actor: "https://remote.example/users/alice",
1460
+ object: {
1461
+ id: "https://remote.example/notes/unsafe-normalized-cache",
1462
+ type: "Note",
1463
+ attributedTo: "https://remote.example/users/alice",
1464
+ content: "Hello from unsafe normalized cache"
1465
+ }
1466
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
1467
+ const normalizedActivity = await compactJsonLd(signed, mockDocumentLoader);
1468
+ const tamperedNormalizedActivity = {
1469
+ ...normalizedActivity,
1470
+ signature: {
1471
+ ...normalizedActivity.signature,
1472
+ "@included": [{
1473
+ id: "https://remote.example/activities/inside-signature",
1474
+ type: "Undo"
1475
+ }]
1476
+ }
1477
+ };
1478
+ await federation.processQueuedTask(void 0, {
1479
+ type: "inbox",
1480
+ id: crypto.randomUUID(),
1481
+ baseUrl: "https://example.com",
1482
+ activity: signed,
1483
+ normalizedActivity: tamperedNormalizedActivity,
1484
+ ldSignatureVerified: false,
1485
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1486
+ attempt: 0,
1487
+ identifier: null,
1488
+ traceContext: {}
1489
+ });
1490
+ assertEquals(receivedCount, 0);
1491
+ assertEquals(errorCount, 1);
1492
+ assertEquals(queuedMessages, []);
1493
+ });
1494
+ await t.step("old queued LDS inbox messages without normalizedActivity still work", async () => {
1495
+ const restrictiveContextLoader = async (resource) => {
1496
+ const url = new URL(resource).href;
1497
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
1498
+ throw new Error(`Unexpected context: ${url}`);
1499
+ };
1500
+ const kv = new MemoryKvStore();
1501
+ let received = null;
1502
+ const federation = new FederationImpl({
1503
+ kv,
1504
+ contextLoaderFactory: () => restrictiveContextLoader
1505
+ });
1506
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, (_ctx, activity) => {
1507
+ received = activity;
1508
+ });
1509
+ const compacted = await compactJsonLd(await signJsonLd({
1510
+ "@context": "https://www.w3.org/ns/activitystreams",
1511
+ id: "https://remote.example/activities/legacy",
1512
+ type: "Create",
1513
+ actor: "https://remote.example/users/alice",
1514
+ object: {
1515
+ id: "https://remote.example/notes/legacy",
1516
+ type: "Note",
1517
+ attributedTo: "https://remote.example/users/alice",
1518
+ content: "Hello from legacy queue"
1519
+ }
1520
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader }), restrictiveContextLoader);
1521
+ await federation.processQueuedTask(void 0, {
1522
+ type: "inbox",
1523
+ id: crypto.randomUUID(),
1524
+ baseUrl: "https://example.com",
1525
+ activity: compacted,
1526
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1527
+ attempt: 0,
1528
+ identifier: null,
1529
+ traceContext: {}
1530
+ });
1531
+ assert(received != null);
1532
+ assertEquals(received.id?.href, "https://remote.example/activities/legacy");
1533
+ });
1534
+ await t.step("queued signature-bearing non-LDS inbox messages keep parse-time normalization contexts", async () => {
1535
+ const signingContextLoader = async (resource) => {
1536
+ const url = new URL(resource).href;
1537
+ 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);
1538
+ throw new Error(`Unexpected context: ${url}`);
1539
+ };
1540
+ const processingContextLoader = async (resource) => {
1541
+ const url = new URL(resource).href;
1542
+ 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");
1543
+ return await signingContextLoader(resource);
1544
+ };
1545
+ const kv = new MemoryKvStore();
1546
+ let received = null;
1547
+ let receivedRaw = null;
1548
+ const federation = new FederationImpl({
1549
+ kv,
1550
+ contextLoaderFactory: () => processingContextLoader
1551
+ });
1552
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, (ctx, activity) => {
1553
+ receivedRaw = ctx.activity;
1554
+ received = activity;
1555
+ });
1556
+ const signed = await signJsonLd({
1557
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
1558
+ id: "https://remote.example/activities/non-lds-queued-signature",
1559
+ type: "Create",
1560
+ actor: "https://remote.example/users/alice",
1561
+ object: {
1562
+ id: "https://remote.example/notes/non-lds-queued-signature",
1563
+ type: "Note",
1564
+ attributedTo: "https://remote.example/users/alice",
1565
+ content: "Hello from non-LDS queued signature"
1566
+ }
1567
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: signingContextLoader });
1568
+ const signedPayload = signed;
1569
+ assert(Array.isArray(signedPayload["@context"]) && signedPayload["@context"].includes("https://w3id.org/security/v1"));
1570
+ await federation.processQueuedTask(void 0, {
1571
+ type: "inbox",
1572
+ id: crypto.randomUUID(),
1573
+ baseUrl: "https://example.com",
1574
+ activity: signed,
1575
+ ldSignatureVerified: false,
1576
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1577
+ attempt: 0,
1578
+ identifier: null,
1579
+ traceContext: {}
1580
+ });
1581
+ if (received == null) throw new Error("Inbox activity not delivered.");
1582
+ assertEquals(received.id?.href, "https://remote.example/activities/non-lds-queued-signature");
1583
+ assertEquals(receivedRaw, signed);
1584
+ });
1585
+ await t.step("queued signature-bearing non-LDS inbox messages reuse normalizedActivity for custom contexts", async () => {
1586
+ const remoteContextUrl = "https://remote.example/contexts/ext";
1587
+ const sourceContextLoader = async (resource) => {
1588
+ const url = new URL(resource).href;
1589
+ if (url === remoteContextUrl) return {
1590
+ contextUrl: null,
1591
+ documentUrl: url,
1592
+ document: { "@context": { ext: "https://example.com/ext" } }
1593
+ };
1594
+ return await mockDocumentLoader(url);
1595
+ };
1596
+ const restrictiveContextLoader = async (resource) => {
1597
+ const url = new URL(resource).href;
1598
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
1599
+ throw new Error(`Unexpected context: ${url}`);
1600
+ };
1601
+ const kv = new MemoryKvStore();
1602
+ let received = null;
1603
+ let receivedRaw = null;
1604
+ const federation = new FederationImpl({
1605
+ kv,
1606
+ contextLoaderFactory: () => restrictiveContextLoader
1607
+ });
1608
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, (ctx, activity) => {
1609
+ receivedRaw = ctx.activity;
1610
+ received = activity;
1611
+ });
1612
+ const unsignedBody = {
1613
+ "@context": [
1614
+ remoteContextUrl,
1615
+ "https://www.w3.org/ns/activitystreams",
1616
+ "https://w3id.org/security/v1"
1617
+ ],
1618
+ id: "https://remote.example/activities/non-lds-queued-custom-context",
1619
+ type: "Create",
1620
+ actor: "https://remote.example/users/alice",
1621
+ ext: "preserve-me",
1622
+ object: {
1623
+ id: "https://remote.example/notes/non-lds-queued-custom-context",
1624
+ type: "Note",
1625
+ attributedTo: "https://remote.example/users/alice",
1626
+ content: "Hello from non-LDS queued custom context"
1627
+ },
1628
+ signature: {
1629
+ type: "RsaSignature2017",
1630
+ creator: "not a url",
1631
+ created: "2024-09-12T16:50:46Z",
1632
+ signatureValue: "Zm9v"
1633
+ }
1634
+ };
1635
+ const normalizedActivity = await compactJsonLd(unsignedBody, sourceContextLoader);
1636
+ await federation.processQueuedTask(void 0, {
1637
+ type: "inbox",
1638
+ id: crypto.randomUUID(),
1639
+ baseUrl: "https://example.com",
1640
+ activity: unsignedBody,
1641
+ normalizedActivity,
1642
+ ldSignatureVerified: false,
1643
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1644
+ attempt: 0,
1645
+ identifier: null,
1646
+ traceContext: {}
1647
+ });
1648
+ if (received == null) throw new Error("Inbox activity not delivered.");
1649
+ assertEquals(received.id?.href, "https://remote.example/activities/non-lds-queued-custom-context");
1650
+ assertEquals(receivedRaw, unsignedBody);
1651
+ });
1652
+ await t.step("legacy raw LDS inbox messages without normalizedActivity retry through worker error handling", async () => {
1653
+ const remoteContextUrl = "https://remote.example/contexts/ext";
1654
+ const restrictiveContextLoader = async (resource) => {
1655
+ const url = new URL(resource).href;
1656
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
1657
+ throw new Error(`Unexpected context: ${url}`);
1658
+ };
1659
+ const queue = {
1660
+ enqueue(message, _options) {
1661
+ queuedMessages.push(message);
1662
+ return Promise.resolve();
1663
+ },
1664
+ listen(_handler, _options) {
1665
+ return Promise.resolve();
1666
+ }
1667
+ };
1668
+ const kv = new MemoryKvStore();
1669
+ const queuedMessages = [];
1670
+ let errorCount = 0;
1671
+ const federation = new FederationImpl({
1672
+ kv,
1673
+ queue,
1674
+ contextLoaderFactory: () => restrictiveContextLoader
1675
+ });
1676
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
1677
+ throw new Error("listener should not run");
1678
+ }).onError(() => {
1679
+ errorCount++;
1680
+ });
1681
+ const signed = await signJsonLd({
1682
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1683
+ id: "https://remote.example/activities/legacy-raw",
1684
+ type: "Create",
1685
+ actor: "https://remote.example/users/alice",
1686
+ ext: "preserve-me",
1687
+ object: {
1688
+ id: "https://remote.example/notes/legacy-raw",
1689
+ type: "Note",
1690
+ attributedTo: "https://remote.example/users/alice",
1691
+ content: "Hello from raw legacy queue"
1692
+ }
1693
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
1694
+ const url = new URL(resource).href;
1695
+ if (url === remoteContextUrl) return {
1696
+ contextUrl: null,
1697
+ documentUrl: url,
1698
+ document: { "@context": { ext: "https://example.com/ext" } }
1699
+ };
1700
+ return await mockDocumentLoader(url);
1701
+ } });
1702
+ const inboxMessage = {
1703
+ type: "inbox",
1704
+ id: crypto.randomUUID(),
1705
+ baseUrl: "https://example.com",
1706
+ activity: signed,
1707
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1708
+ attempt: 0,
1709
+ identifier: null,
1710
+ traceContext: {}
1711
+ };
1712
+ await federation.processQueuedTask(void 0, inboxMessage);
1713
+ assertEquals(errorCount, 1);
1714
+ assertEquals(queuedMessages.length, 1);
1715
+ const retried = queuedMessages[0];
1716
+ assertEquals(retried.attempt, 1);
1717
+ assertEquals(retried.activity, inboxMessage.activity);
1718
+ });
1719
+ await t.step("without inbox queue retriable inbox parse failures bubble to caller", async () => {
1720
+ const remoteContextUrl = "https://remote.example/contexts/ext";
1721
+ const sourceContextLoader = async (resource) => {
1722
+ const url = new URL(resource).href;
1723
+ if (url === remoteContextUrl) return {
1724
+ contextUrl: null,
1725
+ documentUrl: url,
1726
+ document: { "@context": { ext: "https://example.com/ext" } }
1727
+ };
1728
+ return await mockDocumentLoader(url);
1729
+ };
1730
+ let errorCount = 0;
1731
+ const federation = new FederationImpl({
1732
+ kv: new MemoryKvStore(),
1733
+ contextLoaderFactory: () => async (resource) => {
1734
+ const url = new URL(resource).href;
1735
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
1736
+ if (url === remoteContextUrl) throw new Error(`Transient remote context failure: ${url}`);
1737
+ throw new Error(`Unexpected context: ${url}`);
1738
+ }
1739
+ });
1740
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
1741
+ throw new Error("listener should not run");
1742
+ }).onError(() => {
1743
+ errorCount++;
1744
+ });
1745
+ const signed = await signJsonLd({
1746
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1747
+ id: "https://remote.example/activities/manual-retry",
1748
+ type: "Create",
1749
+ actor: "https://remote.example/users/alice",
1750
+ ext: "preserve-me",
1751
+ object: {
1752
+ id: "https://remote.example/notes/manual-retry",
1753
+ type: "Note",
1754
+ attributedTo: "https://remote.example/users/alice",
1755
+ content: "Hello from manual retry queue"
1756
+ }
1757
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: sourceContextLoader });
1758
+ await assertRejects(() => federation.processQueuedTask(void 0, {
1759
+ type: "inbox",
1760
+ id: crypto.randomUUID(),
1761
+ baseUrl: "https://example.com",
1762
+ activity: signed,
1763
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1764
+ attempt: 0,
1765
+ identifier: null,
1766
+ traceContext: {}
1767
+ }), Error);
1768
+ assertEquals(errorCount, 1);
1769
+ });
1770
+ await t.step("legacy raw LDS inbox messages with transient InvalidUrl failures retry", async () => {
1771
+ const remoteContextUrl = "https://remote.example/contexts/ext";
1772
+ const queue = {
1773
+ enqueue(message, _options) {
1774
+ queuedMessages.push(message);
1775
+ return Promise.resolve();
1776
+ },
1777
+ listen(_handler, _options) {
1778
+ return Promise.resolve();
1779
+ }
1780
+ };
1781
+ const kv = new MemoryKvStore();
1782
+ const queuedMessages = [];
1783
+ let errorCount = 0;
1784
+ const federation = new FederationImpl({
1785
+ kv,
1786
+ queue,
1787
+ contextLoaderFactory: () => async (resource) => {
1788
+ const url = new URL(resource).href;
1789
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
1790
+ if (url === remoteContextUrl) {
1791
+ const error = /* @__PURE__ */ new Error(`Transient remote context failure: ${url}`);
1792
+ error.name = "jsonld.InvalidUrl";
1793
+ error.details = {
1794
+ code: "loading remote context failed",
1795
+ url
1796
+ };
1797
+ throw error;
1798
+ }
1799
+ throw new Error(`Unexpected context: ${url}`);
1800
+ }
1801
+ });
1802
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
1803
+ throw new Error("listener should not run");
1804
+ }).onError(() => {
1805
+ errorCount++;
1806
+ });
1807
+ const signed = await signJsonLd({
1808
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1809
+ id: "https://remote.example/activities/legacy-invalid-context",
1810
+ type: "Create",
1811
+ actor: "https://remote.example/users/alice",
1812
+ ext: "preserve-me",
1813
+ object: {
1814
+ id: "https://remote.example/notes/legacy-invalid-context",
1815
+ type: "Note",
1816
+ attributedTo: "https://remote.example/users/alice",
1817
+ content: "Hello from invalid legacy queue"
1818
+ }
1819
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
1820
+ const url = new URL(resource).href;
1821
+ if (url === remoteContextUrl) return {
1822
+ contextUrl: null,
1823
+ documentUrl: url,
1824
+ document: { "@context": { ext: "https://example.com/ext" } }
1825
+ };
1826
+ return await mockDocumentLoader(url);
1827
+ } });
1828
+ const inboxMessage = {
1829
+ type: "inbox",
1830
+ id: crypto.randomUUID(),
1831
+ baseUrl: "https://example.com",
1832
+ activity: signed,
1833
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1834
+ attempt: 0,
1835
+ identifier: null,
1836
+ traceContext: {}
1837
+ };
1838
+ await federation.processQueuedTask(void 0, inboxMessage);
1839
+ assertEquals(errorCount, 1);
1840
+ assertEquals(queuedMessages.length, 1);
1841
+ const retried = queuedMessages[0];
1842
+ assertEquals(retried.attempt, 1);
1843
+ assertEquals(retried.activity, inboxMessage.activity);
1844
+ });
1845
+ await t.step("legacy raw LDS inbox messages with opaque context ids retry", async () => {
1846
+ const queue = {
1847
+ enqueue(message, _options) {
1848
+ queuedMessages.push(message);
1849
+ return Promise.resolve();
1850
+ },
1851
+ listen(_handler, _options) {
1852
+ return Promise.resolve();
1853
+ }
1854
+ };
1855
+ const kv = new MemoryKvStore();
1856
+ const queuedMessages = [];
1857
+ let errorCount = 0;
1858
+ const federation = new FederationImpl({
1859
+ kv,
1860
+ queue,
1861
+ contextLoaderFactory: () => async (resource) => {
1862
+ if (resource === "app-context") {
1863
+ const error = /* @__PURE__ */ new Error(`Opaque context backend is unavailable: ${resource}`);
1864
+ error.name = "jsonld.InvalidUrl";
1865
+ error.details = {
1866
+ code: "loading remote context failed",
1867
+ url: resource
1868
+ };
1869
+ throw error;
1870
+ }
1871
+ const url = new URL(resource).href;
1872
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
1873
+ throw new Error(`Unexpected context: ${resource}`);
1874
+ }
1875
+ });
1876
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
1877
+ throw new Error("listener should not run");
1878
+ }).onError(() => {
1879
+ errorCount++;
1880
+ });
1881
+ const signed = await signJsonLd({
1882
+ "@context": "https://www.w3.org/ns/activitystreams",
1883
+ id: "https://remote.example/activities/legacy-malformed-context",
1884
+ type: "Create",
1885
+ actor: "https://remote.example/users/alice",
1886
+ object: {
1887
+ id: "https://remote.example/notes/legacy-malformed-context",
1888
+ type: "Note",
1889
+ attributedTo: "https://remote.example/users/alice",
1890
+ content: "Hello from malformed legacy queue"
1891
+ }
1892
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
1893
+ const inboxMessage = {
1894
+ type: "inbox",
1895
+ id: crypto.randomUUID(),
1896
+ baseUrl: "https://example.com",
1897
+ activity: {
1898
+ ...signed,
1899
+ "@context": ["app-context", "https://www.w3.org/ns/activitystreams"]
1900
+ },
1901
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1902
+ attempt: 0,
1903
+ identifier: null,
1904
+ traceContext: {}
1905
+ };
1906
+ await federation.processQueuedTask(void 0, inboxMessage);
1907
+ assertEquals(errorCount, 1);
1908
+ assertEquals(queuedMessages, [{
1909
+ ...inboxMessage,
1910
+ attempt: 1
1911
+ }]);
1912
+ });
1913
+ await t.step("legacy raw LDS inbox messages with Invalid URL TypeErrors retry", async () => {
1914
+ const queue = {
1915
+ enqueue(message, _options) {
1916
+ queuedMessages.push(message);
1917
+ return Promise.resolve();
1918
+ },
1919
+ listen(_handler, _options) {
1920
+ return Promise.resolve();
1921
+ }
1922
+ };
1923
+ const kv = new MemoryKvStore();
1924
+ const queuedMessages = [];
1925
+ let errorCount = 0;
1926
+ const federation = new FederationImpl({
1927
+ kv,
1928
+ queue,
1929
+ contextLoaderFactory: () => async (resource) => {
1930
+ if (resource === "app:context") throw new TypeError(`Invalid URL: ${resource}`);
1931
+ const url = new URL(resource).href;
1932
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
1933
+ throw new Error(`Unexpected context: ${resource}`);
1934
+ }
1935
+ });
1936
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
1937
+ throw new Error("listener should not run");
1938
+ }).onError(() => {
1939
+ errorCount++;
1940
+ });
1941
+ const signed = await signJsonLd({
1942
+ "@context": "https://www.w3.org/ns/activitystreams",
1943
+ id: "https://remote.example/activities/legacy-typeerror-invalid-url",
1944
+ type: "Create",
1945
+ actor: "https://remote.example/users/alice",
1946
+ object: {
1947
+ id: "https://remote.example/notes/legacy-typeerror-invalid-url",
1948
+ type: "Note",
1949
+ attributedTo: "https://remote.example/users/alice",
1950
+ content: "Hello from invalid-url typeerror queue"
1951
+ }
1952
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
1953
+ const inboxMessage = {
1954
+ type: "inbox",
1955
+ id: crypto.randomUUID(),
1956
+ baseUrl: "https://example.com",
1957
+ activity: {
1958
+ ...signed,
1959
+ "@context": ["app:context", "https://www.w3.org/ns/activitystreams"]
1960
+ },
1961
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1962
+ attempt: 0,
1963
+ identifier: null,
1964
+ traceContext: {}
1965
+ };
1966
+ await federation.processQueuedTask(void 0, inboxMessage);
1967
+ assertEquals(errorCount, 1);
1968
+ assertEquals(queuedMessages.length, 1);
1969
+ const retried = queuedMessages[0];
1970
+ assertEquals(retried.attempt, 1);
1971
+ assertEquals(retried.activity, inboxMessage.activity);
1972
+ });
1973
+ await t.step("legacy raw LDS inbox messages with malformed absolute context refs do not retry", async () => {
1974
+ const queue = {
1975
+ enqueue(message, _options) {
1976
+ queuedMessages.push(message);
1977
+ return Promise.resolve();
1978
+ },
1979
+ listen(_handler, _options) {
1980
+ return Promise.resolve();
1981
+ }
1982
+ };
1983
+ const kv = new MemoryKvStore();
1984
+ const queuedMessages = [];
1985
+ let errorCount = 0;
1986
+ const federation = new FederationImpl({
1987
+ kv,
1988
+ queue,
1989
+ contextLoaderFactory: () => async (resource) => {
1990
+ if (resource === "http:/[") throw new TypeError(`Invalid URL: ${resource}`);
1991
+ const url = new URL(resource).href;
1992
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
1993
+ throw new Error(`Unexpected context: ${resource}`);
1994
+ }
1995
+ });
1996
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
1997
+ throw new Error("listener should not run");
1998
+ }).onError(() => {
1999
+ errorCount++;
2000
+ });
2001
+ const signed = await signJsonLd({
2002
+ "@context": "https://www.w3.org/ns/activitystreams",
2003
+ id: "https://remote.example/activities/legacy-malformed-absolute-context",
2004
+ type: "Create",
2005
+ actor: "https://remote.example/users/alice",
2006
+ object: {
2007
+ id: "https://remote.example/notes/legacy-malformed-absolute-context",
2008
+ type: "Note",
2009
+ attributedTo: "https://remote.example/users/alice",
2010
+ content: "Hello from malformed absolute context queue"
2011
+ }
2012
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
2013
+ const inboxMessage = {
2014
+ type: "inbox",
2015
+ id: crypto.randomUUID(),
2016
+ baseUrl: "https://example.com",
2017
+ activity: {
2018
+ ...signed,
2019
+ "@context": ["http:/[", "https://www.w3.org/ns/activitystreams"]
2020
+ },
2021
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2022
+ attempt: 0,
2023
+ identifier: null,
2024
+ traceContext: {}
2025
+ };
2026
+ await federation.processQueuedTask(void 0, inboxMessage);
2027
+ assertEquals(errorCount, 1);
2028
+ assertEquals(queuedMessages, []);
2029
+ });
2030
+ await t.step("malformed IRI fields are permanent queued inbox parse errors", async () => {
2031
+ const queuedMessages = [];
2032
+ const queue = {
2033
+ enqueue(message, _options) {
2034
+ queuedMessages.push(message);
2035
+ return Promise.resolve();
2036
+ },
2037
+ listen(_handler, _options) {
2038
+ return Promise.resolve();
2039
+ }
2040
+ };
2041
+ const kv = new MemoryKvStore();
2042
+ let errorCount = 0;
2043
+ const federation = new FederationImpl({
2044
+ kv,
2045
+ queue
2046
+ });
2047
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2048
+ throw new Error("listener should not run");
2049
+ }).onError(() => {
2050
+ errorCount++;
2051
+ });
2052
+ await federation.processQueuedTask(void 0, {
2053
+ type: "inbox",
2054
+ id: crypto.randomUUID(),
2055
+ baseUrl: "https://example.com",
2056
+ activity: {
2057
+ "@context": "https://www.w3.org/ns/activitystreams",
2058
+ id: "http://[",
2059
+ type: "Create",
2060
+ actor: "https://remote.example/users/alice",
2061
+ object: {
2062
+ id: "https://remote.example/notes/invalid-iri",
2063
+ type: "Note",
2064
+ attributedTo: "https://remote.example/users/alice",
2065
+ content: "Hello from invalid IRI queue"
2066
+ }
2067
+ },
2068
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2069
+ attempt: 0,
2070
+ identifier: null,
2071
+ traceContext: {}
2072
+ });
2073
+ assertEquals(errorCount, 1);
2074
+ assertEquals(queuedMessages, []);
2075
+ });
2076
+ await t.step("legacy raw LDS inbox messages with network-path context ids retry", async () => {
2077
+ const queue = {
2078
+ enqueue(message, _options) {
2079
+ queuedMessages.push(message);
2080
+ return Promise.resolve();
2081
+ },
2082
+ listen(_handler, _options) {
2083
+ return Promise.resolve();
2084
+ }
2085
+ };
2086
+ const kv = new MemoryKvStore();
2087
+ const queuedMessages = [];
2088
+ let errorCount = 0;
2089
+ const federation = new FederationImpl({
2090
+ kv,
2091
+ queue,
2092
+ contextLoaderFactory: () => async (resource) => {
2093
+ if (resource === "//cdn.example/ctx") {
2094
+ const error = /* @__PURE__ */ new Error(`Network-path context backend is unavailable: ${resource}`);
2095
+ error.name = "jsonld.InvalidUrl";
2096
+ error.details = {
2097
+ code: "loading remote context failed",
2098
+ url: resource
2099
+ };
2100
+ throw error;
2101
+ }
2102
+ const url = new URL(resource).href;
2103
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2104
+ throw new Error(`Unexpected context: ${resource}`);
2105
+ }
2106
+ });
2107
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2108
+ throw new Error("listener should not run");
2109
+ }).onError(() => {
2110
+ errorCount++;
2111
+ });
2112
+ const signed = await signJsonLd({
2113
+ "@context": "https://www.w3.org/ns/activitystreams",
2114
+ id: "https://remote.example/activities/legacy-network-path-context",
2115
+ type: "Create",
2116
+ actor: "https://remote.example/users/alice",
2117
+ object: {
2118
+ id: "https://remote.example/notes/legacy-network-path-context",
2119
+ type: "Note",
2120
+ attributedTo: "https://remote.example/users/alice",
2121
+ content: "Hello from network-path legacy queue"
2122
+ }
2123
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
2124
+ const inboxMessage = {
2125
+ type: "inbox",
2126
+ id: crypto.randomUUID(),
2127
+ baseUrl: "https://example.com",
2128
+ activity: {
2129
+ ...signed,
2130
+ "@context": ["//cdn.example/ctx", "https://www.w3.org/ns/activitystreams"]
2131
+ },
2132
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2133
+ attempt: 0,
2134
+ identifier: null,
2135
+ traceContext: {}
2136
+ };
2137
+ await federation.processQueuedTask(void 0, inboxMessage);
2138
+ assertEquals(errorCount, 1);
2139
+ assertEquals(queuedMessages, [{
2140
+ ...inboxMessage,
2141
+ attempt: 1
2142
+ }]);
2143
+ });
2144
+ await t.step("legacy raw LDS inbox messages with malformed network-path refs do not retry", async () => {
2145
+ const queue = {
2146
+ enqueue(message, _options) {
2147
+ queuedMessages.push(message);
2148
+ return Promise.resolve();
2149
+ },
2150
+ listen(_handler, _options) {
2151
+ return Promise.resolve();
2152
+ }
2153
+ };
2154
+ const kv = new MemoryKvStore();
2155
+ const queuedMessages = [];
2156
+ let errorCount = 0;
2157
+ const federation = new FederationImpl({
2158
+ kv,
2159
+ queue,
2160
+ contextLoaderFactory: () => async (resource) => {
2161
+ if (resource === "//[") {
2162
+ const error = /* @__PURE__ */ new Error(`Malformed network-path context: ${resource}`);
2163
+ error.name = "jsonld.InvalidUrl";
2164
+ error.details = {
2165
+ code: "loading remote context failed",
2166
+ url: resource
2167
+ };
2168
+ throw error;
2169
+ }
2170
+ const url = new URL(resource).href;
2171
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2172
+ throw new Error(`Unexpected context: ${resource}`);
2173
+ }
2174
+ });
2175
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2176
+ throw new Error("listener should not run");
2177
+ }).onError(() => {
2178
+ errorCount++;
2179
+ });
2180
+ const signed = await signJsonLd({
2181
+ "@context": "https://www.w3.org/ns/activitystreams",
2182
+ id: "https://remote.example/activities/legacy-malformed-network-path-context",
2183
+ type: "Create",
2184
+ actor: "https://remote.example/users/alice",
2185
+ object: {
2186
+ id: "https://remote.example/notes/legacy-malformed-network-path-context",
2187
+ type: "Note",
2188
+ attributedTo: "https://remote.example/users/alice",
2189
+ content: "Hello from malformed network-path legacy queue"
2190
+ }
2191
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
2192
+ const inboxMessage = {
2193
+ type: "inbox",
2194
+ id: crypto.randomUUID(),
2195
+ baseUrl: "https://example.com",
2196
+ activity: {
2197
+ ...signed,
2198
+ "@context": ["//[", "https://www.w3.org/ns/activitystreams"]
2199
+ },
2200
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2201
+ attempt: 0,
2202
+ identifier: null,
2203
+ traceContext: {}
2204
+ };
2205
+ await federation.processQueuedTask(void 0, inboxMessage);
2206
+ assertEquals(errorCount, 1);
2207
+ assertEquals(queuedMessages, []);
2208
+ });
2209
+ await t.step("legacy raw LDS inbox messages with malformed context URLs do not retry", async () => {
2210
+ const queue = {
2211
+ enqueue(message, _options) {
2212
+ queuedMessages.push(message);
2213
+ return Promise.resolve();
2214
+ },
2215
+ listen(_handler, _options) {
2216
+ return Promise.resolve();
2217
+ }
2218
+ };
2219
+ const kv = new MemoryKvStore();
2220
+ const queuedMessages = [];
2221
+ let errorCount = 0;
2222
+ const federation = new FederationImpl({
2223
+ kv,
2224
+ queue,
2225
+ contextLoaderFactory: () => async (resource) => {
2226
+ if (resource === "not a url") {
2227
+ const error = /* @__PURE__ */ new Error(`Invalid remote context URL: ${resource}`);
2228
+ error.name = "jsonld.InvalidUrl";
2229
+ error.details = {
2230
+ code: "loading remote context failed",
2231
+ url: resource
2232
+ };
2233
+ throw error;
2234
+ }
2235
+ const url = new URL(resource).href;
2236
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2237
+ throw new Error(`Unexpected context: ${resource}`);
2238
+ }
2239
+ });
2240
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2241
+ throw new Error("listener should not run");
2242
+ }).onError(() => {
2243
+ errorCount++;
2244
+ });
2245
+ const signed = await signJsonLd({
2246
+ "@context": "https://www.w3.org/ns/activitystreams",
2247
+ id: "https://remote.example/activities/legacy-malformed-context",
2248
+ type: "Create",
2249
+ actor: "https://remote.example/users/alice",
2250
+ object: {
2251
+ id: "https://remote.example/notes/legacy-malformed-context",
2252
+ type: "Note",
2253
+ attributedTo: "https://remote.example/users/alice",
2254
+ content: "Hello from malformed legacy queue"
2255
+ }
2256
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
2257
+ const inboxMessage = {
2258
+ type: "inbox",
2259
+ id: crypto.randomUUID(),
2260
+ baseUrl: "https://example.com",
2261
+ activity: {
2262
+ ...signed,
2263
+ "@context": ["not a url", "https://www.w3.org/ns/activitystreams"]
2264
+ },
2265
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2266
+ attempt: 0,
2267
+ identifier: null,
2268
+ traceContext: {}
2269
+ };
2270
+ await federation.processQueuedTask(void 0, inboxMessage);
2271
+ assertEquals(errorCount, 1);
2272
+ assertEquals(queuedMessages, []);
2273
+ });
2274
+ await t.step("legacy raw LDS inbox messages with invalid percent escapes do not retry", async () => {
2275
+ const queue = {
2276
+ enqueue(message, _options) {
2277
+ queuedMessages.push(message);
2278
+ return Promise.resolve();
2279
+ },
2280
+ listen(_handler, _options) {
2281
+ return Promise.resolve();
2282
+ }
2283
+ };
1179
2284
  const kv = new MemoryKvStore();
1180
2285
  const queuedMessages = [];
2286
+ let errorCount = 0;
1181
2287
  const federation = new FederationImpl({
1182
2288
  kv,
1183
- queue: {
1184
- nativeRetrial: true,
1185
- enqueue(message, _options) {
1186
- queuedMessages.push(message);
1187
- return Promise.resolve();
1188
- },
1189
- listen(_handler, _options) {
1190
- return Promise.resolve();
2289
+ queue,
2290
+ contextLoaderFactory: () => async (resource) => {
2291
+ if (resource === "foo%zz") {
2292
+ const error = /* @__PURE__ */ new Error(`Invalid remote context URL: ${resource}`);
2293
+ error.name = "jsonld.InvalidUrl";
2294
+ error.details = {
2295
+ code: "loading remote context failed",
2296
+ url: resource
2297
+ };
2298
+ throw error;
1191
2299
  }
2300
+ const url = new URL(resource).href;
2301
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2302
+ throw new Error(`Unexpected context: ${resource}`);
1192
2303
  }
1193
2304
  });
1194
- federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(vocab.Create, () => {
1195
- throw new Error("Intended error for testing");
2305
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2306
+ throw new Error("listener should not run");
2307
+ }).onError(() => {
2308
+ errorCount++;
1196
2309
  });
1197
- await assertRejects(() => federation.processQueuedTask(void 0, {
1198
- type: "outbox",
2310
+ const signed = await signJsonLd({
2311
+ "@context": "https://www.w3.org/ns/activitystreams",
2312
+ id: "https://remote.example/activities/legacy-malformed-percent-context",
2313
+ type: "Create",
2314
+ actor: "https://remote.example/users/alice",
2315
+ object: {
2316
+ id: "https://remote.example/notes/legacy-malformed-percent-context",
2317
+ type: "Note",
2318
+ attributedTo: "https://remote.example/users/alice",
2319
+ content: "Hello from malformed percent legacy queue"
2320
+ }
2321
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
2322
+ const inboxMessage = {
2323
+ type: "inbox",
1199
2324
  id: crypto.randomUUID(),
1200
2325
  baseUrl: "https://example.com",
1201
- keys: [],
1202
2326
  activity: {
1203
- "@context": "https://www.w3.org/ns/activitystreams",
1204
- type: "Create",
1205
- actor: "https://example.com/users/alice",
1206
- object: {
1207
- type: "Note",
1208
- content: "test"
1209
- }
2327
+ ...signed,
2328
+ "@context": ["foo%zz", "https://www.w3.org/ns/activitystreams"]
1210
2329
  },
1211
- activityType: "https://www.w3.org/ns/activitystreams#Create",
1212
- inbox: "https://invalid-domain-that-does-not-exist.example/inbox",
1213
- sharedInbox: false,
1214
2330
  started: (/* @__PURE__ */ new Date()).toISOString(),
1215
2331
  attempt: 0,
1216
- headers: {},
2332
+ identifier: null,
1217
2333
  traceContext: {}
1218
- }), Error);
2334
+ };
2335
+ await federation.processQueuedTask(void 0, inboxMessage);
2336
+ assertEquals(errorCount, 1);
1219
2337
  assertEquals(queuedMessages, []);
1220
- await assertRejects(() => federation.processQueuedTask(void 0, {
2338
+ });
2339
+ await t.step("legacy raw LDS inbox messages with invalid remote contexts do not retry", async () => {
2340
+ const remoteContextUrl = "https://remote.example/contexts/ext";
2341
+ const queue = {
2342
+ enqueue(message, _options) {
2343
+ queuedMessages.push(message);
2344
+ return Promise.resolve();
2345
+ },
2346
+ listen(_handler, _options) {
2347
+ return Promise.resolve();
2348
+ }
2349
+ };
2350
+ const kv = new MemoryKvStore();
2351
+ const queuedMessages = [];
2352
+ let errorCount = 0;
2353
+ const federation = new FederationImpl({
2354
+ kv,
2355
+ queue,
2356
+ contextLoaderFactory: () => async (resource) => {
2357
+ const url = new URL(resource).href;
2358
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2359
+ if (url === remoteContextUrl) return {
2360
+ contextUrl: null,
2361
+ documentUrl: url,
2362
+ document: [
2363
+ "not",
2364
+ "an",
2365
+ "object"
2366
+ ]
2367
+ };
2368
+ throw new Error(`Unexpected context: ${url}`);
2369
+ }
2370
+ });
2371
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2372
+ throw new Error("listener should not run");
2373
+ }).onError(() => {
2374
+ errorCount++;
2375
+ });
2376
+ const signed = await signJsonLd({
2377
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
2378
+ id: "https://remote.example/activities/legacy-invalid-remote-context",
2379
+ type: "Create",
2380
+ actor: "https://remote.example/users/alice",
2381
+ ext: "preserve-me",
2382
+ object: {
2383
+ id: "https://remote.example/notes/legacy-invalid-remote-context",
2384
+ type: "Note",
2385
+ attributedTo: "https://remote.example/users/alice",
2386
+ content: "Hello from invalid remote context queue"
2387
+ }
2388
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
2389
+ const url = new URL(resource).href;
2390
+ if (url === remoteContextUrl) return {
2391
+ contextUrl: null,
2392
+ documentUrl: url,
2393
+ document: { "@context": { ext: "https://example.com/ext" } }
2394
+ };
2395
+ return await mockDocumentLoader(url);
2396
+ } });
2397
+ const inboxMessage = {
1221
2398
  type: "inbox",
1222
2399
  id: crypto.randomUUID(),
1223
2400
  baseUrl: "https://example.com",
1224
- activity: {
1225
- "@context": "https://www.w3.org/ns/activitystreams",
1226
- type: "Create",
1227
- actor: "https://remote.example/users/alice",
1228
- object: {
1229
- type: "Note",
1230
- content: "Hello world"
1231
- }
1232
- },
2401
+ activity: signed,
1233
2402
  started: (/* @__PURE__ */ new Date()).toISOString(),
1234
2403
  attempt: 0,
1235
2404
  identifier: null,
1236
2405
  traceContext: {}
1237
- }), Error);
2406
+ };
2407
+ await federation.processQueuedTask(void 0, inboxMessage);
2408
+ assertEquals(errorCount, 1);
1238
2409
  assertEquals(queuedMessages, []);
1239
2410
  });
1240
- await t.step("with MessageQueue having no nativeRetrial", async () => {
2411
+ await t.step("legacy raw LDS inbox messages with string remote contexts retry", async () => {
2412
+ const remoteContextUrl = "https://remote.example/contexts/ext";
2413
+ const queue = {
2414
+ enqueue(message, _options) {
2415
+ queuedMessages.push(message);
2416
+ return Promise.resolve();
2417
+ },
2418
+ listen(_handler, _options) {
2419
+ return Promise.resolve();
2420
+ }
2421
+ };
1241
2422
  const kv = new MemoryKvStore();
1242
- let queuedMessages = [];
2423
+ const queuedMessages = [];
2424
+ let errorCount = 0;
1243
2425
  const federation = new FederationImpl({
1244
2426
  kv,
1245
- queue: {
1246
- enqueue(message, _options) {
1247
- queuedMessages.push(message);
1248
- return Promise.resolve();
1249
- },
1250
- listen(_handler, _options) {
1251
- return Promise.resolve();
1252
- }
2427
+ queue,
2428
+ contextLoaderFactory: () => async (resource) => {
2429
+ const url = new URL(resource).href;
2430
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2431
+ if (url === remoteContextUrl) return {
2432
+ contextUrl: null,
2433
+ documentUrl: url,
2434
+ document: "{not valid json"
2435
+ };
2436
+ throw new Error(`Unexpected context: ${url}`);
1253
2437
  }
1254
2438
  });
1255
- federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(vocab.Create, () => {
1256
- throw new Error("Intended error for testing");
2439
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2440
+ throw new Error("listener should not run");
2441
+ }).onError(() => {
2442
+ errorCount++;
2443
+ });
2444
+ const signed = await signJsonLd({
2445
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
2446
+ id: "https://remote.example/activities/legacy-string-remote-context",
2447
+ type: "Create",
2448
+ actor: "https://remote.example/users/alice",
2449
+ ext: "preserve-me",
2450
+ object: {
2451
+ id: "https://remote.example/notes/legacy-string-remote-context",
2452
+ type: "Note",
2453
+ attributedTo: "https://remote.example/users/alice",
2454
+ content: "Hello from string remote context queue"
2455
+ }
2456
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
2457
+ const url = new URL(resource).href;
2458
+ if (url === remoteContextUrl) return {
2459
+ contextUrl: null,
2460
+ documentUrl: url,
2461
+ document: { "@context": { ext: "https://example.com/ext" } }
2462
+ };
2463
+ return await mockDocumentLoader(url);
2464
+ } });
2465
+ const inboxMessage = {
2466
+ type: "inbox",
2467
+ id: crypto.randomUUID(),
2468
+ baseUrl: "https://example.com",
2469
+ activity: signed,
2470
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2471
+ attempt: 0,
2472
+ identifier: null,
2473
+ traceContext: {}
2474
+ };
2475
+ await federation.processQueuedTask(void 0, inboxMessage);
2476
+ assertEquals(errorCount, 1);
2477
+ assertEquals(queuedMessages, [{
2478
+ ...inboxMessage,
2479
+ attempt: 1
2480
+ }]);
2481
+ });
2482
+ await t.step("legacy raw LDS inbox messages with loader TypeErrors retry", async () => {
2483
+ const remoteContextUrl = "https://remote.example/contexts/ext";
2484
+ const queue = {
2485
+ enqueue(message, _options) {
2486
+ queuedMessages.push(message);
2487
+ return Promise.resolve();
2488
+ },
2489
+ listen(_handler, _options) {
2490
+ return Promise.resolve();
2491
+ }
2492
+ };
2493
+ const kv = new MemoryKvStore();
2494
+ const queuedMessages = [];
2495
+ let errorCount = 0;
2496
+ const federation = new FederationImpl({
2497
+ kv,
2498
+ queue,
2499
+ contextLoaderFactory: () => async (resource) => {
2500
+ const url = new URL(resource).href;
2501
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2502
+ if (url === remoteContextUrl) throw new TypeError(`Cannot initialize remote context loader: ${url}`);
2503
+ throw new Error(`Unexpected context: ${url}`);
2504
+ }
1257
2505
  });
1258
- const outboxMessage = {
1259
- type: "outbox",
2506
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2507
+ throw new Error("listener should not run");
2508
+ }).onError(() => {
2509
+ errorCount++;
2510
+ });
2511
+ const signed = await signJsonLd({
2512
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
2513
+ id: "https://remote.example/activities/legacy-typeerror-context",
2514
+ type: "Create",
2515
+ actor: "https://remote.example/users/alice",
2516
+ ext: "preserve-me",
2517
+ object: {
2518
+ id: "https://remote.example/notes/legacy-typeerror-context",
2519
+ type: "Note",
2520
+ attributedTo: "https://remote.example/users/alice",
2521
+ content: "Hello from typeerror legacy queue"
2522
+ }
2523
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
2524
+ const url = new URL(resource).href;
2525
+ if (url === remoteContextUrl) return {
2526
+ contextUrl: null,
2527
+ documentUrl: url,
2528
+ document: { "@context": { ext: "https://example.com/ext" } }
2529
+ };
2530
+ return await mockDocumentLoader(url);
2531
+ } });
2532
+ const inboxMessage = {
2533
+ type: "inbox",
1260
2534
  id: crypto.randomUUID(),
1261
2535
  baseUrl: "https://example.com",
1262
- keys: [],
1263
- activity: {
1264
- "@context": "https://www.w3.org/ns/activitystreams",
1265
- type: "Create",
1266
- actor: "https://example.com/users/alice",
1267
- object: {
1268
- type: "Note",
1269
- content: "test"
1270
- }
2536
+ activity: signed,
2537
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2538
+ attempt: 0,
2539
+ identifier: null,
2540
+ traceContext: {}
2541
+ };
2542
+ await federation.processQueuedTask(void 0, inboxMessage);
2543
+ assertEquals(errorCount, 1);
2544
+ assertEquals(queuedMessages, [{
2545
+ ...inboxMessage,
2546
+ attempt: 1
2547
+ }]);
2548
+ });
2549
+ await t.step("legacy raw LDS inbox messages with syntax errors in remote contexts retry", async () => {
2550
+ const remoteContextUrl = "https://remote.example/contexts/ext";
2551
+ const queue = {
2552
+ enqueue(message, _options) {
2553
+ queuedMessages.push(message);
2554
+ return Promise.resolve();
1271
2555
  },
1272
- activityType: "https://www.w3.org/ns/activitystreams#Create",
1273
- inbox: "https://invalid-domain-that-does-not-exist.example/inbox",
1274
- sharedInbox: false,
2556
+ listen(_handler, _options) {
2557
+ return Promise.resolve();
2558
+ }
2559
+ };
2560
+ const kv = new MemoryKvStore();
2561
+ const queuedMessages = [];
2562
+ let errorCount = 0;
2563
+ const federation = new FederationImpl({
2564
+ kv,
2565
+ queue,
2566
+ contextLoaderFactory: () => async (resource) => {
2567
+ const url = new URL(resource).href;
2568
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2569
+ if (url === remoteContextUrl) {
2570
+ const error = /* @__PURE__ */ new Error(`Transient syntax failure: ${url}`);
2571
+ error.name = "jsonld.SyntaxError";
2572
+ error.details = { code: "loading remote context failed" };
2573
+ throw error;
2574
+ }
2575
+ throw new Error(`Unexpected context: ${url}`);
2576
+ }
2577
+ });
2578
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2579
+ throw new Error("listener should not run");
2580
+ }).onError(() => {
2581
+ errorCount++;
2582
+ });
2583
+ const signed = await signJsonLd({
2584
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
2585
+ id: "https://remote.example/activities/legacy-syntax-context",
2586
+ type: "Create",
2587
+ actor: "https://remote.example/users/alice",
2588
+ ext: "preserve-me",
2589
+ object: {
2590
+ id: "https://remote.example/notes/legacy-syntax-context",
2591
+ type: "Note",
2592
+ attributedTo: "https://remote.example/users/alice",
2593
+ content: "Hello from syntax legacy queue"
2594
+ }
2595
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
2596
+ const url = new URL(resource).href;
2597
+ if (url === remoteContextUrl) return {
2598
+ contextUrl: null,
2599
+ documentUrl: url,
2600
+ document: { "@context": { ext: "https://example.com/ext" } }
2601
+ };
2602
+ return await mockDocumentLoader(url);
2603
+ } });
2604
+ const inboxMessage = {
2605
+ type: "inbox",
2606
+ id: crypto.randomUUID(),
2607
+ baseUrl: "https://example.com",
2608
+ activity: signed,
1275
2609
  started: (/* @__PURE__ */ new Date()).toISOString(),
1276
2610
  attempt: 0,
1277
- headers: {},
2611
+ identifier: null,
1278
2612
  traceContext: {}
1279
2613
  };
1280
- await federation.processQueuedTask(void 0, outboxMessage);
2614
+ await federation.processQueuedTask(void 0, inboxMessage);
2615
+ assertEquals(errorCount, 1);
1281
2616
  assertEquals(queuedMessages, [{
1282
- ...outboxMessage,
2617
+ ...inboxMessage,
1283
2618
  attempt: 1
1284
2619
  }]);
1285
- queuedMessages = [];
2620
+ });
2621
+ await t.step("legacy raw LDS inbox messages with loader RangeErrors retry", async () => {
2622
+ const remoteContextUrl = "https://remote.example/contexts/ext";
2623
+ const queue = {
2624
+ enqueue(message, _options) {
2625
+ queuedMessages.push(message);
2626
+ return Promise.resolve();
2627
+ },
2628
+ listen(_handler, _options) {
2629
+ return Promise.resolve();
2630
+ }
2631
+ };
2632
+ const kv = new MemoryKvStore();
2633
+ const queuedMessages = [];
2634
+ let errorCount = 0;
2635
+ const federation = new FederationImpl({
2636
+ kv,
2637
+ queue,
2638
+ contextLoaderFactory: () => async (resource) => {
2639
+ const url = new URL(resource).href;
2640
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2641
+ if (url === remoteContextUrl) throw new RangeError(`Temporary remote context cache window exceeded: ${url}`);
2642
+ throw new Error(`Unexpected context: ${url}`);
2643
+ }
2644
+ });
2645
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2646
+ throw new Error("listener should not run");
2647
+ }).onError(() => {
2648
+ errorCount++;
2649
+ });
2650
+ const signed = await signJsonLd({
2651
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
2652
+ id: "https://remote.example/activities/legacy-rangeerror-context",
2653
+ type: "Create",
2654
+ actor: "https://remote.example/users/alice",
2655
+ ext: "preserve-me",
2656
+ object: {
2657
+ id: "https://remote.example/notes/legacy-rangeerror-context",
2658
+ type: "Note",
2659
+ attributedTo: "https://remote.example/users/alice",
2660
+ content: "Hello from rangeerror legacy queue"
2661
+ }
2662
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
2663
+ const url = new URL(resource).href;
2664
+ if (url === remoteContextUrl) return {
2665
+ contextUrl: null,
2666
+ documentUrl: url,
2667
+ document: { "@context": { ext: "https://example.com/ext" } }
2668
+ };
2669
+ return await mockDocumentLoader(url);
2670
+ } });
1286
2671
  const inboxMessage = {
2672
+ type: "inbox",
2673
+ id: crypto.randomUUID(),
2674
+ baseUrl: "https://example.com",
2675
+ activity: signed,
2676
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2677
+ attempt: 0,
2678
+ identifier: null,
2679
+ traceContext: {}
2680
+ };
2681
+ await federation.processQueuedTask(void 0, inboxMessage);
2682
+ assertEquals(errorCount, 1);
2683
+ assertEquals(queuedMessages, [{
2684
+ ...inboxMessage,
2685
+ attempt: 1
2686
+ }]);
2687
+ });
2688
+ await t.step("permanent queued inbox parse errors do not re-enqueue poison messages", async () => {
2689
+ const queuedMessages = [];
2690
+ const queue = {
2691
+ enqueue(message, _options) {
2692
+ queuedMessages.push(message);
2693
+ return Promise.resolve();
2694
+ },
2695
+ listen(_handler, _options) {
2696
+ return Promise.resolve();
2697
+ }
2698
+ };
2699
+ const kv = new MemoryKvStore();
2700
+ let errorCount = 0;
2701
+ const federation = new FederationImpl({
2702
+ kv,
2703
+ queue
2704
+ });
2705
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2706
+ throw new Error("listener should not run");
2707
+ }).onError(() => {
2708
+ errorCount++;
2709
+ });
2710
+ await federation.processQueuedTask(void 0, {
1287
2711
  type: "inbox",
1288
2712
  id: crypto.randomUUID(),
1289
2713
  baseUrl: "https://example.com",
1290
2714
  activity: {
1291
2715
  "@context": "https://www.w3.org/ns/activitystreams",
2716
+ id: "https://remote.example/objects/not-an-activity",
2717
+ type: "Note",
2718
+ attributedTo: "https://remote.example/users/alice",
2719
+ content: "Not an activity"
2720
+ },
2721
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2722
+ attempt: 0,
2723
+ identifier: null,
2724
+ traceContext: {}
2725
+ });
2726
+ assertEquals(errorCount, 1);
2727
+ assertEquals(queuedMessages, []);
2728
+ });
2729
+ await t.step("malformed Temporal fields are permanent queued inbox parse errors", async () => {
2730
+ const queuedMessages = [];
2731
+ const queue = {
2732
+ enqueue(message, _options) {
2733
+ queuedMessages.push(message);
2734
+ return Promise.resolve();
2735
+ },
2736
+ listen(_handler, _options) {
2737
+ return Promise.resolve();
2738
+ }
2739
+ };
2740
+ const kv = new MemoryKvStore();
2741
+ let errorCount = 0;
2742
+ const federation = new FederationImpl({
2743
+ kv,
2744
+ queue,
2745
+ documentLoaderFactory: () => mockDocumentLoader,
2746
+ contextLoaderFactory: () => mockDocumentLoader
2747
+ });
2748
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2749
+ throw new Error("listener should not run");
2750
+ }).onError(() => {
2751
+ errorCount++;
2752
+ });
2753
+ await federation.processQueuedTask(void 0, {
2754
+ type: "inbox",
2755
+ id: crypto.randomUUID(),
2756
+ baseUrl: "https://example.com",
2757
+ activity: {
2758
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
2759
+ id: "https://remote.example/activities/invalid-proof-created",
1292
2760
  type: "Create",
1293
2761
  actor: "https://remote.example/users/alice",
1294
2762
  object: {
2763
+ id: "https://remote.example/notes/invalid-proof-created",
1295
2764
  type: "Note",
1296
- content: "Hello world"
2765
+ attributedTo: "https://remote.example/users/alice",
2766
+ content: "Hello, world!"
2767
+ },
2768
+ proof: {
2769
+ type: "DataIntegrityProof",
2770
+ cryptosuite: "eddsa-jcs-2022",
2771
+ verificationMethod: "https://remote.example/users/alice#ed25519-key",
2772
+ proofPurpose: "assertionMethod",
2773
+ created: { "@value": "not-a-date" },
2774
+ proofValue: "zLaewdp4H9kqtwyrLatK4cjY5oRHwVcw4gibPSUDYDMhi4M49v8pcYk3ZB6D69dNpAPbUmY8ocuJ3m9KhKJEEg7z"
1297
2775
  }
1298
2776
  },
1299
2777
  started: (/* @__PURE__ */ new Date()).toISOString(),
1300
2778
  attempt: 0,
1301
2779
  identifier: null,
1302
2780
  traceContext: {}
1303
- };
1304
- await federation.processQueuedTask(void 0, inboxMessage);
1305
- assertEquals(queuedMessages, [{
1306
- ...inboxMessage,
1307
- attempt: 1
1308
- }]);
2781
+ });
2782
+ assertEquals(errorCount, 1);
2783
+ assertEquals(queuedMessages, []);
1309
2784
  });
1310
2785
  });
1311
2786
  test("FederationImpl.processQueuedTask() permanent failure", async (t) => {
@@ -1995,6 +3470,35 @@ test({
1995
3470
  ]);
1996
3471
  }
1997
3472
  });
3473
+ test("ContextImpl.routeActivity() marks queued signed activities as non-LDS", async () => {
3474
+ let queuedMessage = null;
3475
+ const federation = new FederationImpl({
3476
+ kv: new MemoryKvStore(),
3477
+ queue: {
3478
+ enqueue(message) {
3479
+ queuedMessage = message;
3480
+ return Promise.resolve();
3481
+ },
3482
+ async listen() {}
3483
+ }
3484
+ });
3485
+ federation.setInboxListeners("/u/{identifier}/i", "/i").on(Offer, () => {
3486
+ throw new Error("listener should not run for queued routeActivity");
3487
+ });
3488
+ const ctx = new ContextImpl({
3489
+ url: new URL("https://example.com/"),
3490
+ federation,
3491
+ data: void 0,
3492
+ documentLoader: mockDocumentLoader,
3493
+ contextLoader: documentLoader
3494
+ });
3495
+ const signedOffer = await signObject(new Offer({ actor: new URL("https://example.com/person2") }), ed25519PrivateKey, ed25519Multikey.id);
3496
+ assert(await ctx.routeActivity(null, signedOffer));
3497
+ if (queuedMessage == null) throw new Error("Inbox message not queued.");
3498
+ const inboxMessage = queuedMessage;
3499
+ assertEquals(inboxMessage.ldSignatureVerified, false);
3500
+ assertEquals(inboxMessage.normalizedActivity, void 0);
3501
+ });
1998
3502
  test("ContextImpl.getCollectionUri()", () => {
1999
3503
  const federation = new FederationImpl({ kv: new MemoryKvStore() });
2000
3504
  const base = "https://example.com";