@fedify/fedify 1.10.8 → 1.10.10

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 (121) hide show
  1. package/dist/{actor-stZ3L5bm.mjs → actor-DZtfiGD5.mjs} +1 -1
  2. package/dist/{actor-BtdqoffI.js → actor-q2gTvEOg.js} +187 -187
  3. package/dist/{actor-CHbT9hXW.cjs → actor-yCME0Muf.cjs} +187 -187
  4. package/dist/{authdocloader-qSZCRtME.cjs → authdocloader-C9ucnURr.cjs} +3 -3
  5. package/dist/{authdocloader-BjFtWv8Q.js → authdocloader-CjcVKv5l.js} +3 -3
  6. package/dist/{authdocloader-DiNlCuEL.mjs → authdocloader-Dxs4mL_P.mjs} +3 -3
  7. package/dist/{builder-C06p3NRV.mjs → builder-DBBCeuJN.mjs} +4 -4
  8. package/dist/{client-aAP3RFq_.mjs → client-DfSKNItx.mjs} +1 -1
  9. package/dist/compat/mod.cjs +1 -1
  10. package/dist/compat/mod.d.cts +1 -1
  11. package/dist/compat/mod.d.ts +1 -1
  12. package/dist/compat/transformers.test.mjs +3 -3
  13. package/dist/{context-BzSr5FCW.d.cts → context-CC4UrGc5.d.cts} +48 -6
  14. package/dist/{context-CyacVopT.d.ts → context-xfiK4xM5.d.ts} +48 -6
  15. package/dist/{docloader-Cms83CjK.js → docloader-D8qb26Gj.js} +12 -3
  16. package/dist/{docloader--HMTXXhM.cjs → docloader-DR98w4wL.cjs} +12 -3
  17. package/dist/{esm-CfjSLJ-h.mjs → esm-onODA711.mjs} +1 -1
  18. package/dist/federation/builder.test.mjs +3 -3
  19. package/dist/federation/collection.test.mjs +1 -1
  20. package/dist/federation/handler.test.mjs +1362 -46
  21. package/dist/federation/idempotency.test.mjs +5 -5
  22. package/dist/federation/inbox.test.mjs +3 -3
  23. package/dist/federation/keycache.test.mjs +3 -3
  24. package/dist/federation/kv.test.mjs +1 -1
  25. package/dist/federation/middleware.test.mjs +1586 -82
  26. package/dist/federation/mod.cjs +2 -2
  27. package/dist/federation/mod.d.cts +1 -1
  28. package/dist/federation/mod.d.ts +1 -1
  29. package/dist/federation/mod.js +1 -1
  30. package/dist/federation/mq.test.mjs +3 -3
  31. package/dist/federation/retry.test.mjs +2 -2
  32. package/dist/federation/router.test.mjs +2 -2
  33. package/dist/federation/send.test.mjs +8 -8
  34. package/dist/federation/temporal.test.d.mts +2 -0
  35. package/dist/federation/temporal.test.mjs +71 -0
  36. package/dist/{http-CDZB3DBW.js → http-D6Qz3p20.js} +3 -3
  37. package/dist/{http-BPQ_zPRB.mjs → http-DyCX-dN3.mjs} +2 -2
  38. package/dist/{http-Bg-OkZtX.cjs → http-mdiWJYia.cjs} +3 -3
  39. package/dist/{inbox-Cnih7u27.mjs → inbox-BZN50kyQ.mjs} +7 -4
  40. package/dist/{key-C16D-kud.mjs → key-CSH0Pfat.mjs} +2 -2
  41. package/dist/{key-DGuUMBsU.cjs → key-DPzMtgeE.cjs} +1 -1
  42. package/dist/{key-Bbf-ITpM.cjs → key-DRfL8gil.cjs} +2 -2
  43. package/dist/{key-B0qYOng3.mjs → key-DXUAJBZu.mjs} +1 -1
  44. package/dist/{key-ASS7oK8n.js → key-TRp8WTUL.js} +2 -2
  45. package/dist/{keycache-CBPbgb7d.mjs → keycache-DETDxpbh.mjs} +1 -1
  46. package/dist/{keys-Br4iedk3.mjs → keys-8FIoKqmB.mjs} +1 -1
  47. package/dist/ld-BaJmhpEa.mjs +548 -0
  48. package/dist/{lookup-CA5RreoS.cjs → lookup-B7TLaXFf.cjs} +1 -1
  49. package/dist/{lookup-CpMB-hV5.js → lookup-C6wM-gq1.js} +1 -1
  50. package/dist/{lookup-CCcDhxO2.mjs → lookup-CX2yfN-X.mjs} +1 -1
  51. package/dist/{middleware-Bi5LcEMF.mjs → middleware-BJ7otoEC.mjs} +1 -1
  52. package/dist/{middleware-Crs1EVL7.mjs → middleware-BQ6evC7E.mjs} +320 -87
  53. package/dist/{middleware-BDTuu7QY.js → middleware-BXc5M6YG.js} +408 -83
  54. package/dist/{middleware-C90kKt44.cjs → middleware-DpVqFb2x.cjs} +411 -85
  55. package/dist/{middleware-BPTDstS-.cjs → middleware-cNynSnF6.cjs} +1 -1
  56. package/dist/mod.cjs +10 -10
  57. package/dist/mod.d.cts +1 -1
  58. package/dist/mod.d.ts +1 -1
  59. package/dist/mod.js +9 -9
  60. package/dist/nodeinfo/client.test.mjs +3 -3
  61. package/dist/nodeinfo/handler.test.mjs +2 -2
  62. package/dist/nodeinfo/mod.cjs +1 -1
  63. package/dist/nodeinfo/mod.js +1 -1
  64. package/dist/nodeinfo/semver.test.mjs +1 -1
  65. package/dist/nodeinfo/types.test.mjs +1 -1
  66. package/dist/otel/exporter.test.mjs +1 -1
  67. package/dist/otel/mod.cjs +1 -1
  68. package/dist/{owner-ncOJW9jU.mjs → owner-CLGVwm9P.mjs} +3 -3
  69. package/dist/{proof-BYdxNk_7.cjs → proof-BVB37JmB.cjs} +347 -5
  70. package/dist/{proof-Bd732W1s.mjs → proof-CAOvY-AP.mjs} +2 -2
  71. package/dist/{proof-JCn_Nftz.js → proof-DxQL08gJ.js} +300 -6
  72. package/dist/runtime/authdocloader.test.mjs +6 -6
  73. package/dist/runtime/docloader.test.mjs +4 -4
  74. package/dist/runtime/key.test.mjs +3 -3
  75. package/dist/runtime/langstr.test.mjs +2 -2
  76. package/dist/runtime/link.test.mjs +2 -2
  77. package/dist/runtime/mod.cjs +3 -3
  78. package/dist/runtime/mod.js +3 -3
  79. package/dist/runtime/multibase/multibase.test.mjs +2 -2
  80. package/dist/runtime/url.test.mjs +5 -2
  81. package/dist/{send-BUUNtAjI.mjs → send-BFClgq8T.mjs} +2 -2
  82. package/dist/sig/http.test.mjs +6 -6
  83. package/dist/sig/key.test.mjs +4 -4
  84. package/dist/sig/ld.test.mjs +561 -5
  85. package/dist/sig/mod.cjs +3 -3
  86. package/dist/sig/mod.js +3 -3
  87. package/dist/sig/owner.test.mjs +6 -6
  88. package/dist/sig/proof.test.mjs +4 -4
  89. package/dist/temporal-lmewfA7Y.mjs +95 -0
  90. package/dist/testing/docloader.test.mjs +1 -1
  91. package/dist/testing/mod.d.mts +48 -6
  92. package/dist/testing/mod.mjs +1 -1
  93. package/dist/{testing-CvmsaHLW.mjs → testing-B9FmxxlI.mjs} +2 -2
  94. package/dist/{transformers-Cd6Tdn3M.cjs → transformers-KRl8YJCO.cjs} +1 -1
  95. package/dist/{types-Dy8sMtkH.js → types-B0aIcMDF.js} +1 -1
  96. package/dist/{types-BVCtDIwE.cjs → types-DPWiFqle.cjs} +1 -1
  97. package/dist/vocab/actor.test.mjs +4 -4
  98. package/dist/vocab/cjs.test.mjs +1 -1
  99. package/dist/vocab/lookup.test.mjs +5 -5
  100. package/dist/vocab/mod.cjs +3 -3
  101. package/dist/vocab/mod.js +3 -3
  102. package/dist/vocab/type.test.mjs +2 -2
  103. package/dist/vocab/vocab.test.mjs +3 -3
  104. package/dist/{vocab-DcYXFzyR.mjs → vocab-DP9A9TjH.mjs} +197 -188
  105. package/dist/webfinger/handler.test.mjs +3 -3
  106. package/dist/webfinger/lookup.test.mjs +3 -3
  107. package/dist/webfinger/mod.cjs +1 -1
  108. package/dist/webfinger/mod.js +1 -1
  109. package/dist/x/cfworkers.cjs +1 -1
  110. package/dist/x/cfworkers.test.mjs +1 -1
  111. package/dist/x/hono.cjs +1 -1
  112. package/dist/x/hono.d.cts +1 -1
  113. package/dist/x/hono.d.ts +1 -1
  114. package/dist/x/sveltekit.cjs +1 -1
  115. package/dist/x/sveltekit.d.cts +1 -1
  116. package/dist/x/sveltekit.d.ts +1 -1
  117. package/package.json +1 -1
  118. package/dist/ld-7nQ68-rS.mjs +0 -254
  119. /package/dist/{denokv-NJPMXyK0.mjs → denokv-B0ZRIDsH.mjs} +0 -0
  120. /package/dist/{otel-0v4Zy01l.mjs → otel-D00WjYJ1.mjs} +0 -0
  121. /package/dist/{retry-Ddbq3AcK.mjs → retry-B_E3V_Dx.mjs} +0 -0
@@ -1,9 +1,9 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { C as Person, G as FetchError, U as getTypeId, _ as Note, g as Multikey, m as Invite, n as Announce, o as Create, q as fetchDocumentLoader, t as Activity, v as Object$1, y as Offer } from "../vocab-DcYXFzyR.mjs";
5
- import { i as RouterError, t as lookupObject } from "../lookup-CCcDhxO2.mjs";
6
- import { a as mockDocumentLoader, t as test } from "../testing-CvmsaHLW.mjs";
4
+ import { C as Person, G as FetchError, U as getTypeId, _ as Note, g as Multikey, m as Invite, n as Announce, o as Create, q as fetchDocumentLoader, t as Activity, v as Object$1, y as Offer } from "../vocab-DP9A9TjH.mjs";
5
+ import { i as RouterError, t as lookupObject } from "../lookup-CX2yfN-X.mjs";
6
+ import { a as mockDocumentLoader, t as test } from "../testing-B9FmxxlI.mjs";
7
7
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
8
8
  import { n as assertStrictEquals } from "../std__assert-2v7gYiZp.mjs";
9
9
  import { n as assertFalse, t as assertRejects } from "../assert_rejects-CJC9ThS-.mjs";
@@ -12,14 +12,14 @@ import { t as assertNotEquals } from "../assert_not_equals--wG9hV7u.mjs";
12
12
  import { t as assertThrows } from "../assert_throws-BIL7gChy.mjs";
13
13
  import { t as assert } from "../assert-DikXweDx.mjs";
14
14
  import { t as MemoryKvStore } from "../kv-OuT2IU64.mjs";
15
- import { a as createFederation, i as KvSpecDeterminer, n as FederationImpl, r as InboxContextImpl, t as ContextImpl } from "../middleware-Crs1EVL7.mjs";
16
- import { l as verifyRequest, s as signRequest } from "../http-BPQ_zPRB.mjs";
17
- import { t as getAuthenticatedDocumentLoader } from "../authdocloader-DiNlCuEL.mjs";
18
- import { a as signJsonLd, o as verifyJsonLd, r as detachSignature } from "../ld-7nQ68-rS.mjs";
19
- import { t as doesActorOwnKey } from "../owner-ncOJW9jU.mjs";
20
- import { n as signObject, r as verifyObject } from "../proof-Bd732W1s.mjs";
21
- import { t as esm_default } from "../esm-CfjSLJ-h.mjs";
22
- import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-Br4iedk3.mjs";
15
+ import { a as createFederation, i as KvSpecDeterminer, n as FederationImpl, r as InboxContextImpl, t as ContextImpl } from "../middleware-BQ6evC7E.mjs";
16
+ import { l as verifyRequest, s as signRequest } from "../http-DyCX-dN3.mjs";
17
+ import { t as getAuthenticatedDocumentLoader } from "../authdocloader-Dxs4mL_P.mjs";
18
+ import { a as compactJsonLd, f as signJsonLd, m as verifyJsonLd, s as detachSignature } from "../ld-BaJmhpEa.mjs";
19
+ import { t as doesActorOwnKey } from "../owner-CLGVwm9P.mjs";
20
+ import { n as signObject, r as verifyObject } from "../proof-CAOvY-AP.mjs";
21
+ import { t as esm_default } from "../esm-onODA711.mjs";
22
+ import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-8FIoKqmB.mjs";
23
23
  //#region src/testing/fixtures/example.com/person.json
24
24
  var person_default = {
25
25
  "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
@@ -916,6 +916,54 @@ test("Federation.setInboxListeners()", async (t) => {
916
916
  });
917
917
  esm_default.hardReset();
918
918
  });
919
+ test("Federation.fetch() preserves original LD-signed payload for InboxContextImpl.activity", async () => {
920
+ const remoteContextUrl = "https://remote.example/contexts/ext";
921
+ const sourceContextLoader = async (resource) => {
922
+ const url = new URL(resource).href;
923
+ if (url === remoteContextUrl) return {
924
+ contextUrl: null,
925
+ documentUrl: url,
926
+ document: { "@context": { ext: "https://example.com/ext" } }
927
+ };
928
+ return await mockDocumentLoader(url);
929
+ };
930
+ const federation = createFederation({
931
+ kv: new MemoryKvStore(),
932
+ documentLoader: mockDocumentLoader,
933
+ contextLoader: sourceContextLoader
934
+ });
935
+ federation.setActorDispatcher("/users/{identifier}", (_ctx, identifier) => identifier === "someone" ? new Person({}) : null);
936
+ let receivedRaw = null;
937
+ let receivedTyped = null;
938
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, (ctx, activity) => {
939
+ receivedRaw = ctx.activity;
940
+ receivedTyped = activity;
941
+ });
942
+ const signed = await signJsonLd({
943
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
944
+ id: "https://example.com/activities/preserve-raw",
945
+ type: "Create",
946
+ actor: "https://example.com/person2",
947
+ ext: "preserve-me",
948
+ object: {
949
+ id: "https://example.com/notes/preserve-raw",
950
+ type: "Note",
951
+ attributedTo: "https://example.com/person2",
952
+ content: "Hello, world!"
953
+ }
954
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: sourceContextLoader });
955
+ const response = await federation.fetch(new Request("https://example.com/inbox", {
956
+ method: "POST",
957
+ headers: { "Content-Type": "application/activity+json" },
958
+ body: JSON.stringify(signed)
959
+ }), { contextData: void 0 });
960
+ assertEquals([response.status, await response.text()], [202, ""]);
961
+ assertEquals(receivedRaw, signed);
962
+ assertNotEquals(receivedRaw, await compactJsonLd(signed, sourceContextLoader));
963
+ const delivered = receivedTyped;
964
+ assert(delivered != null);
965
+ assertEquals(delivered.id?.href, "https://example.com/activities/preserve-raw");
966
+ });
919
967
  test("Federation.setInboxDispatcher()", async (t) => {
920
968
  const kv = new MemoryKvStore();
921
969
  await t.step("path match", () => {
@@ -1035,132 +1083,1559 @@ test("FederationImpl.processQueuedTask()", async (t) => {
1035
1083
  const queuedMessages = [];
1036
1084
  const federation = new FederationImpl({
1037
1085
  kv,
1038
- queue: {
1039
- nativeRetrial: true,
1040
- enqueue(message, _options) {
1041
- queuedMessages.push(message);
1042
- return Promise.resolve();
1043
- },
1044
- listen(_handler, _options) {
1045
- return Promise.resolve();
1086
+ queue: {
1087
+ nativeRetrial: true,
1088
+ enqueue(message, _options) {
1089
+ queuedMessages.push(message);
1090
+ return Promise.resolve();
1091
+ },
1092
+ listen(_handler, _options) {
1093
+ return Promise.resolve();
1094
+ }
1095
+ }
1096
+ });
1097
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
1098
+ throw new Error("Intended error for testing");
1099
+ });
1100
+ await assertRejects(() => federation.processQueuedTask(void 0, {
1101
+ type: "outbox",
1102
+ id: crypto.randomUUID(),
1103
+ baseUrl: "https://example.com",
1104
+ keys: [],
1105
+ activity: {
1106
+ "@context": "https://www.w3.org/ns/activitystreams",
1107
+ type: "Create",
1108
+ actor: "https://example.com/users/alice",
1109
+ object: {
1110
+ type: "Note",
1111
+ content: "test"
1112
+ }
1113
+ },
1114
+ activityType: "https://www.w3.org/ns/activitystreams#Create",
1115
+ inbox: "https://invalid-domain-that-does-not-exist.example/inbox",
1116
+ sharedInbox: false,
1117
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1118
+ attempt: 0,
1119
+ headers: {},
1120
+ traceContext: {}
1121
+ }), Error);
1122
+ assertEquals(queuedMessages, []);
1123
+ await assertRejects(() => federation.processQueuedTask(void 0, {
1124
+ type: "inbox",
1125
+ id: crypto.randomUUID(),
1126
+ baseUrl: "https://example.com",
1127
+ activity: {
1128
+ "@context": "https://www.w3.org/ns/activitystreams",
1129
+ type: "Create",
1130
+ actor: "https://remote.example/users/alice",
1131
+ object: {
1132
+ type: "Note",
1133
+ content: "Hello world"
1134
+ }
1135
+ },
1136
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1137
+ attempt: 0,
1138
+ identifier: null,
1139
+ traceContext: {}
1140
+ }), Error);
1141
+ assertEquals(queuedMessages, []);
1142
+ });
1143
+ await t.step("with MessageQueue having no nativeRetrial", async () => {
1144
+ const kv = new MemoryKvStore();
1145
+ let queuedMessages = [];
1146
+ const federation = new FederationImpl({
1147
+ kv,
1148
+ queue: {
1149
+ enqueue(message, _options) {
1150
+ queuedMessages.push(message);
1151
+ return Promise.resolve();
1152
+ },
1153
+ listen(_handler, _options) {
1154
+ return Promise.resolve();
1155
+ }
1156
+ }
1157
+ });
1158
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
1159
+ throw new Error("Intended error for testing");
1160
+ });
1161
+ const outboxMessage = {
1162
+ type: "outbox",
1163
+ id: crypto.randomUUID(),
1164
+ baseUrl: "https://example.com",
1165
+ keys: [],
1166
+ activity: {
1167
+ "@context": "https://www.w3.org/ns/activitystreams",
1168
+ type: "Create",
1169
+ actor: "https://example.com/users/alice",
1170
+ object: {
1171
+ type: "Note",
1172
+ content: "test"
1173
+ }
1174
+ },
1175
+ activityType: "https://www.w3.org/ns/activitystreams#Create",
1176
+ inbox: "https://invalid-domain-that-does-not-exist.example/inbox",
1177
+ sharedInbox: false,
1178
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1179
+ attempt: 0,
1180
+ headers: {},
1181
+ traceContext: {}
1182
+ };
1183
+ await federation.processQueuedTask(void 0, outboxMessage);
1184
+ assertEquals(queuedMessages, [{
1185
+ ...outboxMessage,
1186
+ attempt: 1
1187
+ }]);
1188
+ queuedMessages = [];
1189
+ const inboxMessage = {
1190
+ type: "inbox",
1191
+ id: crypto.randomUUID(),
1192
+ baseUrl: "https://example.com",
1193
+ activity: {
1194
+ "@context": "https://www.w3.org/ns/activitystreams",
1195
+ type: "Create",
1196
+ actor: "https://remote.example/users/alice",
1197
+ object: {
1198
+ type: "Note",
1199
+ content: "Hello world"
1200
+ }
1201
+ },
1202
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1203
+ attempt: 0,
1204
+ identifier: null,
1205
+ traceContext: {}
1206
+ };
1207
+ await federation.processQueuedTask(void 0, inboxMessage);
1208
+ assertEquals(queuedMessages, [{
1209
+ ...inboxMessage,
1210
+ attempt: 1
1211
+ }]);
1212
+ });
1213
+ await t.step("with restrictive context loader and normalized LD-signed inbox activity", async () => {
1214
+ const remoteContextUrl = "https://remote.example/contexts/ext";
1215
+ const sourceContextLoader = async (resource) => {
1216
+ const url = new URL(resource).href;
1217
+ if (url === remoteContextUrl) return {
1218
+ contextUrl: null,
1219
+ documentUrl: url,
1220
+ document: { "@context": { ext: "https://example.com/ext" } }
1221
+ };
1222
+ return await mockDocumentLoader(url);
1223
+ };
1224
+ const restrictiveContextLoader = async (resource) => {
1225
+ const url = new URL(resource).href;
1226
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
1227
+ throw new Error(`Unexpected context: ${url}`);
1228
+ };
1229
+ const kv = new MemoryKvStore();
1230
+ let receivedCount = 0;
1231
+ let received = null;
1232
+ let receivedRaw = null;
1233
+ const federation = new FederationImpl({
1234
+ kv,
1235
+ contextLoader: restrictiveContextLoader
1236
+ });
1237
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, (ctx, activity) => {
1238
+ receivedCount++;
1239
+ receivedRaw = ctx.activity;
1240
+ received = activity;
1241
+ });
1242
+ const signed = await signJsonLd({
1243
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1244
+ id: "https://remote.example/activities/1",
1245
+ type: "Create",
1246
+ actor: "https://remote.example/users/alice",
1247
+ ext: "preserve-me",
1248
+ object: {
1249
+ id: "https://remote.example/notes/1",
1250
+ type: "Note",
1251
+ attributedTo: "https://remote.example/users/alice",
1252
+ content: "Hello, world!"
1253
+ }
1254
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: sourceContextLoader });
1255
+ const normalizedActivity = await compactJsonLd(signed, sourceContextLoader);
1256
+ const messageId = crypto.randomUUID();
1257
+ await federation.processQueuedTask(void 0, {
1258
+ type: "inbox",
1259
+ id: messageId,
1260
+ baseUrl: "https://example.com",
1261
+ activity: signed,
1262
+ normalizedActivity,
1263
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1264
+ attempt: 0,
1265
+ identifier: null,
1266
+ traceContext: {}
1267
+ });
1268
+ const delivered = received;
1269
+ assert(delivered != null);
1270
+ const deliveredCreate = delivered;
1271
+ assertInstanceOf(deliveredCreate, Create);
1272
+ assertEquals(deliveredCreate.id?.href, "https://remote.example/activities/1");
1273
+ assertEquals(receivedRaw, signed);
1274
+ await federation.processQueuedTask(void 0, {
1275
+ type: "inbox",
1276
+ id: messageId,
1277
+ baseUrl: "https://example.com",
1278
+ activity: signed,
1279
+ normalizedActivity,
1280
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1281
+ attempt: 0,
1282
+ identifier: null,
1283
+ traceContext: {}
1284
+ });
1285
+ assertEquals(receivedCount, 1);
1286
+ });
1287
+ await t.step("cached normalizedActivity is rechecked for unsafe JSON-LD keywords", async () => {
1288
+ const queuedMessages = [];
1289
+ const queue = {
1290
+ enqueue(message, _options) {
1291
+ queuedMessages.push(message);
1292
+ return Promise.resolve();
1293
+ },
1294
+ listen(_handler, _options) {
1295
+ return Promise.resolve();
1296
+ }
1297
+ };
1298
+ const kv = new MemoryKvStore();
1299
+ let receivedCount = 0;
1300
+ let errorCount = 0;
1301
+ const federation = new FederationImpl({
1302
+ kv,
1303
+ queue
1304
+ });
1305
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
1306
+ receivedCount++;
1307
+ }).onError(() => {
1308
+ errorCount++;
1309
+ });
1310
+ const signed = await signJsonLd({
1311
+ "@context": "https://www.w3.org/ns/activitystreams",
1312
+ id: "https://remote.example/activities/unsafe-normalized-cache",
1313
+ type: "Create",
1314
+ actor: "https://remote.example/users/alice",
1315
+ object: {
1316
+ id: "https://remote.example/notes/unsafe-normalized-cache",
1317
+ type: "Note",
1318
+ attributedTo: "https://remote.example/users/alice",
1319
+ content: "Hello from unsafe normalized cache"
1320
+ }
1321
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
1322
+ const normalizedActivity = await compactJsonLd(signed, mockDocumentLoader);
1323
+ const tamperedNormalizedActivity = {
1324
+ ...normalizedActivity,
1325
+ signature: {
1326
+ ...normalizedActivity.signature,
1327
+ "@included": [{
1328
+ id: "https://remote.example/activities/inside-signature",
1329
+ type: "Undo"
1330
+ }]
1331
+ }
1332
+ };
1333
+ await federation.processQueuedTask(void 0, {
1334
+ type: "inbox",
1335
+ id: crypto.randomUUID(),
1336
+ baseUrl: "https://example.com",
1337
+ activity: signed,
1338
+ normalizedActivity: tamperedNormalizedActivity,
1339
+ ldSignatureVerified: false,
1340
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1341
+ attempt: 0,
1342
+ identifier: null,
1343
+ traceContext: {}
1344
+ });
1345
+ assertEquals(receivedCount, 0);
1346
+ assertEquals(errorCount, 1);
1347
+ assertEquals(queuedMessages, []);
1348
+ });
1349
+ await t.step("old queued LDS inbox messages without normalizedActivity still work", async () => {
1350
+ const restrictiveContextLoader = async (resource) => {
1351
+ const url = new URL(resource).href;
1352
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
1353
+ throw new Error(`Unexpected context: ${url}`);
1354
+ };
1355
+ const kv = new MemoryKvStore();
1356
+ let received = null;
1357
+ const federation = new FederationImpl({
1358
+ kv,
1359
+ contextLoader: restrictiveContextLoader
1360
+ });
1361
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, (_ctx, activity) => {
1362
+ received = activity;
1363
+ });
1364
+ const compacted = await compactJsonLd(await signJsonLd({
1365
+ "@context": "https://www.w3.org/ns/activitystreams",
1366
+ id: "https://remote.example/activities/legacy",
1367
+ type: "Create",
1368
+ actor: "https://remote.example/users/alice",
1369
+ object: {
1370
+ id: "https://remote.example/notes/legacy",
1371
+ type: "Note",
1372
+ attributedTo: "https://remote.example/users/alice",
1373
+ content: "Hello from legacy queue"
1374
+ }
1375
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader }), restrictiveContextLoader);
1376
+ await federation.processQueuedTask(void 0, {
1377
+ type: "inbox",
1378
+ id: crypto.randomUUID(),
1379
+ baseUrl: "https://example.com",
1380
+ activity: compacted,
1381
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1382
+ attempt: 0,
1383
+ identifier: null,
1384
+ traceContext: {}
1385
+ });
1386
+ assert(received != null);
1387
+ assertEquals(received.id?.href, "https://remote.example/activities/legacy");
1388
+ });
1389
+ await t.step("queued signature-bearing non-LDS inbox messages keep parse-time normalization contexts", async () => {
1390
+ const signingContextLoader = async (resource) => {
1391
+ const url = new URL(resource).href;
1392
+ 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);
1393
+ throw new Error(`Unexpected context: ${url}`);
1394
+ };
1395
+ const processingContextLoader = async (resource) => {
1396
+ const url = new URL(resource).href;
1397
+ 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");
1398
+ return await signingContextLoader(resource);
1399
+ };
1400
+ const kv = new MemoryKvStore();
1401
+ let received = null;
1402
+ let receivedRaw = null;
1403
+ const federation = new FederationImpl({
1404
+ kv,
1405
+ contextLoader: processingContextLoader
1406
+ });
1407
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, (ctx, activity) => {
1408
+ receivedRaw = ctx.activity;
1409
+ received = activity;
1410
+ });
1411
+ const signed = await signJsonLd({
1412
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
1413
+ id: "https://remote.example/activities/non-lds-queued-signature",
1414
+ type: "Create",
1415
+ actor: "https://remote.example/users/alice",
1416
+ object: {
1417
+ id: "https://remote.example/notes/non-lds-queued-signature",
1418
+ type: "Note",
1419
+ attributedTo: "https://remote.example/users/alice",
1420
+ content: "Hello from non-LDS queued signature"
1421
+ }
1422
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: signingContextLoader });
1423
+ const signedPayload = signed;
1424
+ assert(Array.isArray(signedPayload["@context"]) && signedPayload["@context"].includes("https://w3id.org/security/v1"));
1425
+ await federation.processQueuedTask(void 0, {
1426
+ type: "inbox",
1427
+ id: crypto.randomUUID(),
1428
+ baseUrl: "https://example.com",
1429
+ activity: signed,
1430
+ ldSignatureVerified: false,
1431
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1432
+ attempt: 0,
1433
+ identifier: null,
1434
+ traceContext: {}
1435
+ });
1436
+ if (received == null) throw new Error("Inbox activity not delivered.");
1437
+ assertEquals(received.id?.href, "https://remote.example/activities/non-lds-queued-signature");
1438
+ assertEquals(receivedRaw, signed);
1439
+ });
1440
+ await t.step("queued signature-bearing non-LDS inbox messages reuse normalizedActivity for custom contexts", async () => {
1441
+ const remoteContextUrl = "https://remote.example/contexts/ext";
1442
+ const sourceContextLoader = async (resource) => {
1443
+ const url = new URL(resource).href;
1444
+ if (url === remoteContextUrl) return {
1445
+ contextUrl: null,
1446
+ documentUrl: url,
1447
+ document: { "@context": { ext: "https://example.com/ext" } }
1448
+ };
1449
+ return await mockDocumentLoader(url);
1450
+ };
1451
+ const restrictiveContextLoader = async (resource) => {
1452
+ const url = new URL(resource).href;
1453
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
1454
+ throw new Error(`Unexpected context: ${url}`);
1455
+ };
1456
+ const kv = new MemoryKvStore();
1457
+ let received = null;
1458
+ let receivedRaw = null;
1459
+ const federation = new FederationImpl({
1460
+ kv,
1461
+ contextLoader: restrictiveContextLoader
1462
+ });
1463
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, (ctx, activity) => {
1464
+ receivedRaw = ctx.activity;
1465
+ received = activity;
1466
+ });
1467
+ const unsignedBody = {
1468
+ "@context": [
1469
+ remoteContextUrl,
1470
+ "https://www.w3.org/ns/activitystreams",
1471
+ "https://w3id.org/security/v1"
1472
+ ],
1473
+ id: "https://remote.example/activities/non-lds-queued-custom-context",
1474
+ type: "Create",
1475
+ actor: "https://remote.example/users/alice",
1476
+ ext: "preserve-me",
1477
+ object: {
1478
+ id: "https://remote.example/notes/non-lds-queued-custom-context",
1479
+ type: "Note",
1480
+ attributedTo: "https://remote.example/users/alice",
1481
+ content: "Hello from non-LDS queued custom context"
1482
+ },
1483
+ signature: {
1484
+ type: "RsaSignature2017",
1485
+ creator: "not a url",
1486
+ created: "2024-09-12T16:50:46Z",
1487
+ signatureValue: "Zm9v"
1488
+ }
1489
+ };
1490
+ const normalizedActivity = await compactJsonLd(unsignedBody, sourceContextLoader);
1491
+ await federation.processQueuedTask(void 0, {
1492
+ type: "inbox",
1493
+ id: crypto.randomUUID(),
1494
+ baseUrl: "https://example.com",
1495
+ activity: unsignedBody,
1496
+ normalizedActivity,
1497
+ ldSignatureVerified: false,
1498
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1499
+ attempt: 0,
1500
+ identifier: null,
1501
+ traceContext: {}
1502
+ });
1503
+ if (received == null) throw new Error("Inbox activity not delivered.");
1504
+ assertEquals(received.id?.href, "https://remote.example/activities/non-lds-queued-custom-context");
1505
+ assertEquals(receivedRaw, unsignedBody);
1506
+ });
1507
+ await t.step("legacy raw LDS inbox messages without normalizedActivity retry through worker error handling", async () => {
1508
+ const remoteContextUrl = "https://remote.example/contexts/ext";
1509
+ const restrictiveContextLoader = async (resource) => {
1510
+ const url = new URL(resource).href;
1511
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
1512
+ throw new Error(`Unexpected context: ${url}`);
1513
+ };
1514
+ const queue = {
1515
+ enqueue(message, _options) {
1516
+ queuedMessages.push(message);
1517
+ return Promise.resolve();
1518
+ },
1519
+ listen(_handler, _options) {
1520
+ return Promise.resolve();
1521
+ }
1522
+ };
1523
+ const kv = new MemoryKvStore();
1524
+ const queuedMessages = [];
1525
+ let errorCount = 0;
1526
+ const federation = new FederationImpl({
1527
+ kv,
1528
+ queue,
1529
+ contextLoader: restrictiveContextLoader
1530
+ });
1531
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
1532
+ throw new Error("listener should not run");
1533
+ }).onError(() => {
1534
+ errorCount++;
1535
+ });
1536
+ const signed = await signJsonLd({
1537
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1538
+ id: "https://remote.example/activities/legacy-raw",
1539
+ type: "Create",
1540
+ actor: "https://remote.example/users/alice",
1541
+ ext: "preserve-me",
1542
+ object: {
1543
+ id: "https://remote.example/notes/legacy-raw",
1544
+ type: "Note",
1545
+ attributedTo: "https://remote.example/users/alice",
1546
+ content: "Hello from raw legacy queue"
1547
+ }
1548
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
1549
+ const url = new URL(resource).href;
1550
+ if (url === remoteContextUrl) return {
1551
+ contextUrl: null,
1552
+ documentUrl: url,
1553
+ document: { "@context": { ext: "https://example.com/ext" } }
1554
+ };
1555
+ return await mockDocumentLoader(url);
1556
+ } });
1557
+ const inboxMessage = {
1558
+ type: "inbox",
1559
+ id: crypto.randomUUID(),
1560
+ baseUrl: "https://example.com",
1561
+ activity: signed,
1562
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1563
+ attempt: 0,
1564
+ identifier: null,
1565
+ traceContext: {}
1566
+ };
1567
+ await federation.processQueuedTask(void 0, inboxMessage);
1568
+ assertEquals(errorCount, 1);
1569
+ assertEquals(queuedMessages.length, 1);
1570
+ const retried = queuedMessages[0];
1571
+ assertEquals(retried.attempt, 1);
1572
+ assertEquals(retried.activity, inboxMessage.activity);
1573
+ });
1574
+ await t.step("without inbox queue retriable inbox parse failures bubble to caller", async () => {
1575
+ const remoteContextUrl = "https://remote.example/contexts/ext";
1576
+ const sourceContextLoader = async (resource) => {
1577
+ const url = new URL(resource).href;
1578
+ if (url === remoteContextUrl) return {
1579
+ contextUrl: null,
1580
+ documentUrl: url,
1581
+ document: { "@context": { ext: "https://example.com/ext" } }
1582
+ };
1583
+ return await mockDocumentLoader(url);
1584
+ };
1585
+ let errorCount = 0;
1586
+ const federation = new FederationImpl({
1587
+ kv: new MemoryKvStore(),
1588
+ contextLoader: async (resource) => {
1589
+ const url = new URL(resource).href;
1590
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
1591
+ if (url === remoteContextUrl) throw new Error(`Transient remote context failure: ${url}`);
1592
+ throw new Error(`Unexpected context: ${url}`);
1593
+ }
1594
+ });
1595
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
1596
+ throw new Error("listener should not run");
1597
+ }).onError(() => {
1598
+ errorCount++;
1599
+ });
1600
+ const signed = await signJsonLd({
1601
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1602
+ id: "https://remote.example/activities/manual-retry",
1603
+ type: "Create",
1604
+ actor: "https://remote.example/users/alice",
1605
+ ext: "preserve-me",
1606
+ object: {
1607
+ id: "https://remote.example/notes/manual-retry",
1608
+ type: "Note",
1609
+ attributedTo: "https://remote.example/users/alice",
1610
+ content: "Hello from manual retry queue"
1611
+ }
1612
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: sourceContextLoader });
1613
+ await assertRejects(() => federation.processQueuedTask(void 0, {
1614
+ type: "inbox",
1615
+ id: crypto.randomUUID(),
1616
+ baseUrl: "https://example.com",
1617
+ activity: signed,
1618
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1619
+ attempt: 0,
1620
+ identifier: null,
1621
+ traceContext: {}
1622
+ }), Error);
1623
+ assertEquals(errorCount, 1);
1624
+ });
1625
+ await t.step("legacy raw LDS inbox messages with transient InvalidUrl failures retry", async () => {
1626
+ const remoteContextUrl = "https://remote.example/contexts/ext";
1627
+ const queue = {
1628
+ enqueue(message, _options) {
1629
+ queuedMessages.push(message);
1630
+ return Promise.resolve();
1631
+ },
1632
+ listen(_handler, _options) {
1633
+ return Promise.resolve();
1634
+ }
1635
+ };
1636
+ const kv = new MemoryKvStore();
1637
+ const queuedMessages = [];
1638
+ let errorCount = 0;
1639
+ const federation = new FederationImpl({
1640
+ kv,
1641
+ queue,
1642
+ contextLoader: async (resource) => {
1643
+ const url = new URL(resource).href;
1644
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
1645
+ if (url === remoteContextUrl) {
1646
+ const error = /* @__PURE__ */ new Error(`Transient remote context failure: ${url}`);
1647
+ error.name = "jsonld.InvalidUrl";
1648
+ error.details = {
1649
+ code: "loading remote context failed",
1650
+ url
1651
+ };
1652
+ throw error;
1653
+ }
1654
+ throw new Error(`Unexpected context: ${url}`);
1655
+ }
1656
+ });
1657
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
1658
+ throw new Error("listener should not run");
1659
+ }).onError(() => {
1660
+ errorCount++;
1661
+ });
1662
+ const signed = await signJsonLd({
1663
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
1664
+ id: "https://remote.example/activities/legacy-invalid-context",
1665
+ type: "Create",
1666
+ actor: "https://remote.example/users/alice",
1667
+ ext: "preserve-me",
1668
+ object: {
1669
+ id: "https://remote.example/notes/legacy-invalid-context",
1670
+ type: "Note",
1671
+ attributedTo: "https://remote.example/users/alice",
1672
+ content: "Hello from invalid legacy queue"
1673
+ }
1674
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
1675
+ const url = new URL(resource).href;
1676
+ if (url === remoteContextUrl) return {
1677
+ contextUrl: null,
1678
+ documentUrl: url,
1679
+ document: { "@context": { ext: "https://example.com/ext" } }
1680
+ };
1681
+ return await mockDocumentLoader(url);
1682
+ } });
1683
+ const inboxMessage = {
1684
+ type: "inbox",
1685
+ id: crypto.randomUUID(),
1686
+ baseUrl: "https://example.com",
1687
+ activity: signed,
1688
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1689
+ attempt: 0,
1690
+ identifier: null,
1691
+ traceContext: {}
1692
+ };
1693
+ await federation.processQueuedTask(void 0, inboxMessage);
1694
+ assertEquals(errorCount, 1);
1695
+ assertEquals(queuedMessages.length, 1);
1696
+ const retried = queuedMessages[0];
1697
+ assertEquals(retried.attempt, 1);
1698
+ assertEquals(retried.activity, inboxMessage.activity);
1699
+ });
1700
+ await t.step("legacy raw LDS inbox messages with opaque context ids retry", async () => {
1701
+ const queue = {
1702
+ enqueue(message, _options) {
1703
+ queuedMessages.push(message);
1704
+ return Promise.resolve();
1705
+ },
1706
+ listen(_handler, _options) {
1707
+ return Promise.resolve();
1708
+ }
1709
+ };
1710
+ const kv = new MemoryKvStore();
1711
+ const queuedMessages = [];
1712
+ let errorCount = 0;
1713
+ const federation = new FederationImpl({
1714
+ kv,
1715
+ queue,
1716
+ contextLoader: async (resource) => {
1717
+ if (resource === "app-context") {
1718
+ const error = /* @__PURE__ */ new Error(`Opaque context backend is unavailable: ${resource}`);
1719
+ error.name = "jsonld.InvalidUrl";
1720
+ error.details = {
1721
+ code: "loading remote context failed",
1722
+ url: resource
1723
+ };
1724
+ throw error;
1725
+ }
1726
+ const url = new URL(resource).href;
1727
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
1728
+ throw new Error(`Unexpected context: ${resource}`);
1729
+ }
1730
+ });
1731
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
1732
+ throw new Error("listener should not run");
1733
+ }).onError(() => {
1734
+ errorCount++;
1735
+ });
1736
+ const signed = await signJsonLd({
1737
+ "@context": "https://www.w3.org/ns/activitystreams",
1738
+ id: "https://remote.example/activities/legacy-malformed-context",
1739
+ type: "Create",
1740
+ actor: "https://remote.example/users/alice",
1741
+ object: {
1742
+ id: "https://remote.example/notes/legacy-malformed-context",
1743
+ type: "Note",
1744
+ attributedTo: "https://remote.example/users/alice",
1745
+ content: "Hello from malformed legacy queue"
1746
+ }
1747
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
1748
+ const inboxMessage = {
1749
+ type: "inbox",
1750
+ id: crypto.randomUUID(),
1751
+ baseUrl: "https://example.com",
1752
+ activity: {
1753
+ ...signed,
1754
+ "@context": ["app-context", "https://www.w3.org/ns/activitystreams"]
1755
+ },
1756
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1757
+ attempt: 0,
1758
+ identifier: null,
1759
+ traceContext: {}
1760
+ };
1761
+ await federation.processQueuedTask(void 0, inboxMessage);
1762
+ assertEquals(errorCount, 1);
1763
+ assertEquals(queuedMessages, [{
1764
+ ...inboxMessage,
1765
+ attempt: 1
1766
+ }]);
1767
+ });
1768
+ await t.step("legacy raw LDS inbox messages with Invalid URL TypeErrors retry", async () => {
1769
+ const queue = {
1770
+ enqueue(message, _options) {
1771
+ queuedMessages.push(message);
1772
+ return Promise.resolve();
1773
+ },
1774
+ listen(_handler, _options) {
1775
+ return Promise.resolve();
1776
+ }
1777
+ };
1778
+ const kv = new MemoryKvStore();
1779
+ const queuedMessages = [];
1780
+ let errorCount = 0;
1781
+ const federation = new FederationImpl({
1782
+ kv,
1783
+ queue,
1784
+ contextLoader: async (resource) => {
1785
+ if (resource === "app:context") throw new TypeError(`Invalid URL: ${resource}`);
1786
+ const url = new URL(resource).href;
1787
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
1788
+ throw new Error(`Unexpected context: ${resource}`);
1789
+ }
1790
+ });
1791
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
1792
+ throw new Error("listener should not run");
1793
+ }).onError(() => {
1794
+ errorCount++;
1795
+ });
1796
+ const signed = await signJsonLd({
1797
+ "@context": "https://www.w3.org/ns/activitystreams",
1798
+ id: "https://remote.example/activities/legacy-typeerror-invalid-url",
1799
+ type: "Create",
1800
+ actor: "https://remote.example/users/alice",
1801
+ object: {
1802
+ id: "https://remote.example/notes/legacy-typeerror-invalid-url",
1803
+ type: "Note",
1804
+ attributedTo: "https://remote.example/users/alice",
1805
+ content: "Hello from invalid-url typeerror queue"
1806
+ }
1807
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
1808
+ const inboxMessage = {
1809
+ type: "inbox",
1810
+ id: crypto.randomUUID(),
1811
+ baseUrl: "https://example.com",
1812
+ activity: {
1813
+ ...signed,
1814
+ "@context": ["app:context", "https://www.w3.org/ns/activitystreams"]
1815
+ },
1816
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1817
+ attempt: 0,
1818
+ identifier: null,
1819
+ traceContext: {}
1820
+ };
1821
+ await federation.processQueuedTask(void 0, inboxMessage);
1822
+ assertEquals(errorCount, 1);
1823
+ assertEquals(queuedMessages.length, 1);
1824
+ const retried = queuedMessages[0];
1825
+ assertEquals(retried.attempt, 1);
1826
+ assertEquals(retried.activity, inboxMessage.activity);
1827
+ });
1828
+ await t.step("legacy raw LDS inbox messages with malformed absolute context refs do not retry", async () => {
1829
+ const queue = {
1830
+ enqueue(message, _options) {
1831
+ queuedMessages.push(message);
1832
+ return Promise.resolve();
1833
+ },
1834
+ listen(_handler, _options) {
1835
+ return Promise.resolve();
1836
+ }
1837
+ };
1838
+ const kv = new MemoryKvStore();
1839
+ const queuedMessages = [];
1840
+ let errorCount = 0;
1841
+ const federation = new FederationImpl({
1842
+ kv,
1843
+ queue,
1844
+ contextLoader: async (resource) => {
1845
+ if (resource === "http:/[") throw new TypeError(`Invalid URL: ${resource}`);
1846
+ const url = new URL(resource).href;
1847
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
1848
+ throw new Error(`Unexpected context: ${resource}`);
1849
+ }
1850
+ });
1851
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
1852
+ throw new Error("listener should not run");
1853
+ }).onError(() => {
1854
+ errorCount++;
1855
+ });
1856
+ const signed = await signJsonLd({
1857
+ "@context": "https://www.w3.org/ns/activitystreams",
1858
+ id: "https://remote.example/activities/legacy-malformed-absolute-context",
1859
+ type: "Create",
1860
+ actor: "https://remote.example/users/alice",
1861
+ object: {
1862
+ id: "https://remote.example/notes/legacy-malformed-absolute-context",
1863
+ type: "Note",
1864
+ attributedTo: "https://remote.example/users/alice",
1865
+ content: "Hello from malformed absolute context queue"
1866
+ }
1867
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
1868
+ const inboxMessage = {
1869
+ type: "inbox",
1870
+ id: crypto.randomUUID(),
1871
+ baseUrl: "https://example.com",
1872
+ activity: {
1873
+ ...signed,
1874
+ "@context": ["http:/[", "https://www.w3.org/ns/activitystreams"]
1875
+ },
1876
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1877
+ attempt: 0,
1878
+ identifier: null,
1879
+ traceContext: {}
1880
+ };
1881
+ await federation.processQueuedTask(void 0, inboxMessage);
1882
+ assertEquals(errorCount, 1);
1883
+ assertEquals(queuedMessages, []);
1884
+ });
1885
+ await t.step("malformed IRI fields are permanent queued inbox parse errors", async () => {
1886
+ const queuedMessages = [];
1887
+ const queue = {
1888
+ enqueue(message, _options) {
1889
+ queuedMessages.push(message);
1890
+ return Promise.resolve();
1891
+ },
1892
+ listen(_handler, _options) {
1893
+ return Promise.resolve();
1894
+ }
1895
+ };
1896
+ const kv = new MemoryKvStore();
1897
+ let errorCount = 0;
1898
+ const federation = new FederationImpl({
1899
+ kv,
1900
+ queue
1901
+ });
1902
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
1903
+ throw new Error("listener should not run");
1904
+ }).onError(() => {
1905
+ errorCount++;
1906
+ });
1907
+ await federation.processQueuedTask(void 0, {
1908
+ type: "inbox",
1909
+ id: crypto.randomUUID(),
1910
+ baseUrl: "https://example.com",
1911
+ activity: {
1912
+ "@context": "https://www.w3.org/ns/activitystreams",
1913
+ id: "http://[",
1914
+ type: "Create",
1915
+ actor: "https://remote.example/users/alice",
1916
+ object: {
1917
+ id: "https://remote.example/notes/invalid-iri",
1918
+ type: "Note",
1919
+ attributedTo: "https://remote.example/users/alice",
1920
+ content: "Hello from invalid IRI queue"
1921
+ }
1922
+ },
1923
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1924
+ attempt: 0,
1925
+ identifier: null,
1926
+ traceContext: {}
1927
+ });
1928
+ assertEquals(errorCount, 1);
1929
+ assertEquals(queuedMessages, []);
1930
+ });
1931
+ await t.step("legacy raw LDS inbox messages with network-path context ids retry", async () => {
1932
+ const queue = {
1933
+ enqueue(message, _options) {
1934
+ queuedMessages.push(message);
1935
+ return Promise.resolve();
1936
+ },
1937
+ listen(_handler, _options) {
1938
+ return Promise.resolve();
1939
+ }
1940
+ };
1941
+ const kv = new MemoryKvStore();
1942
+ const queuedMessages = [];
1943
+ let errorCount = 0;
1944
+ const federation = new FederationImpl({
1945
+ kv,
1946
+ queue,
1947
+ contextLoader: async (resource) => {
1948
+ if (resource === "//cdn.example/ctx") {
1949
+ const error = /* @__PURE__ */ new Error(`Network-path context backend is unavailable: ${resource}`);
1950
+ error.name = "jsonld.InvalidUrl";
1951
+ error.details = {
1952
+ code: "loading remote context failed",
1953
+ url: resource
1954
+ };
1955
+ throw error;
1956
+ }
1957
+ const url = new URL(resource).href;
1958
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
1959
+ throw new Error(`Unexpected context: ${resource}`);
1960
+ }
1961
+ });
1962
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
1963
+ throw new Error("listener should not run");
1964
+ }).onError(() => {
1965
+ errorCount++;
1966
+ });
1967
+ const signed = await signJsonLd({
1968
+ "@context": "https://www.w3.org/ns/activitystreams",
1969
+ id: "https://remote.example/activities/legacy-network-path-context",
1970
+ type: "Create",
1971
+ actor: "https://remote.example/users/alice",
1972
+ object: {
1973
+ id: "https://remote.example/notes/legacy-network-path-context",
1974
+ type: "Note",
1975
+ attributedTo: "https://remote.example/users/alice",
1976
+ content: "Hello from network-path legacy queue"
1977
+ }
1978
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
1979
+ const inboxMessage = {
1980
+ type: "inbox",
1981
+ id: crypto.randomUUID(),
1982
+ baseUrl: "https://example.com",
1983
+ activity: {
1984
+ ...signed,
1985
+ "@context": ["//cdn.example/ctx", "https://www.w3.org/ns/activitystreams"]
1986
+ },
1987
+ started: (/* @__PURE__ */ new Date()).toISOString(),
1988
+ attempt: 0,
1989
+ identifier: null,
1990
+ traceContext: {}
1991
+ };
1992
+ await federation.processQueuedTask(void 0, inboxMessage);
1993
+ assertEquals(errorCount, 1);
1994
+ assertEquals(queuedMessages, [{
1995
+ ...inboxMessage,
1996
+ attempt: 1
1997
+ }]);
1998
+ });
1999
+ await t.step("legacy raw LDS inbox messages with malformed network-path refs do not retry", async () => {
2000
+ const queue = {
2001
+ enqueue(message, _options) {
2002
+ queuedMessages.push(message);
2003
+ return Promise.resolve();
2004
+ },
2005
+ listen(_handler, _options) {
2006
+ return Promise.resolve();
2007
+ }
2008
+ };
2009
+ const kv = new MemoryKvStore();
2010
+ const queuedMessages = [];
2011
+ let errorCount = 0;
2012
+ const federation = new FederationImpl({
2013
+ kv,
2014
+ queue,
2015
+ contextLoader: async (resource) => {
2016
+ if (resource === "//[") {
2017
+ const error = /* @__PURE__ */ new Error(`Malformed network-path context: ${resource}`);
2018
+ error.name = "jsonld.InvalidUrl";
2019
+ error.details = {
2020
+ code: "loading remote context failed",
2021
+ url: resource
2022
+ };
2023
+ throw error;
2024
+ }
2025
+ const url = new URL(resource).href;
2026
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2027
+ throw new Error(`Unexpected context: ${resource}`);
2028
+ }
2029
+ });
2030
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2031
+ throw new Error("listener should not run");
2032
+ }).onError(() => {
2033
+ errorCount++;
2034
+ });
2035
+ const signed = await signJsonLd({
2036
+ "@context": "https://www.w3.org/ns/activitystreams",
2037
+ id: "https://remote.example/activities/legacy-malformed-network-path-context",
2038
+ type: "Create",
2039
+ actor: "https://remote.example/users/alice",
2040
+ object: {
2041
+ id: "https://remote.example/notes/legacy-malformed-network-path-context",
2042
+ type: "Note",
2043
+ attributedTo: "https://remote.example/users/alice",
2044
+ content: "Hello from malformed network-path legacy queue"
2045
+ }
2046
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
2047
+ const inboxMessage = {
2048
+ type: "inbox",
2049
+ id: crypto.randomUUID(),
2050
+ baseUrl: "https://example.com",
2051
+ activity: {
2052
+ ...signed,
2053
+ "@context": ["//[", "https://www.w3.org/ns/activitystreams"]
2054
+ },
2055
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2056
+ attempt: 0,
2057
+ identifier: null,
2058
+ traceContext: {}
2059
+ };
2060
+ await federation.processQueuedTask(void 0, inboxMessage);
2061
+ assertEquals(errorCount, 1);
2062
+ assertEquals(queuedMessages, []);
2063
+ });
2064
+ await t.step("legacy raw LDS inbox messages with malformed context URLs do not retry", async () => {
2065
+ const queue = {
2066
+ enqueue(message, _options) {
2067
+ queuedMessages.push(message);
2068
+ return Promise.resolve();
2069
+ },
2070
+ listen(_handler, _options) {
2071
+ return Promise.resolve();
2072
+ }
2073
+ };
2074
+ const kv = new MemoryKvStore();
2075
+ const queuedMessages = [];
2076
+ let errorCount = 0;
2077
+ const federation = new FederationImpl({
2078
+ kv,
2079
+ queue,
2080
+ contextLoader: async (resource) => {
2081
+ if (resource === "not a url") {
2082
+ const error = /* @__PURE__ */ new Error(`Invalid remote context URL: ${resource}`);
2083
+ error.name = "jsonld.InvalidUrl";
2084
+ error.details = {
2085
+ code: "loading remote context failed",
2086
+ url: resource
2087
+ };
2088
+ throw error;
2089
+ }
2090
+ const url = new URL(resource).href;
2091
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2092
+ throw new Error(`Unexpected context: ${resource}`);
2093
+ }
2094
+ });
2095
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2096
+ throw new Error("listener should not run");
2097
+ }).onError(() => {
2098
+ errorCount++;
2099
+ });
2100
+ const signed = await signJsonLd({
2101
+ "@context": "https://www.w3.org/ns/activitystreams",
2102
+ id: "https://remote.example/activities/legacy-malformed-context",
2103
+ type: "Create",
2104
+ actor: "https://remote.example/users/alice",
2105
+ object: {
2106
+ id: "https://remote.example/notes/legacy-malformed-context",
2107
+ type: "Note",
2108
+ attributedTo: "https://remote.example/users/alice",
2109
+ content: "Hello from malformed legacy queue"
2110
+ }
2111
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
2112
+ const inboxMessage = {
2113
+ type: "inbox",
2114
+ id: crypto.randomUUID(),
2115
+ baseUrl: "https://example.com",
2116
+ activity: {
2117
+ ...signed,
2118
+ "@context": ["not a url", "https://www.w3.org/ns/activitystreams"]
2119
+ },
2120
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2121
+ attempt: 0,
2122
+ identifier: null,
2123
+ traceContext: {}
2124
+ };
2125
+ await federation.processQueuedTask(void 0, inboxMessage);
2126
+ assertEquals(errorCount, 1);
2127
+ assertEquals(queuedMessages, []);
2128
+ });
2129
+ await t.step("legacy raw LDS inbox messages with invalid percent escapes do not retry", async () => {
2130
+ const queue = {
2131
+ enqueue(message, _options) {
2132
+ queuedMessages.push(message);
2133
+ return Promise.resolve();
2134
+ },
2135
+ listen(_handler, _options) {
2136
+ return Promise.resolve();
2137
+ }
2138
+ };
2139
+ const kv = new MemoryKvStore();
2140
+ const queuedMessages = [];
2141
+ let errorCount = 0;
2142
+ const federation = new FederationImpl({
2143
+ kv,
2144
+ queue,
2145
+ contextLoader: async (resource) => {
2146
+ if (resource === "foo%zz") {
2147
+ const error = /* @__PURE__ */ new Error(`Invalid remote context URL: ${resource}`);
2148
+ error.name = "jsonld.InvalidUrl";
2149
+ error.details = {
2150
+ code: "loading remote context failed",
2151
+ url: resource
2152
+ };
2153
+ throw error;
1046
2154
  }
2155
+ const url = new URL(resource).href;
2156
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2157
+ throw new Error(`Unexpected context: ${resource}`);
1047
2158
  }
1048
2159
  });
1049
2160
  federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
1050
- throw new Error("Intended error for testing");
2161
+ throw new Error("listener should not run");
2162
+ }).onError(() => {
2163
+ errorCount++;
1051
2164
  });
1052
- await assertRejects(() => federation.processQueuedTask(void 0, {
1053
- type: "outbox",
2165
+ const signed = await signJsonLd({
2166
+ "@context": "https://www.w3.org/ns/activitystreams",
2167
+ id: "https://remote.example/activities/legacy-malformed-percent-context",
2168
+ type: "Create",
2169
+ actor: "https://remote.example/users/alice",
2170
+ object: {
2171
+ id: "https://remote.example/notes/legacy-malformed-percent-context",
2172
+ type: "Note",
2173
+ attributedTo: "https://remote.example/users/alice",
2174
+ content: "Hello from malformed percent legacy queue"
2175
+ }
2176
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
2177
+ const inboxMessage = {
2178
+ type: "inbox",
1054
2179
  id: crypto.randomUUID(),
1055
2180
  baseUrl: "https://example.com",
1056
- keys: [],
1057
2181
  activity: {
1058
- "@context": "https://www.w3.org/ns/activitystreams",
1059
- type: "Create",
1060
- actor: "https://example.com/users/alice",
1061
- object: {
1062
- type: "Note",
1063
- content: "test"
1064
- }
2182
+ ...signed,
2183
+ "@context": ["foo%zz", "https://www.w3.org/ns/activitystreams"]
1065
2184
  },
1066
- activityType: "https://www.w3.org/ns/activitystreams#Create",
1067
- inbox: "https://invalid-domain-that-does-not-exist.example/inbox",
1068
- sharedInbox: false,
1069
2185
  started: (/* @__PURE__ */ new Date()).toISOString(),
1070
2186
  attempt: 0,
1071
- headers: {},
2187
+ identifier: null,
1072
2188
  traceContext: {}
1073
- }), Error);
2189
+ };
2190
+ await federation.processQueuedTask(void 0, inboxMessage);
2191
+ assertEquals(errorCount, 1);
1074
2192
  assertEquals(queuedMessages, []);
1075
- await assertRejects(() => federation.processQueuedTask(void 0, {
2193
+ });
2194
+ await t.step("legacy raw LDS inbox messages with invalid remote contexts do not retry", async () => {
2195
+ const remoteContextUrl = "https://remote.example/contexts/ext";
2196
+ const queue = {
2197
+ enqueue(message, _options) {
2198
+ queuedMessages.push(message);
2199
+ return Promise.resolve();
2200
+ },
2201
+ listen(_handler, _options) {
2202
+ return Promise.resolve();
2203
+ }
2204
+ };
2205
+ const kv = new MemoryKvStore();
2206
+ const queuedMessages = [];
2207
+ let errorCount = 0;
2208
+ const federation = new FederationImpl({
2209
+ kv,
2210
+ queue,
2211
+ contextLoader: async (resource) => {
2212
+ const url = new URL(resource).href;
2213
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2214
+ if (url === remoteContextUrl) return {
2215
+ contextUrl: null,
2216
+ documentUrl: url,
2217
+ document: [
2218
+ "not",
2219
+ "an",
2220
+ "object"
2221
+ ]
2222
+ };
2223
+ throw new Error(`Unexpected context: ${url}`);
2224
+ }
2225
+ });
2226
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2227
+ throw new Error("listener should not run");
2228
+ }).onError(() => {
2229
+ errorCount++;
2230
+ });
2231
+ const signed = await signJsonLd({
2232
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
2233
+ id: "https://remote.example/activities/legacy-invalid-remote-context",
2234
+ type: "Create",
2235
+ actor: "https://remote.example/users/alice",
2236
+ ext: "preserve-me",
2237
+ object: {
2238
+ id: "https://remote.example/notes/legacy-invalid-remote-context",
2239
+ type: "Note",
2240
+ attributedTo: "https://remote.example/users/alice",
2241
+ content: "Hello from invalid remote context queue"
2242
+ }
2243
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
2244
+ const url = new URL(resource).href;
2245
+ if (url === remoteContextUrl) return {
2246
+ contextUrl: null,
2247
+ documentUrl: url,
2248
+ document: { "@context": { ext: "https://example.com/ext" } }
2249
+ };
2250
+ return await mockDocumentLoader(url);
2251
+ } });
2252
+ const inboxMessage = {
1076
2253
  type: "inbox",
1077
2254
  id: crypto.randomUUID(),
1078
2255
  baseUrl: "https://example.com",
1079
- activity: {
1080
- "@context": "https://www.w3.org/ns/activitystreams",
1081
- type: "Create",
1082
- actor: "https://remote.example/users/alice",
1083
- object: {
1084
- type: "Note",
1085
- content: "Hello world"
1086
- }
1087
- },
2256
+ activity: signed,
1088
2257
  started: (/* @__PURE__ */ new Date()).toISOString(),
1089
2258
  attempt: 0,
1090
2259
  identifier: null,
1091
2260
  traceContext: {}
1092
- }), Error);
2261
+ };
2262
+ await federation.processQueuedTask(void 0, inboxMessage);
2263
+ assertEquals(errorCount, 1);
1093
2264
  assertEquals(queuedMessages, []);
1094
2265
  });
1095
- await t.step("with MessageQueue having no nativeRetrial", async () => {
2266
+ await t.step("legacy raw LDS inbox messages with string remote contexts retry", async () => {
2267
+ const remoteContextUrl = "https://remote.example/contexts/ext";
2268
+ const queue = {
2269
+ enqueue(message, _options) {
2270
+ queuedMessages.push(message);
2271
+ return Promise.resolve();
2272
+ },
2273
+ listen(_handler, _options) {
2274
+ return Promise.resolve();
2275
+ }
2276
+ };
1096
2277
  const kv = new MemoryKvStore();
1097
- let queuedMessages = [];
2278
+ const queuedMessages = [];
2279
+ let errorCount = 0;
1098
2280
  const federation = new FederationImpl({
1099
2281
  kv,
1100
- queue: {
1101
- enqueue(message, _options) {
1102
- queuedMessages.push(message);
1103
- return Promise.resolve();
1104
- },
1105
- listen(_handler, _options) {
1106
- return Promise.resolve();
1107
- }
2282
+ queue,
2283
+ contextLoader: async (resource) => {
2284
+ const url = new URL(resource).href;
2285
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2286
+ if (url === remoteContextUrl) return {
2287
+ contextUrl: null,
2288
+ documentUrl: url,
2289
+ document: "{not valid json"
2290
+ };
2291
+ throw new Error(`Unexpected context: ${url}`);
1108
2292
  }
1109
2293
  });
1110
2294
  federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
1111
- throw new Error("Intended error for testing");
2295
+ throw new Error("listener should not run");
2296
+ }).onError(() => {
2297
+ errorCount++;
1112
2298
  });
1113
- const outboxMessage = {
1114
- type: "outbox",
2299
+ const signed = await signJsonLd({
2300
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
2301
+ id: "https://remote.example/activities/legacy-string-remote-context",
2302
+ type: "Create",
2303
+ actor: "https://remote.example/users/alice",
2304
+ ext: "preserve-me",
2305
+ object: {
2306
+ id: "https://remote.example/notes/legacy-string-remote-context",
2307
+ type: "Note",
2308
+ attributedTo: "https://remote.example/users/alice",
2309
+ content: "Hello from string remote context queue"
2310
+ }
2311
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
2312
+ const url = new URL(resource).href;
2313
+ if (url === remoteContextUrl) return {
2314
+ contextUrl: null,
2315
+ documentUrl: url,
2316
+ document: { "@context": { ext: "https://example.com/ext" } }
2317
+ };
2318
+ return await mockDocumentLoader(url);
2319
+ } });
2320
+ const inboxMessage = {
2321
+ type: "inbox",
1115
2322
  id: crypto.randomUUID(),
1116
2323
  baseUrl: "https://example.com",
1117
- keys: [],
1118
- activity: {
1119
- "@context": "https://www.w3.org/ns/activitystreams",
1120
- type: "Create",
1121
- actor: "https://example.com/users/alice",
1122
- object: {
1123
- type: "Note",
1124
- content: "test"
1125
- }
2324
+ activity: signed,
2325
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2326
+ attempt: 0,
2327
+ identifier: null,
2328
+ traceContext: {}
2329
+ };
2330
+ await federation.processQueuedTask(void 0, inboxMessage);
2331
+ assertEquals(errorCount, 1);
2332
+ assertEquals(queuedMessages, [{
2333
+ ...inboxMessage,
2334
+ attempt: 1
2335
+ }]);
2336
+ });
2337
+ await t.step("legacy raw LDS inbox messages with loader TypeErrors retry", async () => {
2338
+ const remoteContextUrl = "https://remote.example/contexts/ext";
2339
+ const queue = {
2340
+ enqueue(message, _options) {
2341
+ queuedMessages.push(message);
2342
+ return Promise.resolve();
1126
2343
  },
1127
- activityType: "https://www.w3.org/ns/activitystreams#Create",
1128
- inbox: "https://invalid-domain-that-does-not-exist.example/inbox",
1129
- sharedInbox: false,
2344
+ listen(_handler, _options) {
2345
+ return Promise.resolve();
2346
+ }
2347
+ };
2348
+ const kv = new MemoryKvStore();
2349
+ const queuedMessages = [];
2350
+ let errorCount = 0;
2351
+ const federation = new FederationImpl({
2352
+ kv,
2353
+ queue,
2354
+ contextLoader: async (resource) => {
2355
+ const url = new URL(resource).href;
2356
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2357
+ if (url === remoteContextUrl) throw new TypeError(`Cannot initialize remote context loader: ${url}`);
2358
+ throw new Error(`Unexpected context: ${url}`);
2359
+ }
2360
+ });
2361
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2362
+ throw new Error("listener should not run");
2363
+ }).onError(() => {
2364
+ errorCount++;
2365
+ });
2366
+ const signed = await signJsonLd({
2367
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
2368
+ id: "https://remote.example/activities/legacy-typeerror-context",
2369
+ type: "Create",
2370
+ actor: "https://remote.example/users/alice",
2371
+ ext: "preserve-me",
2372
+ object: {
2373
+ id: "https://remote.example/notes/legacy-typeerror-context",
2374
+ type: "Note",
2375
+ attributedTo: "https://remote.example/users/alice",
2376
+ content: "Hello from typeerror legacy queue"
2377
+ }
2378
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
2379
+ const url = new URL(resource).href;
2380
+ if (url === remoteContextUrl) return {
2381
+ contextUrl: null,
2382
+ documentUrl: url,
2383
+ document: { "@context": { ext: "https://example.com/ext" } }
2384
+ };
2385
+ return await mockDocumentLoader(url);
2386
+ } });
2387
+ const inboxMessage = {
2388
+ type: "inbox",
2389
+ id: crypto.randomUUID(),
2390
+ baseUrl: "https://example.com",
2391
+ activity: signed,
1130
2392
  started: (/* @__PURE__ */ new Date()).toISOString(),
1131
2393
  attempt: 0,
1132
- headers: {},
2394
+ identifier: null,
1133
2395
  traceContext: {}
1134
2396
  };
1135
- await federation.processQueuedTask(void 0, outboxMessage);
2397
+ await federation.processQueuedTask(void 0, inboxMessage);
2398
+ assertEquals(errorCount, 1);
1136
2399
  assertEquals(queuedMessages, [{
1137
- ...outboxMessage,
2400
+ ...inboxMessage,
1138
2401
  attempt: 1
1139
2402
  }]);
1140
- queuedMessages = [];
2403
+ });
2404
+ await t.step("legacy raw LDS inbox messages with syntax errors in remote contexts retry", async () => {
2405
+ const remoteContextUrl = "https://remote.example/contexts/ext";
2406
+ const queue = {
2407
+ enqueue(message, _options) {
2408
+ queuedMessages.push(message);
2409
+ return Promise.resolve();
2410
+ },
2411
+ listen(_handler, _options) {
2412
+ return Promise.resolve();
2413
+ }
2414
+ };
2415
+ const kv = new MemoryKvStore();
2416
+ const queuedMessages = [];
2417
+ let errorCount = 0;
2418
+ const federation = new FederationImpl({
2419
+ kv,
2420
+ queue,
2421
+ contextLoader: async (resource) => {
2422
+ const url = new URL(resource).href;
2423
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2424
+ if (url === remoteContextUrl) {
2425
+ const error = /* @__PURE__ */ new Error(`Transient syntax failure: ${url}`);
2426
+ error.name = "jsonld.SyntaxError";
2427
+ error.details = { code: "loading remote context failed" };
2428
+ throw error;
2429
+ }
2430
+ throw new Error(`Unexpected context: ${url}`);
2431
+ }
2432
+ });
2433
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2434
+ throw new Error("listener should not run");
2435
+ }).onError(() => {
2436
+ errorCount++;
2437
+ });
2438
+ const signed = await signJsonLd({
2439
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
2440
+ id: "https://remote.example/activities/legacy-syntax-context",
2441
+ type: "Create",
2442
+ actor: "https://remote.example/users/alice",
2443
+ ext: "preserve-me",
2444
+ object: {
2445
+ id: "https://remote.example/notes/legacy-syntax-context",
2446
+ type: "Note",
2447
+ attributedTo: "https://remote.example/users/alice",
2448
+ content: "Hello from syntax legacy queue"
2449
+ }
2450
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
2451
+ const url = new URL(resource).href;
2452
+ if (url === remoteContextUrl) return {
2453
+ contextUrl: null,
2454
+ documentUrl: url,
2455
+ document: { "@context": { ext: "https://example.com/ext" } }
2456
+ };
2457
+ return await mockDocumentLoader(url);
2458
+ } });
2459
+ const inboxMessage = {
2460
+ type: "inbox",
2461
+ id: crypto.randomUUID(),
2462
+ baseUrl: "https://example.com",
2463
+ activity: signed,
2464
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2465
+ attempt: 0,
2466
+ identifier: null,
2467
+ traceContext: {}
2468
+ };
2469
+ await federation.processQueuedTask(void 0, inboxMessage);
2470
+ assertEquals(errorCount, 1);
2471
+ assertEquals(queuedMessages, [{
2472
+ ...inboxMessage,
2473
+ attempt: 1
2474
+ }]);
2475
+ });
2476
+ await t.step("legacy raw LDS inbox messages with loader RangeErrors retry", async () => {
2477
+ const remoteContextUrl = "https://remote.example/contexts/ext";
2478
+ const queue = {
2479
+ enqueue(message, _options) {
2480
+ queuedMessages.push(message);
2481
+ return Promise.resolve();
2482
+ },
2483
+ listen(_handler, _options) {
2484
+ return Promise.resolve();
2485
+ }
2486
+ };
2487
+ const kv = new MemoryKvStore();
2488
+ const queuedMessages = [];
2489
+ let errorCount = 0;
2490
+ const federation = new FederationImpl({
2491
+ kv,
2492
+ queue,
2493
+ contextLoader: async (resource) => {
2494
+ const url = new URL(resource).href;
2495
+ if (url === "https://www.w3.org/ns/activitystreams" || url === "https://w3id.org/identity/v1") return await mockDocumentLoader(url);
2496
+ if (url === remoteContextUrl) throw new RangeError(`Temporary remote context cache window exceeded: ${url}`);
2497
+ throw new Error(`Unexpected context: ${url}`);
2498
+ }
2499
+ });
2500
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2501
+ throw new Error("listener should not run");
2502
+ }).onError(() => {
2503
+ errorCount++;
2504
+ });
2505
+ const signed = await signJsonLd({
2506
+ "@context": [remoteContextUrl, "https://www.w3.org/ns/activitystreams"],
2507
+ id: "https://remote.example/activities/legacy-rangeerror-context",
2508
+ type: "Create",
2509
+ actor: "https://remote.example/users/alice",
2510
+ ext: "preserve-me",
2511
+ object: {
2512
+ id: "https://remote.example/notes/legacy-rangeerror-context",
2513
+ type: "Note",
2514
+ attributedTo: "https://remote.example/users/alice",
2515
+ content: "Hello from rangeerror legacy queue"
2516
+ }
2517
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: async (resource) => {
2518
+ const url = new URL(resource).href;
2519
+ if (url === remoteContextUrl) return {
2520
+ contextUrl: null,
2521
+ documentUrl: url,
2522
+ document: { "@context": { ext: "https://example.com/ext" } }
2523
+ };
2524
+ return await mockDocumentLoader(url);
2525
+ } });
1141
2526
  const inboxMessage = {
2527
+ type: "inbox",
2528
+ id: crypto.randomUUID(),
2529
+ baseUrl: "https://example.com",
2530
+ activity: signed,
2531
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2532
+ attempt: 0,
2533
+ identifier: null,
2534
+ traceContext: {}
2535
+ };
2536
+ await federation.processQueuedTask(void 0, inboxMessage);
2537
+ assertEquals(errorCount, 1);
2538
+ assertEquals(queuedMessages, [{
2539
+ ...inboxMessage,
2540
+ attempt: 1
2541
+ }]);
2542
+ });
2543
+ await t.step("permanent queued inbox parse errors do not re-enqueue poison messages", async () => {
2544
+ const queuedMessages = [];
2545
+ const queue = {
2546
+ enqueue(message, _options) {
2547
+ queuedMessages.push(message);
2548
+ return Promise.resolve();
2549
+ },
2550
+ listen(_handler, _options) {
2551
+ return Promise.resolve();
2552
+ }
2553
+ };
2554
+ const kv = new MemoryKvStore();
2555
+ let errorCount = 0;
2556
+ const federation = new FederationImpl({
2557
+ kv,
2558
+ queue
2559
+ });
2560
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2561
+ throw new Error("listener should not run");
2562
+ }).onError(() => {
2563
+ errorCount++;
2564
+ });
2565
+ await federation.processQueuedTask(void 0, {
1142
2566
  type: "inbox",
1143
2567
  id: crypto.randomUUID(),
1144
2568
  baseUrl: "https://example.com",
1145
2569
  activity: {
1146
2570
  "@context": "https://www.w3.org/ns/activitystreams",
2571
+ id: "https://remote.example/objects/not-an-activity",
2572
+ type: "Note",
2573
+ attributedTo: "https://remote.example/users/alice",
2574
+ content: "Not an activity"
2575
+ },
2576
+ started: (/* @__PURE__ */ new Date()).toISOString(),
2577
+ attempt: 0,
2578
+ identifier: null,
2579
+ traceContext: {}
2580
+ });
2581
+ assertEquals(errorCount, 1);
2582
+ assertEquals(queuedMessages, []);
2583
+ });
2584
+ await t.step("malformed Temporal fields are permanent queued inbox parse errors", async () => {
2585
+ const queuedMessages = [];
2586
+ const queue = {
2587
+ enqueue(message, _options) {
2588
+ queuedMessages.push(message);
2589
+ return Promise.resolve();
2590
+ },
2591
+ listen(_handler, _options) {
2592
+ return Promise.resolve();
2593
+ }
2594
+ };
2595
+ const kv = new MemoryKvStore();
2596
+ let errorCount = 0;
2597
+ const federation = new FederationImpl({
2598
+ kv,
2599
+ queue,
2600
+ documentLoader: mockDocumentLoader,
2601
+ contextLoader: mockDocumentLoader
2602
+ });
2603
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
2604
+ throw new Error("listener should not run");
2605
+ }).onError(() => {
2606
+ errorCount++;
2607
+ });
2608
+ await federation.processQueuedTask(void 0, {
2609
+ type: "inbox",
2610
+ id: crypto.randomUUID(),
2611
+ baseUrl: "https://example.com",
2612
+ activity: {
2613
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
2614
+ id: "https://remote.example/activities/invalid-proof-created",
1147
2615
  type: "Create",
1148
2616
  actor: "https://remote.example/users/alice",
1149
2617
  object: {
2618
+ id: "https://remote.example/notes/invalid-proof-created",
1150
2619
  type: "Note",
1151
- content: "Hello world"
2620
+ attributedTo: "https://remote.example/users/alice",
2621
+ content: "Hello, world!"
2622
+ },
2623
+ proof: {
2624
+ type: "DataIntegrityProof",
2625
+ cryptosuite: "eddsa-jcs-2022",
2626
+ verificationMethod: "https://remote.example/users/alice#ed25519-key",
2627
+ proofPurpose: "assertionMethod",
2628
+ created: { "@value": "not-a-date" },
2629
+ proofValue: "zLaewdp4H9kqtwyrLatK4cjY5oRHwVcw4gibPSUDYDMhi4M49v8pcYk3ZB6D69dNpAPbUmY8ocuJ3m9KhKJEEg7z"
1152
2630
  }
1153
2631
  },
1154
2632
  started: (/* @__PURE__ */ new Date()).toISOString(),
1155
2633
  attempt: 0,
1156
2634
  identifier: null,
1157
2635
  traceContext: {}
1158
- };
1159
- await federation.processQueuedTask(void 0, inboxMessage);
1160
- assertEquals(queuedMessages, [{
1161
- ...inboxMessage,
1162
- attempt: 1
1163
- }]);
2636
+ });
2637
+ assertEquals(errorCount, 1);
2638
+ assertEquals(queuedMessages, []);
1164
2639
  });
1165
2640
  });
1166
2641
  test("ContextImpl.lookupObject()", async (t) => {
@@ -1639,6 +3114,35 @@ test({
1639
3114
  ]);
1640
3115
  }
1641
3116
  });
3117
+ test("ContextImpl.routeActivity() marks queued signed activities as non-LDS", async () => {
3118
+ let queuedMessage = null;
3119
+ const federation = new FederationImpl({
3120
+ kv: new MemoryKvStore(),
3121
+ queue: {
3122
+ enqueue(message) {
3123
+ queuedMessage = message;
3124
+ return Promise.resolve();
3125
+ },
3126
+ async listen() {}
3127
+ }
3128
+ });
3129
+ federation.setInboxListeners("/u/{identifier}/i", "/i").on(Offer, () => {
3130
+ throw new Error("listener should not run for queued routeActivity");
3131
+ });
3132
+ const ctx = new ContextImpl({
3133
+ url: new URL("https://example.com/"),
3134
+ federation,
3135
+ data: void 0,
3136
+ documentLoader: mockDocumentLoader,
3137
+ contextLoader: fetchDocumentLoader
3138
+ });
3139
+ const signedOffer = await signObject(new Offer({ actor: new URL("https://example.com/person2") }), ed25519PrivateKey, ed25519Multikey.id);
3140
+ assert(await ctx.routeActivity(null, signedOffer));
3141
+ if (queuedMessage == null) throw new Error("Inbox message not queued.");
3142
+ const inboxMessage = queuedMessage;
3143
+ assertEquals(inboxMessage.ldSignatureVerified, false);
3144
+ assertEquals(inboxMessage.normalizedActivity, void 0);
3145
+ });
1642
3146
  test("ContextImpl.getCollectionUri()", () => {
1643
3147
  const federation = new FederationImpl({ kv: new MemoryKvStore() });
1644
3148
  const base = "https://example.com";