@fedify/vocab 2.4.0-dev.2129 → 2.4.0-dev.2141
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/deno.json +1 -1
- package/dist/mod.cjs +736 -442
- package/dist/mod.d.cts +2458 -1
- package/dist/mod.d.ts +2458 -1
- package/dist/mod.js +736 -442
- package/dist-tests/{actor-Jl479WDt.mjs → actor-QEQj1qb5.mjs} +2 -2
- package/dist-tests/actor.test.mjs +3 -3
- package/dist-tests/{esm-CU6UbTlZ.mjs → esm-CxYTzURx.mjs} +1 -1
- package/dist-tests/gateway.test.mjs +362 -18
- package/dist-tests/{lookup-YM9st5n8.mjs → lookup-CcRxP9xc.mjs} +2 -2
- package/dist-tests/lookup.test.mjs +3 -3
- package/dist-tests/portable-lookup.test.mjs +4 -4
- package/dist-tests/signed-representation.test.mjs +1 -1
- package/dist-tests/translation.test.mjs +3 -3
- package/dist-tests/type.test.mjs +1 -1
- package/dist-tests/{vocab-DFtGFGsb.mjs → vocab-CVEaA48h.mjs} +735 -441
- package/dist-tests/vocab.test.mjs +2 -2
- package/package.json +4 -4
- package/src/gateway.test.ts +452 -45
- package/src/lookup.ts +15 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Temporal } from "temporal-polyfill";
|
|
2
2
|
globalThis.addEventListener = () => {};
|
|
3
|
-
import { A as Object$1, B as QuoteAuthorization, C as Intent, D as Measure, E as Link, F as Person, G as Tombstone, H as Reject, I as Place, L as PropertyValue, N as OrderedCollectionPage, O as Multikey, P as Organization, R as Proposal, S as Image, T as InteractionRule, U as Service, V as QuoteRequest, W as Source, X as vocab_exports, _ as FeaturedCollection, a as Application, b as Group, d as CryptographicKey, f as Delete, g as FeatureRequest, h as FeatureAuthorization, i as Announce, j as Offer, k as Note, l as Commitment, m as Endpoints, n as Activity, p as Document, r as Agreement, s as Collection, t as Accept, u as Create, v as FeaturedItem, w as InteractionPolicy, x as Hashtag, y as Follow, z as Question } from "./vocab-
|
|
4
|
-
import { t as esm_default } from "./esm-
|
|
3
|
+
import { A as Object$1, B as QuoteAuthorization, C as Intent, D as Measure, E as Link, F as Person, G as Tombstone, H as Reject, I as Place, L as PropertyValue, N as OrderedCollectionPage, O as Multikey, P as Organization, R as Proposal, S as Image, T as InteractionRule, U as Service, V as QuoteRequest, W as Source, X as vocab_exports, _ as FeaturedCollection, a as Application, b as Group, d as CryptographicKey, f as Delete, g as FeatureRequest, h as FeatureAuthorization, i as Announce, j as Offer, k as Note, l as Commitment, m as Endpoints, n as Activity, p as Document, r as Agreement, s as Collection, t as Accept, u as Create, v as FeaturedItem, w as InteractionPolicy, x as Hashtag, y as Follow, z as Question } from "./vocab-CVEaA48h.mjs";
|
|
4
|
+
import { t as esm_default } from "./esm-CxYTzURx.mjs";
|
|
5
5
|
import { t as assertInstanceOf } from "./utils-Df2odC27.mjs";
|
|
6
6
|
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
7
7
|
import { deepStrictEqual, notDeepStrictEqual, ok, rejects, throws } from "node:assert/strict";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fedify/vocab",
|
|
3
|
-
"version": "2.4.0-dev.
|
|
3
|
+
"version": "2.4.0-dev.2141+881cc858",
|
|
4
4
|
"homepage": "https://fedify.dev/",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
"jsonld": "^9.0.0",
|
|
44
44
|
"pkijs": "^3.3.3",
|
|
45
45
|
"temporal-polyfill": "^1.0.1",
|
|
46
|
-
"@fedify/vocab-
|
|
47
|
-
"@fedify/webfinger": "2.4.0-dev.
|
|
48
|
-
"@fedify/vocab-
|
|
46
|
+
"@fedify/vocab-tools": "2.4.0-dev.2141+881cc858",
|
|
47
|
+
"@fedify/webfinger": "2.4.0-dev.2141+881cc858",
|
|
48
|
+
"@fedify/vocab-runtime": "2.4.0-dev.2141+881cc858"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/node": "^24.12.2",
|
package/src/gateway.test.ts
CHANGED
|
@@ -2113,48 +2113,34 @@ test("objects use the verifyPortableObject option they were parsed with by defau
|
|
|
2113
2113
|
deepStrictEqual(verifier.calls.length, 2);
|
|
2114
2114
|
});
|
|
2115
2115
|
|
|
2116
|
-
await t.step(
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
contextLoader: mockDocumentLoader,
|
|
2145
|
-
verifyPortableObject: given,
|
|
2146
|
-
});
|
|
2147
|
-
assertInstanceOf(object2, Note);
|
|
2148
|
-
await rejects(
|
|
2149
|
-
async () =>
|
|
2150
|
-
await object2.getAttribution({
|
|
2151
|
-
documentLoader,
|
|
2152
|
-
contextLoader: mockDocumentLoader,
|
|
2153
|
-
gateways: ["https://gw.example"],
|
|
2154
|
-
}),
|
|
2155
|
-
TypeError,
|
|
2156
|
-
);
|
|
2157
|
-
});
|
|
2116
|
+
await t.step(
|
|
2117
|
+
"an explicit option is passed on to fetched objects",
|
|
2118
|
+
async () => {
|
|
2119
|
+
const stored = createRecordingVerifier();
|
|
2120
|
+
const given = createRecordingVerifier();
|
|
2121
|
+
const activity = await createCompatibleActivity(
|
|
2122
|
+
compatibleNoteId,
|
|
2123
|
+
undefined,
|
|
2124
|
+
{ verifyPortableObject: stored },
|
|
2125
|
+
);
|
|
2126
|
+
const object = await activity.getObject({
|
|
2127
|
+
documentLoader,
|
|
2128
|
+
contextLoader: mockDocumentLoader,
|
|
2129
|
+
verifyPortableObject: given,
|
|
2130
|
+
});
|
|
2131
|
+
assertInstanceOf(object, Note);
|
|
2132
|
+
deepStrictEqual([stored.calls.length, given.calls.length], [0, 1]);
|
|
2133
|
+
// The fetched object uses the verifier given to the call by default:
|
|
2134
|
+
await object.getAttribution({
|
|
2135
|
+
documentLoader,
|
|
2136
|
+
contextLoader: mockDocumentLoader,
|
|
2137
|
+
gateways: ["https://gw.example"],
|
|
2138
|
+
});
|
|
2139
|
+
deepStrictEqual([stored.calls.length, given.calls.length], [0, 2]);
|
|
2140
|
+
// ...but the parent keeps its own default:
|
|
2141
|
+
ok(getDefaultVerifier(activity) === stored);
|
|
2142
|
+
},
|
|
2143
|
+
);
|
|
2158
2144
|
|
|
2159
2145
|
await t.step("lookupObject()", async () => {
|
|
2160
2146
|
const verifier = createRecordingVerifier();
|
|
@@ -2186,9 +2172,430 @@ test("objects use the verifyPortableObject option they were parsed with by defau
|
|
|
2186
2172
|
}, { contextLoader: mockDocumentLoader, verifyPortableObject: verifier });
|
|
2187
2173
|
const icon = await person.getIcon({ documentLoader });
|
|
2188
2174
|
assertInstanceOf(icon, Image);
|
|
2189
|
-
ok(
|
|
2190
|
-
|
|
2191
|
-
|
|
2175
|
+
ok(getDefaultVerifier(icon) === verifier);
|
|
2176
|
+
});
|
|
2177
|
+
});
|
|
2178
|
+
|
|
2179
|
+
function getDefaultVerifier(
|
|
2180
|
+
object: object,
|
|
2181
|
+
): PortableObjectVerifier | undefined {
|
|
2182
|
+
return (object as unknown as {
|
|
2183
|
+
_verifyPortableObject?: PortableObjectVerifier;
|
|
2184
|
+
})
|
|
2185
|
+
._verifyPortableObject;
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
test("accessors pass the verifier of a call on to the objects they fetch", async (t) => {
|
|
2189
|
+
const actorId = `ap://${did}/actor`;
|
|
2190
|
+
const attributedNote = (id: string) => ({
|
|
2191
|
+
...note(id),
|
|
2192
|
+
attributedTo: actorId,
|
|
2193
|
+
});
|
|
2194
|
+
const actor = {
|
|
2195
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
2196
|
+
id: actorId,
|
|
2197
|
+
type: "Person",
|
|
2198
|
+
inbox: `${actorId}/inbox`,
|
|
2199
|
+
};
|
|
2200
|
+
const actorUrl = gatewayUrl("https://gw.example", "/actor");
|
|
2201
|
+
const plainNoteUrl = "https://example.com/notes/1";
|
|
2202
|
+
const plainActivityUrl = "https://example.com/activities/1";
|
|
2203
|
+
const documentLoader = createLoader({
|
|
2204
|
+
[compatibleNoteId]: attributedNote(compatibleNoteId),
|
|
2205
|
+
[actorUrl]: actor,
|
|
2206
|
+
[plainNoteUrl]: { ...attributedNote(plainNoteUrl), id: plainNoteUrl },
|
|
2207
|
+
[plainActivityUrl]: {
|
|
2208
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
2209
|
+
type: "Create",
|
|
2210
|
+
id: plainActivityUrl,
|
|
2211
|
+
object: compatibleNoteId,
|
|
2212
|
+
},
|
|
2213
|
+
});
|
|
2214
|
+
const options = { documentLoader, contextLoader: mockDocumentLoader };
|
|
2215
|
+
const secondHop = { ...options, gateways: ["https://gw.example"] };
|
|
2216
|
+
|
|
2217
|
+
await t.step("from objects without a default", async () => {
|
|
2218
|
+
const roots: [string, () => Promise<Create>][] = [
|
|
2219
|
+
[
|
|
2220
|
+
"constructed",
|
|
2221
|
+
// deno-lint-ignore require-await
|
|
2222
|
+
async () =>
|
|
2223
|
+
new Create({
|
|
2224
|
+
id: new URL(gatewayUrl("https://gw.example", "/activities/1")),
|
|
2225
|
+
object: new URL(compatibleNoteId),
|
|
2226
|
+
}, { contextLoader: mockDocumentLoader }),
|
|
2227
|
+
],
|
|
2228
|
+
["parsed", () => createCompatibleActivity(compatibleNoteId)],
|
|
2229
|
+
[
|
|
2230
|
+
"looked up",
|
|
2231
|
+
async () => {
|
|
2232
|
+
const found = await lookupObject(plainActivityUrl, options);
|
|
2233
|
+
assertInstanceOf(found, Create);
|
|
2234
|
+
return found;
|
|
2235
|
+
},
|
|
2236
|
+
],
|
|
2237
|
+
];
|
|
2238
|
+
for (const [name, createRoot] of roots) {
|
|
2239
|
+
// Singular accessors:
|
|
2240
|
+
const verifier = createRecordingVerifier();
|
|
2241
|
+
const root = await createRoot();
|
|
2242
|
+
ok(getDefaultVerifier(root) == null, name);
|
|
2243
|
+
const object = await root.getObject({
|
|
2244
|
+
...options,
|
|
2245
|
+
verifyPortableObject: verifier,
|
|
2246
|
+
});
|
|
2247
|
+
assertInstanceOf(object, Note);
|
|
2248
|
+
deepStrictEqual(verifier.calls.length, 1, name);
|
|
2249
|
+
assertInstanceOf(await object.getAttribution(secondHop), Person);
|
|
2250
|
+
deepStrictEqual(verifier.calls.length, 2, name);
|
|
2251
|
+
ok(getDefaultVerifier(root) == null, name);
|
|
2252
|
+
|
|
2253
|
+
// Plural accessors:
|
|
2254
|
+
const verifier2 = createRecordingVerifier();
|
|
2255
|
+
const objects = await Array.fromAsync((await createRoot()).getObjects({
|
|
2256
|
+
...options,
|
|
2257
|
+
verifyPortableObject: verifier2,
|
|
2258
|
+
}));
|
|
2259
|
+
deepStrictEqual(objects.length, 1, name);
|
|
2260
|
+
assertInstanceOf(objects[0], Note);
|
|
2261
|
+
const authors = await Array.fromAsync(
|
|
2262
|
+
(objects[0] as Note).getAttributions(secondHop),
|
|
2263
|
+
);
|
|
2264
|
+
deepStrictEqual(authors.length, 1, name);
|
|
2265
|
+
assertInstanceOf(authors[0], Person);
|
|
2266
|
+
deepStrictEqual(verifier2.calls.length, 2, name);
|
|
2267
|
+
}
|
|
2268
|
+
});
|
|
2269
|
+
|
|
2270
|
+
await t.step("precedence", async () => {
|
|
2271
|
+
const stored = createRecordingVerifier();
|
|
2272
|
+
const given = createRecordingVerifier();
|
|
2273
|
+
const override = createRecordingVerifier();
|
|
2274
|
+
const counts = () => [
|
|
2275
|
+
stored.calls.length,
|
|
2276
|
+
given.calls.length,
|
|
2277
|
+
override.calls.length,
|
|
2278
|
+
];
|
|
2279
|
+
|
|
2280
|
+
// Without the option, the stored default applies to the call and is
|
|
2281
|
+
// passed on:
|
|
2282
|
+
const byDefault = await (await createCompatibleActivity(
|
|
2283
|
+
compatibleNoteId,
|
|
2284
|
+
undefined,
|
|
2285
|
+
{ verifyPortableObject: stored },
|
|
2286
|
+
)).getObject({ ...options, inheritPortableObjectVerifier: false });
|
|
2287
|
+
assertInstanceOf(byDefault, Note);
|
|
2288
|
+
ok(getDefaultVerifier(byDefault) === stored);
|
|
2289
|
+
deepStrictEqual(counts(), [1, 0, 0]);
|
|
2290
|
+
|
|
2291
|
+
// With the opt-out, the given verifier applies to the call only, and the
|
|
2292
|
+
// fetched object gets the stored default:
|
|
2293
|
+
const optedOut = await (await createCompatibleActivity(
|
|
2294
|
+
compatibleNoteId,
|
|
2295
|
+
undefined,
|
|
2296
|
+
{ verifyPortableObject: stored },
|
|
2297
|
+
)).getObject({
|
|
2298
|
+
...options,
|
|
2299
|
+
verifyPortableObject: given,
|
|
2300
|
+
inheritPortableObjectVerifier: false,
|
|
2301
|
+
});
|
|
2302
|
+
assertInstanceOf(optedOut, Note);
|
|
2303
|
+
deepStrictEqual(counts(), [1, 1, 0]);
|
|
2304
|
+
ok(getDefaultVerifier(optedOut) === stored);
|
|
2305
|
+
assertInstanceOf(await optedOut.getAttribution(secondHop), Person);
|
|
2306
|
+
deepStrictEqual(counts(), [2, 1, 0]);
|
|
2307
|
+
|
|
2308
|
+
// Without a stored default, the fetched object gets none:
|
|
2309
|
+
const noDefault = await (await createCompatibleActivity(compatibleNoteId))
|
|
2310
|
+
.getObject({
|
|
2311
|
+
...options,
|
|
2312
|
+
verifyPortableObject: given,
|
|
2313
|
+
inheritPortableObjectVerifier: false,
|
|
2314
|
+
});
|
|
2315
|
+
assertInstanceOf(noDefault, Note);
|
|
2316
|
+
deepStrictEqual(counts(), [2, 2, 0]);
|
|
2317
|
+
ok(getDefaultVerifier(noDefault) == null);
|
|
2318
|
+
await rejects(
|
|
2319
|
+
async () => await noDefault.getAttribution(secondHop),
|
|
2320
|
+
TypeError,
|
|
2321
|
+
);
|
|
2322
|
+
|
|
2323
|
+
// The opt-out itself is not passed on, and an explicit option given to
|
|
2324
|
+
// the fetched object overrides its inherited default:
|
|
2325
|
+
const inherited = await (await createCompatibleActivity(
|
|
2326
|
+
compatibleNoteId,
|
|
2327
|
+
undefined,
|
|
2328
|
+
{ verifyPortableObject: stored },
|
|
2329
|
+
)).getObject({ ...options, verifyPortableObject: given });
|
|
2330
|
+
assertInstanceOf(inherited, Note);
|
|
2331
|
+
deepStrictEqual(counts(), [2, 3, 0]);
|
|
2332
|
+
ok(getDefaultVerifier(inherited) === given);
|
|
2333
|
+
assertInstanceOf(
|
|
2334
|
+
await inherited.getAttribution({
|
|
2335
|
+
...secondHop,
|
|
2336
|
+
verifyPortableObject: override,
|
|
2337
|
+
}),
|
|
2338
|
+
Person,
|
|
2339
|
+
);
|
|
2340
|
+
deepStrictEqual(counts(), [2, 3, 1]);
|
|
2341
|
+
|
|
2342
|
+
// The opt-out works for plural accessors too:
|
|
2343
|
+
const optedOuts = await Array.fromAsync((await createCompatibleActivity(
|
|
2344
|
+
[compatibleNoteId],
|
|
2345
|
+
undefined,
|
|
2346
|
+
{ verifyPortableObject: stored },
|
|
2347
|
+
)).getObjects({
|
|
2348
|
+
...options,
|
|
2349
|
+
verifyPortableObject: given,
|
|
2350
|
+
inheritPortableObjectVerifier: false,
|
|
2351
|
+
}));
|
|
2352
|
+
deepStrictEqual(optedOuts.length, 1);
|
|
2353
|
+
ok(getDefaultVerifier(optedOuts[0]) === stored);
|
|
2354
|
+
});
|
|
2355
|
+
|
|
2356
|
+
await t.step("ordinary HTTP(S) responses", async () => {
|
|
2357
|
+
const given = createRecordingVerifier();
|
|
2358
|
+
const stored = createRecordingVerifier();
|
|
2359
|
+
const object = await (await createHttpActivity(plainNoteUrl)).getObject({
|
|
2360
|
+
...options,
|
|
2361
|
+
verifyPortableObject: given,
|
|
2362
|
+
});
|
|
2363
|
+
assertInstanceOf(object, Note);
|
|
2364
|
+
// Fetched as an ordinary object, so nothing is verified, but it uses the
|
|
2365
|
+
// call's verifier for its own references:
|
|
2366
|
+
deepStrictEqual(given.calls.length, 0);
|
|
2367
|
+
ok(getDefaultVerifier(object) === given);
|
|
2368
|
+
assertInstanceOf(await object.getAttribution(secondHop), Person);
|
|
2369
|
+
deepStrictEqual(given.calls.length, 1);
|
|
2370
|
+
// Having inherited a verifier does not mean it was verified:
|
|
2371
|
+
const { referrer } = given.calls[0].options;
|
|
2372
|
+
ok(referrer?.object === object);
|
|
2373
|
+
deepStrictEqual(referrer?.acceptance, undefined);
|
|
2374
|
+
|
|
2375
|
+
const optedOut = await (await Create.fromJsonLd({
|
|
2376
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
2377
|
+
type: "Create",
|
|
2378
|
+
id: plainActivityUrl,
|
|
2379
|
+
object: plainNoteUrl,
|
|
2380
|
+
}, { contextLoader: mockDocumentLoader, verifyPortableObject: stored }))
|
|
2381
|
+
.getObject({
|
|
2382
|
+
...options,
|
|
2383
|
+
verifyPortableObject: given,
|
|
2384
|
+
inheritPortableObjectVerifier: false,
|
|
2385
|
+
});
|
|
2386
|
+
assertInstanceOf(optedOut, Note);
|
|
2387
|
+
ok(getDefaultVerifier(optedOut) === stored);
|
|
2388
|
+
});
|
|
2389
|
+
|
|
2390
|
+
await t.step(
|
|
2391
|
+
"HTTP(S) responses that stand for portable objects",
|
|
2392
|
+
async () => {
|
|
2393
|
+
const redirecting = createRedirectingLoader({
|
|
2394
|
+
[plainNoteUrl]: {
|
|
2395
|
+
documentUrl: compatibleNoteId,
|
|
2396
|
+
document: attributedNote(compatibleNoteId),
|
|
2397
|
+
},
|
|
2398
|
+
});
|
|
2399
|
+
const given = createRecordingVerifier();
|
|
2400
|
+
const object = await (await createHttpActivity(plainNoteUrl)).getObject({
|
|
2401
|
+
documentLoader: redirecting,
|
|
2402
|
+
contextLoader: mockDocumentLoader,
|
|
2403
|
+
verifyPortableObject: given,
|
|
2404
|
+
});
|
|
2405
|
+
assertInstanceOf(object, Note);
|
|
2406
|
+
deepStrictEqual(given.calls.length, 1);
|
|
2407
|
+
ok(getDefaultVerifier(object) === given);
|
|
2408
|
+
|
|
2409
|
+
const optedOut = await (await createHttpActivity(plainNoteUrl)).getObject(
|
|
2410
|
+
{
|
|
2411
|
+
documentLoader: redirecting,
|
|
2412
|
+
contextLoader: mockDocumentLoader,
|
|
2413
|
+
verifyPortableObject: given,
|
|
2414
|
+
inheritPortableObjectVerifier: false,
|
|
2415
|
+
},
|
|
2416
|
+
);
|
|
2417
|
+
assertInstanceOf(optedOut, Note);
|
|
2418
|
+
deepStrictEqual(given.calls.length, 2);
|
|
2419
|
+
ok(getDefaultVerifier(optedOut) == null);
|
|
2420
|
+
},
|
|
2421
|
+
);
|
|
2422
|
+
|
|
2423
|
+
await t.step("objects that were fetched without a verifier", async () => {
|
|
2424
|
+
const activity = await createHttpActivity(compatibleNoteId);
|
|
2425
|
+
const unverified = await activity.getObject(options);
|
|
2426
|
+
assertInstanceOf(unverified, Note);
|
|
2427
|
+
ok(getDefaultVerifier(unverified) == null);
|
|
2428
|
+
// It is not cached, so a later call verifies it and passes its verifier
|
|
2429
|
+
// on:
|
|
2430
|
+
const verifier = createRecordingVerifier();
|
|
2431
|
+
const verified = await activity.getObject({
|
|
2432
|
+
...options,
|
|
2433
|
+
verifyPortableObject: verifier,
|
|
2434
|
+
});
|
|
2435
|
+
assertInstanceOf(verified, Note);
|
|
2436
|
+
ok(verified !== unverified);
|
|
2437
|
+
deepStrictEqual(verifier.calls.length, 1);
|
|
2438
|
+
ok(getDefaultVerifier(verified) === verifier);
|
|
2439
|
+
});
|
|
2440
|
+
|
|
2441
|
+
await t.step("property preprocessors", async () => {
|
|
2442
|
+
const iconUrl = "https://example.com/icon";
|
|
2443
|
+
const loader = createLoader({
|
|
2444
|
+
[iconUrl]: {
|
|
2445
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
2446
|
+
type: "Link",
|
|
2447
|
+
href: "https://example.com/icon.png",
|
|
2448
|
+
},
|
|
2449
|
+
});
|
|
2450
|
+
const createPerson = () =>
|
|
2451
|
+
Person.fromJsonLd({
|
|
2452
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
2453
|
+
id: "https://example.com/person",
|
|
2454
|
+
type: "Person",
|
|
2455
|
+
icon: iconUrl,
|
|
2456
|
+
}, { contextLoader: mockDocumentLoader });
|
|
2457
|
+
const verifier = createVerifier();
|
|
2458
|
+
const icon = await (await createPerson()).getIcon({
|
|
2459
|
+
documentLoader: loader,
|
|
2460
|
+
contextLoader: mockDocumentLoader,
|
|
2461
|
+
verifyPortableObject: verifier,
|
|
2462
|
+
});
|
|
2463
|
+
assertInstanceOf(icon, Image);
|
|
2464
|
+
ok(getDefaultVerifier(icon) === verifier);
|
|
2465
|
+
const optedOut = await (await createPerson()).getIcon({
|
|
2466
|
+
documentLoader: loader,
|
|
2467
|
+
contextLoader: mockDocumentLoader,
|
|
2468
|
+
verifyPortableObject: verifier,
|
|
2469
|
+
inheritPortableObjectVerifier: false,
|
|
2470
|
+
});
|
|
2471
|
+
assertInstanceOf(optedOut, Image);
|
|
2472
|
+
ok(getDefaultVerifier(optedOut) == null);
|
|
2473
|
+
});
|
|
2474
|
+
|
|
2475
|
+
await t.step("embedded objects keep the parent's default", async () => {
|
|
2476
|
+
const embedded = attributedNote(`ap://${did}/objects/2`);
|
|
2477
|
+
const withoutContext: Record<string, unknown> = { ...embedded };
|
|
2478
|
+
delete withoutContext["@context"];
|
|
2479
|
+
for (
|
|
2480
|
+
const [name, value] of [["with @context", embedded], [
|
|
2481
|
+
"without @context",
|
|
2482
|
+
withoutContext,
|
|
2483
|
+
]] as const
|
|
2484
|
+
) {
|
|
2485
|
+
for (const stored of [undefined, createRecordingVerifier()]) {
|
|
2486
|
+
const given = createRecordingVerifier();
|
|
2487
|
+
const activity = await createCompatibleActivity(
|
|
2488
|
+
value,
|
|
2489
|
+
`ap://${did}/activities/1`,
|
|
2490
|
+
stored == null ? {} : { verifyPortableObject: stored },
|
|
2491
|
+
);
|
|
2492
|
+
const object = await activity.getObject({
|
|
2493
|
+
...options,
|
|
2494
|
+
verifyPortableObject: given,
|
|
2495
|
+
});
|
|
2496
|
+
assertInstanceOf(object, Note);
|
|
2497
|
+
// However the object is represented, it keeps the default that it
|
|
2498
|
+
// was parsed with along with its parent:
|
|
2499
|
+
ok(getDefaultVerifier(object) === stored, name);
|
|
2500
|
+
deepStrictEqual(given.calls.length, 0, name);
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
});
|
|
2504
|
+
|
|
2505
|
+
await t.step("cached objects", async () => {
|
|
2506
|
+
const weak = createRecordingVerifier();
|
|
2507
|
+
const strict = createRecordingVerifier();
|
|
2508
|
+
for (const [first, second] of [[weak, strict], [strict, weak]]) {
|
|
2509
|
+
const activity = await createCompatibleActivity(compatibleNoteId);
|
|
2510
|
+
const object = await activity.getObject({
|
|
2511
|
+
...options,
|
|
2512
|
+
verifyPortableObject: first,
|
|
2513
|
+
});
|
|
2514
|
+
assertInstanceOf(object, Note);
|
|
2515
|
+
const before = second.calls.length;
|
|
2516
|
+
// A cached object is returned as is, with the default it got when it
|
|
2517
|
+
// was fetched:
|
|
2518
|
+
const cached = await activity.getObject({
|
|
2519
|
+
...options,
|
|
2520
|
+
verifyPortableObject: second,
|
|
2521
|
+
});
|
|
2522
|
+
ok(cached === object);
|
|
2523
|
+
ok(getDefaultVerifier(cached) === first);
|
|
2524
|
+
deepStrictEqual(second.calls.length, before);
|
|
2525
|
+
}
|
|
2526
|
+
});
|
|
2527
|
+
|
|
2528
|
+
await t.step("traverseCollection()", async () => {
|
|
2529
|
+
const collectionUrl = "https://example.com/collection";
|
|
2530
|
+
const pageUrl = "https://example.com/collection?page=1";
|
|
2531
|
+
const loader = createLoader({
|
|
2532
|
+
...Object.fromEntries(
|
|
2533
|
+
[compatibleNoteId, actorUrl, plainNoteUrl].map((url) => [
|
|
2534
|
+
url,
|
|
2535
|
+
url === plainNoteUrl
|
|
2536
|
+
? { ...attributedNote(plainNoteUrl), id: plainNoteUrl }
|
|
2537
|
+
: url === actorUrl
|
|
2538
|
+
? actor
|
|
2539
|
+
: attributedNote(compatibleNoteId),
|
|
2540
|
+
]),
|
|
2541
|
+
),
|
|
2542
|
+
[pageUrl]: {
|
|
2543
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
2544
|
+
id: pageUrl,
|
|
2545
|
+
type: "OrderedCollectionPage",
|
|
2546
|
+
orderedItems: [
|
|
2547
|
+
{
|
|
2548
|
+
id: "https://example.com/notes/2",
|
|
2549
|
+
type: "Note",
|
|
2550
|
+
attributedTo: actorId,
|
|
2551
|
+
},
|
|
2552
|
+
plainNoteUrl,
|
|
2553
|
+
compatibleNoteId,
|
|
2554
|
+
],
|
|
2555
|
+
},
|
|
2556
|
+
});
|
|
2557
|
+
const createCollection = () =>
|
|
2558
|
+
OrderedCollection.fromJsonLd({
|
|
2559
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
2560
|
+
id: collectionUrl,
|
|
2561
|
+
type: "OrderedCollection",
|
|
2562
|
+
first: pageUrl,
|
|
2563
|
+
}, { contextLoader: mockDocumentLoader });
|
|
2564
|
+
const verifier = createRecordingVerifier();
|
|
2565
|
+
const items = await Array.fromAsync(
|
|
2566
|
+
traverseCollection(await createCollection(), {
|
|
2567
|
+
documentLoader: loader,
|
|
2568
|
+
contextLoader: mockDocumentLoader,
|
|
2569
|
+
verifyPortableObject: verifier,
|
|
2570
|
+
}),
|
|
2571
|
+
);
|
|
2572
|
+
deepStrictEqual(items.length, 3);
|
|
2573
|
+
// Only the item at the compatible identifier is verified:
|
|
2574
|
+
deepStrictEqual(verifier.calls.length, 1);
|
|
2575
|
+
for (const item of items) {
|
|
2576
|
+
assertInstanceOf(item, Note);
|
|
2577
|
+
// Embedded in a fetched page, or fetched themselves:
|
|
2578
|
+
ok(getDefaultVerifier(item) === verifier);
|
|
2579
|
+
assertInstanceOf(
|
|
2580
|
+
await item.getAttribution({
|
|
2581
|
+
documentLoader: loader,
|
|
2582
|
+
contextLoader: mockDocumentLoader,
|
|
2583
|
+
gateways: ["https://gw.example"],
|
|
2584
|
+
}),
|
|
2585
|
+
Person,
|
|
2586
|
+
);
|
|
2587
|
+
}
|
|
2588
|
+
deepStrictEqual(verifier.calls.length, 4);
|
|
2589
|
+
|
|
2590
|
+
const optedOut = await Array.fromAsync(
|
|
2591
|
+
traverseCollection(await createCollection(), {
|
|
2592
|
+
documentLoader: loader,
|
|
2593
|
+
contextLoader: mockDocumentLoader,
|
|
2594
|
+
verifyPortableObject: verifier,
|
|
2595
|
+
inheritPortableObjectVerifier: false,
|
|
2596
|
+
}),
|
|
2192
2597
|
);
|
|
2598
|
+
deepStrictEqual(optedOut.length, 3);
|
|
2599
|
+
for (const item of optedOut) ok(getDefaultVerifier(item) == null);
|
|
2193
2600
|
});
|
|
2194
2601
|
});
|
package/src/lookup.ts
CHANGED
|
@@ -737,10 +737,24 @@ export interface TraverseCollectionOptions {
|
|
|
737
737
|
/**
|
|
738
738
|
* The policy to apply to portable pages and items fetched through
|
|
739
739
|
* gateways, typically `verifyPortableObject()` from `@fedify/fedify`.
|
|
740
|
-
* Portable pages and items are not fetched without it.
|
|
740
|
+
* Portable pages and items are not fetched without it. Pages and items
|
|
741
|
+
* that are fetched use it as their default verifier, unless
|
|
742
|
+
* {@link TraverseCollectionOptions.inheritPortableObjectVerifier} is
|
|
743
|
+
* `false`.
|
|
741
744
|
* @since 2.4.0
|
|
742
745
|
*/
|
|
743
746
|
verifyPortableObject?: PortableObjectVerifier;
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Whether pages and items that are newly fetched use
|
|
750
|
+
* {@link TraverseCollectionOptions.verifyPortableObject} as their default
|
|
751
|
+
* verifier for their property accessors. Defaults to `true`. If `false`,
|
|
752
|
+
* they use the default verifier of the object they are obtained from, if
|
|
753
|
+
* any, instead. See the `inheritPortableObjectVerifier` option of property
|
|
754
|
+
* accessors such as `Collection.getItems()`.
|
|
755
|
+
* @since 2.4.0
|
|
756
|
+
*/
|
|
757
|
+
inheritPortableObjectVerifier?: boolean;
|
|
744
758
|
}
|
|
745
759
|
|
|
746
760
|
/**
|