@fedify/fedify 2.5.0-dev.2446 → 2.5.0-dev.2492

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 (141) hide show
  1. package/README.md +3 -0
  2. package/dist/{activity-listener-C-BSbKI7.mjs → activity-listener-CELFAodc.mjs} +8 -4
  3. package/dist/{builder-Da8b9O6v.mjs → builder-DphK9v7f.mjs} +5 -5
  4. package/dist/{client-D-XP_Ccb.d.ts → client-3InDZbba.d.cts} +0 -1
  5. package/dist/{client-D-XP_Ccb.d.cts → client-3InDZbba.d.ts} +0 -1
  6. package/dist/{client-CEnNz8O7.mjs → client-5SIFzW3q.mjs} +113 -64
  7. package/dist/compat/mod.d.cts +1 -1
  8. package/dist/compat/mod.d.ts +1 -1
  9. package/dist/compat/transformers.test.mjs +1 -1
  10. package/dist/{compound-proof-D1djO5fa.mjs → compound-proof-cYtvlTgF.mjs} +2 -2
  11. package/dist/{context-8Tu9kea0.mjs → context-COHd1Rsh.mjs} +2 -0
  12. package/dist/{context-BdB54V-A.d.ts → context-DQJE9YfH.d.ts} +332 -6
  13. package/dist/{context-CQ9H8vNW.d.cts → context-DSuL3YWx.d.cts} +332 -6
  14. package/dist/{deno-eNpfj4oR.mjs → deno-iOAA7iRN.mjs} +1 -1
  15. package/dist/docloader-DCZkiTeF.mjs +136 -0
  16. package/dist/{exporter-DThFfe7P.mjs → exporter-DVG68ozH.mjs} +36 -30
  17. package/dist/federation/builder.test.mjs +1 -1
  18. package/dist/federation/compound-fanout.test.mjs +6 -3
  19. package/dist/federation/handler-compound-proof.test.mjs +5 -5
  20. package/dist/federation/handler.test.mjs +22 -7
  21. package/dist/federation/hashlink-media.test.mjs +2 -2
  22. package/dist/federation/idempotency.test.mjs +2 -2
  23. package/dist/federation/inbox-actorless.test.d.mts +2 -0
  24. package/dist/federation/inbox-actorless.test.mjs +487 -0
  25. package/dist/federation/inbox-authentication.test.d.mts +2 -0
  26. package/dist/federation/inbox-authentication.test.mjs +116 -0
  27. package/dist/federation/inbox-report.test.mjs +5 -5
  28. package/dist/federation/inbox-view.test.d.mts +2 -0
  29. package/dist/federation/inbox-view.test.mjs +369 -0
  30. package/dist/federation/inbox.test.mjs +22 -12
  31. package/dist/federation/metrics.test.mjs +1 -1
  32. package/dist/federation/middleware.test.mjs +470 -35
  33. package/dist/federation/mod.cjs +2 -1
  34. package/dist/federation/mod.d.cts +4 -4
  35. package/dist/federation/mod.d.ts +4 -4
  36. package/dist/federation/mod.js +8 -2
  37. package/dist/federation/mq.test.mjs +36 -1
  38. package/dist/federation/outgoing-proof.test.mjs +9 -6
  39. package/dist/federation/portable-actor.test.mjs +3 -3
  40. package/dist/federation/portable-chain.test.mjs +3 -3
  41. package/dist/federation/portable-collection.test.mjs +4 -4
  42. package/dist/federation/portable-delivery.test.mjs +2 -2
  43. package/dist/federation/portable-gateway-keys.test.mjs +13 -7
  44. package/dist/federation/portable-inbox.test.mjs +4 -4
  45. package/dist/federation/portable-parse-uri.test.mjs +1 -1
  46. package/dist/federation/portable-route-activity.test.mjs +2 -2
  47. package/dist/federation/portable.test.mjs +3 -3
  48. package/dist/federation/send.test.mjs +502 -93
  49. package/dist/federation/tasks/enqueue.test.mjs +3 -3
  50. package/dist/federation/tasks/tasks.test.mjs +4 -4
  51. package/dist/federation/temporal.test.mjs +1 -1
  52. package/dist/federation/webfinger.test.mjs +2 -2
  53. package/dist/{federation-BVH7drJA.cjs → federation-D0AZw_aw.cjs} +1415 -236
  54. package/dist/{http-BUJ5SFh6.mjs → http-BoJCMnjY.mjs} +204 -106
  55. package/dist/{http-DlNxxW_5.js → http-ChXUU_3c.js} +319 -123
  56. package/dist/{http-BfW3wyQ4.cjs → http-kv1LZw-r.cjs} +339 -125
  57. package/dist/inbox-BjihTKLo.mjs +312 -0
  58. package/dist/inbox-authentication-DflPYlLL.mjs +171 -0
  59. package/dist/inbox-view-D-skypg-.mjs +385 -0
  60. package/dist/{key-nYGY4QB9.d.cts → key-DbLNT-vA.d.ts} +1 -2
  61. package/dist/{key-DhsBxxZN.d.ts → key-DlHT4R6k.d.cts} +1 -2
  62. package/dist/{kv-cache-D_m3A241.mjs → kv-cache-Cmsyke4d.mjs} +1 -1
  63. package/dist/{kv-cache-7015lbrx.js → kv-cache-DQcte7ES.js} +73 -28
  64. package/dist/{kv-cache-kkBB0vbI.cjs → kv-cache-Ddh5sMxH.cjs} +72 -27
  65. package/dist/ld-CWVpo_NM.d.cts +250 -0
  66. package/dist/{ld-CD2CsPxq.mjs → ld-CdvcOEXQ.mjs} +52 -9
  67. package/dist/ld-Inagww4R.d.ts +250 -0
  68. package/dist/{metrics-D3LHk6Z4.mjs → metrics-CpxvUrBz.mjs} +1 -1
  69. package/dist/{middleware-N9eM16XE.js → middleware-BHb5toqA.js} +1413 -247
  70. package/dist/{middleware-CCDXvXOG.mjs → middleware-BKs2DLUF.mjs} +1 -1
  71. package/dist/{middleware-CzoWvrdZ.mjs → middleware-CoBLrTKX.mjs} +533 -132
  72. package/dist/{mod-B0dGgQsg.d.cts → mod-6TQ4UQZb.d.cts} +1 -1
  73. package/dist/mod-B4kBw1-8.d.ts +154 -0
  74. package/dist/{mod-DkhfZC1u.d.ts → mod-B7j30IQf.d.ts} +2 -9
  75. package/dist/mod-C1nT74IG.d.cts +154 -0
  76. package/dist/{mod-D0bxB8jS.d.ts → mod-CIeBgsOC.d.ts} +1 -1
  77. package/dist/{mod-B5-wdzmJ.d.cts → mod-YvA4Rk8J.d.cts} +2 -9
  78. package/dist/mod.cjs +6 -5
  79. package/dist/mod.d.cts +9 -9
  80. package/dist/mod.d.ts +9 -9
  81. package/dist/mod.js +6 -6
  82. package/dist/{mq-Dm1Wmtm-.mjs → mq-eF96QxUT.mjs} +6 -0
  83. package/dist/nodeinfo/client.test.mjs +337 -19
  84. package/dist/nodeinfo/handler.test.mjs +2 -2
  85. package/dist/nodeinfo/mod.cjs +1 -1
  86. package/dist/nodeinfo/mod.d.cts +1 -1
  87. package/dist/nodeinfo/mod.d.ts +1 -1
  88. package/dist/nodeinfo/mod.js +1 -1
  89. package/dist/otel/exporter.test.mjs +257 -1
  90. package/dist/otel/mod.cjs +36 -30
  91. package/dist/otel/mod.d.cts +13 -0
  92. package/dist/otel/mod.d.ts +13 -0
  93. package/dist/otel/mod.js +36 -30
  94. package/dist/{owner-C5-FVX6t.mjs → owner-DTnVpUKA.mjs} +3 -3
  95. package/dist/{portable-collection-Cony3_LC.mjs → portable-collection-CJeYTL61.mjs} +2 -2
  96. package/dist/{portable-collection-C-Z1SKDy.cjs → portable-collection-DciwBIia.cjs} +1 -1
  97. package/dist/{portable-collection-CC2zNAje.js → portable-collection-TmtpzD4E.js} +1 -1
  98. package/dist/{portable-key-DNc_flH-.mjs → portable-key-DcYok4ki.mjs} +1 -1
  99. package/dist/{proof-Cryb1c70.mjs → proof-GH7Poos_.mjs} +98 -10
  100. package/dist/{send-AF25pWaJ.mjs → send-BgGJh6pv.mjs} +174 -76
  101. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  102. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  103. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  104. package/dist/sig/compound-proof.test.mjs +2 -2
  105. package/dist/sig/http.test.mjs +553 -2
  106. package/dist/sig/key.test.mjs +1 -1
  107. package/dist/sig/ld.test.mjs +1 -1
  108. package/dist/sig/mitra-receive-interop.test.mjs +4 -4
  109. package/dist/sig/mitra-send-interop.test.mjs +3 -3
  110. package/dist/sig/mod.cjs +2 -2
  111. package/dist/sig/mod.d.cts +3 -3
  112. package/dist/sig/mod.d.ts +3 -3
  113. package/dist/sig/mod.js +2 -2
  114. package/dist/sig/owner.test.mjs +1 -1
  115. package/dist/sig/portable-collection.test.mjs +1 -1
  116. package/dist/sig/portable-interop.test.mjs +3 -3
  117. package/dist/sig/portable-key.test.mjs +5 -5
  118. package/dist/sig/proof.test.mjs +45 -5
  119. package/dist/sig/tootik-request-interop.test.mjs +4 -4
  120. package/dist/sig/tootik-send-interop.test.mjs +3 -3
  121. package/dist/sig/verification.test.mjs +3 -3
  122. package/dist/{temporal-BsAeuUJj.mjs → temporal-gJINzcL4.mjs} +1 -1
  123. package/dist/testing/kv.test.mjs +1 -1
  124. package/dist/testing/mod.d.mts +288 -2
  125. package/dist/testing/mod.mjs +2 -2
  126. package/dist/{testing-t8xDeJZ5.mjs → testing-Dbr21vZk.mjs} +1 -1
  127. package/dist/{types-_S2_MeLF.js → types-DUjZb4i6.js} +112 -63
  128. package/dist/{types-poIao40f.cjs → types-hKCCjii0.cjs} +111 -62
  129. package/dist/utils/docloader.test.mjs +344 -36
  130. package/dist/utils/kv-cache.test.mjs +1 -1
  131. package/dist/utils/mod.cjs +1 -1
  132. package/dist/utils/mod.d.cts +1 -1
  133. package/dist/utils/mod.d.ts +1 -1
  134. package/dist/utils/mod.js +1 -1
  135. package/package.json +7 -7
  136. package/dist/docloader-B-0nqOB1.mjs +0 -91
  137. package/dist/inbox-CHvMaIFX.mjs +0 -188
  138. package/dist/mod-BRpER-B0.d.ts +0 -320
  139. package/dist/mod-xAfaLS4T.d.cts +0 -320
  140. package/dist/owner-BF0L9cMt.d.cts +0 -78
  141. package/dist/owner-BSzr5-I8.d.ts +0 -78
@@ -0,0 +1,116 @@
1
+ import "temporal-polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { a as isActorAuthenticated, i as getEmptyAuthenticationEvidence, n as deserializeAuthentication, o as serializeAuthentication, t as createAuthenticationEvidence } from "../inbox-authentication-DflPYlLL.mjs";
5
+ import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
6
+ import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
7
+ import "../std__assert-DvMDRPes.mjs";
8
+ import { t as assert } from "../assert-D1Eafmd_.mjs";
9
+ import { parseIri } from "@fedify/vocab-runtime";
10
+ import { test } from "@fedify/fixture";
11
+ //#region src/federation/inbox-authentication.test.ts
12
+ const DID = "did:key:z6MkhVPuyvgG1RkMv67azDqDCDERPXVrUg1i3qchXY5EACE3";
13
+ const OTHER_DID = "did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2";
14
+ function evidence(overrides) {
15
+ return createAuthenticationEvidence({
16
+ ...getEmptyAuthenticationEvidence(),
17
+ verified: true,
18
+ ...overrides
19
+ });
20
+ }
21
+ function actor(id, mechanism = "httpSignatures") {
22
+ return {
23
+ kind: "actor",
24
+ id: parseIri(id),
25
+ mechanism,
26
+ keyId: null
27
+ };
28
+ }
29
+ test("isActorAuthenticated() uses the effective principals", () => {
30
+ const alice = new URL("https://example.com/users/alice");
31
+ const relay = new URL("https://relay.example/actor");
32
+ const forwarded = evidence({
33
+ requestSigners: [actor(relay.href)],
34
+ documentSigners: [actor(alice.href, "linkedDataSignatures")]
35
+ });
36
+ assert(isActorAuthenticated(forwarded, alice));
37
+ assertFalse(isActorAuthenticated(forwarded, relay));
38
+ const delivered = evidence({ requestSigners: [actor(alice.href)] });
39
+ assert(isActorAuthenticated(delivered, alice));
40
+ assertFalse(isActorAuthenticated(delivered, alice, { scope: "document" }));
41
+ assert(isActorAuthenticated(evidence({ originAuthenticated: [actor(alice.href, "origin")] }), alice));
42
+ assertFalse(isActorAuthenticated(getEmptyAuthenticationEvidence(), alice));
43
+ assertFalse(isActorAuthenticated(delivered, new URL("https://example.com/users/alice/")));
44
+ });
45
+ test("isActorAuthenticated() compares portable actors canonically", () => {
46
+ const portable = parseIri(`ap://${DID}/actor`);
47
+ const compatible = new URL(`https://gateway.example/.well-known/apgateway/${DID}/actor`);
48
+ const otherGateway = new URL(`https://other.example/.well-known/apgateway/${DID}/actor`);
49
+ const byActor = evidence({ requestSigners: [actor(portable.href)] });
50
+ assert(isActorAuthenticated(byActor, compatible));
51
+ assert(isActorAuthenticated(byActor, otherGateway));
52
+ assertFalse(isActorAuthenticated(byActor, parseIri(`ap://${DID}/other`)));
53
+ const byAuthority = evidence({ documentSigners: [{
54
+ kind: "portableAuthority",
55
+ id: new URL(DID),
56
+ mechanism: "objectIntegrityProofs",
57
+ keyId: new URL(`${DID}#${DID.slice(8)}`)
58
+ }] });
59
+ assert(isActorAuthenticated(byAuthority, portable));
60
+ assert(isActorAuthenticated(byAuthority, compatible));
61
+ assertFalse(isActorAuthenticated(byAuthority, parseIri(`ap://${OTHER_DID}/actor`)));
62
+ assertFalse(isActorAuthenticated(byAuthority, new URL("https://example.com/users/a")));
63
+ assertFalse(isActorAuthenticated(byActor, new URL("https://gateway.example/.well-known/apgateway/did:bogus")));
64
+ });
65
+ test("serializeAuthentication() round-trips through JSON", () => {
66
+ const original = evidence({
67
+ requestSigners: [{
68
+ kind: "actor",
69
+ id: new URL("https://example.com/users/alice"),
70
+ mechanism: "httpSignatures",
71
+ keyId: new URL("https://example.com/users/alice#main-key")
72
+ }],
73
+ documentSigners: [{
74
+ kind: "portableAuthority",
75
+ id: new URL(DID),
76
+ mechanism: "objectIntegrityProofs",
77
+ keyId: null
78
+ }]
79
+ });
80
+ const expanded = [{ "@id": "https://example.com/activities/1" }];
81
+ assertEquals(deserializeAuthentication(JSON.parse(JSON.stringify(serializeAuthentication(original))), expanded, null), {
82
+ ...original,
83
+ expandedActivity: expanded
84
+ });
85
+ for (const malformed of [
86
+ null,
87
+ {},
88
+ {
89
+ ...serializeAuthentication(original),
90
+ verified: "yes"
91
+ },
92
+ {
93
+ ...serializeAuthentication(original),
94
+ documentSigners: null
95
+ },
96
+ {
97
+ ...serializeAuthentication(original),
98
+ requestSigners: [{
99
+ kind: "server",
100
+ id: "x",
101
+ mechanism: "origin"
102
+ }]
103
+ },
104
+ {
105
+ ...serializeAuthentication(original),
106
+ requestSigners: [{
107
+ kind: "actor",
108
+ id: "not a url",
109
+ mechanism: "httpSignatures",
110
+ keyId: null
111
+ }]
112
+ }
113
+ ]) assertEquals(deserializeAuthentication(malformed, [], null), null);
114
+ });
115
+ //#endregion
116
+ export {};
@@ -2,12 +2,12 @@ import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
5
- import { p as signJsonLd } from "../ld-CD2CsPxq.mjs";
6
- import { o as signObject } from "../proof-Cryb1c70.mjs";
7
- import { l as signRequest } from "../http-BUJ5SFh6.mjs";
5
+ import { m as signJsonLd } from "../ld-CdvcOEXQ.mjs";
6
+ import { s as signObject } from "../proof-GH7Poos_.mjs";
7
+ import { u as signRequest } from "../http-BoJCMnjY.mjs";
8
8
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-DIJNHW-Y.mjs";
9
- import { o as createFederation } from "../middleware-CzoWvrdZ.mjs";
10
- import { r as createFederationBuilder } from "../builder-Da8b9O6v.mjs";
9
+ import { o as createFederation } from "../middleware-CoBLrTKX.mjs";
10
+ import { r as createFederationBuilder } from "../builder-DphK9v7f.mjs";
11
11
  import { Create, Person } from "@fedify/vocab";
12
12
  import { exportDidKey, parseIri } from "@fedify/vocab-runtime";
13
13
  import { mockDocumentLoader, test } from "@fedify/fixture";
@@ -0,0 +1,2 @@
1
+ import { Temporal } from "temporal-polyfill";
2
+ import { URLPattern } from "urlpattern-polyfill";
@@ -0,0 +1,369 @@
1
+ import "temporal-polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
5
+ import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
6
+ import "../std__assert-DvMDRPes.mjs";
7
+ import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
8
+ import { t as assert } from "../assert-D1Eafmd_.mjs";
9
+ import { a as createSnapshotOnlyLoader, c as expandDocument, f as restoreVerifiedActivityView, i as createReleasableLoader, l as getActorPresence, n as buildVerifiedActivityView, p as serializeContextSnapshot, r as createContextSnapshot, s as deserializeContextSnapshot, t as ContextSnapshotMissError, u as getPropertyValues } from "../inbox-view-D-skypg-.mjs";
10
+ import { getDocumentLoader, preloadedContexts } from "@fedify/vocab-runtime";
11
+ import { test } from "@fedify/fixture";
12
+ //#region src/federation/inbox-view.test.ts
13
+ const AS = "https://www.w3.org/ns/activitystreams";
14
+ const CUSTOM = "https://remote.example/contexts/custom";
15
+ function createMutableLoader() {
16
+ const loads = [];
17
+ const preloaded = getDocumentLoader();
18
+ return {
19
+ loads,
20
+ loader: (url, options) => {
21
+ loads.push(url);
22
+ if (url === CUSTOM) {
23
+ const n = loads.filter((u) => u === CUSTOM).length;
24
+ return Promise.resolve({
25
+ contextUrl: null,
26
+ documentUrl: url,
27
+ document: { "@context": { tgt: {
28
+ "@id": n % 2 === 1 ? `${AS}#object` : `${AS}#target`,
29
+ "@type": "@id"
30
+ } } }
31
+ });
32
+ }
33
+ return preloaded(url, options);
34
+ }
35
+ };
36
+ }
37
+ test("createContextSnapshot() memoizes responses for a delivery", async () => {
38
+ const { loader, loads } = createMutableLoader();
39
+ const snapshot = createContextSnapshot(loader);
40
+ const standard = snapshot.loader("standard");
41
+ const first = await standard(CUSTOM);
42
+ const second = await standard(CUSTOM);
43
+ assertEquals(first, second);
44
+ assertEquals(loads.filter((url) => url === CUSTOM).length, 1);
45
+ first.document["@context"] = null;
46
+ assertEquals((await standard(CUSTOM)).document, second.document);
47
+ assertEquals(snapshot.entries().map((entry) => [entry.policy, entry.url]), [["standard", CUSTOM]]);
48
+ snapshot.release();
49
+ await standard(CUSTOM);
50
+ assertEquals(loads.filter((url) => url === CUSTOM).length, 2);
51
+ });
52
+ test("createContextSnapshot() memoizes failures for a delivery", async () => {
53
+ let calls = 0;
54
+ const snapshot = createContextSnapshot((url) => {
55
+ calls++;
56
+ return calls === 1 ? Promise.reject(/* @__PURE__ */ new TypeError("fetch failed")) : Promise.resolve({
57
+ contextUrl: null,
58
+ documentUrl: url,
59
+ document: {}
60
+ });
61
+ });
62
+ const loader = snapshot.loader("standard");
63
+ await assertRejects(() => loader(CUSTOM), TypeError);
64
+ await assertRejects(() => loader(CUSTOM), TypeError);
65
+ assertEquals(calls, 1);
66
+ assertEquals(snapshot.entries(), []);
67
+ });
68
+ test("createContextSnapshot() records the normalization overrides", async () => {
69
+ const snapshot = createContextSnapshot(() => Promise.reject(/* @__PURE__ */ new Error("The caller's loader must not be asked.")));
70
+ await snapshot.loader("normalization")("https://w3id.org/security/v1");
71
+ assertEquals(snapshot.entries().map((entry) => [entry.policy, entry.url]), [["normalization", "https://w3id.org/security/v1"]]);
72
+ const [entry] = await serializeContextSnapshot(snapshot.entries());
73
+ assert("bundled" in entry && entry.bundled);
74
+ });
75
+ test("serializeContextSnapshot() stores only exactly bundled responses by hash", async () => {
76
+ const bundled = {
77
+ contextUrl: null,
78
+ documentUrl: AS,
79
+ document: preloadedContexts[AS]
80
+ };
81
+ const serialized = await serializeContextSnapshot([
82
+ {
83
+ policy: "standard",
84
+ url: AS,
85
+ response: bundled
86
+ },
87
+ {
88
+ policy: "standard",
89
+ url: "https://www.w3.org/ns/activitystreams",
90
+ response: {
91
+ ...bundled,
92
+ documentUrl: "https://mirror.example/as"
93
+ }
94
+ },
95
+ {
96
+ policy: "standard",
97
+ url: AS,
98
+ response: {
99
+ ...bundled,
100
+ document: { "@context": {} }
101
+ }
102
+ }
103
+ ]);
104
+ assert("bundled" in serialized[0]);
105
+ assert("response" in serialized[1]);
106
+ assert("response" in serialized[2]);
107
+ const restored = await deserializeContextSnapshot(JSON.parse(JSON.stringify(serialized)));
108
+ assertEquals(restored[0].response.document, preloadedContexts[AS]);
109
+ assertEquals(restored[1].response.documentUrl, "https://mirror.example/as");
110
+ assertEquals(restored[2].response.document, { "@context": {} });
111
+ });
112
+ test("deserializeContextSnapshot() rejects tampered or unknown entries", async () => {
113
+ const [entry] = await serializeContextSnapshot([{
114
+ policy: "standard",
115
+ url: CUSTOM,
116
+ response: {
117
+ contextUrl: null,
118
+ documentUrl: CUSTOM,
119
+ document: { a: 1 }
120
+ }
121
+ }]);
122
+ await assertRejects(() => deserializeContextSnapshot([{
123
+ ...entry,
124
+ response: {
125
+ contextUrl: null,
126
+ documentUrl: CUSTOM,
127
+ document: { a: 2 }
128
+ }
129
+ }]), ContextSnapshotMissError);
130
+ await assertRejects(() => deserializeContextSnapshot([{
131
+ policy: "standard",
132
+ url: CUSTOM,
133
+ sha256: entry.sha256,
134
+ bundled: true
135
+ }]), ContextSnapshotMissError);
136
+ await assertRejects(() => deserializeContextSnapshot([{ url: CUSTOM }]), ContextSnapshotMissError);
137
+ await assertRejects(() => deserializeContextSnapshot("nope"), ContextSnapshotMissError);
138
+ });
139
+ test("createSnapshotOnlyLoader() serves only the snapshot", async () => {
140
+ const loader = createSnapshotOnlyLoader([{
141
+ policy: "standard",
142
+ url: CUSTOM,
143
+ response: {
144
+ contextUrl: null,
145
+ documentUrl: CUSTOM,
146
+ document: { a: 1 }
147
+ }
148
+ }], "standard");
149
+ assertEquals((await loader(CUSTOM)).document, { a: 1 });
150
+ await assertRejects(() => loader(AS), ContextSnapshotMissError);
151
+ const normalization = createSnapshotOnlyLoader([{
152
+ policy: "standard",
153
+ url: CUSTOM,
154
+ response: {
155
+ contextUrl: null,
156
+ documentUrl: CUSTOM,
157
+ document: { a: 1 }
158
+ }
159
+ }], "normalization");
160
+ await assertRejects(() => normalization(CUSTOM), ContextSnapshotMissError);
161
+ });
162
+ test("createReleasableLoader() is strict until released", async () => {
163
+ const strict = () => Promise.reject(/* @__PURE__ */ new Error("strict"));
164
+ const fallback = (url) => Promise.resolve({
165
+ contextUrl: null,
166
+ documentUrl: url,
167
+ document: {}
168
+ });
169
+ const releasable = createReleasableLoader(strict, fallback, "standard");
170
+ await assertRejects(() => releasable.loader(CUSTOM), Error, "strict");
171
+ releasable.release();
172
+ assertEquals((await releasable.loader(CUSTOM)).document, {});
173
+ });
174
+ test("getActorPresence() counts every declared actor value", async () => {
175
+ const expand = (actor) => expandDocument({
176
+ "@context": AS,
177
+ id: "https://example.com/activities/1",
178
+ type: "Create",
179
+ ...actor === void 0 ? {} : { actor }
180
+ }, getDocumentLoader());
181
+ assertEquals(getActorPresence(await expand()), "absent");
182
+ assertEquals(getActorPresence(await expand([])), "absent");
183
+ assertEquals(getActorPresence(await expand({ "@list": [] })), "absent");
184
+ for (const actor of [
185
+ "https://example.com/users/alice",
186
+ {
187
+ type: "Person",
188
+ name: "Anonymous"
189
+ },
190
+ { name: "Anonymous" },
191
+ { id: "_:b0" },
192
+ { "@list": [{ name: "Anonymous" }] }
193
+ ]) assertEquals(getActorPresence(await expand(actor)), "present");
194
+ assertEquals(getActorPresence([]), "present");
195
+ });
196
+ test("getPropertyValues() unwraps a single list like the decoders", () => {
197
+ const node = {
198
+ a: [{ "@list": [{ "@id": "x" }, { "@id": "y" }] }],
199
+ b: [{ "@id": "x" }, { "@list": [] }]
200
+ };
201
+ assertEquals(getPropertyValues(node, "a").length, 2);
202
+ assertEquals(getPropertyValues(node, "b").length, 2);
203
+ assertEquals(getPropertyValues(node, "c"), []);
204
+ });
205
+ test("restoreVerifiedActivityView() replays the authenticated interpretation", async () => {
206
+ const { loader, loads } = createMutableLoader();
207
+ const document = {
208
+ "@context": [AS, CUSTOM],
209
+ id: "https://example.com/activities/1",
210
+ type: "Create",
211
+ tgt: "https://example.com/notes/1"
212
+ };
213
+ const { view, expanded } = await buildVerifiedActivityView(createContextSnapshot(loader), {
214
+ policy: "standard",
215
+ source: "activity",
216
+ document,
217
+ baseUrl: null
218
+ });
219
+ const queued = JSON.parse(JSON.stringify(view));
220
+ const loadsBefore = loads.length;
221
+ const restored = await restoreVerifiedActivityView(queued, { activity: document });
222
+ assertEquals(loads.length, loadsBefore);
223
+ assertEquals(restored.expanded, expanded);
224
+ assertEquals(getPropertyValues(restored.expanded[0], `${AS}#object`).length, 1);
225
+ await assertRejects(() => restoreVerifiedActivityView({
226
+ ...queued,
227
+ expandedDigest: "0"
228
+ }, { activity: document }), ContextSnapshotMissError);
229
+ await assertRejects(() => restoreVerifiedActivityView(queued, { activity: {
230
+ ...document,
231
+ tgt: "https://example.com/notes/2"
232
+ } }), ContextSnapshotMissError);
233
+ await assertRejects(() => restoreVerifiedActivityView({
234
+ ...queued,
235
+ source: "normalizedActivity"
236
+ }, { activity: document }), ContextSnapshotMissError);
237
+ });
238
+ test("restoreVerifiedActivityView() survives changes to unused bundled terms", async () => {
239
+ const document = {
240
+ "@context": [AS, "http://joinmastodon.org/ns"],
241
+ id: "https://example.com/likes/1",
242
+ type: "Like",
243
+ object: "https://example.com/notes/1"
244
+ };
245
+ const { view, expanded } = await buildVerifiedActivityView(createContextSnapshot(getDocumentLoader()), {
246
+ policy: "standard",
247
+ source: "activity",
248
+ document,
249
+ baseUrl: null
250
+ });
251
+ const mastodon = view.contexts.find((entry) => entry.url === "http://joinmastodon.org/ns");
252
+ assert(mastodon != null && "bundled" in mastodon);
253
+ assertEquals((await restoreVerifiedActivityView({
254
+ ...view,
255
+ contexts: view.contexts.map((entry) => entry === mastodon ? {
256
+ ...entry,
257
+ sha256: "0".repeat(64)
258
+ } : entry)
259
+ }, { activity: document })).expanded, expanded);
260
+ });
261
+ test("buildVerifiedActivityView() keeps only the contexts it uses", async () => {
262
+ const { loader } = createMutableLoader();
263
+ const snapshot = createContextSnapshot(loader);
264
+ await snapshot.loader("standard")("https://w3id.org/security/v1");
265
+ const { view } = await buildVerifiedActivityView(snapshot, {
266
+ policy: "standard",
267
+ source: "activity",
268
+ document: {
269
+ "@context": [AS, CUSTOM],
270
+ id: "https://example.com/activities/1",
271
+ type: "Create",
272
+ tgt: "https://example.com/notes/1"
273
+ },
274
+ baseUrl: null
275
+ });
276
+ assertEquals(view.contexts.map((entry) => entry.url).sort(), [CUSTOM, AS].sort());
277
+ });
278
+ test("createContextSnapshot() keeps contexts out of jsonld.js's shared cache", async () => {
279
+ let loads = 0;
280
+ const document = {
281
+ "@context": [AS, "https://remote.example/contexts/static"],
282
+ id: "https://example.com/activities/static",
283
+ type: "Create"
284
+ };
285
+ const tagging = (url, options) => {
286
+ if (url !== "https://remote.example/contexts/static") return getDocumentLoader()(url, options);
287
+ loads++;
288
+ return Promise.resolve({
289
+ contextUrl: null,
290
+ documentUrl: url,
291
+ document: { "@context": { x: "https://example.com/x" } },
292
+ tag: "static"
293
+ });
294
+ };
295
+ for (let i = 0; i < 2; i++) {
296
+ const { view } = await buildVerifiedActivityView(createContextSnapshot(tagging), {
297
+ policy: "standard",
298
+ source: "activity",
299
+ document,
300
+ baseUrl: null
301
+ });
302
+ assert(view.contexts.some((entry) => entry.url === "https://remote.example/contexts/static"));
303
+ }
304
+ assertEquals(loads, 2);
305
+ });
306
+ test("buildVerifiedActivityView() tells contexts it could not record", async () => {
307
+ const cached = "https://remote.example/contexts/shared-cache";
308
+ const preloaded = getDocumentLoader();
309
+ await expandDocument({
310
+ "@context": cached,
311
+ "y": "z"
312
+ }, (url, options) => url === cached ? Promise.resolve({
313
+ contextUrl: null,
314
+ documentUrl: url,
315
+ document: { "@context": { y: "https://example.com/y" } },
316
+ tag: "static"
317
+ }) : preloaded(url, options));
318
+ const build = (document) => buildVerifiedActivityView(createContextSnapshot(preloaded), {
319
+ policy: "standard",
320
+ source: "activity",
321
+ document,
322
+ baseUrl: null
323
+ });
324
+ const { replayable, view } = await build({
325
+ "@context": [AS, cached],
326
+ id: "https://example.com/activities/cached",
327
+ type: "Create"
328
+ });
329
+ assertEquals(replayable, false);
330
+ assertFalse(view.contexts.some((entry) => entry.url === cached));
331
+ assertEquals((await build({
332
+ "@context": AS,
333
+ id: "https://example.com/activities/plain",
334
+ type: "Create"
335
+ })).replayable, true);
336
+ });
337
+ test("buildVerifiedActivityView() tells contexts linked from a context", async () => {
338
+ const cached = "https://remote.example/contexts/linked-shared-cache";
339
+ const linking = "https://remote.example/contexts/linking";
340
+ const preloaded = getDocumentLoader();
341
+ await expandDocument({
342
+ "@context": cached,
343
+ "w": "z"
344
+ }, (url, options) => url === cached ? Promise.resolve({
345
+ contextUrl: null,
346
+ documentUrl: url,
347
+ document: { "@context": { w: "https://example.com/w" } },
348
+ tag: "static"
349
+ }) : preloaded(url, options));
350
+ const { replayable, view } = await buildVerifiedActivityView(createContextSnapshot((url, options) => url === linking ? Promise.resolve({
351
+ contextUrl: cached,
352
+ documentUrl: url,
353
+ document: { "@context": { v: "https://example.com/v" } }
354
+ }) : preloaded(url, options)), {
355
+ policy: "standard",
356
+ source: "activity",
357
+ document: {
358
+ "@context": [AS, linking],
359
+ id: "https://example.com/activities/linked",
360
+ type: "Create"
361
+ },
362
+ baseUrl: null
363
+ });
364
+ assert(view.contexts.some((entry) => entry.url === linking));
365
+ assertFalse(view.contexts.some((entry) => entry.url === cached));
366
+ assertEquals(replayable, false);
367
+ });
368
+ //#endregion
369
+ export {};
@@ -4,8 +4,8 @@ globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
5
5
  import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
6
6
  import { t as assertThrows } from "../assert_throws-BYITOvPO.mjs";
7
- import { t as ActivityListenerSet } from "../activity-listener-C-BSbKI7.mjs";
8
- import { t as actorsHaveSameOrigin } from "../inbox-CHvMaIFX.mjs";
7
+ import { t as ActivityListenerSet } from "../activity-listener-CELFAodc.mjs";
8
+ import { t as actorsHaveSameOrigin } from "../inbox-BjihTKLo.mjs";
9
9
  import { Activity, Create, Invite, Offer, Update } from "@fedify/vocab";
10
10
  import { parseIri } from "@fedify/vocab-runtime";
11
11
  import { test } from "@fedify/fixture";
@@ -67,12 +67,14 @@ test("ActivityListenerSet", () => {
67
67
  assertEquals(listeners.dispatch(activity), null);
68
68
  assertEquals(listeners.dispatchWithClass(offer), {
69
69
  class: Offer,
70
- listener: listenOffer
70
+ listener: listenOffer,
71
+ options: {}
71
72
  });
72
73
  assertEquals(listeners.dispatch(offer), listenOffer);
73
74
  assertEquals(listeners.dispatchWithClass(invite), {
74
75
  class: Offer,
75
- listener: listenOffer
76
+ listener: listenOffer,
77
+ options: {}
76
78
  });
77
79
  assertEquals(listeners.dispatch(invite), listenOffer);
78
80
  assertEquals(listeners.dispatch(create), null);
@@ -83,17 +85,20 @@ test("ActivityListenerSet", () => {
83
85
  assertEquals(listeners.dispatch(offer), listenOffer);
84
86
  assertEquals(listeners.dispatchWithClass(offer), {
85
87
  class: Offer,
86
- listener: listenOffer
88
+ listener: listenOffer,
89
+ options: {}
87
90
  });
88
91
  assertEquals(listeners.dispatch(invite), listenOffer);
89
92
  assertEquals(listeners.dispatchWithClass(invite), {
90
93
  class: Offer,
91
- listener: listenOffer
94
+ listener: listenOffer,
95
+ options: {}
92
96
  });
93
97
  assertEquals(listeners.dispatch(create), listenCreate);
94
98
  assertEquals(listeners.dispatchWithClass(create), {
95
99
  class: Create,
96
- listener: listenCreate
100
+ listener: listenCreate,
101
+ options: {}
97
102
  });
98
103
  assertEquals(listeners.dispatch(update), null);
99
104
  const listenActivity = () => {};
@@ -101,27 +106,32 @@ test("ActivityListenerSet", () => {
101
106
  assertEquals(listeners.dispatch(activity), listenActivity);
102
107
  assertEquals(listeners.dispatchWithClass(activity), {
103
108
  class: Activity,
104
- listener: listenActivity
109
+ listener: listenActivity,
110
+ options: {}
105
111
  });
106
112
  assertEquals(listeners.dispatch(offer), listenOffer);
107
113
  assertEquals(listeners.dispatchWithClass(offer), {
108
114
  class: Offer,
109
- listener: listenOffer
115
+ listener: listenOffer,
116
+ options: {}
110
117
  });
111
118
  assertEquals(listeners.dispatch(invite), listenOffer);
112
119
  assertEquals(listeners.dispatchWithClass(invite), {
113
120
  class: Offer,
114
- listener: listenOffer
121
+ listener: listenOffer,
122
+ options: {}
115
123
  });
116
124
  assertEquals(listeners.dispatch(create), listenCreate);
117
125
  assertEquals(listeners.dispatchWithClass(create), {
118
126
  class: Create,
119
- listener: listenCreate
127
+ listener: listenCreate,
128
+ options: {}
120
129
  });
121
130
  assertEquals(listeners.dispatch(update), listenActivity);
122
131
  assertEquals(listeners.dispatchWithClass(update), {
123
132
  class: Activity,
124
- listener: listenActivity
133
+ listener: listenActivity,
134
+ options: {}
125
135
  });
126
136
  assertThrows(() => listeners.add(Activity, listenActivity), TypeError, "Listener already set for this type.");
127
137
  });
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
5
5
  import "../std__assert-DvMDRPes.mjs";
6
6
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
7
- import { _ as recordKeyLookup, a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, m as recordDocumentFetch, p as recordDocumentCache, r as getFederationMetrics, t as classifyFetchError, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "../metrics-D3LHk6Z4.mjs";
7
+ import { _ as recordKeyLookup, a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, m as recordDocumentFetch, p as recordDocumentCache, r as getFederationMetrics, t as classifyFetchError, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "../metrics-CpxvUrBz.mjs";
8
8
  import { FetchError } from "@fedify/vocab-runtime";
9
9
  import { createTestMeterProvider, test } from "@fedify/fixture";
10
10
  import { DataPointType, MeterProvider, MetricReader } from "@opentelemetry/sdk-metrics";