@fedify/fedify 2.5.0-dev.2440 → 2.5.0-dev.2448

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 (117) hide show
  1. package/dist/{activity-listener-C-BSbKI7.mjs → activity-listener-CELFAodc.mjs} +8 -4
  2. package/dist/{builder-CTasaZgj.mjs → builder-DbC8bn6k.mjs} +5 -5
  3. package/dist/compat/mod.d.cts +1 -1
  4. package/dist/compat/mod.d.ts +1 -1
  5. package/dist/compat/transformers.test.mjs +1 -1
  6. package/dist/{compound-proof-D5r-dQMr.mjs → compound-proof-heeXW2_A.mjs} +2 -2
  7. package/dist/{context-DPocjp1l.d.cts → context-BazwQrHn.d.cts} +307 -5
  8. package/dist/{context-8Tu9kea0.mjs → context-COHd1Rsh.mjs} +2 -0
  9. package/dist/{context-2wLkQrjb.d.ts → context-GlHrzdSP.d.ts} +307 -5
  10. package/dist/{deno-DR1uYWpm.mjs → deno-Dpewt_jJ.mjs} +1 -1
  11. package/dist/{docloader-BpCJ2dHm.mjs → docloader-CpsDyqVN.mjs} +2 -2
  12. package/dist/federation/builder.test.mjs +1 -1
  13. package/dist/federation/compound-fanout.test.mjs +3 -3
  14. package/dist/federation/handler-compound-proof.test.mjs +5 -5
  15. package/dist/federation/handler.test.mjs +21 -6
  16. package/dist/federation/hashlink-media.test.mjs +2 -2
  17. package/dist/federation/idempotency.test.mjs +2 -2
  18. package/dist/federation/inbox-actorless.test.d.mts +2 -0
  19. package/dist/federation/inbox-actorless.test.mjs +487 -0
  20. package/dist/federation/inbox-authentication.test.d.mts +2 -0
  21. package/dist/federation/inbox-authentication.test.mjs +116 -0
  22. package/dist/federation/inbox-report.test.mjs +5 -5
  23. package/dist/federation/inbox-view.test.d.mts +2 -0
  24. package/dist/federation/inbox-view.test.mjs +369 -0
  25. package/dist/federation/inbox.test.mjs +22 -12
  26. package/dist/federation/metrics.test.mjs +1 -1
  27. package/dist/federation/middleware.test.mjs +24 -9
  28. package/dist/federation/mod.cjs +2 -1
  29. package/dist/federation/mod.d.cts +3 -3
  30. package/dist/federation/mod.d.ts +3 -3
  31. package/dist/federation/mod.js +8 -2
  32. package/dist/federation/mq.test.mjs +36 -1
  33. package/dist/federation/outgoing-proof.test.mjs +4 -4
  34. package/dist/federation/portable-actor.test.mjs +3 -3
  35. package/dist/federation/portable-chain.test.mjs +3 -3
  36. package/dist/federation/portable-collection.test.mjs +4 -4
  37. package/dist/federation/portable-delivery.test.mjs +2 -2
  38. package/dist/federation/portable-gateway-keys.test.mjs +6 -6
  39. package/dist/federation/portable-inbox.test.mjs +4 -4
  40. package/dist/federation/portable-parse-uri.test.mjs +1 -1
  41. package/dist/federation/portable-route-activity.test.mjs +2 -2
  42. package/dist/federation/portable.test.mjs +3 -3
  43. package/dist/federation/send.test.mjs +3 -3
  44. package/dist/federation/tasks/enqueue.test.mjs +3 -3
  45. package/dist/federation/tasks/tasks.test.mjs +4 -4
  46. package/dist/federation/temporal.test.mjs +1 -1
  47. package/dist/federation/webfinger.test.mjs +2 -2
  48. package/dist/{federation-eYRUxkUW.cjs → federation-D8YexkP1.cjs} +1174 -132
  49. package/dist/{http-CgaoFGXV.js → http-BBr-VLil.js} +143 -16
  50. package/dist/{http-CRrg14tX.mjs → http-BZkyQIkb.mjs} +4 -4
  51. package/dist/{http-CmFHJm6o.cjs → http-CAl7Num5.cjs} +161 -16
  52. package/dist/inbox-authentication-DflPYlLL.mjs +171 -0
  53. package/dist/inbox-view-NE9wXdLo.mjs +385 -0
  54. package/dist/inbox-wDuMKaFW.mjs +312 -0
  55. package/dist/{kv-cache-MxJrD9gD.mjs → kv-cache-CTQSfkmp.mjs} +1 -1
  56. package/dist/{kv-cache-DvVRGOMw.cjs → kv-cache-CjgCOz3i.cjs} +1 -1
  57. package/dist/{kv-cache-DeagaIIB.js → kv-cache-U7uz5nx4.js} +1 -1
  58. package/dist/{ld-CUM16-EE.mjs → ld-CQFuiah9.mjs} +52 -9
  59. package/dist/ld-CcuZ4NZa.d.ts +250 -0
  60. package/dist/ld-Cel1RRZ5.d.cts +250 -0
  61. package/dist/{metrics-DIM8ZCKq.mjs → metrics-B-QxRSSR.mjs} +1 -1
  62. package/dist/{middleware-Bx_QxJ0s.mjs → middleware-BdlNa8BD.mjs} +1 -1
  63. package/dist/{middleware-CDdFG6xf.mjs → middleware-CuVa0UuS.mjs} +461 -99
  64. package/dist/{middleware-CGfFooYv.js → middleware-P-AZEQMn.js} +1171 -142
  65. package/dist/mod-B2783_3v.d.cts +154 -0
  66. package/dist/mod-BHQE-7WQ.d.ts +154 -0
  67. package/dist/{mod-Dm-7RdhR.d.ts → mod-DjZfJj9R.d.ts} +1 -1
  68. package/dist/{mod-DKGoPoQC.d.cts → mod-DvWOv2Ho.d.cts} +1 -1
  69. package/dist/mod.cjs +5 -4
  70. package/dist/mod.d.cts +6 -6
  71. package/dist/mod.d.ts +6 -6
  72. package/dist/mod.js +5 -5
  73. package/dist/{mq-Dm1Wmtm-.mjs → mq-eF96QxUT.mjs} +6 -0
  74. package/dist/nodeinfo/handler.test.mjs +2 -2
  75. package/dist/{owner-DoQDS_g0.mjs → owner-ccKkQOAf.mjs} +3 -3
  76. package/dist/{portable-collection-DiBqtGSM.mjs → portable-collection-CndnpXfo.mjs} +2 -2
  77. package/dist/{portable-collection-BVKSWfBe.js → portable-collection-DUlQ3s2M.js} +1 -1
  78. package/dist/{portable-collection-C-etRRF0.cjs → portable-collection-TC-kvkVI.cjs} +1 -1
  79. package/dist/{portable-key-DSRuCUpF.mjs → portable-key-D5tFOoV-.mjs} +1 -1
  80. package/dist/{proof-DWpruPWs.mjs → proof-DPoF06nz.mjs} +98 -10
  81. package/dist/{send-H0z9Ey5l.mjs → send-CF44O1Df.mjs} +3 -3
  82. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  83. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  84. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  85. package/dist/sig/compound-proof.test.mjs +2 -2
  86. package/dist/sig/http.test.mjs +2 -2
  87. package/dist/sig/key.test.mjs +1 -1
  88. package/dist/sig/ld.test.mjs +1 -1
  89. package/dist/sig/mitra-receive-interop.test.mjs +4 -4
  90. package/dist/sig/mitra-send-interop.test.mjs +3 -3
  91. package/dist/sig/mod.cjs +2 -2
  92. package/dist/sig/mod.d.cts +2 -2
  93. package/dist/sig/mod.d.ts +2 -2
  94. package/dist/sig/mod.js +2 -2
  95. package/dist/sig/owner.test.mjs +1 -1
  96. package/dist/sig/portable-collection.test.mjs +1 -1
  97. package/dist/sig/portable-interop.test.mjs +3 -3
  98. package/dist/sig/portable-key.test.mjs +5 -5
  99. package/dist/sig/proof.test.mjs +44 -4
  100. package/dist/sig/tootik-request-interop.test.mjs +4 -4
  101. package/dist/sig/tootik-send-interop.test.mjs +3 -3
  102. package/dist/sig/verification.test.mjs +3 -3
  103. package/dist/{temporal-BiSBCImA.mjs → temporal-XlZ4Wb94.mjs} +1 -1
  104. package/dist/testing/kv.test.mjs +1 -1
  105. package/dist/testing/mod.d.mts +265 -3
  106. package/dist/testing/mod.mjs +2 -2
  107. package/dist/{testing-t8xDeJZ5.mjs → testing-Dbr21vZk.mjs} +1 -1
  108. package/dist/utils/docloader.test.mjs +2 -2
  109. package/dist/utils/kv-cache.test.mjs +1 -1
  110. package/dist/utils/mod.cjs +1 -1
  111. package/dist/utils/mod.js +1 -1
  112. package/package.json +7 -7
  113. package/dist/inbox-BwPoSUEv.mjs +0 -188
  114. package/dist/mod-BRpER-B0.d.ts +0 -320
  115. package/dist/mod-xAfaLS4T.d.cts +0 -320
  116. package/dist/owner-BF0L9cMt.d.cts +0 -78
  117. package/dist/owner-BSzr5-I8.d.ts +0 -78
@@ -0,0 +1,171 @@
1
+ import "temporal-polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { i as getPortableDid, l as isPortableId, n as getCanonicalPortableId } from "./portable-key-id-Ca30-hOa.mjs";
5
+ //#region src/federation/inbox-authentication.ts
6
+ /**
7
+ * The evidence of an activity that has not been authenticated by Fedify.
8
+ */
9
+ const EMPTY_EVIDENCE = Object.freeze({
10
+ verified: false,
11
+ requestSigners: Object.freeze([]),
12
+ documentSigners: Object.freeze([]),
13
+ originAuthenticated: Object.freeze([]),
14
+ expandedActivity: Object.freeze([]),
15
+ baseUrl: null
16
+ });
17
+ /**
18
+ * Gets the evidence of an activity that has not been authenticated.
19
+ * @internal
20
+ */
21
+ function getEmptyAuthenticationEvidence() {
22
+ return EMPTY_EVIDENCE;
23
+ }
24
+ /**
25
+ * Gets the principals that an authorization decision should rely on.
26
+ * @param evidence The evidence.
27
+ * @param scope See {@link IsActorAuthenticatedOptions.scope}.
28
+ * @returns The principals.
29
+ * @internal
30
+ */
31
+ function getEffectivePrincipals(evidence, scope = "effective") {
32
+ if (scope === "document" || evidence.documentSigners.length > 0) return evidence.documentSigners;
33
+ if (evidence.requestSigners.length > 0) return evidence.requestSigners;
34
+ return evidence.originAuthenticated;
35
+ }
36
+ /**
37
+ * Normalizes an actor ID for comparison: portable IDs and compatible
38
+ * identifiers to their canonical portable ID, other IDs to their `href`.
39
+ * @returns The normalized ID, or `null` for a malformed portable ID.
40
+ */
41
+ function normalizeActorId(id) {
42
+ return isPortableId(id) ? getCanonicalPortableId(id) : id.href;
43
+ }
44
+ /**
45
+ * Checks whether the given principal authenticates the given actor.
46
+ * @param principal The principal.
47
+ * @param actorId The actor ID.
48
+ * @returns `true` if the principal authenticates the actor.
49
+ * @internal
50
+ */
51
+ function principalAuthenticates(principal, actorId) {
52
+ if (principal.kind === "portableAuthority") {
53
+ if (!isPortableId(actorId)) return false;
54
+ const did = getPortableDid(actorId);
55
+ return did != null && did === getPortableDid(principal.id);
56
+ }
57
+ const expected = normalizeActorId(actorId);
58
+ return expected != null && normalizeActorId(principal.id) === expected;
59
+ }
60
+ /**
61
+ * Checks whether the given evidence authenticates the given actor.
62
+ *
63
+ * Actor IDs are compared exactly, except that [FEP-ef61] portable IDs and
64
+ * compatible identifiers are compared by their canonical portable IDs, and
65
+ * a portable authority (a DID) authenticates the portable actors of that DID.
66
+ *
67
+ * @example
68
+ * ``` typescript
69
+ * federation
70
+ * .setInboxListeners("/users/{identifier}/inbox", "/inbox")
71
+ * .on(Follow, async (ctx, follow) => {
72
+ * if (follow.actorId == null) return;
73
+ * // The actor is always authenticated for activities that declare one,
74
+ * // but the same check works for any actor an activity names:
75
+ * isActorAuthenticated(ctx.authentication, follow.actorId);
76
+ * });
77
+ * ```
78
+ *
79
+ * [FEP-ef61]: https://w3id.org/fep/ef61
80
+ * @param evidence The authentication evidence, usually
81
+ * {@link InboxContext.authentication}.
82
+ * @param actorId The actor ID to check.
83
+ * @param options Options.
84
+ * @returns `true` if the actor is authenticated.
85
+ * @since 2.5.0
86
+ */
87
+ function isActorAuthenticated(evidence, actorId, options = {}) {
88
+ return getEffectivePrincipals(evidence, options.scope).some((principal) => principalAuthenticates(principal, actorId));
89
+ }
90
+ function serializePrincipal(principal) {
91
+ return {
92
+ kind: principal.kind,
93
+ id: principal.id.href,
94
+ mechanism: principal.mechanism,
95
+ keyId: principal.keyId?.href ?? null
96
+ };
97
+ }
98
+ /**
99
+ * Serializes the principals of the given evidence for a queued message.
100
+ * @internal
101
+ */
102
+ function serializeAuthentication(evidence) {
103
+ return {
104
+ verified: evidence.verified,
105
+ requestSigners: evidence.requestSigners.map(serializePrincipal),
106
+ documentSigners: evidence.documentSigners.map(serializePrincipal),
107
+ originAuthenticated: evidence.originAuthenticated.map(serializePrincipal)
108
+ };
109
+ }
110
+ const KINDS = /* @__PURE__ */ new Set(["actor", "portableAuthority"]);
111
+ const MECHANISMS = /* @__PURE__ */ new Set([
112
+ "httpSignatures",
113
+ "linkedDataSignatures",
114
+ "objectIntegrityProofs",
115
+ "origin"
116
+ ]);
117
+ function deserializePrincipals(value) {
118
+ if (!Array.isArray(value)) return null;
119
+ const principals = [];
120
+ for (const item of value) {
121
+ if (item == null || typeof item !== "object" || !KINDS.has(item.kind) || !MECHANISMS.has(item.mechanism) || typeof item.id !== "string" || !URL.canParse(item.id) || item.keyId != null && (typeof item.keyId !== "string" || !URL.canParse(item.keyId))) return null;
122
+ principals.push(Object.freeze({
123
+ kind: item.kind,
124
+ id: new URL(item.id),
125
+ mechanism: item.mechanism,
126
+ keyId: item.keyId == null ? null : new URL(item.keyId)
127
+ }));
128
+ }
129
+ return principals;
130
+ }
131
+ /**
132
+ * Restores the evidence of a queued message.
133
+ * @param value The serialized principals.
134
+ * @param expandedActivity The expanded activity, restored from the queued
135
+ * verified view.
136
+ * @param baseUrl The base URL, restored from the queued verified view.
137
+ * @returns The evidence, or `null` if the serialized value is malformed.
138
+ * @internal
139
+ */
140
+ function deserializeAuthentication(value, expandedActivity, baseUrl) {
141
+ if (value == null || typeof value !== "object" || typeof value.verified !== "boolean") return null;
142
+ const record = value;
143
+ const requestSigners = deserializePrincipals(record.requestSigners);
144
+ const documentSigners = deserializePrincipals(record.documentSigners);
145
+ const originAuthenticated = deserializePrincipals(record.originAuthenticated);
146
+ if (requestSigners == null || documentSigners == null || originAuthenticated == null) return null;
147
+ return createAuthenticationEvidence({
148
+ verified: record.verified,
149
+ requestSigners,
150
+ documentSigners,
151
+ originAuthenticated,
152
+ expandedActivity,
153
+ baseUrl
154
+ });
155
+ }
156
+ /**
157
+ * Creates frozen evidence.
158
+ * @internal
159
+ */
160
+ function createAuthenticationEvidence(evidence) {
161
+ return Object.freeze({
162
+ verified: evidence.verified,
163
+ requestSigners: Object.freeze([...evidence.requestSigners]),
164
+ documentSigners: Object.freeze([...evidence.documentSigners]),
165
+ originAuthenticated: Object.freeze([...evidence.originAuthenticated]),
166
+ expandedActivity: evidence.expandedActivity,
167
+ baseUrl: evidence.baseUrl
168
+ });
169
+ }
170
+ //#endregion
171
+ export { isActorAuthenticated as a, getEmptyAuthenticationEvidence as i, deserializeAuthentication as n, serializeAuthentication as o, getEffectivePrincipals as r, createAuthenticationEvidence as t };
@@ -0,0 +1,385 @@
1
+ import "temporal-polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { c as getNormalizationContextLoader, p as markEffectiveContextLoader, s as detachSignature, y as wrapContextLoaderForJsonLd } from "./ld-CQFuiah9.mjs";
5
+ import { preloadedContexts } from "@fedify/vocab-runtime";
6
+ import { encodeHex } from "byte-encodings/hex";
7
+ import jsonld from "@fedify/vocab-runtime/jsonld";
8
+ import serialize from "json-canon";
9
+ //#region src/federation/inbox-view.ts
10
+ /**
11
+ * The interpretation of an incoming activity that Fedify authenticated, kept
12
+ * so that listeners, authorization helpers, and queue workers observe the
13
+ * same JSON-LD meaning that signature verification observed.
14
+ *
15
+ * Every JSON-LD processing step of one delivery goes through a request-scoped
16
+ * *context snapshot*, which memoizes the remote context responses so that
17
+ * a nondeterministic or mutable remote context cannot give verification and
18
+ * dispatch different meanings. The snapshot is serialized into queued
19
+ * messages, so that workers and retries replay the very same responses
20
+ * without loading contexts again.
21
+ * @module
22
+ */
23
+ /**
24
+ * Thrown when replaying an interpretation needs a context response that its
25
+ * snapshot does not have, or has with a different content.
26
+ * @internal
27
+ */
28
+ var ContextSnapshotMissError = class extends Error {
29
+ url;
30
+ constructor(url, reason) {
31
+ super(`Context ${url} cannot be replayed: ${reason}.`);
32
+ this.name = "ContextSnapshotMissError";
33
+ this.url = url;
34
+ }
35
+ };
36
+ function normalizeUrl(url) {
37
+ return URL.canParse(url) ? new URL(url).href : url;
38
+ }
39
+ /**
40
+ * Creates the context snapshot of a delivery.
41
+ * @param base The caller's context loader.
42
+ * @returns The snapshot.
43
+ * @internal
44
+ */
45
+ function createContextSnapshot(base) {
46
+ const underlying = {
47
+ standard: wrapContextLoaderForJsonLd(base),
48
+ normalization: getNormalizationContextLoader(base)
49
+ };
50
+ const cache = /* @__PURE__ */ new Map();
51
+ const recorded = /* @__PURE__ */ new Map();
52
+ const loaders = /* @__PURE__ */ new Map();
53
+ let released = false;
54
+ return {
55
+ loader(policy) {
56
+ let loader = loaders.get(policy);
57
+ if (loader != null) return loader;
58
+ const effective = underlying[policy];
59
+ loader = markEffectiveContextLoader(async (url, options) => {
60
+ if (released) return await effective(url, options);
61
+ const normalizedUrl = normalizeUrl(url);
62
+ const key = `${policy}\n${normalizedUrl}`;
63
+ let promise = cache.get(key);
64
+ if (promise == null) {
65
+ promise = Promise.resolve(effective(url, options)).then((response) => {
66
+ const cloned = normalizeResponse(structuredClone(response));
67
+ recorded.set(key, {
68
+ policy,
69
+ url: normalizedUrl,
70
+ response: structuredClone(cloned)
71
+ });
72
+ return cloned;
73
+ });
74
+ promise.catch(() => {});
75
+ cache.set(key, promise);
76
+ }
77
+ return structuredClone(await promise);
78
+ }, policy);
79
+ loaders.set(policy, loader);
80
+ return loader;
81
+ },
82
+ entries() {
83
+ return [...recorded.values()];
84
+ },
85
+ release() {
86
+ released = true;
87
+ }
88
+ };
89
+ }
90
+ function getBundledResponse(url) {
91
+ if (!(url in preloadedContexts)) return null;
92
+ return {
93
+ contextUrl: null,
94
+ document: preloadedContexts[url],
95
+ documentUrl: url
96
+ };
97
+ }
98
+ async function sha256(value) {
99
+ return encodeHex(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(serialize(value))));
100
+ }
101
+ function normalizeResponse(response) {
102
+ return {
103
+ contextUrl: response.contextUrl ?? null,
104
+ document: response.document,
105
+ documentUrl: response.documentUrl
106
+ };
107
+ }
108
+ /**
109
+ * Serializes the entries of a context snapshot for a queued message.
110
+ * @param entries The entries.
111
+ * @returns The serialized entries, which are plain JSON values.
112
+ * @internal
113
+ */
114
+ async function serializeContextSnapshot(entries) {
115
+ const result = [];
116
+ for (const entry of entries) {
117
+ const response = normalizeResponse(entry.response);
118
+ const hash = await sha256(response);
119
+ const bundled = getBundledResponse(entry.url);
120
+ if (bundled != null && await sha256(bundled) === hash) result.push({
121
+ policy: entry.policy,
122
+ url: entry.url,
123
+ sha256: hash,
124
+ bundled: true
125
+ });
126
+ else result.push({
127
+ policy: entry.policy,
128
+ url: entry.url,
129
+ sha256: hash,
130
+ response: structuredClone(response)
131
+ });
132
+ }
133
+ return result;
134
+ }
135
+ function isRemoteDocument(value) {
136
+ if (value == null || typeof value !== "object") return false;
137
+ const record = value;
138
+ return typeof record.documentUrl === "string" && "document" in record && (record.contextUrl == null || typeof record.contextUrl === "string");
139
+ }
140
+ /**
141
+ * Restores the entries of a serialized context snapshot, verifying the
142
+ * hashes of stored responses. Bundled entries are reconstructed from the
143
+ * contexts this version of Fedify bundles.
144
+ * @param entries The serialized entries.
145
+ * @returns The entries.
146
+ * @throws {ContextSnapshotMissError} If an entry is malformed, or its
147
+ * content does not match its hash.
148
+ * @internal
149
+ */
150
+ async function deserializeContextSnapshot(entries) {
151
+ if (!Array.isArray(entries)) throw new ContextSnapshotMissError("(snapshot)", "malformed snapshot");
152
+ const result = [];
153
+ for (const entry of entries) {
154
+ if (entry == null || typeof entry !== "object" || entry.policy !== "standard" && entry.policy !== "normalization" || typeof entry.url !== "string" || typeof entry.sha256 !== "string") throw new ContextSnapshotMissError("(snapshot)", "malformed entry");
155
+ let response;
156
+ if (entry.bundled === true) {
157
+ response = getBundledResponse(entry.url);
158
+ if (response == null) throw new ContextSnapshotMissError(entry.url, "not bundled");
159
+ } else if (isRemoteDocument(entry.response)) response = normalizeResponse(entry.response);
160
+ else throw new ContextSnapshotMissError(entry.url, "malformed entry");
161
+ if (entry.bundled !== true && await sha256(response) !== entry.sha256) throw new ContextSnapshotMissError(entry.url, "content mismatch");
162
+ result.push({
163
+ policy: entry.policy,
164
+ url: entry.url,
165
+ response: structuredClone(response)
166
+ });
167
+ }
168
+ return result;
169
+ }
170
+ /**
171
+ * Creates a context loader of a policy that serves only the responses of
172
+ * a snapshot. It is marked as the effective loader of the policy.
173
+ * @param entries The snapshot's entries.
174
+ * @param policy The policy.
175
+ * @returns The loader, which throws {@link ContextSnapshotMissError} for
176
+ * anything else.
177
+ * @internal
178
+ */
179
+ function createSnapshotOnlyLoader(entries, policy) {
180
+ const responses = /* @__PURE__ */ new Map();
181
+ for (const entry of entries) if (entry.policy === policy) responses.set(entry.url, entry.response);
182
+ return markEffectiveContextLoader(async (url) => {
183
+ const response = responses.get(normalizeUrl(url));
184
+ if (response == null) throw new ContextSnapshotMissError(url, "not in the snapshot");
185
+ return structuredClone(response);
186
+ }, policy);
187
+ }
188
+ /**
189
+ * Creates a {@link ReleasableLoader}.
190
+ * @param strict The loader to use until release.
191
+ * @param fallback The loader to use after release.
192
+ * @param policy The policy both loaders apply.
193
+ * @returns The releasable loader.
194
+ * @internal
195
+ */
196
+ function createReleasableLoader(strict, fallback, policy) {
197
+ let released = false;
198
+ return {
199
+ loader: markEffectiveContextLoader((url, options) => released ? fallback(url, options) : strict(url, options), policy),
200
+ release() {
201
+ released = true;
202
+ }
203
+ };
204
+ }
205
+ /**
206
+ * Expands a JSON-LD document with the given context loader.
207
+ * @param document The document.
208
+ * @param contextLoader The context loader.
209
+ * @returns The expanded document.
210
+ * @internal
211
+ */
212
+ async function expandDocument(document, contextLoader) {
213
+ return await jsonld.expand(document, { documentLoader: contextLoader });
214
+ }
215
+ /**
216
+ * Computes the digest of an expanded document.
217
+ * @param expanded The expanded document.
218
+ * @returns The hex-encoded SHA-256 of its canonical JSON.
219
+ * @internal
220
+ */
221
+ function digestExpandedDocument(expanded) {
222
+ return sha256(expanded);
223
+ }
224
+ /**
225
+ * Deeply freezes a JSON value.
226
+ * @param value The value.
227
+ * @returns The same value, frozen.
228
+ * @internal
229
+ */
230
+ function deepFreeze(value) {
231
+ if (value != null && typeof value === "object" && !Object.isFrozen(value)) {
232
+ Object.freeze(value);
233
+ for (const child of Object.values(value)) deepFreeze(child);
234
+ }
235
+ return value;
236
+ }
237
+ const ACTOR_IRI = "https://www.w3.org/ns/activitystreams#actor";
238
+ /**
239
+ * Gets the single root node of an expanded document.
240
+ * @param expanded The expanded document.
241
+ * @returns The root node, or `null` if there is not exactly one.
242
+ * @internal
243
+ */
244
+ function getRootNode(expanded) {
245
+ if (expanded.length !== 1) return null;
246
+ const node = expanded[0];
247
+ if (node == null || typeof node !== "object" || Array.isArray(node)) return null;
248
+ return node;
249
+ }
250
+ /**
251
+ * Gets the values of a property of an expanded node, unwrapping a single
252
+ * `@list` container the way generated vocabulary decoders do.
253
+ * @param node The expanded node.
254
+ * @param iri The property IRI.
255
+ * @returns The values, or an empty array if the property is absent.
256
+ * @internal
257
+ */
258
+ function getPropertyValues(node, iri) {
259
+ const values = node[iri];
260
+ if (values == null) return [];
261
+ if (!Array.isArray(values)) return [values];
262
+ if (values.length === 1 && values[0] != null && typeof values[0] === "object" && "@list" in values[0]) {
263
+ const list = values[0]["@list"];
264
+ return Array.isArray(list) ? list : [list];
265
+ }
266
+ return values;
267
+ }
268
+ /**
269
+ * Tells whether the root node of an expanded activity declares any `actor`
270
+ * value at all, including blank nodes, anonymous or untyped nodes, and
271
+ * literals, which ID accessors and typed parsing may drop.
272
+ * @param expanded The expanded activity.
273
+ * @returns `"absent"` if the root node declares no actor; `"present"`
274
+ * otherwise, including when there is no single root node.
275
+ * @internal
276
+ */
277
+ function getActorPresence(expanded) {
278
+ const root = getRootNode(expanded);
279
+ if (root == null) return "present";
280
+ return getPropertyValues(root, ACTOR_IRI).length < 1 ? "absent" : "present";
281
+ }
282
+ /**
283
+ * Builds the serialized view of an authenticated activity. The document is
284
+ * expanded through the snapshot, so that the snapshot holds every context
285
+ * response the interpretation needs.
286
+ * @param snapshot The delivery's context snapshot.
287
+ * @param source Which document the activity was parsed from.
288
+ * @returns The serialized view and the expanded activity.
289
+ * @internal
290
+ */
291
+ async function buildVerifiedActivityView(snapshot, source) {
292
+ const loader = snapshot.loader(source.policy);
293
+ const used = /* @__PURE__ */ new Set();
294
+ const expanded = await expandDocument(source.document, markEffectiveContextLoader((url, options) => {
295
+ used.add(normalizeUrl(url));
296
+ return loader(url, options);
297
+ }, source.policy));
298
+ const entries = snapshot.entries().filter((entry) => entry.policy === source.policy && used.has(entry.url));
299
+ const contexts = await serializeContextSnapshot(entries);
300
+ const references = /* @__PURE__ */ new Set();
301
+ collectContextReferences(source.document, source.baseUrl?.href, references);
302
+ for (const entry of entries) {
303
+ const { contextUrl, document } = entry.response;
304
+ collectContextReferences(document, entry.url, references);
305
+ if (contextUrl != null) collectContextReferences({ "@context": contextUrl }, entry.url, references);
306
+ }
307
+ return {
308
+ replayable: [...references].every((url) => used.has(url)),
309
+ view: {
310
+ version: 1,
311
+ policy: source.policy,
312
+ source: source.source,
313
+ baseUrl: source.baseUrl?.href ?? null,
314
+ contexts,
315
+ expandedDigest: await digestExpandedDocument(expanded)
316
+ },
317
+ expanded
318
+ };
319
+ }
320
+ /**
321
+ * Collects the remote context references of a JSON-LD document or context
322
+ * document: every IRI given as a value of `@context` at any depth, including
323
+ * scoped contexts of term definitions, and of `@import`.
324
+ * @param value The document.
325
+ * @param base The URL to resolve relative references against.
326
+ * @param references The set to add the normalized references to.
327
+ */
328
+ function collectContextReferences(value, base, references) {
329
+ const add = (reference) => {
330
+ let url = reference;
331
+ if (base != null && URL.canParse(reference, base)) url = new URL(reference, base).href;
332
+ references.add(normalizeUrl(url));
333
+ };
334
+ const collectContext = (context) => {
335
+ if (typeof context === "string") add(context);
336
+ else if (Array.isArray(context)) for (const item of context) collectContext(item);
337
+ else if (context != null && typeof context === "object") {
338
+ const imported = context["@import"];
339
+ if (typeof imported === "string") add(imported);
340
+ walk(context);
341
+ }
342
+ };
343
+ const walk = (node) => {
344
+ if (Array.isArray(node)) for (const item of node) walk(item);
345
+ else if (node != null && typeof node === "object") for (const [key, item] of Object.entries(node)) if (key === "@context") collectContext(item);
346
+ else walk(item);
347
+ };
348
+ walk(value);
349
+ }
350
+ /**
351
+ * Validates the shape of a serialized view taken from a queued message.
352
+ * @param value The value of the message's `verifiedView` field.
353
+ * @returns The view, or `null` if it is malformed or of an unsupported
354
+ * version.
355
+ * @internal
356
+ */
357
+ function parseSerializedVerifiedActivityView(value) {
358
+ if (value == null || typeof value !== "object") return null;
359
+ const view = value;
360
+ if (view.version !== 1 || view.policy !== "standard" && view.policy !== "normalization" || view.source !== "activity" && view.source !== "normalizedActivity" || view.baseUrl !== null && (typeof view.baseUrl !== "string" || !URL.canParse(view.baseUrl)) || !Array.isArray(view.contexts) || typeof view.expandedDigest !== "string") return null;
361
+ return view;
362
+ }
363
+ /**
364
+ * Restores a view from a queued message without loading any context.
365
+ * @param view The serialized view.
366
+ * @param message The fields of the message the view refers to.
367
+ * @returns The restored view.
368
+ * @throws {ContextSnapshotMissError} If the view cannot be replayed exactly.
369
+ * @internal
370
+ */
371
+ async function restoreVerifiedActivityView(view, message) {
372
+ const loader = createSnapshotOnlyLoader(await deserializeContextSnapshot(view.contexts), view.policy);
373
+ if (view.source === "normalizedActivity" && message.normalizedActivity == null) throw new ContextSnapshotMissError("(normalizedActivity)", "the normalized activity is missing");
374
+ const document = detachSignature(view.source === "activity" ? message.activity : message.normalizedActivity);
375
+ const expanded = await expandDocument(document, loader);
376
+ if (await digestExpandedDocument(expanded) !== view.expandedDigest) throw new ContextSnapshotMissError("(activity)", "the interpretation does not match the authenticated one");
377
+ return {
378
+ document,
379
+ loader,
380
+ expanded,
381
+ baseUrl: view.baseUrl == null ? null : new URL(view.baseUrl)
382
+ };
383
+ }
384
+ //#endregion
385
+ export { createSnapshotOnlyLoader as a, expandDocument as c, parseSerializedVerifiedActivityView as d, restoreVerifiedActivityView as f, createReleasableLoader as i, getActorPresence as l, buildVerifiedActivityView as n, deepFreeze as o, serializeContextSnapshot as p, createContextSnapshot as r, deserializeContextSnapshot as s, ContextSnapshotMissError as t, getPropertyValues as u };