@fedify/vocab 2.4.0-dev.1504 → 2.4.0-dev.1528
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 +3731 -2360
- package/dist/mod.js +3732 -2361
- package/dist-tests/{actor-B-hIveNP.mjs → actor-BP_hyy_7.mjs} +2 -2
- package/dist-tests/actor.test.mjs +2 -2
- package/dist-tests/lookup.test.mjs +7 -5
- package/dist-tests/type.test.mjs +1 -1
- package/dist-tests/{vocab-XnSGFOqq.mjs → vocab-CQX0Zq_l.mjs} +3727 -2358
- package/dist-tests/vocab.test.mjs +788 -1
- package/package.json +5 -8
- package/scripts/codegen.ts +38 -13
- package/src/lookup.ts +6 -2
- package/src/vocab.test.ts +1132 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Temporal } from "@js-temporal/polyfill";
|
|
2
2
|
globalThis.addEventListener = () => {};
|
|
3
|
-
import { A as QuoteAuthorization, C as Offer, D as Place, E as Person, F as Tombstone, M as Reject, O as Proposal, P as Source, R as vocab_exports, S as Object$1, _ as InteractionPolicy, b as Measure, c as Create, d as Document, f as Endpoints, g as Intent, h as Hashtag, i as Announce, j as QuoteRequest, k as Question, l as CryptographicKey, n as Activity, o as Collection, p as Follow, r as Agreement, s as Commitment, t as Accept, u as Delete, v as InteractionRule, w as OrderedCollectionPage, x as Note, y as Link } from "./vocab-
|
|
3
|
+
import { A as QuoteAuthorization, C as Offer, D as Place, E as Person, F as Tombstone, M as Reject, O as Proposal, P as Source, R as vocab_exports, S as Object$1, _ as InteractionPolicy, b as Measure, c as Create, d as Document, f as Endpoints, g as Intent, h as Hashtag, i as Announce, j as QuoteRequest, k as Question, l as CryptographicKey, n as Activity, o as Collection, p as Follow, r as Agreement, s as Commitment, t as Accept, u as Delete, v as InteractionRule, w as OrderedCollectionPage, x as Note, y as Link } from "./vocab-CQX0Zq_l.mjs";
|
|
4
4
|
import { t as assertInstanceOf } from "./utils-CE8Dk5hm.mjs";
|
|
5
5
|
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
6
6
|
import { deepStrictEqual, notDeepStrictEqual, ok, rejects, throws } from "node:assert/strict";
|
|
@@ -302,6 +302,662 @@ test("Activity.fromJsonLd()", async () => {
|
|
|
302
302
|
deepStrictEqual(proofs[0].proofValue, decodeMultibase("z3sXaxjKs4M3BRicwWA9peyNPJvJqxtGsDmpt1jjoHCjgeUf71TRFz56osPSfDErszyLp5Ks1EhYSgpDaNM977Rg2"));
|
|
303
303
|
deepStrictEqual(proofs[0].created, Temporal.Instant.from("2023-02-24T23:36:38Z"));
|
|
304
304
|
});
|
|
305
|
+
test("fromJsonLd() handles portable ActivityPub IRIs", async () => {
|
|
306
|
+
const did = "did:key:z6Mkabc";
|
|
307
|
+
const portableActor = `ap://${did}/actor?gateways=https%3A%2F%2Fserver.example`;
|
|
308
|
+
const portableObject = `ap://${did}/objects/1?gateways=https%3A%2F%2Fserver.example`;
|
|
309
|
+
const uppercasePortableObject = `AP://${did}/objects/1?gateways=https%3A%2F%2Fserver.example`;
|
|
310
|
+
const portablePage = `ap://${did}/actor/outbox?page=2`;
|
|
311
|
+
const note = await Note.fromJsonLd({
|
|
312
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
313
|
+
type: "Note",
|
|
314
|
+
id: uppercasePortableObject,
|
|
315
|
+
attributedTo: `ap+ef61://${encodeURIComponent(did)}/actor?gateways=https%3A%2F%2Fserver.example`
|
|
316
|
+
}, {
|
|
317
|
+
documentLoader: mockDocumentLoader,
|
|
318
|
+
contextLoader: mockDocumentLoader
|
|
319
|
+
});
|
|
320
|
+
deepStrictEqual(note.id, new URL("ap+ef61://did%3Akey%3Az6Mkabc/objects/1?gateways=https%3A%2F%2Fserver.example"));
|
|
321
|
+
deepStrictEqual(note.attributionId, new URL("ap+ef61://did%3Akey%3Az6Mkabc/actor?gateways=https%3A%2F%2Fserver.example"));
|
|
322
|
+
const noteJson = await note.toJsonLd({ contextLoader: mockDocumentLoader });
|
|
323
|
+
deepStrictEqual(noteJson.type, "Note");
|
|
324
|
+
deepStrictEqual(noteJson.id, "ap+ef61://did:key:z6Mkabc/objects/1?gateways=https%3A%2F%2Fserver.example");
|
|
325
|
+
deepStrictEqual(noteJson.attributedTo, "ap+ef61://did:key:z6Mkabc/actor?gateways=https%3A%2F%2Fserver.example");
|
|
326
|
+
const activity = await Activity.fromJsonLd({
|
|
327
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
328
|
+
type: "Create",
|
|
329
|
+
actor: portableActor,
|
|
330
|
+
object: portableObject
|
|
331
|
+
}, {
|
|
332
|
+
documentLoader: mockDocumentLoader,
|
|
333
|
+
contextLoader: mockDocumentLoader
|
|
334
|
+
});
|
|
335
|
+
deepStrictEqual(activity.actorId, new URL("ap+ef61://did%3Akey%3Az6Mkabc/actor?gateways=https%3A%2F%2Fserver.example"));
|
|
336
|
+
const activityJson = await activity.toJsonLd({ contextLoader: mockDocumentLoader });
|
|
337
|
+
deepStrictEqual(activityJson.type, "Create");
|
|
338
|
+
deepStrictEqual(activityJson.actor, "ap+ef61://did:key:z6Mkabc/actor?gateways=https%3A%2F%2Fserver.example");
|
|
339
|
+
deepStrictEqual(activityJson.object, "ap+ef61://did:key:z6Mkabc/objects/1?gateways=https%3A%2F%2Fserver.example");
|
|
340
|
+
const person = await Person.fromJsonLd({
|
|
341
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
342
|
+
type: "Person",
|
|
343
|
+
inbox: `ap+ef61://${did}/actor/inbox`,
|
|
344
|
+
outbox: `ap+ef61://${did}/actor/outbox`
|
|
345
|
+
}, {
|
|
346
|
+
documentLoader: mockDocumentLoader,
|
|
347
|
+
contextLoader: mockDocumentLoader
|
|
348
|
+
});
|
|
349
|
+
deepStrictEqual(person.inboxId, new URL("ap+ef61://did%3Akey%3Az6Mkabc/actor/inbox"));
|
|
350
|
+
deepStrictEqual(person.outboxId, new URL("ap+ef61://did%3Akey%3Az6Mkabc/actor/outbox"));
|
|
351
|
+
const personJson = await person.toJsonLd({ contextLoader: mockDocumentLoader });
|
|
352
|
+
deepStrictEqual(personJson.type, "Person");
|
|
353
|
+
deepStrictEqual(personJson.inbox, "ap+ef61://did:key:z6Mkabc/actor/inbox");
|
|
354
|
+
deepStrictEqual(personJson.outbox, "ap+ef61://did:key:z6Mkabc/actor/outbox");
|
|
355
|
+
const page = await OrderedCollectionPage.fromJsonLd({
|
|
356
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
357
|
+
type: "OrderedCollectionPage",
|
|
358
|
+
next: portablePage,
|
|
359
|
+
prev: `ap+ef61://${encodeURIComponent(did)}/actor/outbox?page=1`
|
|
360
|
+
}, {
|
|
361
|
+
documentLoader: mockDocumentLoader,
|
|
362
|
+
contextLoader: mockDocumentLoader
|
|
363
|
+
});
|
|
364
|
+
deepStrictEqual(page.nextId, new URL("ap+ef61://did%3Akey%3Az6Mkabc/actor/outbox?page=2"));
|
|
365
|
+
deepStrictEqual(page.prevId, new URL("ap+ef61://did%3Akey%3Az6Mkabc/actor/outbox?page=1"));
|
|
366
|
+
const pageJson = await page.toJsonLd({ contextLoader: mockDocumentLoader });
|
|
367
|
+
deepStrictEqual(pageJson.type, "OrderedCollectionPage");
|
|
368
|
+
deepStrictEqual(pageJson.next, "ap+ef61://did:key:z6Mkabc/actor/outbox?page=2");
|
|
369
|
+
deepStrictEqual(pageJson.prev, "ap+ef61://did:key:z6Mkabc/actor/outbox?page=1");
|
|
370
|
+
});
|
|
371
|
+
test("fromJsonLd() caches text that mentions portable ActivityPub IRIs", async () => {
|
|
372
|
+
const noteJson = {
|
|
373
|
+
"@context": ["https://www.w3.org/ns/activitystreams", { extra: "https://example.com/ns#extra" }],
|
|
374
|
+
type: "Note",
|
|
375
|
+
id: "https://example.com/notes/1",
|
|
376
|
+
content: "This is text about ap://did:key:z6Mkabc/actor.",
|
|
377
|
+
extra: "This extension property should stay cached."
|
|
378
|
+
};
|
|
379
|
+
deepStrictEqual(await (await Note.fromJsonLd(noteJson, {
|
|
380
|
+
documentLoader: mockDocumentLoader,
|
|
381
|
+
contextLoader: mockDocumentLoader
|
|
382
|
+
})).toJsonLd(), noteJson);
|
|
383
|
+
});
|
|
384
|
+
test("fromJsonLd() preserves extensions with portable ActivityPub IRIs", async () => {
|
|
385
|
+
const jsonLd = await (await Note.fromJsonLd({
|
|
386
|
+
"@context": ["https://www.w3.org/ns/activitystreams", { extra: "https://example.com/ns#extra" }],
|
|
387
|
+
type: "Note",
|
|
388
|
+
id: "ap://did:key:z6Mkabc/objects/1",
|
|
389
|
+
extra: "This extension property should stay cached."
|
|
390
|
+
}, {
|
|
391
|
+
documentLoader: mockDocumentLoader,
|
|
392
|
+
contextLoader: mockDocumentLoader
|
|
393
|
+
})).toJsonLd();
|
|
394
|
+
deepStrictEqual(jsonLd.extra, "This extension property should stay cached.");
|
|
395
|
+
deepStrictEqual(jsonLd.id, "ap+ef61://did:key:z6Mkabc/objects/1");
|
|
396
|
+
});
|
|
397
|
+
test("fromJsonLd() preserves unmapped terms with portable IRIs", async () => {
|
|
398
|
+
const jsonLd = await (await Note.fromJsonLd({
|
|
399
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
400
|
+
type: "Note",
|
|
401
|
+
id: "ap://did:key:z6Mkabc/objects/1",
|
|
402
|
+
extra: "This unmapped property should stay cached."
|
|
403
|
+
}, {
|
|
404
|
+
documentLoader: mockDocumentLoader,
|
|
405
|
+
contextLoader: mockDocumentLoader
|
|
406
|
+
})).toJsonLd();
|
|
407
|
+
deepStrictEqual(jsonLd.extra, "This unmapped property should stay cached.");
|
|
408
|
+
deepStrictEqual(jsonLd.id, "ap+ef61://did:key:z6Mkabc/objects/1");
|
|
409
|
+
});
|
|
410
|
+
test("fromJsonLd() preserves expanded arrays with portable IRIs", async () => {
|
|
411
|
+
const expanded = [{
|
|
412
|
+
"@id": "https://example.com/activities/1",
|
|
413
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Create"],
|
|
414
|
+
"https://www.w3.org/ns/activitystreams#actor": [{ "@id": "ap://did:key:z6Mkabc/actor" }],
|
|
415
|
+
"https://www.w3.org/ns/activitystreams#object": [{ "@id": "https://example.com/objects/1" }]
|
|
416
|
+
}, {
|
|
417
|
+
"@id": "https://example.com/objects/1",
|
|
418
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Note"],
|
|
419
|
+
"https://www.w3.org/ns/activitystreams#content": [{ "@value": "Sibling node should stay cached." }]
|
|
420
|
+
}];
|
|
421
|
+
const activity = await Activity.fromJsonLd(expanded, {
|
|
422
|
+
documentLoader: mockDocumentLoader,
|
|
423
|
+
contextLoader: mockDocumentLoader
|
|
424
|
+
});
|
|
425
|
+
deepStrictEqual(activity.actorId, new URL("ap+ef61://did%3Akey%3Az6Mkabc/actor"));
|
|
426
|
+
deepStrictEqual(await activity.toJsonLd(), [{
|
|
427
|
+
"@id": "https://example.com/activities/1",
|
|
428
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Create"],
|
|
429
|
+
"https://www.w3.org/ns/activitystreams#actor": [{ "@id": "ap+ef61://did:key:z6Mkabc/actor" }],
|
|
430
|
+
"https://www.w3.org/ns/activitystreams#object": [{ "@id": "https://example.com/objects/1" }]
|
|
431
|
+
}, {
|
|
432
|
+
"@id": "https://example.com/objects/1",
|
|
433
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Note"],
|
|
434
|
+
"https://www.w3.org/ns/activitystreams#content": [{ "@value": "Sibling node should stay cached." }]
|
|
435
|
+
}]);
|
|
436
|
+
deepStrictEqual(expanded[0]["https://www.w3.org/ns/activitystreams#actor"], [{ "@id": "ap://did:key:z6Mkabc/actor" }]);
|
|
437
|
+
});
|
|
438
|
+
test("fromJsonLd() preserves single-node expanded arrays with portable IRIs", async () => {
|
|
439
|
+
const expanded = [{
|
|
440
|
+
"@id": "ap://did:key:z6Mkabc/objects/1",
|
|
441
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Note"],
|
|
442
|
+
"https://www.w3.org/ns/activitystreams#attributedTo": [{ "@id": "ap://did:key:z6Mkabc/actor" }],
|
|
443
|
+
"https://www.w3.org/ns/activitystreams#content": [{ "@value": "Single expanded node should stay cached as an array." }]
|
|
444
|
+
}];
|
|
445
|
+
deepStrictEqual(await (await Note.fromJsonLd(expanded, {
|
|
446
|
+
documentLoader: mockDocumentLoader,
|
|
447
|
+
contextLoader: mockDocumentLoader
|
|
448
|
+
})).toJsonLd(), [{
|
|
449
|
+
"@id": "ap+ef61://did:key:z6Mkabc/objects/1",
|
|
450
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Note"],
|
|
451
|
+
"https://www.w3.org/ns/activitystreams#attributedTo": [{ "@id": "ap+ef61://did:key:z6Mkabc/actor" }],
|
|
452
|
+
"https://www.w3.org/ns/activitystreams#content": [{ "@value": "Single expanded node should stay cached as an array." }]
|
|
453
|
+
}]);
|
|
454
|
+
deepStrictEqual(expanded[0]["@id"], "ap://did:key:z6Mkabc/objects/1");
|
|
455
|
+
});
|
|
456
|
+
test("fromJsonLd() preserves no-context object shape with portable IRIs", async () => {
|
|
457
|
+
const expanded = {
|
|
458
|
+
"@id": "ap://did:key:z6Mkabc/objects/1",
|
|
459
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Note"],
|
|
460
|
+
"https://www.w3.org/ns/activitystreams#attributedTo": [{ "@id": "ap://did:key:z6Mkabc/actor" }],
|
|
461
|
+
"https://www.w3.org/ns/activitystreams#content": [{ "@value": "No-context object shape should stay cached." }]
|
|
462
|
+
};
|
|
463
|
+
deepStrictEqual(await (await Note.fromJsonLd(expanded, {
|
|
464
|
+
documentLoader: mockDocumentLoader,
|
|
465
|
+
contextLoader: mockDocumentLoader
|
|
466
|
+
})).toJsonLd(), {
|
|
467
|
+
"@id": "ap+ef61://did:key:z6Mkabc/objects/1",
|
|
468
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Note"],
|
|
469
|
+
"https://www.w3.org/ns/activitystreams#attributedTo": [{ "@id": "ap+ef61://did:key:z6Mkabc/actor" }],
|
|
470
|
+
"https://www.w3.org/ns/activitystreams#content": [{ "@value": "No-context object shape should stay cached." }]
|
|
471
|
+
});
|
|
472
|
+
deepStrictEqual(expanded["@id"], "ap://did:key:z6Mkabc/objects/1");
|
|
473
|
+
});
|
|
474
|
+
test("fromJsonLd() preserves expanded subtype cache types", async () => {
|
|
475
|
+
const expanded = [{
|
|
476
|
+
"@id": "https://example.com/activities/1",
|
|
477
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Create"],
|
|
478
|
+
"https://www.w3.org/ns/activitystreams#actor": [{ "@id": "https://example.com/actors/alice" }],
|
|
479
|
+
"https://www.w3.org/ns/activitystreams#object": [{ "@id": "https://example.com/objects/1" }]
|
|
480
|
+
}, {
|
|
481
|
+
"@id": "https://example.com/objects/1",
|
|
482
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Note"],
|
|
483
|
+
"https://www.w3.org/ns/activitystreams#attributedTo": [{ "@id": "ap://did:key:z6Mkabc/actor" }]
|
|
484
|
+
}];
|
|
485
|
+
deepStrictEqual(await (await Activity.fromJsonLd(expanded, {
|
|
486
|
+
documentLoader: mockDocumentLoader,
|
|
487
|
+
contextLoader: mockDocumentLoader
|
|
488
|
+
})).toJsonLd(), [{
|
|
489
|
+
"@id": "https://example.com/activities/1",
|
|
490
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Create"],
|
|
491
|
+
"https://www.w3.org/ns/activitystreams#actor": [{ "@id": "https://example.com/actors/alice" }],
|
|
492
|
+
"https://www.w3.org/ns/activitystreams#object": [{ "@id": "https://example.com/objects/1" }]
|
|
493
|
+
}, {
|
|
494
|
+
"@id": "https://example.com/objects/1",
|
|
495
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Note"],
|
|
496
|
+
"https://www.w3.org/ns/activitystreams#attributedTo": [{ "@id": "ap+ef61://did:key:z6Mkabc/actor" }]
|
|
497
|
+
}]);
|
|
498
|
+
deepStrictEqual(expanded[0]["@type"], ["https://www.w3.org/ns/activitystreams#Create"]);
|
|
499
|
+
});
|
|
500
|
+
test("fromJsonLd() preserves compact array contexts with portable IRIs", async () => {
|
|
501
|
+
deepStrictEqual(await (await Note.fromJsonLd([{
|
|
502
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
503
|
+
type: "Note",
|
|
504
|
+
id: "ap://did:key:z6Mkabc/objects/1"
|
|
505
|
+
}], {
|
|
506
|
+
documentLoader: mockDocumentLoader,
|
|
507
|
+
contextLoader: mockDocumentLoader
|
|
508
|
+
})).toJsonLd(), [{
|
|
509
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
510
|
+
type: "Note",
|
|
511
|
+
id: "ap+ef61://did:key:z6Mkabc/objects/1"
|
|
512
|
+
}]);
|
|
513
|
+
});
|
|
514
|
+
test("fromJsonLd() preserves compact single-item arrays with portable IRIs", async () => {
|
|
515
|
+
const createJson = {
|
|
516
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
517
|
+
type: "Create",
|
|
518
|
+
actor: ["ap://did:key:z6Mkabc/actor"],
|
|
519
|
+
object: "https://example.com/objects/1"
|
|
520
|
+
};
|
|
521
|
+
deepStrictEqual(await (await Create.fromJsonLd(createJson, {
|
|
522
|
+
documentLoader: mockDocumentLoader,
|
|
523
|
+
contextLoader: mockDocumentLoader
|
|
524
|
+
})).toJsonLd(), {
|
|
525
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
526
|
+
type: "Create",
|
|
527
|
+
actor: ["ap+ef61://did:key:z6Mkabc/actor"],
|
|
528
|
+
object: "https://example.com/objects/1"
|
|
529
|
+
});
|
|
530
|
+
deepStrictEqual(createJson.actor, ["ap://did:key:z6Mkabc/actor"]);
|
|
531
|
+
});
|
|
532
|
+
test("fromJsonLd() preserves compact multi-node arrays with portable IRIs", async () => {
|
|
533
|
+
deepStrictEqual(await (await Activity.fromJsonLd([{
|
|
534
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
535
|
+
type: "Create",
|
|
536
|
+
id: "https://example.com/activities/1",
|
|
537
|
+
actor: "https://example.com/actors/alice",
|
|
538
|
+
object: "https://example.com/objects/1"
|
|
539
|
+
}, {
|
|
540
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
541
|
+
type: "Note",
|
|
542
|
+
id: "https://example.com/objects/1",
|
|
543
|
+
attributedTo: "ap://did:key:z6Mkabc/actor"
|
|
544
|
+
}], {
|
|
545
|
+
documentLoader: mockDocumentLoader,
|
|
546
|
+
contextLoader: mockDocumentLoader
|
|
547
|
+
})).toJsonLd(), [{
|
|
548
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
549
|
+
type: "Create",
|
|
550
|
+
id: "https://example.com/activities/1",
|
|
551
|
+
actor: "https://example.com/actors/alice",
|
|
552
|
+
object: "https://example.com/objects/1"
|
|
553
|
+
}, {
|
|
554
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
555
|
+
type: "Note",
|
|
556
|
+
id: "https://example.com/objects/1",
|
|
557
|
+
attributedTo: "ap+ef61://did:key:z6Mkabc/actor"
|
|
558
|
+
}]);
|
|
559
|
+
});
|
|
560
|
+
test("fromJsonLd() preserves nested unmapped terms with portable IRIs", async () => {
|
|
561
|
+
deepStrictEqual(await (await Note.fromJsonLd({
|
|
562
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
563
|
+
type: "Note",
|
|
564
|
+
id: "ap://did:key:z6Mkabc/objects/1",
|
|
565
|
+
attachment: {
|
|
566
|
+
type: "Object",
|
|
567
|
+
name: "Attachment with an unmapped extension.",
|
|
568
|
+
extra: "This nested unmapped property should stay cached."
|
|
569
|
+
}
|
|
570
|
+
}, {
|
|
571
|
+
documentLoader: mockDocumentLoader,
|
|
572
|
+
contextLoader: mockDocumentLoader
|
|
573
|
+
})).toJsonLd(), {
|
|
574
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
575
|
+
type: "Note",
|
|
576
|
+
id: "ap+ef61://did:key:z6Mkabc/objects/1",
|
|
577
|
+
attachment: {
|
|
578
|
+
type: "Object",
|
|
579
|
+
name: "Attachment with an unmapped extension.",
|
|
580
|
+
extra: "This nested unmapped property should stay cached."
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
});
|
|
584
|
+
test("fromJsonLd() preserves compact array item extension contexts with portable IRIs", async () => {
|
|
585
|
+
deepStrictEqual(await (await Activity.fromJsonLd([{
|
|
586
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
587
|
+
type: "Create",
|
|
588
|
+
id: "https://example.com/activities/1",
|
|
589
|
+
actor: "https://example.com/actors/alice",
|
|
590
|
+
object: "https://example.com/objects/1"
|
|
591
|
+
}, {
|
|
592
|
+
"@context": ["https://www.w3.org/ns/activitystreams", { extraRef: {
|
|
593
|
+
"@id": "https://example.com/ns#extraRef",
|
|
594
|
+
"@type": "@id"
|
|
595
|
+
} }],
|
|
596
|
+
type: "Note",
|
|
597
|
+
id: "https://example.com/objects/1",
|
|
598
|
+
extraRef: "ap://did:key:z6Mkabc/extra"
|
|
599
|
+
}], {
|
|
600
|
+
documentLoader: mockDocumentLoader,
|
|
601
|
+
contextLoader: mockDocumentLoader
|
|
602
|
+
})).toJsonLd(), [{
|
|
603
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
604
|
+
type: "Create",
|
|
605
|
+
id: "https://example.com/activities/1",
|
|
606
|
+
actor: "https://example.com/actors/alice",
|
|
607
|
+
object: "https://example.com/objects/1"
|
|
608
|
+
}, {
|
|
609
|
+
"@context": ["https://www.w3.org/ns/activitystreams", { extraRef: {
|
|
610
|
+
"@id": "https://example.com/ns#extraRef",
|
|
611
|
+
"@type": "@id"
|
|
612
|
+
} }],
|
|
613
|
+
type: "Note",
|
|
614
|
+
id: "https://example.com/objects/1",
|
|
615
|
+
extraRef: "ap+ef61://did:key:z6Mkabc/extra"
|
|
616
|
+
}]);
|
|
617
|
+
});
|
|
618
|
+
test("fromJsonLd() formats portable IRIs in JSON-LD containers", async () => {
|
|
619
|
+
const jsonLd = await (await Note.fromJsonLd({
|
|
620
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
621
|
+
type: "Note",
|
|
622
|
+
id: "https://example.com/notes/1",
|
|
623
|
+
attributedTo: { "@list": ["ap://did:key:z6Mkabc/actor"] },
|
|
624
|
+
to: { "@set": ["ap://did:key:z6Mkabc/followers"] }
|
|
625
|
+
}, {
|
|
626
|
+
documentLoader: mockDocumentLoader,
|
|
627
|
+
contextLoader: mockDocumentLoader
|
|
628
|
+
})).toJsonLd();
|
|
629
|
+
deepStrictEqual(jsonLd.attributedTo, { "@list": ["ap+ef61://did:key:z6Mkabc/actor"] });
|
|
630
|
+
deepStrictEqual(jsonLd.to, "ap+ef61://did:key:z6Mkabc/followers");
|
|
631
|
+
});
|
|
632
|
+
test("fromJsonLd() formats portable IRIs hidden behind JSON-LD aliases", async () => {
|
|
633
|
+
const activity = await Activity.fromJsonLd({
|
|
634
|
+
"@context": ["https://www.w3.org/ns/activitystreams", {
|
|
635
|
+
as: {
|
|
636
|
+
"@id": "https://www.w3.org/ns/activitystreams#",
|
|
637
|
+
"@prefix": true
|
|
638
|
+
},
|
|
639
|
+
extra: "https://example.com/ns#extra",
|
|
640
|
+
extraRef: {
|
|
641
|
+
"@id": "https://example.com/ns#extraRef",
|
|
642
|
+
"@type": "@id"
|
|
643
|
+
},
|
|
644
|
+
actorRef: {
|
|
645
|
+
"@id": "as:actor",
|
|
646
|
+
"@type": "@id"
|
|
647
|
+
},
|
|
648
|
+
targetRef: {
|
|
649
|
+
"@id": "as:target",
|
|
650
|
+
"@type": "@id"
|
|
651
|
+
}
|
|
652
|
+
}],
|
|
653
|
+
type: "Create",
|
|
654
|
+
actorRef: "ap://did:key:z6Mkabc/actor",
|
|
655
|
+
object: "https://example.com/objects/1",
|
|
656
|
+
targetRef: "ap://did:key:z6Mkabc/target",
|
|
657
|
+
extra: "This extension property should stay cached.",
|
|
658
|
+
extraRef: "ap://did:key:z6Mkabc/extra"
|
|
659
|
+
}, {
|
|
660
|
+
documentLoader: mockDocumentLoader,
|
|
661
|
+
contextLoader: mockDocumentLoader
|
|
662
|
+
});
|
|
663
|
+
deepStrictEqual(activity.actorId, new URL("ap+ef61://did%3Akey%3Az6Mkabc/actor"));
|
|
664
|
+
deepStrictEqual(activity.targetId, new URL("ap+ef61://did%3Akey%3Az6Mkabc/target"));
|
|
665
|
+
const jsonLd = await activity.toJsonLd({ contextLoader: mockDocumentLoader });
|
|
666
|
+
deepStrictEqual(jsonLd.actor, "ap+ef61://did:key:z6Mkabc/actor");
|
|
667
|
+
deepStrictEqual("actorRef" in jsonLd, false);
|
|
668
|
+
deepStrictEqual(jsonLd.target, "ap+ef61://did:key:z6Mkabc/target");
|
|
669
|
+
deepStrictEqual("targetRef" in jsonLd, false);
|
|
670
|
+
deepStrictEqual(jsonLd.extra, "This extension property should stay cached.");
|
|
671
|
+
deepStrictEqual(jsonLd.extraRef, "ap+ef61://did:key:z6Mkabc/extra");
|
|
672
|
+
});
|
|
673
|
+
test("fromJsonLd() preserves portable IRIs in @id extension terms", async () => {
|
|
674
|
+
deepStrictEqual((await (await Note.fromJsonLd({
|
|
675
|
+
"@context": ["https://www.w3.org/ns/activitystreams", { extraRef: {
|
|
676
|
+
"@id": "https://example.com/ns#extraRef",
|
|
677
|
+
"@type": "@id"
|
|
678
|
+
} }],
|
|
679
|
+
type: "Note",
|
|
680
|
+
id: "https://example.com/notes/1",
|
|
681
|
+
extraRef: "ap://did:key:z6Mkabc/extra"
|
|
682
|
+
}, {
|
|
683
|
+
documentLoader: mockDocumentLoader,
|
|
684
|
+
contextLoader: mockDocumentLoader
|
|
685
|
+
})).toJsonLd({ contextLoader: mockDocumentLoader })).extraRef, "ap+ef61://did:key:z6Mkabc/extra");
|
|
686
|
+
});
|
|
687
|
+
test("fromJsonLd() ignores malformed portable IRIs in extension cache terms", async () => {
|
|
688
|
+
const noteJson = {
|
|
689
|
+
"@context": ["https://www.w3.org/ns/activitystreams", { extraRef: {
|
|
690
|
+
"@id": "https://example.com/ns#extraRef",
|
|
691
|
+
"@type": "@id"
|
|
692
|
+
} }],
|
|
693
|
+
type: "Note",
|
|
694
|
+
id: "https://example.com/notes/1",
|
|
695
|
+
extraRef: "ap://example.com/not-portable"
|
|
696
|
+
};
|
|
697
|
+
deepStrictEqual(await (await Note.fromJsonLd(noteJson, {
|
|
698
|
+
documentLoader: mockDocumentLoader,
|
|
699
|
+
contextLoader: mockDocumentLoader
|
|
700
|
+
})).toJsonLd(), noteJson);
|
|
701
|
+
});
|
|
702
|
+
test("fromJsonLd() preserves portable IRIs in @id typed terms", async () => {
|
|
703
|
+
const jsonLd = await (await Note.fromJsonLd({
|
|
704
|
+
"@context": ["https://www.w3.org/ns/activitystreams", {
|
|
705
|
+
"@vocab": "https://example.com/ns#",
|
|
706
|
+
extraRef: { "@type": "@id" }
|
|
707
|
+
}],
|
|
708
|
+
type: "Note",
|
|
709
|
+
id: "https://example.com/notes/1",
|
|
710
|
+
content: "This text mentions ap://did:key:z6Mkabc/text.",
|
|
711
|
+
extraRef: "ap://did:key:z6Mkabc/extra"
|
|
712
|
+
}, {
|
|
713
|
+
documentLoader: mockDocumentLoader,
|
|
714
|
+
contextLoader: mockDocumentLoader
|
|
715
|
+
})).toJsonLd({ contextLoader: mockDocumentLoader });
|
|
716
|
+
deepStrictEqual(jsonLd.content, "This text mentions ap://did:key:z6Mkabc/text.");
|
|
717
|
+
deepStrictEqual(jsonLd.extraRef, "ap+ef61://did:key:z6Mkabc/extra");
|
|
718
|
+
});
|
|
719
|
+
test("fromJsonLd() preserves portable IRIs hidden behind remote contexts", async () => {
|
|
720
|
+
const contextUrl = "https://example.com/contexts/portable-iris";
|
|
721
|
+
const contextLoader = async (resource, options) => {
|
|
722
|
+
if (resource === contextUrl) return {
|
|
723
|
+
contextUrl: null,
|
|
724
|
+
documentUrl: resource,
|
|
725
|
+
document: { "@context": ["https://www.w3.org/ns/activitystreams", {
|
|
726
|
+
"@vocab": "https://example.com/ns#",
|
|
727
|
+
extra: "https://example.com/ns#extra",
|
|
728
|
+
extraRef: { "@type": "@id" }
|
|
729
|
+
}] }
|
|
730
|
+
};
|
|
731
|
+
return await mockDocumentLoader(resource, options);
|
|
732
|
+
};
|
|
733
|
+
const jsonLd = await (await Note.fromJsonLd({
|
|
734
|
+
"@context": contextUrl,
|
|
735
|
+
type: "Note",
|
|
736
|
+
id: "https://example.com/notes/1",
|
|
737
|
+
content: "This text mentions ap://did:key:z6Mkabc/text.",
|
|
738
|
+
extra: "This extension property should stay cached.",
|
|
739
|
+
extraRef: "ap://did:key:z6Mkabc/extra"
|
|
740
|
+
}, {
|
|
741
|
+
documentLoader: mockDocumentLoader,
|
|
742
|
+
contextLoader
|
|
743
|
+
})).toJsonLd({ contextLoader });
|
|
744
|
+
deepStrictEqual(jsonLd.content, "This text mentions ap://did:key:z6Mkabc/text.");
|
|
745
|
+
deepStrictEqual(jsonLd.extra, "This extension property should stay cached.");
|
|
746
|
+
deepStrictEqual(jsonLd.extraRef, "ap+ef61://did:key:z6Mkabc/extra");
|
|
747
|
+
});
|
|
748
|
+
test("fromJsonLd() formats portable IRIs hidden behind nested remote contexts", async () => {
|
|
749
|
+
const rootContextUrl = "https://example.com/contexts/nested-portable-iris";
|
|
750
|
+
const nestedContextUrl = "https://example.com/contexts/nested-portable-ref";
|
|
751
|
+
const contextLoader = async (resource, options) => {
|
|
752
|
+
if (resource === rootContextUrl) return {
|
|
753
|
+
contextUrl: null,
|
|
754
|
+
documentUrl: resource,
|
|
755
|
+
document: { "@context": ["https://www.w3.org/ns/activitystreams", {
|
|
756
|
+
"@vocab": "https://example.com/ns#",
|
|
757
|
+
extraContainer: "https://example.com/ns#extraContainer"
|
|
758
|
+
}] }
|
|
759
|
+
};
|
|
760
|
+
if (resource === nestedContextUrl) return {
|
|
761
|
+
contextUrl: null,
|
|
762
|
+
documentUrl: resource,
|
|
763
|
+
document: { "@context": {
|
|
764
|
+
"@vocab": "https://example.com/ns#",
|
|
765
|
+
extra: "https://example.com/ns#extra",
|
|
766
|
+
extraRef: { "@type": "@id" }
|
|
767
|
+
} }
|
|
768
|
+
};
|
|
769
|
+
return await mockDocumentLoader(resource, options);
|
|
770
|
+
};
|
|
771
|
+
deepStrictEqual((await (await Note.fromJsonLd({
|
|
772
|
+
"@context": rootContextUrl,
|
|
773
|
+
type: "Note",
|
|
774
|
+
id: "https://example.com/notes/1",
|
|
775
|
+
extraContainer: {
|
|
776
|
+
"@context": nestedContextUrl,
|
|
777
|
+
content: "This text mentions ap://did:key:z6Mkabc/text.",
|
|
778
|
+
extra: "This nested extension object should stay cached.",
|
|
779
|
+
extraRef: "ap://did:key:z6Mkabc/extra"
|
|
780
|
+
}
|
|
781
|
+
}, {
|
|
782
|
+
documentLoader: mockDocumentLoader,
|
|
783
|
+
contextLoader
|
|
784
|
+
})).toJsonLd({ contextLoader })).extraContainer, {
|
|
785
|
+
"@context": nestedContextUrl,
|
|
786
|
+
content: "This text mentions ap://did:key:z6Mkabc/text.",
|
|
787
|
+
extra: "This nested extension object should stay cached.",
|
|
788
|
+
extraRef: { id: "ap+ef61://did:key:z6Mkabc/extra" }
|
|
789
|
+
});
|
|
790
|
+
});
|
|
791
|
+
test("fromJsonLd() formats portable IRIs in sibling remote-context objects", async () => {
|
|
792
|
+
const rootContextUrl = "https://example.com/contexts/sibling-containers";
|
|
793
|
+
const nestedContextUrl = "https://example.com/contexts/sibling-extra-ref";
|
|
794
|
+
const contextLoader = async (resource, options) => {
|
|
795
|
+
if (resource === rootContextUrl) return {
|
|
796
|
+
contextUrl: null,
|
|
797
|
+
documentUrl: resource,
|
|
798
|
+
document: { "@context": ["https://www.w3.org/ns/activitystreams", {
|
|
799
|
+
"@vocab": "https://example.com/ns#",
|
|
800
|
+
firstExtraContainer: "https://example.com/ns#firstExtraContainer",
|
|
801
|
+
secondExtraContainer: "https://example.com/ns#secondExtraContainer"
|
|
802
|
+
}] }
|
|
803
|
+
};
|
|
804
|
+
if (resource === nestedContextUrl) return {
|
|
805
|
+
contextUrl: null,
|
|
806
|
+
documentUrl: resource,
|
|
807
|
+
document: { "@context": {
|
|
808
|
+
"@vocab": "https://example.com/ns#",
|
|
809
|
+
extraRef: { "@type": "@id" }
|
|
810
|
+
} }
|
|
811
|
+
};
|
|
812
|
+
return await mockDocumentLoader(resource, options);
|
|
813
|
+
};
|
|
814
|
+
const jsonLd = await (await Note.fromJsonLd({
|
|
815
|
+
"@context": rootContextUrl,
|
|
816
|
+
type: "Note",
|
|
817
|
+
id: "https://example.com/notes/1",
|
|
818
|
+
firstExtraContainer: {
|
|
819
|
+
"@context": nestedContextUrl,
|
|
820
|
+
content: "No portable IRI here."
|
|
821
|
+
},
|
|
822
|
+
secondExtraContainer: {
|
|
823
|
+
"@context": nestedContextUrl,
|
|
824
|
+
extraRef: "ap://did:key:z6Mkabc/second"
|
|
825
|
+
}
|
|
826
|
+
}, {
|
|
827
|
+
documentLoader: mockDocumentLoader,
|
|
828
|
+
contextLoader
|
|
829
|
+
})).toJsonLd({ contextLoader });
|
|
830
|
+
deepStrictEqual(jsonLd.firstExtraContainer, {
|
|
831
|
+
"@context": nestedContextUrl,
|
|
832
|
+
content: "No portable IRI here."
|
|
833
|
+
});
|
|
834
|
+
deepStrictEqual(jsonLd.secondExtraContainer, {
|
|
835
|
+
"@context": nestedContextUrl,
|
|
836
|
+
extraRef: { id: "ap+ef61://did:key:z6Mkabc/second" }
|
|
837
|
+
});
|
|
838
|
+
});
|
|
839
|
+
test("fromJsonLd() batches unmapped portable IRI term checks", async () => {
|
|
840
|
+
const contextUrl = "https://example.com/contexts/batched-portable-aliases";
|
|
841
|
+
let contextLoads = 0;
|
|
842
|
+
const contextLoader = async (resource, options) => {
|
|
843
|
+
if (resource === contextUrl) {
|
|
844
|
+
contextLoads++;
|
|
845
|
+
return {
|
|
846
|
+
contextUrl: null,
|
|
847
|
+
documentUrl: resource,
|
|
848
|
+
document: { "@context": ["https://www.w3.org/ns/activitystreams", {
|
|
849
|
+
as: {
|
|
850
|
+
"@id": "https://www.w3.org/ns/activitystreams#",
|
|
851
|
+
"@prefix": true
|
|
852
|
+
},
|
|
853
|
+
actorRef0: {
|
|
854
|
+
"@id": "as:actor",
|
|
855
|
+
"@type": "@id"
|
|
856
|
+
},
|
|
857
|
+
actorRef1: {
|
|
858
|
+
"@id": "as:actor",
|
|
859
|
+
"@type": "@id"
|
|
860
|
+
},
|
|
861
|
+
targetRef0: {
|
|
862
|
+
"@id": "as:target",
|
|
863
|
+
"@type": "@id"
|
|
864
|
+
},
|
|
865
|
+
targetRef1: {
|
|
866
|
+
"@id": "as:target",
|
|
867
|
+
"@type": "@id"
|
|
868
|
+
},
|
|
869
|
+
objectRef0: {
|
|
870
|
+
"@id": "as:object",
|
|
871
|
+
"@type": "@id"
|
|
872
|
+
},
|
|
873
|
+
objectRef1: {
|
|
874
|
+
"@id": "as:object",
|
|
875
|
+
"@type": "@id"
|
|
876
|
+
}
|
|
877
|
+
}] }
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
return await mockDocumentLoader(resource, options);
|
|
881
|
+
};
|
|
882
|
+
await (await Activity.fromJsonLd({
|
|
883
|
+
"@context": contextUrl,
|
|
884
|
+
type: "Create",
|
|
885
|
+
actorRef0: "ap://did:key:z6Mkabc/actor0",
|
|
886
|
+
actorRef1: "ap://did:key:z6Mkabc/actor1",
|
|
887
|
+
targetRef0: "ap://did:key:z6Mkabc/target0",
|
|
888
|
+
targetRef1: "ap://did:key:z6Mkabc/target1",
|
|
889
|
+
objectRef0: "https://example.com/objects/0",
|
|
890
|
+
objectRef1: "https://example.com/objects/1"
|
|
891
|
+
}, {
|
|
892
|
+
documentLoader: mockDocumentLoader,
|
|
893
|
+
contextLoader
|
|
894
|
+
})).toJsonLd({ contextLoader });
|
|
895
|
+
ok(contextLoads <= 5);
|
|
896
|
+
});
|
|
897
|
+
test("fromJsonLd() falls back when portable IRI cache merge fails", async () => {
|
|
898
|
+
const contextUrl = "https://example.com/contexts/failing-cache-merge";
|
|
899
|
+
let contextLoads = 0;
|
|
900
|
+
const contextLoader = async (resource, options) => {
|
|
901
|
+
if (resource === contextUrl) {
|
|
902
|
+
contextLoads++;
|
|
903
|
+
if (contextLoads > 1) throw new Error("merge context unavailable");
|
|
904
|
+
return {
|
|
905
|
+
contextUrl: null,
|
|
906
|
+
documentUrl: resource,
|
|
907
|
+
document: { "@context": ["https://www.w3.org/ns/activitystreams", {
|
|
908
|
+
"@vocab": "https://example.com/ns#",
|
|
909
|
+
extraRef: { "@type": "@id" }
|
|
910
|
+
}] }
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
return await mockDocumentLoader(resource, options);
|
|
914
|
+
};
|
|
915
|
+
const jsonLd = await (await Note.fromJsonLd({
|
|
916
|
+
"@context": contextUrl,
|
|
917
|
+
type: "Note",
|
|
918
|
+
id: "ap://did:key:z6Mkabc/objects/1",
|
|
919
|
+
extraRef: "ap://did:key:z6Mkabc/extra"
|
|
920
|
+
}, {
|
|
921
|
+
documentLoader: mockDocumentLoader,
|
|
922
|
+
contextLoader
|
|
923
|
+
})).toJsonLd({ contextLoader: mockDocumentLoader });
|
|
924
|
+
deepStrictEqual(jsonLd.type, "Note");
|
|
925
|
+
deepStrictEqual(jsonLd.id, "ap+ef61://did:key:z6Mkabc/objects/1");
|
|
926
|
+
});
|
|
927
|
+
test("fromJsonLd() formats portable IRIs in scalar URL values", async () => {
|
|
928
|
+
const note = await Note.fromJsonLd({
|
|
929
|
+
"@context": [
|
|
930
|
+
"https://www.w3.org/ns/activitystreams",
|
|
931
|
+
"https://gotosocial.org/ns",
|
|
932
|
+
{ quoteUrl: "as:quoteUrl" }
|
|
933
|
+
],
|
|
934
|
+
type: "Note",
|
|
935
|
+
id: "https://example.com/notes/1",
|
|
936
|
+
quoteUrl: "ap://did:key:z6Mkabc/objects/1"
|
|
937
|
+
}, {
|
|
938
|
+
documentLoader: mockDocumentLoader,
|
|
939
|
+
contextLoader: mockDocumentLoader
|
|
940
|
+
});
|
|
941
|
+
deepStrictEqual(note.quoteUrl, new URL("ap+ef61://did%3Akey%3Az6Mkabc/objects/1"));
|
|
942
|
+
deepStrictEqual((await note.toJsonLd({ contextLoader: mockDocumentLoader })).quoteUrl, "ap+ef61://did:key:z6Mkabc/objects/1");
|
|
943
|
+
});
|
|
944
|
+
test("fromJsonLd() formats portable IRIs in URL value lists", async () => {
|
|
945
|
+
const note = await Note.fromJsonLd({
|
|
946
|
+
"@context": [
|
|
947
|
+
"https://www.w3.org/ns/activitystreams",
|
|
948
|
+
"https://gotosocial.org/ns",
|
|
949
|
+
{ quoteUrl: "as:quoteUrl" }
|
|
950
|
+
],
|
|
951
|
+
type: "Note",
|
|
952
|
+
id: "https://example.com/notes/1",
|
|
953
|
+
quoteUrl: { "@list": [{ "@value": "ap://did:key:z6Mkabc/objects/1" }] }
|
|
954
|
+
}, {
|
|
955
|
+
documentLoader: mockDocumentLoader,
|
|
956
|
+
contextLoader: mockDocumentLoader
|
|
957
|
+
});
|
|
958
|
+
deepStrictEqual(note.quoteUrl, new URL("ap+ef61://did%3Akey%3Az6Mkabc/objects/1"));
|
|
959
|
+
deepStrictEqual((await note.toJsonLd({ contextLoader: mockDocumentLoader })).quoteUrl, { "@list": ["ap+ef61://did:key:z6Mkabc/objects/1"] });
|
|
960
|
+
});
|
|
305
961
|
test({
|
|
306
962
|
name: "Activity.getObject()",
|
|
307
963
|
permissions: {
|
|
@@ -357,6 +1013,34 @@ test({
|
|
|
357
1013
|
});
|
|
358
1014
|
}
|
|
359
1015
|
});
|
|
1016
|
+
test("Activity.getObject() fetches canonical portable IRIs", async () => {
|
|
1017
|
+
const fetchedUrls = [];
|
|
1018
|
+
const documentLoader = async (url) => {
|
|
1019
|
+
fetchedUrls.push(url);
|
|
1020
|
+
return {
|
|
1021
|
+
contextUrl: null,
|
|
1022
|
+
documentUrl: url,
|
|
1023
|
+
document: {
|
|
1024
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1025
|
+
id: url,
|
|
1026
|
+
type: "Note",
|
|
1027
|
+
content: "Fetched portable object"
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
};
|
|
1031
|
+
assertInstanceOf(await (await Activity.fromJsonLd({
|
|
1032
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1033
|
+
type: "Create",
|
|
1034
|
+
object: "ap://did:key:z6Mkabc/objects/1"
|
|
1035
|
+
}, {
|
|
1036
|
+
documentLoader,
|
|
1037
|
+
contextLoader: mockDocumentLoader
|
|
1038
|
+
})).getObject({
|
|
1039
|
+
documentLoader,
|
|
1040
|
+
contextLoader: mockDocumentLoader
|
|
1041
|
+
}), Note);
|
|
1042
|
+
deepStrictEqual(fetchedUrls, ["ap+ef61://did:key:z6Mkabc/objects/1"]);
|
|
1043
|
+
});
|
|
360
1044
|
test({
|
|
361
1045
|
name: "Activity.getObjects()",
|
|
362
1046
|
permissions: {
|
|
@@ -1630,6 +2314,26 @@ test("FEP-fe34: crossOrigin trust behavior", async () => {
|
|
|
1630
2314
|
deepStrictEqual(result?.id, new URL("https://malicious.com/fake-note"));
|
|
1631
2315
|
deepStrictEqual(result?.content, "This is a spoofed note");
|
|
1632
2316
|
});
|
|
2317
|
+
test("FEP-fe34: id-less owners honor crossOrigin trust", async () => {
|
|
2318
|
+
const result = await (await Create.fromJsonLd({
|
|
2319
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
2320
|
+
"@type": "Create",
|
|
2321
|
+
"actor": "https://example.com/actor",
|
|
2322
|
+
"object": {
|
|
2323
|
+
"@type": "Note",
|
|
2324
|
+
"@id": "https://different-origin.com/note",
|
|
2325
|
+
"content": "Embedded note"
|
|
2326
|
+
}
|
|
2327
|
+
})).getObject({
|
|
2328
|
+
crossOrigin: "trust",
|
|
2329
|
+
documentLoader: async (url) => {
|
|
2330
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
2331
|
+
}
|
|
2332
|
+
});
|
|
2333
|
+
assertInstanceOf(result, Note);
|
|
2334
|
+
deepStrictEqual(result.id, new URL("https://different-origin.com/note"));
|
|
2335
|
+
deepStrictEqual(result.content, "Embedded note");
|
|
2336
|
+
});
|
|
1633
2337
|
test("FEP-fe34: Same origin objects are trusted", async () => {
|
|
1634
2338
|
const sameOriginDocumentLoader = async (url) => {
|
|
1635
2339
|
if (url === "https://example.com/note") return {
|
|
@@ -1725,6 +2429,35 @@ test("FEP-fe34: Constructor vs JSON-LD parsing trust difference", async () => {
|
|
|
1725
2429
|
};
|
|
1726
2430
|
deepStrictEqual((await jsonLdCreate.getObject({ documentLoader }))?.content, "Fetched from origin");
|
|
1727
2431
|
});
|
|
2432
|
+
test("FEP-fe34: Portable DID authorities are cross-origin boundaries", async () => {
|
|
2433
|
+
const create = await Create.fromJsonLd({
|
|
2434
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
2435
|
+
"@type": "Create",
|
|
2436
|
+
"@id": "ap://did:key:z6MkOwner/create",
|
|
2437
|
+
"actor": "ap://did:key:z6MkOwner/actor",
|
|
2438
|
+
"object": {
|
|
2439
|
+
"@type": "Note",
|
|
2440
|
+
"@id": "ap://did:key:z6MkOther/note",
|
|
2441
|
+
"content": "Embedded portable note"
|
|
2442
|
+
}
|
|
2443
|
+
});
|
|
2444
|
+
const documentLoader = async (url) => {
|
|
2445
|
+
if (url === "ap+ef61://did:key:z6MkOther/note") return {
|
|
2446
|
+
documentUrl: url,
|
|
2447
|
+
contextUrl: null,
|
|
2448
|
+
document: {
|
|
2449
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
2450
|
+
"@type": "Note",
|
|
2451
|
+
"@id": "ap://did:key:z6MkOther/note",
|
|
2452
|
+
"content": "Fetched portable note"
|
|
2453
|
+
}
|
|
2454
|
+
};
|
|
2455
|
+
throw new Error("Document not found");
|
|
2456
|
+
};
|
|
2457
|
+
const result = await create.getObject({ documentLoader });
|
|
2458
|
+
assertInstanceOf(result, Note);
|
|
2459
|
+
deepStrictEqual(result.content, "Fetched portable note");
|
|
2460
|
+
});
|
|
1728
2461
|
test("FEP-fe34: Array properties respect cross-origin policy", async () => {
|
|
1729
2462
|
const crossOriginDocumentLoader = async (url) => {
|
|
1730
2463
|
if (url === "https://different-origin.com/note1") return {
|
|
@@ -1798,6 +2531,60 @@ test("FEP-fe34: Array properties with crossOrigin trust option", async () => {
|
|
|
1798
2531
|
deepStrictEqual(items[0].content, "Fake note 1");
|
|
1799
2532
|
deepStrictEqual(items[1].content, "Legitimate note 2");
|
|
1800
2533
|
});
|
|
2534
|
+
test("FEP-fe34: id-less arrays honor crossOrigin trust", async () => {
|
|
2535
|
+
const collection = await Collection.fromJsonLd({
|
|
2536
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
2537
|
+
"@type": "Collection",
|
|
2538
|
+
"items": [{
|
|
2539
|
+
"@type": "Note",
|
|
2540
|
+
"@id": "https://different-origin.com/note1",
|
|
2541
|
+
"content": "Embedded note 1"
|
|
2542
|
+
}, {
|
|
2543
|
+
"@type": "Note",
|
|
2544
|
+
"@id": "https://different-origin.com/note2",
|
|
2545
|
+
"content": "Embedded note 2"
|
|
2546
|
+
}]
|
|
2547
|
+
});
|
|
2548
|
+
const items = [];
|
|
2549
|
+
for await (const item of collection.getItems({
|
|
2550
|
+
crossOrigin: "trust",
|
|
2551
|
+
documentLoader: async (url) => {
|
|
2552
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
2553
|
+
}
|
|
2554
|
+
})) items.push(item);
|
|
2555
|
+
deepStrictEqual(items.length, 2);
|
|
2556
|
+
assertInstanceOf(items[0], Note);
|
|
2557
|
+
assertInstanceOf(items[1], Note);
|
|
2558
|
+
deepStrictEqual(items[0].content, "Embedded note 1");
|
|
2559
|
+
deepStrictEqual(items[1].content, "Embedded note 2");
|
|
2560
|
+
});
|
|
2561
|
+
test("FEP-fe34: Array properties track trust per item", async () => {
|
|
2562
|
+
const collection = new Collection({
|
|
2563
|
+
id: new URL("https://example.com/collection"),
|
|
2564
|
+
items: [new Note({
|
|
2565
|
+
id: new URL("https://malicious.com/fake-note1"),
|
|
2566
|
+
content: "Trusted constructor note 1"
|
|
2567
|
+
}), new URL("https://different-origin.com/note2")]
|
|
2568
|
+
});
|
|
2569
|
+
const documentLoader = async (url) => {
|
|
2570
|
+
if (url === "https://different-origin.com/note2") return {
|
|
2571
|
+
documentUrl: url,
|
|
2572
|
+
contextUrl: null,
|
|
2573
|
+
document: {
|
|
2574
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
2575
|
+
"@type": "Note",
|
|
2576
|
+
"@id": "https://malicious.com/fake-note2",
|
|
2577
|
+
"content": "Untrusted fetched note 2"
|
|
2578
|
+
}
|
|
2579
|
+
};
|
|
2580
|
+
throw new Error("Document not found");
|
|
2581
|
+
};
|
|
2582
|
+
const items = [];
|
|
2583
|
+
for await (const item of collection.getItems({ documentLoader })) items.push(item);
|
|
2584
|
+
deepStrictEqual(items.length, 1);
|
|
2585
|
+
assertInstanceOf(items[0], Note);
|
|
2586
|
+
deepStrictEqual(items[0].content, "Trusted constructor note 1");
|
|
2587
|
+
});
|
|
1801
2588
|
test("FEP-fe34: Embedded objects in arrays from JSON-LD respect cross-origin policy", async () => {
|
|
1802
2589
|
const collectionDocumentLoader = async (url) => {
|
|
1803
2590
|
if (url === "https://example.com/collection") return {
|