@fedify/fedify 2.1.12 → 2.1.14
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.
- package/dist/{builder-D9hlyCx5.mjs → builder-BBifZZ5G.mjs} +3 -3
- package/dist/{chunk-nlSIicah.js → chunk-CRNNMoPX.js} +2 -2
- package/dist/{client-z-8dc-e1.d.cts → client-CAM_bQXx.d.cts} +1 -0
- package/dist/{client-AtlibPOU.d.ts → client-CSddvgWN.d.ts} +1 -2
- package/dist/compat/mod.d.cts +2 -1
- package/dist/compat/mod.d.ts +2 -3
- package/dist/compat/mod.js +3 -3
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/{context-C0fssarc.d.ts → context-_l9t359k.d.ts} +53 -12
- package/dist/{context-LmrZPwti.d.cts → context-o22vjzS2.d.cts} +53 -10
- package/dist/{deno-Ddb3P7G7.mjs → deno-bqnW1QlD.mjs} +1 -1
- package/dist/{docloader-CK8tewt1.mjs → docloader-D-Fu0kBF.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/handler.test.mjs +1361 -41
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/inbox.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +1588 -84
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +4 -3
- package/dist/federation/mod.d.ts +4 -5
- package/dist/federation/mod.js +2 -2
- package/dist/federation/retry.test.mjs +1 -1
- package/dist/federation/send.test.mjs +3 -3
- package/dist/federation/temporal.test.d.mts +2 -0
- package/dist/federation/temporal.test.mjs +71 -0
- package/dist/federation/webfinger.test.mjs +1 -1
- package/dist/{http-CrGuipxe.d.cts → http-BDZeS5om.d.ts} +2 -1
- package/dist/{http-aQzN9Ayi.d.ts → http-C87EWkO0.d.cts} +2 -3
- package/dist/{http-CmDErAnD.cjs → http-CEXtUHix.cjs} +57 -9
- package/dist/{http-qKyJKSnF.js → http-Cw1EorUA.js} +58 -10
- package/dist/{http-DRDpStn2.mjs → http-DvhFwy2E.mjs} +58 -10
- package/dist/{inbox-DTuPJEma.mjs → inbox-7b3VYnec.mjs} +7 -4
- package/dist/{key-WSedbAA-.mjs → key-B3OBz6A_.mjs} +1 -1
- package/dist/{kv-CbLNp3zQ.d.cts → kv-D6hNiMTK.d.ts} +1 -0
- package/dist/{kv-cache-9cdQDeT0.cjs → kv-cache-BIapFiSV.cjs} +2 -2
- package/dist/{kv-cache-DNNRFY8u.js → kv-cache-C6nynGhh.js} +2 -2
- package/dist/{kv-cache-B01V7s3h.mjs → kv-cache-FsT1ljhs.mjs} +1 -1
- package/dist/{kv-GFYnFoOl.d.ts → kv-gJ8LYbxX.d.cts} +1 -3
- package/dist/ld-DTxYKZTT.mjs +550 -0
- package/dist/{middleware-UThUK30x.js → middleware-0vceVN10.js} +407 -82
- package/dist/{middleware-DFcgN1cH.cjs → middleware-BcbU5lKS.cjs} +406 -80
- package/dist/{middleware-8P6Z15Wq.cjs → middleware-DUT--Qen.cjs} +1 -1
- package/dist/{middleware-DDsnRvFC.mjs → middleware-DmT9t0BJ.mjs} +317 -84
- package/dist/{middleware-C2c1rlmE.mjs → middleware-Li_g_vqz.mjs} +1 -1
- package/dist/{mod-CnwZoDzx.d.ts → mod-0dF0OeGw.d.cts} +2 -3
- package/dist/{mod-B505FZBC.d.cts → mod-BTWFtZwa.d.ts} +4 -3
- package/dist/{mod-Cgpc49fA.d.cts → mod-Bv1elC-b.d.ts} +2 -1
- package/dist/{mod-DoJBjjnO.d.cts → mod-CjiK2zrW.d.ts} +3 -2
- package/dist/{mod-DvxszxXC.d.ts → mod-D5YlzzP3.d.cts} +3 -4
- package/dist/{mod-D7PAuO6k.d.ts → mod-dxOnKp2Y.d.cts} +4 -5
- package/dist/mod.cjs +4 -4
- package/dist/mod.d.cts +9 -8
- package/dist/mod.d.ts +9 -10
- package/dist/mod.js +9 -9
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/nodeinfo/mod.d.cts +2 -1
- package/dist/nodeinfo/mod.d.ts +2 -3
- package/dist/nodeinfo/mod.js +3 -3
- package/dist/otel/mod.d.cts +2 -1
- package/dist/otel/mod.d.ts +2 -3
- package/dist/otel/mod.js +2 -2
- package/dist/{owner-CptqhsOy.d.cts → owner-CnngXDNJ.d.ts} +2 -1
- package/dist/{owner-74ARJ5TL.d.ts → owner-DEvZuyOE.d.cts} +2 -3
- package/dist/{owner-BceqCVOs.mjs → owner-DzcnuWYo.mjs} +2 -2
- package/dist/{proof-4NUQlbQV.js → proof-CtTT8A7y.js} +299 -5
- package/dist/{proof-BojC8L94.mjs → proof-DmVGi7mI.mjs} +2 -2
- package/dist/{proof-kuTIVfyZ.cjs → proof-JEoW3ODq.cjs} +345 -3
- package/dist/runtime/mod.d.cts +1 -0
- package/dist/runtime/mod.d.ts +1 -2
- package/dist/runtime/mod.js +3 -3
- package/dist/{send-Buz5Gy8x.mjs → send-5ulOKVCW.mjs} +2 -2
- package/dist/sig/http.test.mjs +120 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +558 -2
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.cts +4 -3
- package/dist/sig/mod.d.ts +4 -5
- package/dist/sig/mod.js +4 -4
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/proof.test.mjs +1 -1
- package/dist/temporal-CmJkd-25.mjs +95 -0
- package/dist/testing/mod.d.mts +48 -6
- package/dist/{transformers-ve6e2xcg.js → transformers-BGMIq1cs.js} +2 -2
- package/dist/{types-hvL8ElAs.js → types-CAY3OdLq.js} +2 -2
- package/dist/utils/docloader.test.mjs +2 -2
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +2 -1
- package/dist/utils/mod.d.ts +2 -3
- package/dist/utils/mod.js +3 -3
- package/dist/vocab/mod.d.cts +1 -0
- package/dist/vocab/mod.d.ts +1 -2
- package/dist/vocab/mod.js +2 -2
- package/package.json +7 -7
- package/dist/ld-OBpaLfHR.mjs +0 -256
- /package/dist/{retry-B_E3V_Dx.mjs → retry-CJL0poaU.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-
|
|
13
|
+
import { l as verifyRequest, s as signRequest } from "../http-DvhFwy2E.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-BAK-tUlf.mjs";
|
|
15
|
-
import { t as getAuthenticatedDocumentLoader } from "../docloader-
|
|
16
|
-
import { a as signJsonLd,
|
|
17
|
-
import { t as doesActorOwnKey } from "../owner-
|
|
18
|
-
import { n as signObject, r as verifyObject } from "../proof-
|
|
15
|
+
import { t as getAuthenticatedDocumentLoader } from "../docloader-D-Fu0kBF.mjs";
|
|
16
|
+
import { a as compactJsonLd, f as signJsonLd, m as verifyJsonLd, s as detachSignature } from "../ld-DTxYKZTT.mjs";
|
|
17
|
+
import { t as doesActorOwnKey } from "../owner-DzcnuWYo.mjs";
|
|
18
|
+
import { n as signObject, r as verifyObject } from "../proof-DmVGi7mI.mjs";
|
|
19
19
|
import { t as MemoryKvStore } from "../kv-tL2TOE9X.mjs";
|
|
20
|
-
import { a as createFederation, i as KvSpecDeterminer, n as FederationImpl, r as InboxContextImpl, t as ContextImpl } from "../middleware-
|
|
20
|
+
import { a as createFederation, i as KvSpecDeterminer, n as FederationImpl, r as InboxContextImpl, t as ContextImpl } from "../middleware-DmT9t0BJ.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 { FetchError, getDocumentLoader } from "@fedify/vocab-runtime";
|
|
25
25
|
//#region ../fixture/src/fixtures/example.com/create.json
|
|
26
26
|
var id$2 = "https://example.com/create";
|
|
@@ -1261,6 +1261,54 @@ test("Federation.setInboxListeners()", async (t) => {
|
|
|
1261
1261
|
});
|
|
1262
1262
|
esm_default.hardReset();
|
|
1263
1263
|
});
|
|
1264
|
+
test("Federation.fetch() preserves original LD-signed payload for InboxContextImpl.activity", async () => {
|
|
1265
|
+
const remoteContextUrl = "https://remote.example/contexts/ext";
|
|
1266
|
+
const sourceContextLoader = async (resource) => {
|
|
1267
|
+
const url = new URL(resource).href;
|
|
1268
|
+
if (url === remoteContextUrl) return {
|
|
1269
|
+
contextUrl: null,
|
|
1270
|
+
documentUrl: url,
|
|
1271
|
+
document: { "@context": { ext: "https://example.com/ext" } }
|
|
1272
|
+
};
|
|
1273
|
+
return await mockDocumentLoader(url);
|
|
1274
|
+
};
|
|
1275
|
+
const federation = createFederation({
|
|
1276
|
+
kv: new MemoryKvStore(),
|
|
1277
|
+
documentLoaderFactory: () => mockDocumentLoader,
|
|
1278
|
+
contextLoaderFactory: () => sourceContextLoader
|
|
1279
|
+
});
|
|
1280
|
+
federation.setActorDispatcher("/users/{identifier}", (_ctx, identifier) => identifier === "someone" ? new Person({}) : null);
|
|
1281
|
+
let receivedRaw = null;
|
|
1282
|
+
let receivedTyped = null;
|
|
1283
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, (ctx, activity) => {
|
|
1284
|
+
receivedRaw = ctx.activity;
|
|
1285
|
+
receivedTyped = activity;
|
|
1286
|
+
});
|
|
1287
|
+
const signed = await signJsonLd({
|
|
1288
|
+
"@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
|
|
1289
|
+
id: "https://example.com/activities/preserve-raw",
|
|
1290
|
+
type: "Create",
|
|
1291
|
+
actor: "https://example.com/person2",
|
|
1292
|
+
ext: "preserve-me",
|
|
1293
|
+
object: {
|
|
1294
|
+
id: "https://example.com/notes/preserve-raw",
|
|
1295
|
+
type: "Note",
|
|
1296
|
+
attributedTo: "https://example.com/person2",
|
|
1297
|
+
content: "Hello, world!"
|
|
1298
|
+
}
|
|
1299
|
+
}, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: sourceContextLoader });
|
|
1300
|
+
const response = await federation.fetch(new Request("https://example.com/inbox", {
|
|
1301
|
+
method: "POST",
|
|
1302
|
+
headers: { "Content-Type": "application/activity+json" },
|
|
1303
|
+
body: JSON.stringify(signed)
|
|
1304
|
+
}), { contextData: void 0 });
|
|
1305
|
+
assertEquals([response.status, await response.text()], [202, ""]);
|
|
1306
|
+
assertEquals(receivedRaw, signed);
|
|
1307
|
+
assertNotEquals(receivedRaw, await compactJsonLd(signed, sourceContextLoader));
|
|
1308
|
+
const delivered = receivedTyped;
|
|
1309
|
+
assert(delivered != null);
|
|
1310
|
+
assertEquals(delivered.id?.href, "https://example.com/activities/preserve-raw");
|
|
1311
|
+
});
|
|
1264
1312
|
test("Federation.setInboxDispatcher()", async (t) => {
|
|
1265
1313
|
const kv = new MemoryKvStore();
|
|
1266
1314
|
await t.step("path match", () => {
|
|
@@ -1372,140 +1420,1567 @@ test("FederationImpl.sendActivity()", async (t) => {
|
|
|
1372
1420
|
assertEquals(request?.url, "https://example.com/inbox");
|
|
1373
1421
|
assertEquals(request?.headers.get("Content-Type"), "application/activity+json");
|
|
1374
1422
|
});
|
|
1375
|
-
esm_default.hardReset();
|
|
1376
|
-
});
|
|
1377
|
-
test("FederationImpl.processQueuedTask()", async (t) => {
|
|
1378
|
-
await t.step("with MessageQueue having nativeRetrial", async () => {
|
|
1423
|
+
esm_default.hardReset();
|
|
1424
|
+
});
|
|
1425
|
+
test("FederationImpl.processQueuedTask()", async (t) => {
|
|
1426
|
+
await t.step("with MessageQueue having nativeRetrial", async () => {
|
|
1427
|
+
const kv = new MemoryKvStore();
|
|
1428
|
+
const queuedMessages = [];
|
|
1429
|
+
const federation = new FederationImpl({
|
|
1430
|
+
kv,
|
|
1431
|
+
queue: {
|
|
1432
|
+
nativeRetrial: true,
|
|
1433
|
+
enqueue(message, _options) {
|
|
1434
|
+
queuedMessages.push(message);
|
|
1435
|
+
return Promise.resolve();
|
|
1436
|
+
},
|
|
1437
|
+
listen(_handler, _options) {
|
|
1438
|
+
return Promise.resolve();
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
});
|
|
1442
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(vocab.Create, () => {
|
|
1443
|
+
throw new Error("Intended error for testing");
|
|
1444
|
+
});
|
|
1445
|
+
await assertRejects(() => federation.processQueuedTask(void 0, {
|
|
1446
|
+
type: "outbox",
|
|
1447
|
+
id: crypto.randomUUID(),
|
|
1448
|
+
baseUrl: "https://example.com",
|
|
1449
|
+
keys: [],
|
|
1450
|
+
activity: {
|
|
1451
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1452
|
+
type: "Create",
|
|
1453
|
+
actor: "https://example.com/users/alice",
|
|
1454
|
+
object: {
|
|
1455
|
+
type: "Note",
|
|
1456
|
+
content: "test"
|
|
1457
|
+
}
|
|
1458
|
+
},
|
|
1459
|
+
activityType: "https://www.w3.org/ns/activitystreams#Create",
|
|
1460
|
+
inbox: "https://invalid-domain-that-does-not-exist.example/inbox",
|
|
1461
|
+
sharedInbox: false,
|
|
1462
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1463
|
+
attempt: 0,
|
|
1464
|
+
headers: {},
|
|
1465
|
+
traceContext: {}
|
|
1466
|
+
}), Error);
|
|
1467
|
+
assertEquals(queuedMessages, []);
|
|
1468
|
+
await assertRejects(() => federation.processQueuedTask(void 0, {
|
|
1469
|
+
type: "inbox",
|
|
1470
|
+
id: crypto.randomUUID(),
|
|
1471
|
+
baseUrl: "https://example.com",
|
|
1472
|
+
activity: {
|
|
1473
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1474
|
+
type: "Create",
|
|
1475
|
+
actor: "https://remote.example/users/alice",
|
|
1476
|
+
object: {
|
|
1477
|
+
type: "Note",
|
|
1478
|
+
content: "Hello world"
|
|
1479
|
+
}
|
|
1480
|
+
},
|
|
1481
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1482
|
+
attempt: 0,
|
|
1483
|
+
identifier: null,
|
|
1484
|
+
traceContext: {}
|
|
1485
|
+
}), Error);
|
|
1486
|
+
assertEquals(queuedMessages, []);
|
|
1487
|
+
});
|
|
1488
|
+
await t.step("with MessageQueue having no nativeRetrial", async () => {
|
|
1489
|
+
const kv = new MemoryKvStore();
|
|
1490
|
+
let queuedMessages = [];
|
|
1491
|
+
const federation = new FederationImpl({
|
|
1492
|
+
kv,
|
|
1493
|
+
queue: {
|
|
1494
|
+
enqueue(message, _options) {
|
|
1495
|
+
queuedMessages.push(message);
|
|
1496
|
+
return Promise.resolve();
|
|
1497
|
+
},
|
|
1498
|
+
listen(_handler, _options) {
|
|
1499
|
+
return Promise.resolve();
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
});
|
|
1503
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(vocab.Create, () => {
|
|
1504
|
+
throw new Error("Intended error for testing");
|
|
1505
|
+
});
|
|
1506
|
+
const outboxMessage = {
|
|
1507
|
+
type: "outbox",
|
|
1508
|
+
id: crypto.randomUUID(),
|
|
1509
|
+
baseUrl: "https://example.com",
|
|
1510
|
+
keys: [],
|
|
1511
|
+
activity: {
|
|
1512
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1513
|
+
type: "Create",
|
|
1514
|
+
actor: "https://example.com/users/alice",
|
|
1515
|
+
object: {
|
|
1516
|
+
type: "Note",
|
|
1517
|
+
content: "test"
|
|
1518
|
+
}
|
|
1519
|
+
},
|
|
1520
|
+
activityType: "https://www.w3.org/ns/activitystreams#Create",
|
|
1521
|
+
inbox: "https://invalid-domain-that-does-not-exist.example/inbox",
|
|
1522
|
+
sharedInbox: false,
|
|
1523
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1524
|
+
attempt: 0,
|
|
1525
|
+
headers: {},
|
|
1526
|
+
traceContext: {}
|
|
1527
|
+
};
|
|
1528
|
+
await federation.processQueuedTask(void 0, outboxMessage);
|
|
1529
|
+
assertEquals(queuedMessages, [{
|
|
1530
|
+
...outboxMessage,
|
|
1531
|
+
attempt: 1
|
|
1532
|
+
}]);
|
|
1533
|
+
queuedMessages = [];
|
|
1534
|
+
const inboxMessage = {
|
|
1535
|
+
type: "inbox",
|
|
1536
|
+
id: crypto.randomUUID(),
|
|
1537
|
+
baseUrl: "https://example.com",
|
|
1538
|
+
activity: {
|
|
1539
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1540
|
+
type: "Create",
|
|
1541
|
+
actor: "https://remote.example/users/alice",
|
|
1542
|
+
object: {
|
|
1543
|
+
type: "Note",
|
|
1544
|
+
content: "Hello world"
|
|
1545
|
+
}
|
|
1546
|
+
},
|
|
1547
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1548
|
+
attempt: 0,
|
|
1549
|
+
identifier: null,
|
|
1550
|
+
traceContext: {}
|
|
1551
|
+
};
|
|
1552
|
+
await federation.processQueuedTask(void 0, inboxMessage);
|
|
1553
|
+
assertEquals(queuedMessages, [{
|
|
1554
|
+
...inboxMessage,
|
|
1555
|
+
attempt: 1
|
|
1556
|
+
}]);
|
|
1557
|
+
});
|
|
1558
|
+
await t.step("with restrictive context loader and normalized LD-signed inbox activity", async () => {
|
|
1559
|
+
const remoteContextUrl = "https://remote.example/contexts/ext";
|
|
1560
|
+
const sourceContextLoader = async (resource) => {
|
|
1561
|
+
const url = new URL(resource).href;
|
|
1562
|
+
if (url === remoteContextUrl) return {
|
|
1563
|
+
contextUrl: null,
|
|
1564
|
+
documentUrl: url,
|
|
1565
|
+
document: { "@context": { ext: "https://example.com/ext" } }
|
|
1566
|
+
};
|
|
1567
|
+
return await mockDocumentLoader(url);
|
|
1568
|
+
};
|
|
1569
|
+
const restrictiveContextLoader = async (resource) => {
|
|
1570
|
+
const url = new URL(resource).href;
|
|
1571
|
+
if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
|
|
1572
|
+
throw new Error(`Unexpected context: ${url}`);
|
|
1573
|
+
};
|
|
1574
|
+
const kv = new MemoryKvStore();
|
|
1575
|
+
let receivedCount = 0;
|
|
1576
|
+
let received = null;
|
|
1577
|
+
let receivedRaw = null;
|
|
1578
|
+
const federation = new FederationImpl({
|
|
1579
|
+
kv,
|
|
1580
|
+
contextLoaderFactory: () => restrictiveContextLoader
|
|
1581
|
+
});
|
|
1582
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, (ctx, activity) => {
|
|
1583
|
+
receivedCount++;
|
|
1584
|
+
receivedRaw = ctx.activity;
|
|
1585
|
+
received = activity;
|
|
1586
|
+
});
|
|
1587
|
+
const signed = await signJsonLd({
|
|
1588
|
+
"@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
|
|
1589
|
+
id: "https://remote.example/activities/1",
|
|
1590
|
+
type: "Create",
|
|
1591
|
+
actor: "https://remote.example/users/alice",
|
|
1592
|
+
ext: "preserve-me",
|
|
1593
|
+
object: {
|
|
1594
|
+
id: "https://remote.example/notes/1",
|
|
1595
|
+
type: "Note",
|
|
1596
|
+
attributedTo: "https://remote.example/users/alice",
|
|
1597
|
+
content: "Hello, world!"
|
|
1598
|
+
}
|
|
1599
|
+
}, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: sourceContextLoader });
|
|
1600
|
+
const normalizedActivity = await compactJsonLd(signed, sourceContextLoader);
|
|
1601
|
+
const messageId = crypto.randomUUID();
|
|
1602
|
+
await federation.processQueuedTask(void 0, {
|
|
1603
|
+
type: "inbox",
|
|
1604
|
+
id: messageId,
|
|
1605
|
+
baseUrl: "https://example.com",
|
|
1606
|
+
activity: signed,
|
|
1607
|
+
normalizedActivity,
|
|
1608
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1609
|
+
attempt: 0,
|
|
1610
|
+
identifier: null,
|
|
1611
|
+
traceContext: {}
|
|
1612
|
+
});
|
|
1613
|
+
const delivered = received;
|
|
1614
|
+
assert(delivered != null);
|
|
1615
|
+
const deliveredCreate = delivered;
|
|
1616
|
+
assertInstanceOf(deliveredCreate, Create);
|
|
1617
|
+
assertEquals(deliveredCreate.id?.href, "https://remote.example/activities/1");
|
|
1618
|
+
assertEquals(receivedRaw, signed);
|
|
1619
|
+
await federation.processQueuedTask(void 0, {
|
|
1620
|
+
type: "inbox",
|
|
1621
|
+
id: messageId,
|
|
1622
|
+
baseUrl: "https://example.com",
|
|
1623
|
+
activity: signed,
|
|
1624
|
+
normalizedActivity,
|
|
1625
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1626
|
+
attempt: 0,
|
|
1627
|
+
identifier: null,
|
|
1628
|
+
traceContext: {}
|
|
1629
|
+
});
|
|
1630
|
+
assertEquals(receivedCount, 1);
|
|
1631
|
+
});
|
|
1632
|
+
await t.step("cached normalizedActivity is rechecked for unsafe JSON-LD keywords", async () => {
|
|
1633
|
+
const queuedMessages = [];
|
|
1634
|
+
const queue = {
|
|
1635
|
+
enqueue(message, _options) {
|
|
1636
|
+
queuedMessages.push(message);
|
|
1637
|
+
return Promise.resolve();
|
|
1638
|
+
},
|
|
1639
|
+
listen(_handler, _options) {
|
|
1640
|
+
return Promise.resolve();
|
|
1641
|
+
}
|
|
1642
|
+
};
|
|
1643
|
+
const kv = new MemoryKvStore();
|
|
1644
|
+
let receivedCount = 0;
|
|
1645
|
+
let errorCount = 0;
|
|
1646
|
+
const federation = new FederationImpl({
|
|
1647
|
+
kv,
|
|
1648
|
+
queue
|
|
1649
|
+
});
|
|
1650
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
|
|
1651
|
+
receivedCount++;
|
|
1652
|
+
}).onError(() => {
|
|
1653
|
+
errorCount++;
|
|
1654
|
+
});
|
|
1655
|
+
const signed = await signJsonLd({
|
|
1656
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1657
|
+
id: "https://remote.example/activities/unsafe-normalized-cache",
|
|
1658
|
+
type: "Create",
|
|
1659
|
+
actor: "https://remote.example/users/alice",
|
|
1660
|
+
object: {
|
|
1661
|
+
id: "https://remote.example/notes/unsafe-normalized-cache",
|
|
1662
|
+
type: "Note",
|
|
1663
|
+
attributedTo: "https://remote.example/users/alice",
|
|
1664
|
+
content: "Hello from unsafe normalized cache"
|
|
1665
|
+
}
|
|
1666
|
+
}, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
|
|
1667
|
+
const normalizedActivity = await compactJsonLd(signed, mockDocumentLoader);
|
|
1668
|
+
const tamperedNormalizedActivity = {
|
|
1669
|
+
...normalizedActivity,
|
|
1670
|
+
signature: {
|
|
1671
|
+
...normalizedActivity.signature,
|
|
1672
|
+
"@included": [{
|
|
1673
|
+
id: "https://remote.example/activities/inside-signature",
|
|
1674
|
+
type: "Undo"
|
|
1675
|
+
}]
|
|
1676
|
+
}
|
|
1677
|
+
};
|
|
1678
|
+
await federation.processQueuedTask(void 0, {
|
|
1679
|
+
type: "inbox",
|
|
1680
|
+
id: crypto.randomUUID(),
|
|
1681
|
+
baseUrl: "https://example.com",
|
|
1682
|
+
activity: signed,
|
|
1683
|
+
normalizedActivity: tamperedNormalizedActivity,
|
|
1684
|
+
ldSignatureVerified: false,
|
|
1685
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1686
|
+
attempt: 0,
|
|
1687
|
+
identifier: null,
|
|
1688
|
+
traceContext: {}
|
|
1689
|
+
});
|
|
1690
|
+
assertEquals(receivedCount, 0);
|
|
1691
|
+
assertEquals(errorCount, 1);
|
|
1692
|
+
assertEquals(queuedMessages, []);
|
|
1693
|
+
});
|
|
1694
|
+
await t.step("old queued LDS inbox messages without normalizedActivity still work", async () => {
|
|
1695
|
+
const restrictiveContextLoader = async (resource) => {
|
|
1696
|
+
const url = new URL(resource).href;
|
|
1697
|
+
if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
|
|
1698
|
+
throw new Error(`Unexpected context: ${url}`);
|
|
1699
|
+
};
|
|
1700
|
+
const kv = new MemoryKvStore();
|
|
1701
|
+
let received = null;
|
|
1702
|
+
const federation = new FederationImpl({
|
|
1703
|
+
kv,
|
|
1704
|
+
contextLoaderFactory: () => restrictiveContextLoader
|
|
1705
|
+
});
|
|
1706
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, (_ctx, activity) => {
|
|
1707
|
+
received = activity;
|
|
1708
|
+
});
|
|
1709
|
+
const compacted = await compactJsonLd(await signJsonLd({
|
|
1710
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1711
|
+
id: "https://remote.example/activities/legacy",
|
|
1712
|
+
type: "Create",
|
|
1713
|
+
actor: "https://remote.example/users/alice",
|
|
1714
|
+
object: {
|
|
1715
|
+
id: "https://remote.example/notes/legacy",
|
|
1716
|
+
type: "Note",
|
|
1717
|
+
attributedTo: "https://remote.example/users/alice",
|
|
1718
|
+
content: "Hello from legacy queue"
|
|
1719
|
+
}
|
|
1720
|
+
}, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader }), restrictiveContextLoader);
|
|
1721
|
+
await federation.processQueuedTask(void 0, {
|
|
1722
|
+
type: "inbox",
|
|
1723
|
+
id: crypto.randomUUID(),
|
|
1724
|
+
baseUrl: "https://example.com",
|
|
1725
|
+
activity: compacted,
|
|
1726
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1727
|
+
attempt: 0,
|
|
1728
|
+
identifier: null,
|
|
1729
|
+
traceContext: {}
|
|
1730
|
+
});
|
|
1731
|
+
assert(received != null);
|
|
1732
|
+
assertEquals(received.id?.href, "https://remote.example/activities/legacy");
|
|
1733
|
+
});
|
|
1734
|
+
await t.step("queued signature-bearing non-LDS inbox messages keep parse-time normalization contexts", async () => {
|
|
1735
|
+
const signingContextLoader = async (resource) => {
|
|
1736
|
+
const url = new URL(resource).href;
|
|
1737
|
+
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);
|
|
1738
|
+
throw new Error(`Unexpected context: ${url}`);
|
|
1739
|
+
};
|
|
1740
|
+
const processingContextLoader = async (resource) => {
|
|
1741
|
+
const url = new URL(resource).href;
|
|
1742
|
+
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");
|
|
1743
|
+
return await signingContextLoader(resource);
|
|
1744
|
+
};
|
|
1745
|
+
const kv = new MemoryKvStore();
|
|
1746
|
+
let received = null;
|
|
1747
|
+
let receivedRaw = null;
|
|
1748
|
+
const federation = new FederationImpl({
|
|
1749
|
+
kv,
|
|
1750
|
+
contextLoaderFactory: () => processingContextLoader
|
|
1751
|
+
});
|
|
1752
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, (ctx, activity) => {
|
|
1753
|
+
receivedRaw = ctx.activity;
|
|
1754
|
+
received = activity;
|
|
1755
|
+
});
|
|
1756
|
+
const signed = await signJsonLd({
|
|
1757
|
+
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
|
|
1758
|
+
id: "https://remote.example/activities/non-lds-queued-signature",
|
|
1759
|
+
type: "Create",
|
|
1760
|
+
actor: "https://remote.example/users/alice",
|
|
1761
|
+
object: {
|
|
1762
|
+
id: "https://remote.example/notes/non-lds-queued-signature",
|
|
1763
|
+
type: "Note",
|
|
1764
|
+
attributedTo: "https://remote.example/users/alice",
|
|
1765
|
+
content: "Hello from non-LDS queued signature"
|
|
1766
|
+
}
|
|
1767
|
+
}, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: signingContextLoader });
|
|
1768
|
+
const signedPayload = signed;
|
|
1769
|
+
assert(Array.isArray(signedPayload["@context"]) && signedPayload["@context"].includes("https://w3id.org/security/v1"));
|
|
1770
|
+
await federation.processQueuedTask(void 0, {
|
|
1771
|
+
type: "inbox",
|
|
1772
|
+
id: crypto.randomUUID(),
|
|
1773
|
+
baseUrl: "https://example.com",
|
|
1774
|
+
activity: signed,
|
|
1775
|
+
ldSignatureVerified: false,
|
|
1776
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1777
|
+
attempt: 0,
|
|
1778
|
+
identifier: null,
|
|
1779
|
+
traceContext: {}
|
|
1780
|
+
});
|
|
1781
|
+
if (received == null) throw new Error("Inbox activity not delivered.");
|
|
1782
|
+
assertEquals(received.id?.href, "https://remote.example/activities/non-lds-queued-signature");
|
|
1783
|
+
assertEquals(receivedRaw, signed);
|
|
1784
|
+
});
|
|
1785
|
+
await t.step("queued signature-bearing non-LDS inbox messages reuse normalizedActivity for custom contexts", async () => {
|
|
1786
|
+
const remoteContextUrl = "https://remote.example/contexts/ext";
|
|
1787
|
+
const sourceContextLoader = async (resource) => {
|
|
1788
|
+
const url = new URL(resource).href;
|
|
1789
|
+
if (url === remoteContextUrl) return {
|
|
1790
|
+
contextUrl: null,
|
|
1791
|
+
documentUrl: url,
|
|
1792
|
+
document: { "@context": { ext: "https://example.com/ext" } }
|
|
1793
|
+
};
|
|
1794
|
+
return await mockDocumentLoader(url);
|
|
1795
|
+
};
|
|
1796
|
+
const restrictiveContextLoader = async (resource) => {
|
|
1797
|
+
const url = new URL(resource).href;
|
|
1798
|
+
if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
|
|
1799
|
+
throw new Error(`Unexpected context: ${url}`);
|
|
1800
|
+
};
|
|
1801
|
+
const kv = new MemoryKvStore();
|
|
1802
|
+
let received = null;
|
|
1803
|
+
let receivedRaw = null;
|
|
1804
|
+
const federation = new FederationImpl({
|
|
1805
|
+
kv,
|
|
1806
|
+
contextLoaderFactory: () => restrictiveContextLoader
|
|
1807
|
+
});
|
|
1808
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, (ctx, activity) => {
|
|
1809
|
+
receivedRaw = ctx.activity;
|
|
1810
|
+
received = activity;
|
|
1811
|
+
});
|
|
1812
|
+
const unsignedBody = {
|
|
1813
|
+
"@context": [
|
|
1814
|
+
remoteContextUrl,
|
|
1815
|
+
"https://www.w3.org/ns/activitystreams",
|
|
1816
|
+
"https://w3id.org/security/v1"
|
|
1817
|
+
],
|
|
1818
|
+
id: "https://remote.example/activities/non-lds-queued-custom-context",
|
|
1819
|
+
type: "Create",
|
|
1820
|
+
actor: "https://remote.example/users/alice",
|
|
1821
|
+
ext: "preserve-me",
|
|
1822
|
+
object: {
|
|
1823
|
+
id: "https://remote.example/notes/non-lds-queued-custom-context",
|
|
1824
|
+
type: "Note",
|
|
1825
|
+
attributedTo: "https://remote.example/users/alice",
|
|
1826
|
+
content: "Hello from non-LDS queued custom context"
|
|
1827
|
+
},
|
|
1828
|
+
signature: {
|
|
1829
|
+
type: "RsaSignature2017",
|
|
1830
|
+
creator: "not a url",
|
|
1831
|
+
created: "2024-09-12T16:50:46Z",
|
|
1832
|
+
signatureValue: "Zm9v"
|
|
1833
|
+
}
|
|
1834
|
+
};
|
|
1835
|
+
const normalizedActivity = await compactJsonLd(unsignedBody, sourceContextLoader);
|
|
1836
|
+
await federation.processQueuedTask(void 0, {
|
|
1837
|
+
type: "inbox",
|
|
1838
|
+
id: crypto.randomUUID(),
|
|
1839
|
+
baseUrl: "https://example.com",
|
|
1840
|
+
activity: unsignedBody,
|
|
1841
|
+
normalizedActivity,
|
|
1842
|
+
ldSignatureVerified: false,
|
|
1843
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1844
|
+
attempt: 0,
|
|
1845
|
+
identifier: null,
|
|
1846
|
+
traceContext: {}
|
|
1847
|
+
});
|
|
1848
|
+
if (received == null) throw new Error("Inbox activity not delivered.");
|
|
1849
|
+
assertEquals(received.id?.href, "https://remote.example/activities/non-lds-queued-custom-context");
|
|
1850
|
+
assertEquals(receivedRaw, unsignedBody);
|
|
1851
|
+
});
|
|
1852
|
+
await t.step("legacy raw LDS inbox messages without normalizedActivity retry through worker error handling", async () => {
|
|
1853
|
+
const remoteContextUrl = "https://remote.example/contexts/ext";
|
|
1854
|
+
const restrictiveContextLoader = async (resource) => {
|
|
1855
|
+
const url = new URL(resource).href;
|
|
1856
|
+
if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
|
|
1857
|
+
throw new Error(`Unexpected context: ${url}`);
|
|
1858
|
+
};
|
|
1859
|
+
const queue = {
|
|
1860
|
+
enqueue(message, _options) {
|
|
1861
|
+
queuedMessages.push(message);
|
|
1862
|
+
return Promise.resolve();
|
|
1863
|
+
},
|
|
1864
|
+
listen(_handler, _options) {
|
|
1865
|
+
return Promise.resolve();
|
|
1866
|
+
}
|
|
1867
|
+
};
|
|
1868
|
+
const kv = new MemoryKvStore();
|
|
1869
|
+
const queuedMessages = [];
|
|
1870
|
+
let errorCount = 0;
|
|
1871
|
+
const federation = new FederationImpl({
|
|
1872
|
+
kv,
|
|
1873
|
+
queue,
|
|
1874
|
+
contextLoaderFactory: () => restrictiveContextLoader
|
|
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": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
|
|
1883
|
+
id: "https://remote.example/activities/legacy-raw",
|
|
1884
|
+
type: "Create",
|
|
1885
|
+
actor: "https://remote.example/users/alice",
|
|
1886
|
+
ext: "preserve-me",
|
|
1887
|
+
object: {
|
|
1888
|
+
id: "https://remote.example/notes/legacy-raw",
|
|
1889
|
+
type: "Note",
|
|
1890
|
+
attributedTo: "https://remote.example/users/alice",
|
|
1891
|
+
content: "Hello from raw legacy queue"
|
|
1892
|
+
}
|
|
1893
|
+
}, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
|
|
1894
|
+
const url = new URL(resource).href;
|
|
1895
|
+
if (url === remoteContextUrl) return {
|
|
1896
|
+
contextUrl: null,
|
|
1897
|
+
documentUrl: url,
|
|
1898
|
+
document: { "@context": { ext: "https://example.com/ext" } }
|
|
1899
|
+
};
|
|
1900
|
+
return await mockDocumentLoader(url);
|
|
1901
|
+
} });
|
|
1902
|
+
const inboxMessage = {
|
|
1903
|
+
type: "inbox",
|
|
1904
|
+
id: crypto.randomUUID(),
|
|
1905
|
+
baseUrl: "https://example.com",
|
|
1906
|
+
activity: signed,
|
|
1907
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1908
|
+
attempt: 0,
|
|
1909
|
+
identifier: null,
|
|
1910
|
+
traceContext: {}
|
|
1911
|
+
};
|
|
1912
|
+
await federation.processQueuedTask(void 0, inboxMessage);
|
|
1913
|
+
assertEquals(errorCount, 1);
|
|
1914
|
+
assertEquals(queuedMessages.length, 1);
|
|
1915
|
+
const retried = queuedMessages[0];
|
|
1916
|
+
assertEquals(retried.attempt, 1);
|
|
1917
|
+
assertEquals(retried.activity, inboxMessage.activity);
|
|
1918
|
+
});
|
|
1919
|
+
await t.step("without inbox queue retriable inbox parse failures bubble to caller", async () => {
|
|
1920
|
+
const remoteContextUrl = "https://remote.example/contexts/ext";
|
|
1921
|
+
const sourceContextLoader = async (resource) => {
|
|
1922
|
+
const url = new URL(resource).href;
|
|
1923
|
+
if (url === remoteContextUrl) return {
|
|
1924
|
+
contextUrl: null,
|
|
1925
|
+
documentUrl: url,
|
|
1926
|
+
document: { "@context": { ext: "https://example.com/ext" } }
|
|
1927
|
+
};
|
|
1928
|
+
return await mockDocumentLoader(url);
|
|
1929
|
+
};
|
|
1930
|
+
let errorCount = 0;
|
|
1931
|
+
const federation = new FederationImpl({
|
|
1932
|
+
kv: new MemoryKvStore(),
|
|
1933
|
+
contextLoaderFactory: () => async (resource) => {
|
|
1934
|
+
const url = new URL(resource).href;
|
|
1935
|
+
if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
|
|
1936
|
+
if (url === remoteContextUrl) throw new Error(`Transient remote context failure: ${url}`);
|
|
1937
|
+
throw new Error(`Unexpected context: ${url}`);
|
|
1938
|
+
}
|
|
1939
|
+
});
|
|
1940
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
|
|
1941
|
+
throw new Error("listener should not run");
|
|
1942
|
+
}).onError(() => {
|
|
1943
|
+
errorCount++;
|
|
1944
|
+
});
|
|
1945
|
+
const signed = await signJsonLd({
|
|
1946
|
+
"@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
|
|
1947
|
+
id: "https://remote.example/activities/manual-retry",
|
|
1948
|
+
type: "Create",
|
|
1949
|
+
actor: "https://remote.example/users/alice",
|
|
1950
|
+
ext: "preserve-me",
|
|
1951
|
+
object: {
|
|
1952
|
+
id: "https://remote.example/notes/manual-retry",
|
|
1953
|
+
type: "Note",
|
|
1954
|
+
attributedTo: "https://remote.example/users/alice",
|
|
1955
|
+
content: "Hello from manual retry queue"
|
|
1956
|
+
}
|
|
1957
|
+
}, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: sourceContextLoader });
|
|
1958
|
+
await assertRejects(() => federation.processQueuedTask(void 0, {
|
|
1959
|
+
type: "inbox",
|
|
1960
|
+
id: crypto.randomUUID(),
|
|
1961
|
+
baseUrl: "https://example.com",
|
|
1962
|
+
activity: signed,
|
|
1963
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1964
|
+
attempt: 0,
|
|
1965
|
+
identifier: null,
|
|
1966
|
+
traceContext: {}
|
|
1967
|
+
}), Error);
|
|
1968
|
+
assertEquals(errorCount, 1);
|
|
1969
|
+
});
|
|
1970
|
+
await t.step("legacy raw LDS inbox messages with transient InvalidUrl failures retry", async () => {
|
|
1971
|
+
const remoteContextUrl = "https://remote.example/contexts/ext";
|
|
1972
|
+
const queue = {
|
|
1973
|
+
enqueue(message, _options) {
|
|
1974
|
+
queuedMessages.push(message);
|
|
1975
|
+
return Promise.resolve();
|
|
1976
|
+
},
|
|
1977
|
+
listen(_handler, _options) {
|
|
1978
|
+
return Promise.resolve();
|
|
1979
|
+
}
|
|
1980
|
+
};
|
|
1981
|
+
const kv = new MemoryKvStore();
|
|
1982
|
+
const queuedMessages = [];
|
|
1983
|
+
let errorCount = 0;
|
|
1984
|
+
const federation = new FederationImpl({
|
|
1985
|
+
kv,
|
|
1986
|
+
queue,
|
|
1987
|
+
contextLoaderFactory: () => async (resource) => {
|
|
1988
|
+
const url = new URL(resource).href;
|
|
1989
|
+
if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
|
|
1990
|
+
if (url === remoteContextUrl) {
|
|
1991
|
+
const error = /* @__PURE__ */ new Error(`Transient remote context failure: ${url}`);
|
|
1992
|
+
error.name = "jsonld.InvalidUrl";
|
|
1993
|
+
error.details = {
|
|
1994
|
+
code: "loading remote context failed",
|
|
1995
|
+
url
|
|
1996
|
+
};
|
|
1997
|
+
throw error;
|
|
1998
|
+
}
|
|
1999
|
+
throw new Error(`Unexpected context: ${url}`);
|
|
2000
|
+
}
|
|
2001
|
+
});
|
|
2002
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
|
|
2003
|
+
throw new Error("listener should not run");
|
|
2004
|
+
}).onError(() => {
|
|
2005
|
+
errorCount++;
|
|
2006
|
+
});
|
|
2007
|
+
const signed = await signJsonLd({
|
|
2008
|
+
"@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
|
|
2009
|
+
id: "https://remote.example/activities/legacy-invalid-context",
|
|
2010
|
+
type: "Create",
|
|
2011
|
+
actor: "https://remote.example/users/alice",
|
|
2012
|
+
ext: "preserve-me",
|
|
2013
|
+
object: {
|
|
2014
|
+
id: "https://remote.example/notes/legacy-invalid-context",
|
|
2015
|
+
type: "Note",
|
|
2016
|
+
attributedTo: "https://remote.example/users/alice",
|
|
2017
|
+
content: "Hello from invalid legacy queue"
|
|
2018
|
+
}
|
|
2019
|
+
}, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
|
|
2020
|
+
const url = new URL(resource).href;
|
|
2021
|
+
if (url === remoteContextUrl) return {
|
|
2022
|
+
contextUrl: null,
|
|
2023
|
+
documentUrl: url,
|
|
2024
|
+
document: { "@context": { ext: "https://example.com/ext" } }
|
|
2025
|
+
};
|
|
2026
|
+
return await mockDocumentLoader(url);
|
|
2027
|
+
} });
|
|
2028
|
+
const inboxMessage = {
|
|
2029
|
+
type: "inbox",
|
|
2030
|
+
id: crypto.randomUUID(),
|
|
2031
|
+
baseUrl: "https://example.com",
|
|
2032
|
+
activity: signed,
|
|
2033
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2034
|
+
attempt: 0,
|
|
2035
|
+
identifier: null,
|
|
2036
|
+
traceContext: {}
|
|
2037
|
+
};
|
|
2038
|
+
await federation.processQueuedTask(void 0, inboxMessage);
|
|
2039
|
+
assertEquals(errorCount, 1);
|
|
2040
|
+
assertEquals(queuedMessages.length, 1);
|
|
2041
|
+
const retried = queuedMessages[0];
|
|
2042
|
+
assertEquals(retried.attempt, 1);
|
|
2043
|
+
assertEquals(retried.activity, inboxMessage.activity);
|
|
2044
|
+
});
|
|
2045
|
+
await t.step("legacy raw LDS inbox messages with opaque context ids retry", async () => {
|
|
2046
|
+
const queue = {
|
|
2047
|
+
enqueue(message, _options) {
|
|
2048
|
+
queuedMessages.push(message);
|
|
2049
|
+
return Promise.resolve();
|
|
2050
|
+
},
|
|
2051
|
+
listen(_handler, _options) {
|
|
2052
|
+
return Promise.resolve();
|
|
2053
|
+
}
|
|
2054
|
+
};
|
|
2055
|
+
const kv = new MemoryKvStore();
|
|
2056
|
+
const queuedMessages = [];
|
|
2057
|
+
let errorCount = 0;
|
|
2058
|
+
const federation = new FederationImpl({
|
|
2059
|
+
kv,
|
|
2060
|
+
queue,
|
|
2061
|
+
contextLoaderFactory: () => async (resource) => {
|
|
2062
|
+
if (resource === "app-context") {
|
|
2063
|
+
const error = /* @__PURE__ */ new Error(`Opaque context backend is unavailable: ${resource}`);
|
|
2064
|
+
error.name = "jsonld.InvalidUrl";
|
|
2065
|
+
error.details = {
|
|
2066
|
+
code: "loading remote context failed",
|
|
2067
|
+
url: resource
|
|
2068
|
+
};
|
|
2069
|
+
throw error;
|
|
2070
|
+
}
|
|
2071
|
+
const url = new URL(resource).href;
|
|
2072
|
+
if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
|
|
2073
|
+
throw new Error(`Unexpected context: ${resource}`);
|
|
2074
|
+
}
|
|
2075
|
+
});
|
|
2076
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
|
|
2077
|
+
throw new Error("listener should not run");
|
|
2078
|
+
}).onError(() => {
|
|
2079
|
+
errorCount++;
|
|
2080
|
+
});
|
|
2081
|
+
const signed = await signJsonLd({
|
|
2082
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
2083
|
+
id: "https://remote.example/activities/legacy-malformed-context",
|
|
2084
|
+
type: "Create",
|
|
2085
|
+
actor: "https://remote.example/users/alice",
|
|
2086
|
+
object: {
|
|
2087
|
+
id: "https://remote.example/notes/legacy-malformed-context",
|
|
2088
|
+
type: "Note",
|
|
2089
|
+
attributedTo: "https://remote.example/users/alice",
|
|
2090
|
+
content: "Hello from malformed legacy queue"
|
|
2091
|
+
}
|
|
2092
|
+
}, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
|
|
2093
|
+
const inboxMessage = {
|
|
2094
|
+
type: "inbox",
|
|
2095
|
+
id: crypto.randomUUID(),
|
|
2096
|
+
baseUrl: "https://example.com",
|
|
2097
|
+
activity: {
|
|
2098
|
+
...signed,
|
|
2099
|
+
"@context": ["app-context", "https://www.w3.org/ns/activitystreams"]
|
|
2100
|
+
},
|
|
2101
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2102
|
+
attempt: 0,
|
|
2103
|
+
identifier: null,
|
|
2104
|
+
traceContext: {}
|
|
2105
|
+
};
|
|
2106
|
+
await federation.processQueuedTask(void 0, inboxMessage);
|
|
2107
|
+
assertEquals(errorCount, 1);
|
|
2108
|
+
assertEquals(queuedMessages, [{
|
|
2109
|
+
...inboxMessage,
|
|
2110
|
+
attempt: 1
|
|
2111
|
+
}]);
|
|
2112
|
+
});
|
|
2113
|
+
await t.step("legacy raw LDS inbox messages with Invalid URL TypeErrors retry", async () => {
|
|
2114
|
+
const queue = {
|
|
2115
|
+
enqueue(message, _options) {
|
|
2116
|
+
queuedMessages.push(message);
|
|
2117
|
+
return Promise.resolve();
|
|
2118
|
+
},
|
|
2119
|
+
listen(_handler, _options) {
|
|
2120
|
+
return Promise.resolve();
|
|
2121
|
+
}
|
|
2122
|
+
};
|
|
2123
|
+
const kv = new MemoryKvStore();
|
|
2124
|
+
const queuedMessages = [];
|
|
2125
|
+
let errorCount = 0;
|
|
2126
|
+
const federation = new FederationImpl({
|
|
2127
|
+
kv,
|
|
2128
|
+
queue,
|
|
2129
|
+
contextLoaderFactory: () => async (resource) => {
|
|
2130
|
+
if (resource === "app:context") throw new TypeError(`Invalid URL: ${resource}`);
|
|
2131
|
+
const url = new URL(resource).href;
|
|
2132
|
+
if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
|
|
2133
|
+
throw new Error(`Unexpected context: ${resource}`);
|
|
2134
|
+
}
|
|
2135
|
+
});
|
|
2136
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
|
|
2137
|
+
throw new Error("listener should not run");
|
|
2138
|
+
}).onError(() => {
|
|
2139
|
+
errorCount++;
|
|
2140
|
+
});
|
|
2141
|
+
const signed = await signJsonLd({
|
|
2142
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
2143
|
+
id: "https://remote.example/activities/legacy-typeerror-invalid-url",
|
|
2144
|
+
type: "Create",
|
|
2145
|
+
actor: "https://remote.example/users/alice",
|
|
2146
|
+
object: {
|
|
2147
|
+
id: "https://remote.example/notes/legacy-typeerror-invalid-url",
|
|
2148
|
+
type: "Note",
|
|
2149
|
+
attributedTo: "https://remote.example/users/alice",
|
|
2150
|
+
content: "Hello from invalid-url typeerror queue"
|
|
2151
|
+
}
|
|
2152
|
+
}, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
|
|
2153
|
+
const inboxMessage = {
|
|
2154
|
+
type: "inbox",
|
|
2155
|
+
id: crypto.randomUUID(),
|
|
2156
|
+
baseUrl: "https://example.com",
|
|
2157
|
+
activity: {
|
|
2158
|
+
...signed,
|
|
2159
|
+
"@context": ["app:context", "https://www.w3.org/ns/activitystreams"]
|
|
2160
|
+
},
|
|
2161
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2162
|
+
attempt: 0,
|
|
2163
|
+
identifier: null,
|
|
2164
|
+
traceContext: {}
|
|
2165
|
+
};
|
|
2166
|
+
await federation.processQueuedTask(void 0, inboxMessage);
|
|
2167
|
+
assertEquals(errorCount, 1);
|
|
2168
|
+
assertEquals(queuedMessages.length, 1);
|
|
2169
|
+
const retried = queuedMessages[0];
|
|
2170
|
+
assertEquals(retried.attempt, 1);
|
|
2171
|
+
assertEquals(retried.activity, inboxMessage.activity);
|
|
2172
|
+
});
|
|
2173
|
+
await t.step("legacy raw LDS inbox messages with malformed absolute context refs do not retry", async () => {
|
|
2174
|
+
const queue = {
|
|
2175
|
+
enqueue(message, _options) {
|
|
2176
|
+
queuedMessages.push(message);
|
|
2177
|
+
return Promise.resolve();
|
|
2178
|
+
},
|
|
2179
|
+
listen(_handler, _options) {
|
|
2180
|
+
return Promise.resolve();
|
|
2181
|
+
}
|
|
2182
|
+
};
|
|
2183
|
+
const kv = new MemoryKvStore();
|
|
2184
|
+
const queuedMessages = [];
|
|
2185
|
+
let errorCount = 0;
|
|
2186
|
+
const federation = new FederationImpl({
|
|
2187
|
+
kv,
|
|
2188
|
+
queue,
|
|
2189
|
+
contextLoaderFactory: () => async (resource) => {
|
|
2190
|
+
if (resource === "http:/[") throw new TypeError(`Invalid URL: ${resource}`);
|
|
2191
|
+
const url = new URL(resource).href;
|
|
2192
|
+
if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
|
|
2193
|
+
throw new Error(`Unexpected context: ${resource}`);
|
|
2194
|
+
}
|
|
2195
|
+
});
|
|
2196
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
|
|
2197
|
+
throw new Error("listener should not run");
|
|
2198
|
+
}).onError(() => {
|
|
2199
|
+
errorCount++;
|
|
2200
|
+
});
|
|
2201
|
+
const signed = await signJsonLd({
|
|
2202
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
2203
|
+
id: "https://remote.example/activities/legacy-malformed-absolute-context",
|
|
2204
|
+
type: "Create",
|
|
2205
|
+
actor: "https://remote.example/users/alice",
|
|
2206
|
+
object: {
|
|
2207
|
+
id: "https://remote.example/notes/legacy-malformed-absolute-context",
|
|
2208
|
+
type: "Note",
|
|
2209
|
+
attributedTo: "https://remote.example/users/alice",
|
|
2210
|
+
content: "Hello from malformed absolute context queue"
|
|
2211
|
+
}
|
|
2212
|
+
}, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
|
|
2213
|
+
const inboxMessage = {
|
|
2214
|
+
type: "inbox",
|
|
2215
|
+
id: crypto.randomUUID(),
|
|
2216
|
+
baseUrl: "https://example.com",
|
|
2217
|
+
activity: {
|
|
2218
|
+
...signed,
|
|
2219
|
+
"@context": ["http:/[", "https://www.w3.org/ns/activitystreams"]
|
|
2220
|
+
},
|
|
2221
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2222
|
+
attempt: 0,
|
|
2223
|
+
identifier: null,
|
|
2224
|
+
traceContext: {}
|
|
2225
|
+
};
|
|
2226
|
+
await federation.processQueuedTask(void 0, inboxMessage);
|
|
2227
|
+
assertEquals(errorCount, 1);
|
|
2228
|
+
assertEquals(queuedMessages, []);
|
|
2229
|
+
});
|
|
2230
|
+
await t.step("malformed IRI fields are permanent queued inbox parse errors", async () => {
|
|
2231
|
+
const queuedMessages = [];
|
|
2232
|
+
const queue = {
|
|
2233
|
+
enqueue(message, _options) {
|
|
2234
|
+
queuedMessages.push(message);
|
|
2235
|
+
return Promise.resolve();
|
|
2236
|
+
},
|
|
2237
|
+
listen(_handler, _options) {
|
|
2238
|
+
return Promise.resolve();
|
|
2239
|
+
}
|
|
2240
|
+
};
|
|
2241
|
+
const kv = new MemoryKvStore();
|
|
2242
|
+
let errorCount = 0;
|
|
2243
|
+
const federation = new FederationImpl({
|
|
2244
|
+
kv,
|
|
2245
|
+
queue
|
|
2246
|
+
});
|
|
2247
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
|
|
2248
|
+
throw new Error("listener should not run");
|
|
2249
|
+
}).onError(() => {
|
|
2250
|
+
errorCount++;
|
|
2251
|
+
});
|
|
2252
|
+
await federation.processQueuedTask(void 0, {
|
|
2253
|
+
type: "inbox",
|
|
2254
|
+
id: crypto.randomUUID(),
|
|
2255
|
+
baseUrl: "https://example.com",
|
|
2256
|
+
activity: {
|
|
2257
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
2258
|
+
id: "http://[",
|
|
2259
|
+
type: "Create",
|
|
2260
|
+
actor: "https://remote.example/users/alice",
|
|
2261
|
+
object: {
|
|
2262
|
+
id: "https://remote.example/notes/invalid-iri",
|
|
2263
|
+
type: "Note",
|
|
2264
|
+
attributedTo: "https://remote.example/users/alice",
|
|
2265
|
+
content: "Hello from invalid IRI queue"
|
|
2266
|
+
}
|
|
2267
|
+
},
|
|
2268
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2269
|
+
attempt: 0,
|
|
2270
|
+
identifier: null,
|
|
2271
|
+
traceContext: {}
|
|
2272
|
+
});
|
|
2273
|
+
assertEquals(errorCount, 1);
|
|
2274
|
+
assertEquals(queuedMessages, []);
|
|
2275
|
+
});
|
|
2276
|
+
await t.step("legacy raw LDS inbox messages with network-path context ids retry", async () => {
|
|
2277
|
+
const queue = {
|
|
2278
|
+
enqueue(message, _options) {
|
|
2279
|
+
queuedMessages.push(message);
|
|
2280
|
+
return Promise.resolve();
|
|
2281
|
+
},
|
|
2282
|
+
listen(_handler, _options) {
|
|
2283
|
+
return Promise.resolve();
|
|
2284
|
+
}
|
|
2285
|
+
};
|
|
2286
|
+
const kv = new MemoryKvStore();
|
|
2287
|
+
const queuedMessages = [];
|
|
2288
|
+
let errorCount = 0;
|
|
2289
|
+
const federation = new FederationImpl({
|
|
2290
|
+
kv,
|
|
2291
|
+
queue,
|
|
2292
|
+
contextLoaderFactory: () => async (resource) => {
|
|
2293
|
+
if (resource === "//cdn.example/ctx") {
|
|
2294
|
+
const error = /* @__PURE__ */ new Error(`Network-path context backend is unavailable: ${resource}`);
|
|
2295
|
+
error.name = "jsonld.InvalidUrl";
|
|
2296
|
+
error.details = {
|
|
2297
|
+
code: "loading remote context failed",
|
|
2298
|
+
url: resource
|
|
2299
|
+
};
|
|
2300
|
+
throw error;
|
|
2301
|
+
}
|
|
2302
|
+
const url = new URL(resource).href;
|
|
2303
|
+
if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
|
|
2304
|
+
throw new Error(`Unexpected context: ${resource}`);
|
|
2305
|
+
}
|
|
2306
|
+
});
|
|
2307
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
|
|
2308
|
+
throw new Error("listener should not run");
|
|
2309
|
+
}).onError(() => {
|
|
2310
|
+
errorCount++;
|
|
2311
|
+
});
|
|
2312
|
+
const signed = await signJsonLd({
|
|
2313
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
2314
|
+
id: "https://remote.example/activities/legacy-network-path-context",
|
|
2315
|
+
type: "Create",
|
|
2316
|
+
actor: "https://remote.example/users/alice",
|
|
2317
|
+
object: {
|
|
2318
|
+
id: "https://remote.example/notes/legacy-network-path-context",
|
|
2319
|
+
type: "Note",
|
|
2320
|
+
attributedTo: "https://remote.example/users/alice",
|
|
2321
|
+
content: "Hello from network-path legacy queue"
|
|
2322
|
+
}
|
|
2323
|
+
}, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
|
|
2324
|
+
const inboxMessage = {
|
|
2325
|
+
type: "inbox",
|
|
2326
|
+
id: crypto.randomUUID(),
|
|
2327
|
+
baseUrl: "https://example.com",
|
|
2328
|
+
activity: {
|
|
2329
|
+
...signed,
|
|
2330
|
+
"@context": ["//cdn.example/ctx", "https://www.w3.org/ns/activitystreams"]
|
|
2331
|
+
},
|
|
2332
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2333
|
+
attempt: 0,
|
|
2334
|
+
identifier: null,
|
|
2335
|
+
traceContext: {}
|
|
2336
|
+
};
|
|
2337
|
+
await federation.processQueuedTask(void 0, inboxMessage);
|
|
2338
|
+
assertEquals(errorCount, 1);
|
|
2339
|
+
assertEquals(queuedMessages, [{
|
|
2340
|
+
...inboxMessage,
|
|
2341
|
+
attempt: 1
|
|
2342
|
+
}]);
|
|
2343
|
+
});
|
|
2344
|
+
await t.step("legacy raw LDS inbox messages with malformed network-path refs do not retry", async () => {
|
|
2345
|
+
const queue = {
|
|
2346
|
+
enqueue(message, _options) {
|
|
2347
|
+
queuedMessages.push(message);
|
|
2348
|
+
return Promise.resolve();
|
|
2349
|
+
},
|
|
2350
|
+
listen(_handler, _options) {
|
|
2351
|
+
return Promise.resolve();
|
|
2352
|
+
}
|
|
2353
|
+
};
|
|
2354
|
+
const kv = new MemoryKvStore();
|
|
2355
|
+
const queuedMessages = [];
|
|
2356
|
+
let errorCount = 0;
|
|
2357
|
+
const federation = new FederationImpl({
|
|
2358
|
+
kv,
|
|
2359
|
+
queue,
|
|
2360
|
+
contextLoaderFactory: () => async (resource) => {
|
|
2361
|
+
if (resource === "//[") {
|
|
2362
|
+
const error = /* @__PURE__ */ new Error(`Malformed network-path context: ${resource}`);
|
|
2363
|
+
error.name = "jsonld.InvalidUrl";
|
|
2364
|
+
error.details = {
|
|
2365
|
+
code: "loading remote context failed",
|
|
2366
|
+
url: resource
|
|
2367
|
+
};
|
|
2368
|
+
throw error;
|
|
2369
|
+
}
|
|
2370
|
+
const url = new URL(resource).href;
|
|
2371
|
+
if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
|
|
2372
|
+
throw new Error(`Unexpected context: ${resource}`);
|
|
2373
|
+
}
|
|
2374
|
+
});
|
|
2375
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
|
|
2376
|
+
throw new Error("listener should not run");
|
|
2377
|
+
}).onError(() => {
|
|
2378
|
+
errorCount++;
|
|
2379
|
+
});
|
|
2380
|
+
const signed = await signJsonLd({
|
|
2381
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
2382
|
+
id: "https://remote.example/activities/legacy-malformed-network-path-context",
|
|
2383
|
+
type: "Create",
|
|
2384
|
+
actor: "https://remote.example/users/alice",
|
|
2385
|
+
object: {
|
|
2386
|
+
id: "https://remote.example/notes/legacy-malformed-network-path-context",
|
|
2387
|
+
type: "Note",
|
|
2388
|
+
attributedTo: "https://remote.example/users/alice",
|
|
2389
|
+
content: "Hello from malformed network-path legacy queue"
|
|
2390
|
+
}
|
|
2391
|
+
}, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
|
|
2392
|
+
const inboxMessage = {
|
|
2393
|
+
type: "inbox",
|
|
2394
|
+
id: crypto.randomUUID(),
|
|
2395
|
+
baseUrl: "https://example.com",
|
|
2396
|
+
activity: {
|
|
2397
|
+
...signed,
|
|
2398
|
+
"@context": ["//[", "https://www.w3.org/ns/activitystreams"]
|
|
2399
|
+
},
|
|
2400
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2401
|
+
attempt: 0,
|
|
2402
|
+
identifier: null,
|
|
2403
|
+
traceContext: {}
|
|
2404
|
+
};
|
|
2405
|
+
await federation.processQueuedTask(void 0, inboxMessage);
|
|
2406
|
+
assertEquals(errorCount, 1);
|
|
2407
|
+
assertEquals(queuedMessages, []);
|
|
2408
|
+
});
|
|
2409
|
+
await t.step("legacy raw LDS inbox messages with malformed context URLs do not retry", async () => {
|
|
2410
|
+
const queue = {
|
|
2411
|
+
enqueue(message, _options) {
|
|
2412
|
+
queuedMessages.push(message);
|
|
2413
|
+
return Promise.resolve();
|
|
2414
|
+
},
|
|
2415
|
+
listen(_handler, _options) {
|
|
2416
|
+
return Promise.resolve();
|
|
2417
|
+
}
|
|
2418
|
+
};
|
|
2419
|
+
const kv = new MemoryKvStore();
|
|
2420
|
+
const queuedMessages = [];
|
|
2421
|
+
let errorCount = 0;
|
|
2422
|
+
const federation = new FederationImpl({
|
|
2423
|
+
kv,
|
|
2424
|
+
queue,
|
|
2425
|
+
contextLoaderFactory: () => async (resource) => {
|
|
2426
|
+
if (resource === "not a url") {
|
|
2427
|
+
const error = /* @__PURE__ */ new Error(`Invalid remote context URL: ${resource}`);
|
|
2428
|
+
error.name = "jsonld.InvalidUrl";
|
|
2429
|
+
error.details = {
|
|
2430
|
+
code: "loading remote context failed",
|
|
2431
|
+
url: resource
|
|
2432
|
+
};
|
|
2433
|
+
throw error;
|
|
2434
|
+
}
|
|
2435
|
+
const url = new URL(resource).href;
|
|
2436
|
+
if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
|
|
2437
|
+
throw new Error(`Unexpected context: ${resource}`);
|
|
2438
|
+
}
|
|
2439
|
+
});
|
|
2440
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
|
|
2441
|
+
throw new Error("listener should not run");
|
|
2442
|
+
}).onError(() => {
|
|
2443
|
+
errorCount++;
|
|
2444
|
+
});
|
|
2445
|
+
const signed = await signJsonLd({
|
|
2446
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
2447
|
+
id: "https://remote.example/activities/legacy-malformed-context",
|
|
2448
|
+
type: "Create",
|
|
2449
|
+
actor: "https://remote.example/users/alice",
|
|
2450
|
+
object: {
|
|
2451
|
+
id: "https://remote.example/notes/legacy-malformed-context",
|
|
2452
|
+
type: "Note",
|
|
2453
|
+
attributedTo: "https://remote.example/users/alice",
|
|
2454
|
+
content: "Hello from malformed legacy queue"
|
|
2455
|
+
}
|
|
2456
|
+
}, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
|
|
2457
|
+
const inboxMessage = {
|
|
2458
|
+
type: "inbox",
|
|
2459
|
+
id: crypto.randomUUID(),
|
|
2460
|
+
baseUrl: "https://example.com",
|
|
2461
|
+
activity: {
|
|
2462
|
+
...signed,
|
|
2463
|
+
"@context": ["not a url", "https://www.w3.org/ns/activitystreams"]
|
|
2464
|
+
},
|
|
2465
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2466
|
+
attempt: 0,
|
|
2467
|
+
identifier: null,
|
|
2468
|
+
traceContext: {}
|
|
2469
|
+
};
|
|
2470
|
+
await federation.processQueuedTask(void 0, inboxMessage);
|
|
2471
|
+
assertEquals(errorCount, 1);
|
|
2472
|
+
assertEquals(queuedMessages, []);
|
|
2473
|
+
});
|
|
2474
|
+
await t.step("legacy raw LDS inbox messages with invalid percent escapes do not retry", async () => {
|
|
2475
|
+
const queue = {
|
|
2476
|
+
enqueue(message, _options) {
|
|
2477
|
+
queuedMessages.push(message);
|
|
2478
|
+
return Promise.resolve();
|
|
2479
|
+
},
|
|
2480
|
+
listen(_handler, _options) {
|
|
2481
|
+
return Promise.resolve();
|
|
2482
|
+
}
|
|
2483
|
+
};
|
|
1379
2484
|
const kv = new MemoryKvStore();
|
|
1380
2485
|
const queuedMessages = [];
|
|
2486
|
+
let errorCount = 0;
|
|
1381
2487
|
const federation = new FederationImpl({
|
|
1382
2488
|
kv,
|
|
1383
|
-
queue
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
2489
|
+
queue,
|
|
2490
|
+
contextLoaderFactory: () => async (resource) => {
|
|
2491
|
+
if (resource === "foo%zz") {
|
|
2492
|
+
const error = /* @__PURE__ */ new Error(`Invalid remote context URL: ${resource}`);
|
|
2493
|
+
error.name = "jsonld.InvalidUrl";
|
|
2494
|
+
error.details = {
|
|
2495
|
+
code: "loading remote context failed",
|
|
2496
|
+
url: resource
|
|
2497
|
+
};
|
|
2498
|
+
throw error;
|
|
1391
2499
|
}
|
|
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
|
+
throw new Error(`Unexpected context: ${resource}`);
|
|
1392
2503
|
}
|
|
1393
2504
|
});
|
|
1394
|
-
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(
|
|
1395
|
-
throw new Error("
|
|
2505
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
|
|
2506
|
+
throw new Error("listener should not run");
|
|
2507
|
+
}).onError(() => {
|
|
2508
|
+
errorCount++;
|
|
1396
2509
|
});
|
|
1397
|
-
|
|
1398
|
-
|
|
2510
|
+
const signed = await signJsonLd({
|
|
2511
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
2512
|
+
id: "https://remote.example/activities/legacy-malformed-percent-context",
|
|
2513
|
+
type: "Create",
|
|
2514
|
+
actor: "https://remote.example/users/alice",
|
|
2515
|
+
object: {
|
|
2516
|
+
id: "https://remote.example/notes/legacy-malformed-percent-context",
|
|
2517
|
+
type: "Note",
|
|
2518
|
+
attributedTo: "https://remote.example/users/alice",
|
|
2519
|
+
content: "Hello from malformed percent legacy queue"
|
|
2520
|
+
}
|
|
2521
|
+
}, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
|
|
2522
|
+
const inboxMessage = {
|
|
2523
|
+
type: "inbox",
|
|
1399
2524
|
id: crypto.randomUUID(),
|
|
1400
2525
|
baseUrl: "https://example.com",
|
|
1401
|
-
keys: [],
|
|
1402
2526
|
activity: {
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
actor: "https://example.com/users/alice",
|
|
1406
|
-
object: {
|
|
1407
|
-
type: "Note",
|
|
1408
|
-
content: "test"
|
|
1409
|
-
}
|
|
2527
|
+
...signed,
|
|
2528
|
+
"@context": ["foo%zz", "https://www.w3.org/ns/activitystreams"]
|
|
1410
2529
|
},
|
|
1411
|
-
activityType: "https://www.w3.org/ns/activitystreams#Create",
|
|
1412
|
-
inbox: "https://invalid-domain-that-does-not-exist.example/inbox",
|
|
1413
|
-
sharedInbox: false,
|
|
1414
2530
|
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1415
2531
|
attempt: 0,
|
|
1416
|
-
|
|
2532
|
+
identifier: null,
|
|
1417
2533
|
traceContext: {}
|
|
1418
|
-
}
|
|
2534
|
+
};
|
|
2535
|
+
await federation.processQueuedTask(void 0, inboxMessage);
|
|
2536
|
+
assertEquals(errorCount, 1);
|
|
1419
2537
|
assertEquals(queuedMessages, []);
|
|
1420
|
-
|
|
2538
|
+
});
|
|
2539
|
+
await t.step("legacy raw LDS inbox messages with invalid remote contexts do not retry", async () => {
|
|
2540
|
+
const remoteContextUrl = "https://remote.example/contexts/ext";
|
|
2541
|
+
const queue = {
|
|
2542
|
+
enqueue(message, _options) {
|
|
2543
|
+
queuedMessages.push(message);
|
|
2544
|
+
return Promise.resolve();
|
|
2545
|
+
},
|
|
2546
|
+
listen(_handler, _options) {
|
|
2547
|
+
return Promise.resolve();
|
|
2548
|
+
}
|
|
2549
|
+
};
|
|
2550
|
+
const kv = new MemoryKvStore();
|
|
2551
|
+
const queuedMessages = [];
|
|
2552
|
+
let errorCount = 0;
|
|
2553
|
+
const federation = new FederationImpl({
|
|
2554
|
+
kv,
|
|
2555
|
+
queue,
|
|
2556
|
+
contextLoaderFactory: () => async (resource) => {
|
|
2557
|
+
const url = new URL(resource).href;
|
|
2558
|
+
if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
|
|
2559
|
+
if (url === remoteContextUrl) return {
|
|
2560
|
+
contextUrl: null,
|
|
2561
|
+
documentUrl: url,
|
|
2562
|
+
document: [
|
|
2563
|
+
"not",
|
|
2564
|
+
"an",
|
|
2565
|
+
"object"
|
|
2566
|
+
]
|
|
2567
|
+
};
|
|
2568
|
+
throw new Error(`Unexpected context: ${url}`);
|
|
2569
|
+
}
|
|
2570
|
+
});
|
|
2571
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
|
|
2572
|
+
throw new Error("listener should not run");
|
|
2573
|
+
}).onError(() => {
|
|
2574
|
+
errorCount++;
|
|
2575
|
+
});
|
|
2576
|
+
const signed = await signJsonLd({
|
|
2577
|
+
"@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
|
|
2578
|
+
id: "https://remote.example/activities/legacy-invalid-remote-context",
|
|
2579
|
+
type: "Create",
|
|
2580
|
+
actor: "https://remote.example/users/alice",
|
|
2581
|
+
ext: "preserve-me",
|
|
2582
|
+
object: {
|
|
2583
|
+
id: "https://remote.example/notes/legacy-invalid-remote-context",
|
|
2584
|
+
type: "Note",
|
|
2585
|
+
attributedTo: "https://remote.example/users/alice",
|
|
2586
|
+
content: "Hello from invalid remote context queue"
|
|
2587
|
+
}
|
|
2588
|
+
}, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
|
|
2589
|
+
const url = new URL(resource).href;
|
|
2590
|
+
if (url === remoteContextUrl) return {
|
|
2591
|
+
contextUrl: null,
|
|
2592
|
+
documentUrl: url,
|
|
2593
|
+
document: { "@context": { ext: "https://example.com/ext" } }
|
|
2594
|
+
};
|
|
2595
|
+
return await mockDocumentLoader(url);
|
|
2596
|
+
} });
|
|
2597
|
+
const inboxMessage = {
|
|
1421
2598
|
type: "inbox",
|
|
1422
2599
|
id: crypto.randomUUID(),
|
|
1423
2600
|
baseUrl: "https://example.com",
|
|
1424
|
-
activity:
|
|
1425
|
-
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1426
|
-
type: "Create",
|
|
1427
|
-
actor: "https://remote.example/users/alice",
|
|
1428
|
-
object: {
|
|
1429
|
-
type: "Note",
|
|
1430
|
-
content: "Hello world"
|
|
1431
|
-
}
|
|
1432
|
-
},
|
|
2601
|
+
activity: signed,
|
|
1433
2602
|
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1434
2603
|
attempt: 0,
|
|
1435
2604
|
identifier: null,
|
|
1436
2605
|
traceContext: {}
|
|
1437
|
-
}
|
|
2606
|
+
};
|
|
2607
|
+
await federation.processQueuedTask(void 0, inboxMessage);
|
|
2608
|
+
assertEquals(errorCount, 1);
|
|
1438
2609
|
assertEquals(queuedMessages, []);
|
|
1439
2610
|
});
|
|
1440
|
-
await t.step("with
|
|
2611
|
+
await t.step("legacy raw LDS inbox messages with string remote contexts retry", async () => {
|
|
2612
|
+
const remoteContextUrl = "https://remote.example/contexts/ext";
|
|
2613
|
+
const queue = {
|
|
2614
|
+
enqueue(message, _options) {
|
|
2615
|
+
queuedMessages.push(message);
|
|
2616
|
+
return Promise.resolve();
|
|
2617
|
+
},
|
|
2618
|
+
listen(_handler, _options) {
|
|
2619
|
+
return Promise.resolve();
|
|
2620
|
+
}
|
|
2621
|
+
};
|
|
1441
2622
|
const kv = new MemoryKvStore();
|
|
1442
|
-
|
|
2623
|
+
const queuedMessages = [];
|
|
2624
|
+
let errorCount = 0;
|
|
1443
2625
|
const federation = new FederationImpl({
|
|
1444
2626
|
kv,
|
|
1445
|
-
queue
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
2627
|
+
queue,
|
|
2628
|
+
contextLoaderFactory: () => async (resource) => {
|
|
2629
|
+
const url = new URL(resource).href;
|
|
2630
|
+
if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
|
|
2631
|
+
if (url === remoteContextUrl) return {
|
|
2632
|
+
contextUrl: null,
|
|
2633
|
+
documentUrl: url,
|
|
2634
|
+
document: "{not valid json"
|
|
2635
|
+
};
|
|
2636
|
+
throw new Error(`Unexpected context: ${url}`);
|
|
1453
2637
|
}
|
|
1454
2638
|
});
|
|
1455
|
-
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(
|
|
1456
|
-
throw new Error("
|
|
2639
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
|
|
2640
|
+
throw new Error("listener should not run");
|
|
2641
|
+
}).onError(() => {
|
|
2642
|
+
errorCount++;
|
|
2643
|
+
});
|
|
2644
|
+
const signed = await signJsonLd({
|
|
2645
|
+
"@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
|
|
2646
|
+
id: "https://remote.example/activities/legacy-string-remote-context",
|
|
2647
|
+
type: "Create",
|
|
2648
|
+
actor: "https://remote.example/users/alice",
|
|
2649
|
+
ext: "preserve-me",
|
|
2650
|
+
object: {
|
|
2651
|
+
id: "https://remote.example/notes/legacy-string-remote-context",
|
|
2652
|
+
type: "Note",
|
|
2653
|
+
attributedTo: "https://remote.example/users/alice",
|
|
2654
|
+
content: "Hello from string remote context queue"
|
|
2655
|
+
}
|
|
2656
|
+
}, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
|
|
2657
|
+
const url = new URL(resource).href;
|
|
2658
|
+
if (url === remoteContextUrl) return {
|
|
2659
|
+
contextUrl: null,
|
|
2660
|
+
documentUrl: url,
|
|
2661
|
+
document: { "@context": { ext: "https://example.com/ext" } }
|
|
2662
|
+
};
|
|
2663
|
+
return await mockDocumentLoader(url);
|
|
2664
|
+
} });
|
|
2665
|
+
const inboxMessage = {
|
|
2666
|
+
type: "inbox",
|
|
2667
|
+
id: crypto.randomUUID(),
|
|
2668
|
+
baseUrl: "https://example.com",
|
|
2669
|
+
activity: signed,
|
|
2670
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2671
|
+
attempt: 0,
|
|
2672
|
+
identifier: null,
|
|
2673
|
+
traceContext: {}
|
|
2674
|
+
};
|
|
2675
|
+
await federation.processQueuedTask(void 0, inboxMessage);
|
|
2676
|
+
assertEquals(errorCount, 1);
|
|
2677
|
+
assertEquals(queuedMessages, [{
|
|
2678
|
+
...inboxMessage,
|
|
2679
|
+
attempt: 1
|
|
2680
|
+
}]);
|
|
2681
|
+
});
|
|
2682
|
+
await t.step("legacy raw LDS inbox messages with loader TypeErrors retry", async () => {
|
|
2683
|
+
const remoteContextUrl = "https://remote.example/contexts/ext";
|
|
2684
|
+
const queue = {
|
|
2685
|
+
enqueue(message, _options) {
|
|
2686
|
+
queuedMessages.push(message);
|
|
2687
|
+
return Promise.resolve();
|
|
2688
|
+
},
|
|
2689
|
+
listen(_handler, _options) {
|
|
2690
|
+
return Promise.resolve();
|
|
2691
|
+
}
|
|
2692
|
+
};
|
|
2693
|
+
const kv = new MemoryKvStore();
|
|
2694
|
+
const queuedMessages = [];
|
|
2695
|
+
let errorCount = 0;
|
|
2696
|
+
const federation = new FederationImpl({
|
|
2697
|
+
kv,
|
|
2698
|
+
queue,
|
|
2699
|
+
contextLoaderFactory: () => async (resource) => {
|
|
2700
|
+
const url = new URL(resource).href;
|
|
2701
|
+
if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
|
|
2702
|
+
if (url === remoteContextUrl) throw new TypeError(`Cannot initialize remote context loader: ${url}`);
|
|
2703
|
+
throw new Error(`Unexpected context: ${url}`);
|
|
2704
|
+
}
|
|
1457
2705
|
});
|
|
1458
|
-
|
|
1459
|
-
|
|
2706
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
|
|
2707
|
+
throw new Error("listener should not run");
|
|
2708
|
+
}).onError(() => {
|
|
2709
|
+
errorCount++;
|
|
2710
|
+
});
|
|
2711
|
+
const signed = await signJsonLd({
|
|
2712
|
+
"@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
|
|
2713
|
+
id: "https://remote.example/activities/legacy-typeerror-context",
|
|
2714
|
+
type: "Create",
|
|
2715
|
+
actor: "https://remote.example/users/alice",
|
|
2716
|
+
ext: "preserve-me",
|
|
2717
|
+
object: {
|
|
2718
|
+
id: "https://remote.example/notes/legacy-typeerror-context",
|
|
2719
|
+
type: "Note",
|
|
2720
|
+
attributedTo: "https://remote.example/users/alice",
|
|
2721
|
+
content: "Hello from typeerror legacy queue"
|
|
2722
|
+
}
|
|
2723
|
+
}, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
|
|
2724
|
+
const url = new URL(resource).href;
|
|
2725
|
+
if (url === remoteContextUrl) return {
|
|
2726
|
+
contextUrl: null,
|
|
2727
|
+
documentUrl: url,
|
|
2728
|
+
document: { "@context": { ext: "https://example.com/ext" } }
|
|
2729
|
+
};
|
|
2730
|
+
return await mockDocumentLoader(url);
|
|
2731
|
+
} });
|
|
2732
|
+
const inboxMessage = {
|
|
2733
|
+
type: "inbox",
|
|
1460
2734
|
id: crypto.randomUUID(),
|
|
1461
2735
|
baseUrl: "https://example.com",
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
2736
|
+
activity: signed,
|
|
2737
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2738
|
+
attempt: 0,
|
|
2739
|
+
identifier: null,
|
|
2740
|
+
traceContext: {}
|
|
2741
|
+
};
|
|
2742
|
+
await federation.processQueuedTask(void 0, inboxMessage);
|
|
2743
|
+
assertEquals(errorCount, 1);
|
|
2744
|
+
assertEquals(queuedMessages, [{
|
|
2745
|
+
...inboxMessage,
|
|
2746
|
+
attempt: 1
|
|
2747
|
+
}]);
|
|
2748
|
+
});
|
|
2749
|
+
await t.step("legacy raw LDS inbox messages with syntax errors in remote contexts retry", async () => {
|
|
2750
|
+
const remoteContextUrl = "https://remote.example/contexts/ext";
|
|
2751
|
+
const queue = {
|
|
2752
|
+
enqueue(message, _options) {
|
|
2753
|
+
queuedMessages.push(message);
|
|
2754
|
+
return Promise.resolve();
|
|
1471
2755
|
},
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
2756
|
+
listen(_handler, _options) {
|
|
2757
|
+
return Promise.resolve();
|
|
2758
|
+
}
|
|
2759
|
+
};
|
|
2760
|
+
const kv = new MemoryKvStore();
|
|
2761
|
+
const queuedMessages = [];
|
|
2762
|
+
let errorCount = 0;
|
|
2763
|
+
const federation = new FederationImpl({
|
|
2764
|
+
kv,
|
|
2765
|
+
queue,
|
|
2766
|
+
contextLoaderFactory: () => async (resource) => {
|
|
2767
|
+
const url = new URL(resource).href;
|
|
2768
|
+
if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
|
|
2769
|
+
if (url === remoteContextUrl) {
|
|
2770
|
+
const error = /* @__PURE__ */ new Error(`Transient syntax failure: ${url}`);
|
|
2771
|
+
error.name = "jsonld.SyntaxError";
|
|
2772
|
+
error.details = { code: "loading remote context failed" };
|
|
2773
|
+
throw error;
|
|
2774
|
+
}
|
|
2775
|
+
throw new Error(`Unexpected context: ${url}`);
|
|
2776
|
+
}
|
|
2777
|
+
});
|
|
2778
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
|
|
2779
|
+
throw new Error("listener should not run");
|
|
2780
|
+
}).onError(() => {
|
|
2781
|
+
errorCount++;
|
|
2782
|
+
});
|
|
2783
|
+
const signed = await signJsonLd({
|
|
2784
|
+
"@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
|
|
2785
|
+
id: "https://remote.example/activities/legacy-syntax-context",
|
|
2786
|
+
type: "Create",
|
|
2787
|
+
actor: "https://remote.example/users/alice",
|
|
2788
|
+
ext: "preserve-me",
|
|
2789
|
+
object: {
|
|
2790
|
+
id: "https://remote.example/notes/legacy-syntax-context",
|
|
2791
|
+
type: "Note",
|
|
2792
|
+
attributedTo: "https://remote.example/users/alice",
|
|
2793
|
+
content: "Hello from syntax legacy queue"
|
|
2794
|
+
}
|
|
2795
|
+
}, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
|
|
2796
|
+
const url = new URL(resource).href;
|
|
2797
|
+
if (url === remoteContextUrl) return {
|
|
2798
|
+
contextUrl: null,
|
|
2799
|
+
documentUrl: url,
|
|
2800
|
+
document: { "@context": { ext: "https://example.com/ext" } }
|
|
2801
|
+
};
|
|
2802
|
+
return await mockDocumentLoader(url);
|
|
2803
|
+
} });
|
|
2804
|
+
const inboxMessage = {
|
|
2805
|
+
type: "inbox",
|
|
2806
|
+
id: crypto.randomUUID(),
|
|
2807
|
+
baseUrl: "https://example.com",
|
|
2808
|
+
activity: signed,
|
|
1475
2809
|
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1476
2810
|
attempt: 0,
|
|
1477
|
-
|
|
2811
|
+
identifier: null,
|
|
1478
2812
|
traceContext: {}
|
|
1479
2813
|
};
|
|
1480
|
-
await federation.processQueuedTask(void 0,
|
|
2814
|
+
await federation.processQueuedTask(void 0, inboxMessage);
|
|
2815
|
+
assertEquals(errorCount, 1);
|
|
1481
2816
|
assertEquals(queuedMessages, [{
|
|
1482
|
-
...
|
|
2817
|
+
...inboxMessage,
|
|
1483
2818
|
attempt: 1
|
|
1484
2819
|
}]);
|
|
1485
|
-
|
|
2820
|
+
});
|
|
2821
|
+
await t.step("legacy raw LDS inbox messages with loader RangeErrors retry", async () => {
|
|
2822
|
+
const remoteContextUrl = "https://remote.example/contexts/ext";
|
|
2823
|
+
const queue = {
|
|
2824
|
+
enqueue(message, _options) {
|
|
2825
|
+
queuedMessages.push(message);
|
|
2826
|
+
return Promise.resolve();
|
|
2827
|
+
},
|
|
2828
|
+
listen(_handler, _options) {
|
|
2829
|
+
return Promise.resolve();
|
|
2830
|
+
}
|
|
2831
|
+
};
|
|
2832
|
+
const kv = new MemoryKvStore();
|
|
2833
|
+
const queuedMessages = [];
|
|
2834
|
+
let errorCount = 0;
|
|
2835
|
+
const federation = new FederationImpl({
|
|
2836
|
+
kv,
|
|
2837
|
+
queue,
|
|
2838
|
+
contextLoaderFactory: () => async (resource) => {
|
|
2839
|
+
const url = new URL(resource).href;
|
|
2840
|
+
if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
|
|
2841
|
+
if (url === remoteContextUrl) throw new RangeError(`Temporary remote context cache window exceeded: ${url}`);
|
|
2842
|
+
throw new Error(`Unexpected context: ${url}`);
|
|
2843
|
+
}
|
|
2844
|
+
});
|
|
2845
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
|
|
2846
|
+
throw new Error("listener should not run");
|
|
2847
|
+
}).onError(() => {
|
|
2848
|
+
errorCount++;
|
|
2849
|
+
});
|
|
2850
|
+
const signed = await signJsonLd({
|
|
2851
|
+
"@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
|
|
2852
|
+
id: "https://remote.example/activities/legacy-rangeerror-context",
|
|
2853
|
+
type: "Create",
|
|
2854
|
+
actor: "https://remote.example/users/alice",
|
|
2855
|
+
ext: "preserve-me",
|
|
2856
|
+
object: {
|
|
2857
|
+
id: "https://remote.example/notes/legacy-rangeerror-context",
|
|
2858
|
+
type: "Note",
|
|
2859
|
+
attributedTo: "https://remote.example/users/alice",
|
|
2860
|
+
content: "Hello from rangeerror legacy queue"
|
|
2861
|
+
}
|
|
2862
|
+
}, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
|
|
2863
|
+
const url = new URL(resource).href;
|
|
2864
|
+
if (url === remoteContextUrl) return {
|
|
2865
|
+
contextUrl: null,
|
|
2866
|
+
documentUrl: url,
|
|
2867
|
+
document: { "@context": { ext: "https://example.com/ext" } }
|
|
2868
|
+
};
|
|
2869
|
+
return await mockDocumentLoader(url);
|
|
2870
|
+
} });
|
|
1486
2871
|
const inboxMessage = {
|
|
2872
|
+
type: "inbox",
|
|
2873
|
+
id: crypto.randomUUID(),
|
|
2874
|
+
baseUrl: "https://example.com",
|
|
2875
|
+
activity: signed,
|
|
2876
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2877
|
+
attempt: 0,
|
|
2878
|
+
identifier: null,
|
|
2879
|
+
traceContext: {}
|
|
2880
|
+
};
|
|
2881
|
+
await federation.processQueuedTask(void 0, inboxMessage);
|
|
2882
|
+
assertEquals(errorCount, 1);
|
|
2883
|
+
assertEquals(queuedMessages, [{
|
|
2884
|
+
...inboxMessage,
|
|
2885
|
+
attempt: 1
|
|
2886
|
+
}]);
|
|
2887
|
+
});
|
|
2888
|
+
await t.step("permanent queued inbox parse errors do not re-enqueue poison messages", async () => {
|
|
2889
|
+
const queuedMessages = [];
|
|
2890
|
+
const queue = {
|
|
2891
|
+
enqueue(message, _options) {
|
|
2892
|
+
queuedMessages.push(message);
|
|
2893
|
+
return Promise.resolve();
|
|
2894
|
+
},
|
|
2895
|
+
listen(_handler, _options) {
|
|
2896
|
+
return Promise.resolve();
|
|
2897
|
+
}
|
|
2898
|
+
};
|
|
2899
|
+
const kv = new MemoryKvStore();
|
|
2900
|
+
let errorCount = 0;
|
|
2901
|
+
const federation = new FederationImpl({
|
|
2902
|
+
kv,
|
|
2903
|
+
queue
|
|
2904
|
+
});
|
|
2905
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
|
|
2906
|
+
throw new Error("listener should not run");
|
|
2907
|
+
}).onError(() => {
|
|
2908
|
+
errorCount++;
|
|
2909
|
+
});
|
|
2910
|
+
await federation.processQueuedTask(void 0, {
|
|
1487
2911
|
type: "inbox",
|
|
1488
2912
|
id: crypto.randomUUID(),
|
|
1489
2913
|
baseUrl: "https://example.com",
|
|
1490
2914
|
activity: {
|
|
1491
2915
|
"@context": "https://www.w3.org/ns/activitystreams",
|
|
2916
|
+
id: "https://remote.example/objects/not-an-activity",
|
|
2917
|
+
type: "Note",
|
|
2918
|
+
attributedTo: "https://remote.example/users/alice",
|
|
2919
|
+
content: "Not an activity"
|
|
2920
|
+
},
|
|
2921
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2922
|
+
attempt: 0,
|
|
2923
|
+
identifier: null,
|
|
2924
|
+
traceContext: {}
|
|
2925
|
+
});
|
|
2926
|
+
assertEquals(errorCount, 1);
|
|
2927
|
+
assertEquals(queuedMessages, []);
|
|
2928
|
+
});
|
|
2929
|
+
await t.step("malformed Temporal fields are permanent queued inbox parse errors", async () => {
|
|
2930
|
+
const queuedMessages = [];
|
|
2931
|
+
const queue = {
|
|
2932
|
+
enqueue(message, _options) {
|
|
2933
|
+
queuedMessages.push(message);
|
|
2934
|
+
return Promise.resolve();
|
|
2935
|
+
},
|
|
2936
|
+
listen(_handler, _options) {
|
|
2937
|
+
return Promise.resolve();
|
|
2938
|
+
}
|
|
2939
|
+
};
|
|
2940
|
+
const kv = new MemoryKvStore();
|
|
2941
|
+
let errorCount = 0;
|
|
2942
|
+
const federation = new FederationImpl({
|
|
2943
|
+
kv,
|
|
2944
|
+
queue,
|
|
2945
|
+
documentLoaderFactory: () => mockDocumentLoader,
|
|
2946
|
+
contextLoaderFactory: () => mockDocumentLoader
|
|
2947
|
+
});
|
|
2948
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
|
|
2949
|
+
throw new Error("listener should not run");
|
|
2950
|
+
}).onError(() => {
|
|
2951
|
+
errorCount++;
|
|
2952
|
+
});
|
|
2953
|
+
await federation.processQueuedTask(void 0, {
|
|
2954
|
+
type: "inbox",
|
|
2955
|
+
id: crypto.randomUUID(),
|
|
2956
|
+
baseUrl: "https://example.com",
|
|
2957
|
+
activity: {
|
|
2958
|
+
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
|
|
2959
|
+
id: "https://remote.example/activities/invalid-proof-created",
|
|
1492
2960
|
type: "Create",
|
|
1493
2961
|
actor: "https://remote.example/users/alice",
|
|
1494
2962
|
object: {
|
|
2963
|
+
id: "https://remote.example/notes/invalid-proof-created",
|
|
1495
2964
|
type: "Note",
|
|
1496
|
-
|
|
2965
|
+
attributedTo: "https://remote.example/users/alice",
|
|
2966
|
+
content: "Hello, world!"
|
|
2967
|
+
},
|
|
2968
|
+
proof: {
|
|
2969
|
+
type: "DataIntegrityProof",
|
|
2970
|
+
cryptosuite: "eddsa-jcs-2022",
|
|
2971
|
+
verificationMethod: "https://remote.example/users/alice#ed25519-key",
|
|
2972
|
+
proofPurpose: "assertionMethod",
|
|
2973
|
+
created: { "@value": "not-a-date" },
|
|
2974
|
+
proofValue: "zLaewdp4H9kqtwyrLatK4cjY5oRHwVcw4gibPSUDYDMhi4M49v8pcYk3ZB6D69dNpAPbUmY8ocuJ3m9KhKJEEg7z"
|
|
1497
2975
|
}
|
|
1498
2976
|
},
|
|
1499
2977
|
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1500
2978
|
attempt: 0,
|
|
1501
2979
|
identifier: null,
|
|
1502
2980
|
traceContext: {}
|
|
1503
|
-
};
|
|
1504
|
-
|
|
1505
|
-
assertEquals(queuedMessages, [
|
|
1506
|
-
...inboxMessage,
|
|
1507
|
-
attempt: 1
|
|
1508
|
-
}]);
|
|
2981
|
+
});
|
|
2982
|
+
assertEquals(errorCount, 1);
|
|
2983
|
+
assertEquals(queuedMessages, []);
|
|
1509
2984
|
});
|
|
1510
2985
|
});
|
|
1511
2986
|
test("FederationImpl.processQueuedTask() permanent failure", async (t) => {
|
|
@@ -2195,6 +3670,35 @@ test({
|
|
|
2195
3670
|
]);
|
|
2196
3671
|
}
|
|
2197
3672
|
});
|
|
3673
|
+
test("ContextImpl.routeActivity() marks queued signed activities as non-LDS", async () => {
|
|
3674
|
+
let queuedMessage = null;
|
|
3675
|
+
const federation = new FederationImpl({
|
|
3676
|
+
kv: new MemoryKvStore(),
|
|
3677
|
+
queue: {
|
|
3678
|
+
enqueue(message) {
|
|
3679
|
+
queuedMessage = message;
|
|
3680
|
+
return Promise.resolve();
|
|
3681
|
+
},
|
|
3682
|
+
async listen() {}
|
|
3683
|
+
}
|
|
3684
|
+
});
|
|
3685
|
+
federation.setInboxListeners("/u/{identifier}/i", "/i").on(Offer, () => {
|
|
3686
|
+
throw new Error("listener should not run for queued routeActivity");
|
|
3687
|
+
});
|
|
3688
|
+
const ctx = new ContextImpl({
|
|
3689
|
+
url: new URL("https://example.com/"),
|
|
3690
|
+
federation,
|
|
3691
|
+
data: void 0,
|
|
3692
|
+
documentLoader: mockDocumentLoader,
|
|
3693
|
+
contextLoader: documentLoader
|
|
3694
|
+
});
|
|
3695
|
+
const signedOffer = await signObject(new Offer({ actor: new URL("https://example.com/person2") }), ed25519PrivateKey, ed25519Multikey.id);
|
|
3696
|
+
assert(await ctx.routeActivity(null, signedOffer));
|
|
3697
|
+
if (queuedMessage == null) throw new Error("Inbox message not queued.");
|
|
3698
|
+
const inboxMessage = queuedMessage;
|
|
3699
|
+
assertEquals(inboxMessage.ldSignatureVerified, false);
|
|
3700
|
+
assertEquals(inboxMessage.normalizedActivity, void 0);
|
|
3701
|
+
});
|
|
2198
3702
|
test("ContextImpl.getCollectionUri()", () => {
|
|
2199
3703
|
const federation = new FederationImpl({ kv: new MemoryKvStore() });
|
|
2200
3704
|
const base = "https://example.com";
|