@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.
- package/README.md +3 -0
- package/dist/{activity-listener-C-BSbKI7.mjs → activity-listener-CELFAodc.mjs} +8 -4
- package/dist/{builder-Da8b9O6v.mjs → builder-DphK9v7f.mjs} +5 -5
- package/dist/{client-D-XP_Ccb.d.ts → client-3InDZbba.d.cts} +0 -1
- package/dist/{client-D-XP_Ccb.d.cts → client-3InDZbba.d.ts} +0 -1
- package/dist/{client-CEnNz8O7.mjs → client-5SIFzW3q.mjs} +113 -64
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/{compound-proof-D1djO5fa.mjs → compound-proof-cYtvlTgF.mjs} +2 -2
- package/dist/{context-8Tu9kea0.mjs → context-COHd1Rsh.mjs} +2 -0
- package/dist/{context-BdB54V-A.d.ts → context-DQJE9YfH.d.ts} +332 -6
- package/dist/{context-CQ9H8vNW.d.cts → context-DSuL3YWx.d.cts} +332 -6
- package/dist/{deno-eNpfj4oR.mjs → deno-iOAA7iRN.mjs} +1 -1
- package/dist/docloader-DCZkiTeF.mjs +136 -0
- package/dist/{exporter-DThFfe7P.mjs → exporter-DVG68ozH.mjs} +36 -30
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/compound-fanout.test.mjs +6 -3
- package/dist/federation/handler-compound-proof.test.mjs +5 -5
- package/dist/federation/handler.test.mjs +22 -7
- package/dist/federation/hashlink-media.test.mjs +2 -2
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/inbox-actorless.test.d.mts +2 -0
- package/dist/federation/inbox-actorless.test.mjs +487 -0
- package/dist/federation/inbox-authentication.test.d.mts +2 -0
- package/dist/federation/inbox-authentication.test.mjs +116 -0
- package/dist/federation/inbox-report.test.mjs +5 -5
- package/dist/federation/inbox-view.test.d.mts +2 -0
- package/dist/federation/inbox-view.test.mjs +369 -0
- package/dist/federation/inbox.test.mjs +22 -12
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +470 -35
- package/dist/federation/mod.cjs +2 -1
- package/dist/federation/mod.d.cts +4 -4
- package/dist/federation/mod.d.ts +4 -4
- package/dist/federation/mod.js +8 -2
- package/dist/federation/mq.test.mjs +36 -1
- package/dist/federation/outgoing-proof.test.mjs +9 -6
- package/dist/federation/portable-actor.test.mjs +3 -3
- package/dist/federation/portable-chain.test.mjs +3 -3
- package/dist/federation/portable-collection.test.mjs +4 -4
- package/dist/federation/portable-delivery.test.mjs +2 -2
- package/dist/federation/portable-gateway-keys.test.mjs +13 -7
- package/dist/federation/portable-inbox.test.mjs +4 -4
- package/dist/federation/portable-parse-uri.test.mjs +1 -1
- package/dist/federation/portable-route-activity.test.mjs +2 -2
- package/dist/federation/portable.test.mjs +3 -3
- package/dist/federation/send.test.mjs +502 -93
- package/dist/federation/tasks/enqueue.test.mjs +3 -3
- package/dist/federation/tasks/tasks.test.mjs +4 -4
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +2 -2
- package/dist/{federation-BVH7drJA.cjs → federation-D0AZw_aw.cjs} +1415 -236
- package/dist/{http-BUJ5SFh6.mjs → http-BoJCMnjY.mjs} +204 -106
- package/dist/{http-DlNxxW_5.js → http-ChXUU_3c.js} +319 -123
- package/dist/{http-BfW3wyQ4.cjs → http-kv1LZw-r.cjs} +339 -125
- package/dist/inbox-BjihTKLo.mjs +312 -0
- package/dist/inbox-authentication-DflPYlLL.mjs +171 -0
- package/dist/inbox-view-D-skypg-.mjs +385 -0
- package/dist/{key-nYGY4QB9.d.cts → key-DbLNT-vA.d.ts} +1 -2
- package/dist/{key-DhsBxxZN.d.ts → key-DlHT4R6k.d.cts} +1 -2
- package/dist/{kv-cache-D_m3A241.mjs → kv-cache-Cmsyke4d.mjs} +1 -1
- package/dist/{kv-cache-7015lbrx.js → kv-cache-DQcte7ES.js} +73 -28
- package/dist/{kv-cache-kkBB0vbI.cjs → kv-cache-Ddh5sMxH.cjs} +72 -27
- package/dist/ld-CWVpo_NM.d.cts +250 -0
- package/dist/{ld-CD2CsPxq.mjs → ld-CdvcOEXQ.mjs} +52 -9
- package/dist/ld-Inagww4R.d.ts +250 -0
- package/dist/{metrics-D3LHk6Z4.mjs → metrics-CpxvUrBz.mjs} +1 -1
- package/dist/{middleware-N9eM16XE.js → middleware-BHb5toqA.js} +1413 -247
- package/dist/{middleware-CCDXvXOG.mjs → middleware-BKs2DLUF.mjs} +1 -1
- package/dist/{middleware-CzoWvrdZ.mjs → middleware-CoBLrTKX.mjs} +533 -132
- package/dist/{mod-B0dGgQsg.d.cts → mod-6TQ4UQZb.d.cts} +1 -1
- package/dist/mod-B4kBw1-8.d.ts +154 -0
- package/dist/{mod-DkhfZC1u.d.ts → mod-B7j30IQf.d.ts} +2 -9
- package/dist/mod-C1nT74IG.d.cts +154 -0
- package/dist/{mod-D0bxB8jS.d.ts → mod-CIeBgsOC.d.ts} +1 -1
- package/dist/{mod-B5-wdzmJ.d.cts → mod-YvA4Rk8J.d.cts} +2 -9
- package/dist/mod.cjs +6 -5
- package/dist/mod.d.cts +9 -9
- package/dist/mod.d.ts +9 -9
- package/dist/mod.js +6 -6
- package/dist/{mq-Dm1Wmtm-.mjs → mq-eF96QxUT.mjs} +6 -0
- package/dist/nodeinfo/client.test.mjs +337 -19
- package/dist/nodeinfo/handler.test.mjs +2 -2
- package/dist/nodeinfo/mod.cjs +1 -1
- package/dist/nodeinfo/mod.d.cts +1 -1
- package/dist/nodeinfo/mod.d.ts +1 -1
- package/dist/nodeinfo/mod.js +1 -1
- package/dist/otel/exporter.test.mjs +257 -1
- package/dist/otel/mod.cjs +36 -30
- package/dist/otel/mod.d.cts +13 -0
- package/dist/otel/mod.d.ts +13 -0
- package/dist/otel/mod.js +36 -30
- package/dist/{owner-C5-FVX6t.mjs → owner-DTnVpUKA.mjs} +3 -3
- package/dist/{portable-collection-Cony3_LC.mjs → portable-collection-CJeYTL61.mjs} +2 -2
- package/dist/{portable-collection-C-Z1SKDy.cjs → portable-collection-DciwBIia.cjs} +1 -1
- package/dist/{portable-collection-CC2zNAje.js → portable-collection-TmtpzD4E.js} +1 -1
- package/dist/{portable-key-DNc_flH-.mjs → portable-key-DcYok4ki.mjs} +1 -1
- package/dist/{proof-Cryb1c70.mjs → proof-GH7Poos_.mjs} +98 -10
- package/dist/{send-AF25pWaJ.mjs → send-BgGJh6pv.mjs} +174 -76
- package/dist/sig/compound-proof-discovery.test.mjs +1 -1
- package/dist/sig/compound-proof-producer.test.mjs +1 -1
- package/dist/sig/compound-proof-verification.test.mjs +2 -2
- package/dist/sig/compound-proof.test.mjs +2 -2
- package/dist/sig/http.test.mjs +553 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +1 -1
- package/dist/sig/mitra-receive-interop.test.mjs +4 -4
- package/dist/sig/mitra-send-interop.test.mjs +3 -3
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.cts +3 -3
- package/dist/sig/mod.d.ts +3 -3
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/portable-collection.test.mjs +1 -1
- package/dist/sig/portable-interop.test.mjs +3 -3
- package/dist/sig/portable-key.test.mjs +5 -5
- package/dist/sig/proof.test.mjs +45 -5
- package/dist/sig/tootik-request-interop.test.mjs +4 -4
- package/dist/sig/tootik-send-interop.test.mjs +3 -3
- package/dist/sig/verification.test.mjs +3 -3
- package/dist/{temporal-BsAeuUJj.mjs → temporal-gJINzcL4.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/testing/mod.d.mts +288 -2
- package/dist/testing/mod.mjs +2 -2
- package/dist/{testing-t8xDeJZ5.mjs → testing-Dbr21vZk.mjs} +1 -1
- package/dist/{types-_S2_MeLF.js → types-DUjZb4i6.js} +112 -63
- package/dist/{types-poIao40f.cjs → types-hKCCjii0.cjs} +111 -62
- package/dist/utils/docloader.test.mjs +344 -36
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +1 -1
- package/dist/utils/mod.d.ts +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +7 -7
- package/dist/docloader-B-0nqOB1.mjs +0 -91
- package/dist/inbox-CHvMaIFX.mjs +0 -188
- package/dist/mod-BRpER-B0.d.ts +0 -320
- package/dist/mod-xAfaLS4T.d.cts +0 -320
- package/dist/owner-BF0L9cMt.d.cts +0 -78
- package/dist/owner-BSzr5-I8.d.ts +0 -78
|
@@ -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-CdvcOEXQ.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 };
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/// <reference lib="esnext.temporal" />
|
|
2
2
|
import { Actor, CryptographicKey, DataIntegrityProof, Multikey, Object as Object$1 } from "@fedify/vocab";
|
|
3
|
-
import { DocumentLoader, PortableObjectVerifier } from "@fedify/vocab-runtime";
|
|
4
3
|
import { MeterProvider, TracerProvider } from "@opentelemetry/api";
|
|
5
|
-
|
|
4
|
+
import { DocumentLoader, PortableObjectVerifier } from "@fedify/vocab-runtime";
|
|
6
5
|
//#region src/sig/proof.d.ts
|
|
7
6
|
/**
|
|
8
7
|
* Checks if the given JSON-LD document has a DataIntegrityProof-like object,
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/// <reference lib="esnext.temporal" />
|
|
2
2
|
import { Actor, CryptographicKey, DataIntegrityProof, Multikey, Object as Object$1 } from "@fedify/vocab";
|
|
3
|
-
import { MeterProvider, TracerProvider } from "@opentelemetry/api";
|
|
4
3
|
import { DocumentLoader, PortableObjectVerifier } from "@fedify/vocab-runtime";
|
|
5
|
-
|
|
4
|
+
import { MeterProvider, TracerProvider } from "@opentelemetry/api";
|
|
6
5
|
//#region src/sig/proof.d.ts
|
|
7
6
|
/**
|
|
8
7
|
* Checks if the given JSON-LD document has a DataIntegrityProof-like object,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Temporal } from "temporal-polyfill";
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { p as recordDocumentCache } from "./metrics-
|
|
4
|
+
import { p as recordDocumentCache } from "./metrics-CpxvUrBz.mjs";
|
|
5
5
|
import { getLogger } from "@logtape/logtape";
|
|
6
6
|
import { preloadedContexts } from "@fedify/vocab-runtime";
|
|
7
7
|
//#region src/utils/kv-cache.ts
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Temporal } from "temporal-polyfill";
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
|
-
import {
|
|
3
|
+
import { X as validateCryptoKey, dt as recordDocumentCache, t as doubleKnockScoped } from "./http-ChXUU_3c.js";
|
|
4
4
|
import { getLogger } from "@logtape/logtape";
|
|
5
|
-
import { FetchError, UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, resolveDocumentLoaderTimeout,
|
|
5
|
+
import { FetchError, UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, resolveDocumentLoaderTimeout, withDocumentLoaderTimeout } from "@fedify/vocab-runtime";
|
|
6
6
|
import { curry } from "es-toolkit";
|
|
7
|
+
import { openDirectFetch, openPublicFetch } from "@fedify/vocab-runtime/internal";
|
|
7
8
|
//#region src/utils/docloader.ts
|
|
8
9
|
const logger$1 = getLogger([
|
|
9
10
|
"fedify",
|
|
@@ -27,13 +28,35 @@ const DEFAULT_MAX_REDIRECTION = 20;
|
|
|
27
28
|
* @throws {RangeError} If the `timeout` option is invalid.
|
|
28
29
|
* @since 0.4.0
|
|
29
30
|
*/
|
|
30
|
-
function getAuthenticatedDocumentLoader(identity,
|
|
31
|
+
function getAuthenticatedDocumentLoader(identity, options = {}) {
|
|
32
|
+
return createAuthenticatedDocumentLoader(identity, options);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The placeholder that the alternate document callback of
|
|
36
|
+
* `getRemoteDocument()` returns, so that the alternate document is loaded
|
|
37
|
+
* after the current hop is closed.
|
|
38
|
+
*/
|
|
39
|
+
const ALTERNATE_PLACEHOLDER = Object.freeze({
|
|
40
|
+
contextUrl: null,
|
|
41
|
+
document: null,
|
|
42
|
+
documentUrl: "about:blank"
|
|
43
|
+
});
|
|
44
|
+
/**
|
|
45
|
+
* The implementation of {@link getAuthenticatedDocumentLoader}. This is not
|
|
46
|
+
* exported from any entry point.
|
|
47
|
+
* @param identity The identity to get the document loader for.
|
|
48
|
+
* @param options The options for the document loader.
|
|
49
|
+
* @param deps The dependencies, for tests.
|
|
50
|
+
* @returns The authenticated document loader.
|
|
51
|
+
* @throws {TypeError} If the key is invalid or unsupported.
|
|
52
|
+
*/
|
|
53
|
+
function createAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedirection, userAgent, specDeterminer, timeout, tracerProvider } = {}, deps = {}) {
|
|
31
54
|
validateCryptoKey(identity.privateKey);
|
|
32
55
|
const resolvedTimeout = resolveDocumentLoaderTimeout(timeout);
|
|
33
56
|
async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
|
|
34
57
|
options?.signal?.throwIfAborted();
|
|
35
58
|
let currentUrl = new URL(url).href;
|
|
36
|
-
|
|
59
|
+
validateUrl(currentUrl);
|
|
37
60
|
visited.add(currentUrl);
|
|
38
61
|
const originalRequest = createActivityPubRequest(currentUrl, { userAgent });
|
|
39
62
|
function follow(nextUrl) {
|
|
@@ -42,46 +65,68 @@ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedi
|
|
|
42
65
|
if (visited.has(nextUrl)) throw new FetchError(currentUrl, `Redirect loop detected: ${nextUrl}`);
|
|
43
66
|
redirected++;
|
|
44
67
|
}
|
|
45
|
-
const
|
|
68
|
+
const result = await doubleKnockScoped(originalRequest, identity, {
|
|
46
69
|
maxRedirection,
|
|
47
70
|
specDeterminer,
|
|
48
71
|
log: curry(logRequest)(logger$1),
|
|
49
72
|
tracerProvider,
|
|
50
73
|
signal: options?.signal,
|
|
51
|
-
validateRedirect:
|
|
74
|
+
validateRedirect: (nextUrl) => {
|
|
52
75
|
follow(nextUrl);
|
|
53
|
-
|
|
76
|
+
validateUrl(nextUrl);
|
|
54
77
|
visited.add(nextUrl);
|
|
55
78
|
currentUrl = nextUrl;
|
|
56
79
|
}
|
|
80
|
+
}, (hopUrl) => openHop(hopUrl, options), async (response) => {
|
|
81
|
+
options?.signal?.throwIfAborted();
|
|
82
|
+
let alternateUrl;
|
|
83
|
+
const document = await getRemoteDocument(currentUrl, response, (url) => {
|
|
84
|
+
alternateUrl = url;
|
|
85
|
+
return Promise.resolve(ALTERNATE_PLACEHOLDER);
|
|
86
|
+
}, options);
|
|
87
|
+
if (alternateUrl == null) return { document };
|
|
88
|
+
if (!response.bodyUsed && response.body?.locked !== true) response.body?.cancel().catch((error) => {
|
|
89
|
+
logger$1.debug("Failed to cancel the response body from {url}: {error}", {
|
|
90
|
+
url: currentUrl,
|
|
91
|
+
error
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
return { alternateUrl };
|
|
57
95
|
});
|
|
58
96
|
options?.signal?.throwIfAborted();
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
97
|
+
if ("document" in result) return result.document;
|
|
98
|
+
follow(result.alternateUrl);
|
|
99
|
+
return load(result.alternateUrl, options, redirected, visited);
|
|
100
|
+
}
|
|
101
|
+
function openUnvalidatedHop(url, signal) {
|
|
102
|
+
if (deps.openHop != null) return deps.openHop(url);
|
|
103
|
+
return allowPrivateAddress ? openDirectFetch(url) : openPublicFetch(url, { signal });
|
|
104
|
+
}
|
|
105
|
+
async function openHop(url, options) {
|
|
106
|
+
try {
|
|
107
|
+
const session = await openUnvalidatedHop(url, options?.signal);
|
|
108
|
+
if (options?.signal?.aborted) {
|
|
109
|
+
await session.close();
|
|
110
|
+
options.signal.throwIfAborted();
|
|
111
|
+
}
|
|
112
|
+
return session;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (error instanceof UrlError) if (error.reason === "dns") logger$1.debug("DNS lookup failed for {url}", {
|
|
115
|
+
url,
|
|
116
|
+
error
|
|
117
|
+
});
|
|
118
|
+
else logger$1[options?.suppressError ? "warn" : "error"]("Disallowed private URL: {url}", {
|
|
119
|
+
url,
|
|
120
|
+
error
|
|
121
|
+
});
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
63
124
|
}
|
|
64
|
-
|
|
125
|
+
function validateUrl(url) {
|
|
65
126
|
const parsedUrl = new URL(url);
|
|
66
127
|
const protocol = parsedUrl.protocol;
|
|
67
128
|
if (protocol !== "http:" && protocol !== "https:") throw new UrlError(`Unsupported protocol: ${protocol}`);
|
|
68
129
|
if (parsedUrl.username !== "" || parsedUrl.password !== "") throw new UrlError("URLs with credentials are not allowed");
|
|
69
|
-
if (!allowPrivateAddress) {
|
|
70
|
-
try {
|
|
71
|
-
await validatePublicUrl(url);
|
|
72
|
-
} catch (error) {
|
|
73
|
-
if (error instanceof UrlError) if (error.reason === "dns") logger$1.debug("DNS lookup failed for {url}", {
|
|
74
|
-
url,
|
|
75
|
-
error
|
|
76
|
-
});
|
|
77
|
-
else logger$1[options?.suppressError ? "warn" : "error"]("Disallowed private URL: {url}", {
|
|
78
|
-
url,
|
|
79
|
-
error
|
|
80
|
-
});
|
|
81
|
-
throw error;
|
|
82
|
-
}
|
|
83
|
-
options?.signal?.throwIfAborted();
|
|
84
|
-
}
|
|
85
130
|
}
|
|
86
131
|
return withDocumentLoaderTimeout((url, options) => load(url, options), resolvedTimeout);
|
|
87
132
|
}
|