@fedify/vocab 2.4.0-dev.1570 → 2.4.0-dev.1571
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 +13961 -10045
- package/dist/mod.d.cts +1618 -665
- package/dist/mod.d.ts +1618 -665
- package/dist/mod.js +13959 -10047
- package/dist-tests/{actor-BTMC-aMT.mjs → actor-DbccxbiS.mjs} +2 -2
- package/dist-tests/actor.test.mjs +2 -2
- package/dist-tests/application.yaml +13 -0
- package/dist-tests/featureauthorization.yaml +33 -0
- package/dist-tests/featuredcollection.yaml +41 -0
- package/dist-tests/featureditem.yaml +34 -0
- package/dist-tests/featurerequest.yaml +21 -0
- package/dist-tests/group.yaml +13 -0
- package/dist-tests/interactionpolicy.yaml +16 -1
- package/dist-tests/lookup.test.mjs +7 -5
- package/dist-tests/organization.yaml +13 -0
- package/dist-tests/person.yaml +13 -0
- package/dist-tests/service.yaml +13 -0
- package/dist-tests/type.test.mjs +1 -1
- package/dist-tests/{vocab-XnSGFOqq.mjs → vocab-DOHLQ4UQ.mjs} +13958 -10044
- package/dist-tests/vocab.test.mjs +986 -1
- package/package.json +4 -4
- package/src/__snapshots__/vocab.test.ts.snap +609 -0
- package/src/application.yaml +13 -0
- package/src/featureauthorization.yaml +33 -0
- package/src/featuredcollection.yaml +41 -0
- package/src/featureditem.yaml +34 -0
- package/src/featurerequest.yaml +21 -0
- package/src/group.yaml +13 -0
- package/src/interactionpolicy.yaml +16 -1
- package/src/lookup.ts +6 -2
- package/src/organization.yaml +13 -0
- package/src/person.yaml +13 -0
- package/src/service.yaml +13 -0
- package/src/vocab.test.ts +1534 -116
package/src/vocab.test.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
2
2
|
import {
|
|
3
3
|
decodeMultibase,
|
|
4
|
+
type DocumentLoader,
|
|
4
5
|
LanguageString,
|
|
5
6
|
parseDecimal,
|
|
7
|
+
type RemoteDocument,
|
|
6
8
|
} from "@fedify/vocab-runtime";
|
|
7
9
|
import {
|
|
8
10
|
areAllScalarTypes,
|
|
@@ -34,6 +36,10 @@ import {
|
|
|
34
36
|
Delete,
|
|
35
37
|
Document,
|
|
36
38
|
Endpoints,
|
|
39
|
+
FeatureAuthorization,
|
|
40
|
+
FeaturedCollection,
|
|
41
|
+
FeaturedItem,
|
|
42
|
+
FeatureRequest,
|
|
37
43
|
Follow,
|
|
38
44
|
Hashtag,
|
|
39
45
|
Intent,
|
|
@@ -102,6 +108,23 @@ const QUOTE_REQUEST_CONTEXT = [
|
|
|
102
108
|
},
|
|
103
109
|
] as const;
|
|
104
110
|
|
|
111
|
+
const FEATURE_CONTEXT = [
|
|
112
|
+
"https://www.w3.org/ns/activitystreams",
|
|
113
|
+
"https://w3id.org/security/data-integrity/v1",
|
|
114
|
+
"https://gotosocial.org/ns",
|
|
115
|
+
"https://w3id.org/fep/7aa9",
|
|
116
|
+
] as const;
|
|
117
|
+
|
|
118
|
+
const FEATURED_COLLECTION_CONTEXT = [
|
|
119
|
+
...FEATURE_CONTEXT,
|
|
120
|
+
{
|
|
121
|
+
Hashtag: "as:Hashtag",
|
|
122
|
+
discoverable: "toot:discoverable",
|
|
123
|
+
sensitive: "as:sensitive",
|
|
124
|
+
toot: "http://joinmastodon.org/ns#",
|
|
125
|
+
},
|
|
126
|
+
] as const;
|
|
127
|
+
|
|
105
128
|
const DELETE_QUOTE_REQUEST_CONTEXT = [
|
|
106
129
|
"https://w3id.org/identity/v1",
|
|
107
130
|
"https://www.w3.org/ns/activitystreams",
|
|
@@ -474,9 +497,969 @@ test("Activity.fromJsonLd()", async () => {
|
|
|
474
497
|
),
|
|
475
498
|
);
|
|
476
499
|
deepStrictEqual(
|
|
477
|
-
proofs[0].created,
|
|
478
|
-
Temporal.Instant.from("2023-02-24T23:36:38Z"),
|
|
500
|
+
proofs[0].created,
|
|
501
|
+
Temporal.Instant.from("2023-02-24T23:36:38Z"),
|
|
502
|
+
);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
test("fromJsonLd() handles portable ActivityPub IRIs", async () => {
|
|
506
|
+
const did = "did:key:z6Mkabc";
|
|
507
|
+
const portableActor =
|
|
508
|
+
`ap://${did}/actor?gateways=https%3A%2F%2Fserver.example`;
|
|
509
|
+
const portableObject =
|
|
510
|
+
`ap://${did}/objects/1?gateways=https%3A%2F%2Fserver.example`;
|
|
511
|
+
const uppercasePortableObject =
|
|
512
|
+
`AP://${did}/objects/1?gateways=https%3A%2F%2Fserver.example`;
|
|
513
|
+
const portablePage = `ap://${did}/actor/outbox?page=2`;
|
|
514
|
+
|
|
515
|
+
const note = await Note.fromJsonLd({
|
|
516
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
517
|
+
type: "Note",
|
|
518
|
+
id: uppercasePortableObject,
|
|
519
|
+
attributedTo: `ap+ef61://${
|
|
520
|
+
encodeURIComponent(did)
|
|
521
|
+
}/actor?gateways=https%3A%2F%2Fserver.example`,
|
|
522
|
+
}, { documentLoader: mockDocumentLoader, contextLoader: mockDocumentLoader });
|
|
523
|
+
deepStrictEqual(
|
|
524
|
+
note.id,
|
|
525
|
+
new URL(
|
|
526
|
+
"ap+ef61://did%3Akey%3Az6Mkabc/objects/1?gateways=https%3A%2F%2Fserver.example",
|
|
527
|
+
),
|
|
528
|
+
);
|
|
529
|
+
deepStrictEqual(
|
|
530
|
+
note.attributionId,
|
|
531
|
+
new URL(
|
|
532
|
+
"ap+ef61://did%3Akey%3Az6Mkabc/actor?gateways=https%3A%2F%2Fserver.example",
|
|
533
|
+
),
|
|
534
|
+
);
|
|
535
|
+
const noteJson = await note.toJsonLd({
|
|
536
|
+
contextLoader: mockDocumentLoader,
|
|
537
|
+
}) as Record<string, unknown>;
|
|
538
|
+
deepStrictEqual(noteJson.type, "Note");
|
|
539
|
+
deepStrictEqual(
|
|
540
|
+
noteJson.id,
|
|
541
|
+
"ap+ef61://did:key:z6Mkabc/objects/1?gateways=https%3A%2F%2Fserver.example",
|
|
542
|
+
);
|
|
543
|
+
deepStrictEqual(
|
|
544
|
+
noteJson.attributedTo,
|
|
545
|
+
"ap+ef61://did:key:z6Mkabc/actor?gateways=https%3A%2F%2Fserver.example",
|
|
546
|
+
);
|
|
547
|
+
|
|
548
|
+
const activity = await Activity.fromJsonLd({
|
|
549
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
550
|
+
type: "Create",
|
|
551
|
+
actor: portableActor,
|
|
552
|
+
object: portableObject,
|
|
553
|
+
}, { documentLoader: mockDocumentLoader, contextLoader: mockDocumentLoader });
|
|
554
|
+
deepStrictEqual(
|
|
555
|
+
activity.actorId,
|
|
556
|
+
new URL(
|
|
557
|
+
"ap+ef61://did%3Akey%3Az6Mkabc/actor?gateways=https%3A%2F%2Fserver.example",
|
|
558
|
+
),
|
|
559
|
+
);
|
|
560
|
+
const activityJson = await activity.toJsonLd({
|
|
561
|
+
contextLoader: mockDocumentLoader,
|
|
562
|
+
}) as Record<string, unknown>;
|
|
563
|
+
deepStrictEqual(activityJson.type, "Create");
|
|
564
|
+
deepStrictEqual(
|
|
565
|
+
activityJson.actor,
|
|
566
|
+
"ap+ef61://did:key:z6Mkabc/actor?gateways=https%3A%2F%2Fserver.example",
|
|
567
|
+
);
|
|
568
|
+
deepStrictEqual(
|
|
569
|
+
activityJson.object,
|
|
570
|
+
"ap+ef61://did:key:z6Mkabc/objects/1?gateways=https%3A%2F%2Fserver.example",
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
const person = await Person.fromJsonLd({
|
|
574
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
575
|
+
type: "Person",
|
|
576
|
+
inbox: `ap+ef61://${did}/actor/inbox`,
|
|
577
|
+
outbox: `ap+ef61://${did}/actor/outbox`,
|
|
578
|
+
}, { documentLoader: mockDocumentLoader, contextLoader: mockDocumentLoader });
|
|
579
|
+
deepStrictEqual(
|
|
580
|
+
person.inboxId,
|
|
581
|
+
new URL("ap+ef61://did%3Akey%3Az6Mkabc/actor/inbox"),
|
|
582
|
+
);
|
|
583
|
+
deepStrictEqual(
|
|
584
|
+
person.outboxId,
|
|
585
|
+
new URL("ap+ef61://did%3Akey%3Az6Mkabc/actor/outbox"),
|
|
586
|
+
);
|
|
587
|
+
const personJson = await person.toJsonLd({
|
|
588
|
+
contextLoader: mockDocumentLoader,
|
|
589
|
+
}) as Record<string, unknown>;
|
|
590
|
+
deepStrictEqual(personJson.type, "Person");
|
|
591
|
+
deepStrictEqual(personJson.inbox, "ap+ef61://did:key:z6Mkabc/actor/inbox");
|
|
592
|
+
deepStrictEqual(
|
|
593
|
+
personJson.outbox,
|
|
594
|
+
"ap+ef61://did:key:z6Mkabc/actor/outbox",
|
|
595
|
+
);
|
|
596
|
+
|
|
597
|
+
const page = await OrderedCollectionPage.fromJsonLd({
|
|
598
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
599
|
+
type: "OrderedCollectionPage",
|
|
600
|
+
next: portablePage,
|
|
601
|
+
prev: `ap+ef61://${encodeURIComponent(did)}/actor/outbox?page=1`,
|
|
602
|
+
}, { documentLoader: mockDocumentLoader, contextLoader: mockDocumentLoader });
|
|
603
|
+
deepStrictEqual(
|
|
604
|
+
page.nextId,
|
|
605
|
+
new URL("ap+ef61://did%3Akey%3Az6Mkabc/actor/outbox?page=2"),
|
|
606
|
+
);
|
|
607
|
+
deepStrictEqual(
|
|
608
|
+
page.prevId,
|
|
609
|
+
new URL("ap+ef61://did%3Akey%3Az6Mkabc/actor/outbox?page=1"),
|
|
610
|
+
);
|
|
611
|
+
const pageJson = await page.toJsonLd({
|
|
612
|
+
contextLoader: mockDocumentLoader,
|
|
613
|
+
}) as Record<string, unknown>;
|
|
614
|
+
deepStrictEqual(pageJson.type, "OrderedCollectionPage");
|
|
615
|
+
deepStrictEqual(
|
|
616
|
+
pageJson.next,
|
|
617
|
+
"ap+ef61://did:key:z6Mkabc/actor/outbox?page=2",
|
|
618
|
+
);
|
|
619
|
+
deepStrictEqual(
|
|
620
|
+
pageJson.prev,
|
|
621
|
+
"ap+ef61://did:key:z6Mkabc/actor/outbox?page=1",
|
|
622
|
+
);
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
test("fromJsonLd() caches text that mentions portable ActivityPub IRIs", async () => {
|
|
626
|
+
const noteJson = {
|
|
627
|
+
"@context": [
|
|
628
|
+
"https://www.w3.org/ns/activitystreams",
|
|
629
|
+
{ extra: "https://example.com/ns#extra" },
|
|
630
|
+
],
|
|
631
|
+
type: "Note",
|
|
632
|
+
id: "https://example.com/notes/1",
|
|
633
|
+
content: "This is text about ap://did:key:z6Mkabc/actor.",
|
|
634
|
+
extra: "This extension property should stay cached.",
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
const note = await Note.fromJsonLd(noteJson, {
|
|
638
|
+
documentLoader: mockDocumentLoader,
|
|
639
|
+
contextLoader: mockDocumentLoader,
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
deepStrictEqual(await note.toJsonLd(), noteJson);
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
test("fromJsonLd() preserves extensions with portable ActivityPub IRIs", async () => {
|
|
646
|
+
const note = await Note.fromJsonLd({
|
|
647
|
+
"@context": [
|
|
648
|
+
"https://www.w3.org/ns/activitystreams",
|
|
649
|
+
{ extra: "https://example.com/ns#extra" },
|
|
650
|
+
],
|
|
651
|
+
type: "Note",
|
|
652
|
+
id: "ap://did:key:z6Mkabc/objects/1",
|
|
653
|
+
extra: "This extension property should stay cached.",
|
|
654
|
+
}, { documentLoader: mockDocumentLoader, contextLoader: mockDocumentLoader });
|
|
655
|
+
|
|
656
|
+
const jsonLd = await note.toJsonLd() as Record<string, unknown>;
|
|
657
|
+
deepStrictEqual(jsonLd.extra, "This extension property should stay cached.");
|
|
658
|
+
deepStrictEqual(jsonLd.id, "ap+ef61://did:key:z6Mkabc/objects/1");
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
test("fromJsonLd() preserves unmapped terms with portable IRIs", async () => {
|
|
662
|
+
const note = await Note.fromJsonLd({
|
|
663
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
664
|
+
type: "Note",
|
|
665
|
+
id: "ap://did:key:z6Mkabc/objects/1",
|
|
666
|
+
extra: "This unmapped property should stay cached.",
|
|
667
|
+
}, { documentLoader: mockDocumentLoader, contextLoader: mockDocumentLoader });
|
|
668
|
+
|
|
669
|
+
const jsonLd = await note.toJsonLd() as Record<string, unknown>;
|
|
670
|
+
deepStrictEqual(
|
|
671
|
+
jsonLd.extra,
|
|
672
|
+
"This unmapped property should stay cached.",
|
|
673
|
+
);
|
|
674
|
+
deepStrictEqual(jsonLd.id, "ap+ef61://did:key:z6Mkabc/objects/1");
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
test("fromJsonLd() preserves expanded arrays with portable IRIs", async () => {
|
|
678
|
+
const expanded = [
|
|
679
|
+
{
|
|
680
|
+
"@id": "https://example.com/activities/1",
|
|
681
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Create"],
|
|
682
|
+
"https://www.w3.org/ns/activitystreams#actor": [
|
|
683
|
+
{ "@id": "ap://did:key:z6Mkabc/actor" },
|
|
684
|
+
],
|
|
685
|
+
"https://www.w3.org/ns/activitystreams#object": [
|
|
686
|
+
{ "@id": "https://example.com/objects/1" },
|
|
687
|
+
],
|
|
688
|
+
},
|
|
689
|
+
{
|
|
690
|
+
"@id": "https://example.com/objects/1",
|
|
691
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Note"],
|
|
692
|
+
"https://www.w3.org/ns/activitystreams#content": [
|
|
693
|
+
{ "@value": "Sibling node should stay cached." },
|
|
694
|
+
],
|
|
695
|
+
},
|
|
696
|
+
];
|
|
697
|
+
|
|
698
|
+
const activity = await Activity.fromJsonLd(expanded, {
|
|
699
|
+
documentLoader: mockDocumentLoader,
|
|
700
|
+
contextLoader: mockDocumentLoader,
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
deepStrictEqual(
|
|
704
|
+
activity.actorId,
|
|
705
|
+
new URL("ap+ef61://did%3Akey%3Az6Mkabc/actor"),
|
|
706
|
+
);
|
|
707
|
+
deepStrictEqual(await activity.toJsonLd(), [
|
|
708
|
+
{
|
|
709
|
+
"@id": "https://example.com/activities/1",
|
|
710
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Create"],
|
|
711
|
+
"https://www.w3.org/ns/activitystreams#actor": [
|
|
712
|
+
{ "@id": "ap+ef61://did:key:z6Mkabc/actor" },
|
|
713
|
+
],
|
|
714
|
+
"https://www.w3.org/ns/activitystreams#object": [
|
|
715
|
+
{ "@id": "https://example.com/objects/1" },
|
|
716
|
+
],
|
|
717
|
+
},
|
|
718
|
+
{
|
|
719
|
+
"@id": "https://example.com/objects/1",
|
|
720
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Note"],
|
|
721
|
+
"https://www.w3.org/ns/activitystreams#content": [
|
|
722
|
+
{ "@value": "Sibling node should stay cached." },
|
|
723
|
+
],
|
|
724
|
+
},
|
|
725
|
+
]);
|
|
726
|
+
deepStrictEqual(expanded[0]["https://www.w3.org/ns/activitystreams#actor"], [
|
|
727
|
+
{ "@id": "ap://did:key:z6Mkabc/actor" },
|
|
728
|
+
]);
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
test("fromJsonLd() preserves single-node expanded arrays with portable IRIs", async () => {
|
|
732
|
+
const expanded = [
|
|
733
|
+
{
|
|
734
|
+
"@id": "ap://did:key:z6Mkabc/objects/1",
|
|
735
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Note"],
|
|
736
|
+
"https://www.w3.org/ns/activitystreams#attributedTo": [
|
|
737
|
+
{ "@id": "ap://did:key:z6Mkabc/actor" },
|
|
738
|
+
],
|
|
739
|
+
"https://www.w3.org/ns/activitystreams#content": [
|
|
740
|
+
{ "@value": "Single expanded node should stay cached as an array." },
|
|
741
|
+
],
|
|
742
|
+
},
|
|
743
|
+
];
|
|
744
|
+
|
|
745
|
+
const note = await Note.fromJsonLd(expanded, {
|
|
746
|
+
documentLoader: mockDocumentLoader,
|
|
747
|
+
contextLoader: mockDocumentLoader,
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
deepStrictEqual(await note.toJsonLd(), [
|
|
751
|
+
{
|
|
752
|
+
"@id": "ap+ef61://did:key:z6Mkabc/objects/1",
|
|
753
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Note"],
|
|
754
|
+
"https://www.w3.org/ns/activitystreams#attributedTo": [
|
|
755
|
+
{ "@id": "ap+ef61://did:key:z6Mkabc/actor" },
|
|
756
|
+
],
|
|
757
|
+
"https://www.w3.org/ns/activitystreams#content": [
|
|
758
|
+
{ "@value": "Single expanded node should stay cached as an array." },
|
|
759
|
+
],
|
|
760
|
+
},
|
|
761
|
+
]);
|
|
762
|
+
deepStrictEqual(expanded[0]["@id"], "ap://did:key:z6Mkabc/objects/1");
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
test("fromJsonLd() preserves no-context object shape with portable IRIs", async () => {
|
|
766
|
+
const expanded = {
|
|
767
|
+
"@id": "ap://did:key:z6Mkabc/objects/1",
|
|
768
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Note"],
|
|
769
|
+
"https://www.w3.org/ns/activitystreams#attributedTo": [
|
|
770
|
+
{ "@id": "ap://did:key:z6Mkabc/actor" },
|
|
771
|
+
],
|
|
772
|
+
"https://www.w3.org/ns/activitystreams#content": [
|
|
773
|
+
{ "@value": "No-context object shape should stay cached." },
|
|
774
|
+
],
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
const note = await Note.fromJsonLd(expanded, {
|
|
778
|
+
documentLoader: mockDocumentLoader,
|
|
779
|
+
contextLoader: mockDocumentLoader,
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
deepStrictEqual(await note.toJsonLd(), {
|
|
783
|
+
"@id": "ap+ef61://did:key:z6Mkabc/objects/1",
|
|
784
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Note"],
|
|
785
|
+
"https://www.w3.org/ns/activitystreams#attributedTo": [
|
|
786
|
+
{ "@id": "ap+ef61://did:key:z6Mkabc/actor" },
|
|
787
|
+
],
|
|
788
|
+
"https://www.w3.org/ns/activitystreams#content": [
|
|
789
|
+
{ "@value": "No-context object shape should stay cached." },
|
|
790
|
+
],
|
|
791
|
+
});
|
|
792
|
+
deepStrictEqual(expanded["@id"], "ap://did:key:z6Mkabc/objects/1");
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
test("fromJsonLd() preserves expanded subtype cache types", async () => {
|
|
796
|
+
const expanded = [
|
|
797
|
+
{
|
|
798
|
+
"@id": "https://example.com/activities/1",
|
|
799
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Create"],
|
|
800
|
+
"https://www.w3.org/ns/activitystreams#actor": [
|
|
801
|
+
{ "@id": "https://example.com/actors/alice" },
|
|
802
|
+
],
|
|
803
|
+
"https://www.w3.org/ns/activitystreams#object": [
|
|
804
|
+
{ "@id": "https://example.com/objects/1" },
|
|
805
|
+
],
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
"@id": "https://example.com/objects/1",
|
|
809
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Note"],
|
|
810
|
+
"https://www.w3.org/ns/activitystreams#attributedTo": [
|
|
811
|
+
{ "@id": "ap://did:key:z6Mkabc/actor" },
|
|
812
|
+
],
|
|
813
|
+
},
|
|
814
|
+
];
|
|
815
|
+
|
|
816
|
+
const activity = await Activity.fromJsonLd(expanded, {
|
|
817
|
+
documentLoader: mockDocumentLoader,
|
|
818
|
+
contextLoader: mockDocumentLoader,
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
deepStrictEqual(await activity.toJsonLd(), [
|
|
822
|
+
{
|
|
823
|
+
"@id": "https://example.com/activities/1",
|
|
824
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Create"],
|
|
825
|
+
"https://www.w3.org/ns/activitystreams#actor": [
|
|
826
|
+
{ "@id": "https://example.com/actors/alice" },
|
|
827
|
+
],
|
|
828
|
+
"https://www.w3.org/ns/activitystreams#object": [
|
|
829
|
+
{ "@id": "https://example.com/objects/1" },
|
|
830
|
+
],
|
|
831
|
+
},
|
|
832
|
+
{
|
|
833
|
+
"@id": "https://example.com/objects/1",
|
|
834
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Note"],
|
|
835
|
+
"https://www.w3.org/ns/activitystreams#attributedTo": [
|
|
836
|
+
{ "@id": "ap+ef61://did:key:z6Mkabc/actor" },
|
|
837
|
+
],
|
|
838
|
+
},
|
|
839
|
+
]);
|
|
840
|
+
deepStrictEqual(expanded[0]["@type"], [
|
|
841
|
+
"https://www.w3.org/ns/activitystreams#Create",
|
|
842
|
+
]);
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
test("fromJsonLd() preserves compact array contexts with portable IRIs", async () => {
|
|
846
|
+
const note = await Note.fromJsonLd([{
|
|
847
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
848
|
+
type: "Note",
|
|
849
|
+
id: "ap://did:key:z6Mkabc/objects/1",
|
|
850
|
+
}], {
|
|
851
|
+
documentLoader: mockDocumentLoader,
|
|
852
|
+
contextLoader: mockDocumentLoader,
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
deepStrictEqual(await note.toJsonLd(), [{
|
|
856
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
857
|
+
type: "Note",
|
|
858
|
+
id: "ap+ef61://did:key:z6Mkabc/objects/1",
|
|
859
|
+
}]);
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
test("fromJsonLd() preserves compact single-item arrays with portable IRIs", async () => {
|
|
863
|
+
const createJson = {
|
|
864
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
865
|
+
type: "Create",
|
|
866
|
+
actor: ["ap://did:key:z6Mkabc/actor"],
|
|
867
|
+
object: "https://example.com/objects/1",
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
const create = await Create.fromJsonLd(createJson, {
|
|
871
|
+
documentLoader: mockDocumentLoader,
|
|
872
|
+
contextLoader: mockDocumentLoader,
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
deepStrictEqual(await create.toJsonLd(), {
|
|
876
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
877
|
+
type: "Create",
|
|
878
|
+
actor: ["ap+ef61://did:key:z6Mkabc/actor"],
|
|
879
|
+
object: "https://example.com/objects/1",
|
|
880
|
+
});
|
|
881
|
+
deepStrictEqual(createJson.actor, ["ap://did:key:z6Mkabc/actor"]);
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
test("fromJsonLd() preserves compact multi-node arrays with portable IRIs", async () => {
|
|
885
|
+
const activityJson = [
|
|
886
|
+
{
|
|
887
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
888
|
+
type: "Create",
|
|
889
|
+
id: "https://example.com/activities/1",
|
|
890
|
+
actor: "https://example.com/actors/alice",
|
|
891
|
+
object: "https://example.com/objects/1",
|
|
892
|
+
},
|
|
893
|
+
{
|
|
894
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
895
|
+
type: "Note",
|
|
896
|
+
id: "https://example.com/objects/1",
|
|
897
|
+
attributedTo: "ap://did:key:z6Mkabc/actor",
|
|
898
|
+
},
|
|
899
|
+
];
|
|
900
|
+
|
|
901
|
+
const activity = await Activity.fromJsonLd(activityJson, {
|
|
902
|
+
documentLoader: mockDocumentLoader,
|
|
903
|
+
contextLoader: mockDocumentLoader,
|
|
904
|
+
});
|
|
905
|
+
|
|
906
|
+
deepStrictEqual(await activity.toJsonLd(), [
|
|
907
|
+
{
|
|
908
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
909
|
+
type: "Create",
|
|
910
|
+
id: "https://example.com/activities/1",
|
|
911
|
+
actor: "https://example.com/actors/alice",
|
|
912
|
+
object: "https://example.com/objects/1",
|
|
913
|
+
},
|
|
914
|
+
{
|
|
915
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
916
|
+
type: "Note",
|
|
917
|
+
id: "https://example.com/objects/1",
|
|
918
|
+
attributedTo: "ap+ef61://did:key:z6Mkabc/actor",
|
|
919
|
+
},
|
|
920
|
+
]);
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
test("fromJsonLd() preserves nested unmapped terms with portable IRIs", async () => {
|
|
924
|
+
const noteJson = {
|
|
925
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
926
|
+
type: "Note",
|
|
927
|
+
id: "ap://did:key:z6Mkabc/objects/1",
|
|
928
|
+
attachment: {
|
|
929
|
+
type: "Object",
|
|
930
|
+
name: "Attachment with an unmapped extension.",
|
|
931
|
+
extra: "This nested unmapped property should stay cached.",
|
|
932
|
+
},
|
|
933
|
+
};
|
|
934
|
+
|
|
935
|
+
const note = await Note.fromJsonLd(noteJson, {
|
|
936
|
+
documentLoader: mockDocumentLoader,
|
|
937
|
+
contextLoader: mockDocumentLoader,
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
deepStrictEqual(await note.toJsonLd(), {
|
|
941
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
942
|
+
type: "Note",
|
|
943
|
+
id: "ap+ef61://did:key:z6Mkabc/objects/1",
|
|
944
|
+
attachment: {
|
|
945
|
+
type: "Object",
|
|
946
|
+
name: "Attachment with an unmapped extension.",
|
|
947
|
+
extra: "This nested unmapped property should stay cached.",
|
|
948
|
+
},
|
|
949
|
+
});
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
test("fromJsonLd() preserves compact array item extension contexts with portable IRIs", async () => {
|
|
953
|
+
const activityJson = [
|
|
954
|
+
{
|
|
955
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
956
|
+
type: "Create",
|
|
957
|
+
id: "https://example.com/activities/1",
|
|
958
|
+
actor: "https://example.com/actors/alice",
|
|
959
|
+
object: "https://example.com/objects/1",
|
|
960
|
+
},
|
|
961
|
+
{
|
|
962
|
+
"@context": [
|
|
963
|
+
"https://www.w3.org/ns/activitystreams",
|
|
964
|
+
{
|
|
965
|
+
extraRef: {
|
|
966
|
+
"@id": "https://example.com/ns#extraRef",
|
|
967
|
+
"@type": "@id",
|
|
968
|
+
},
|
|
969
|
+
},
|
|
970
|
+
],
|
|
971
|
+
type: "Note",
|
|
972
|
+
id: "https://example.com/objects/1",
|
|
973
|
+
extraRef: "ap://did:key:z6Mkabc/extra",
|
|
974
|
+
},
|
|
975
|
+
];
|
|
976
|
+
|
|
977
|
+
const activity = await Activity.fromJsonLd(activityJson, {
|
|
978
|
+
documentLoader: mockDocumentLoader,
|
|
979
|
+
contextLoader: mockDocumentLoader,
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
deepStrictEqual(await activity.toJsonLd(), [
|
|
983
|
+
{
|
|
984
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
985
|
+
type: "Create",
|
|
986
|
+
id: "https://example.com/activities/1",
|
|
987
|
+
actor: "https://example.com/actors/alice",
|
|
988
|
+
object: "https://example.com/objects/1",
|
|
989
|
+
},
|
|
990
|
+
{
|
|
991
|
+
"@context": [
|
|
992
|
+
"https://www.w3.org/ns/activitystreams",
|
|
993
|
+
{
|
|
994
|
+
extraRef: {
|
|
995
|
+
"@id": "https://example.com/ns#extraRef",
|
|
996
|
+
"@type": "@id",
|
|
997
|
+
},
|
|
998
|
+
},
|
|
999
|
+
],
|
|
1000
|
+
type: "Note",
|
|
1001
|
+
id: "https://example.com/objects/1",
|
|
1002
|
+
extraRef: "ap+ef61://did:key:z6Mkabc/extra",
|
|
1003
|
+
},
|
|
1004
|
+
]);
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
test("fromJsonLd() formats portable IRIs in JSON-LD containers", async () => {
|
|
1008
|
+
const note = await Note.fromJsonLd({
|
|
1009
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1010
|
+
type: "Note",
|
|
1011
|
+
id: "https://example.com/notes/1",
|
|
1012
|
+
attributedTo: {
|
|
1013
|
+
"@list": ["ap://did:key:z6Mkabc/actor"],
|
|
1014
|
+
},
|
|
1015
|
+
to: {
|
|
1016
|
+
"@set": ["ap://did:key:z6Mkabc/followers"],
|
|
1017
|
+
},
|
|
1018
|
+
}, { documentLoader: mockDocumentLoader, contextLoader: mockDocumentLoader });
|
|
1019
|
+
|
|
1020
|
+
const jsonLd = await note.toJsonLd() as Record<string, unknown>;
|
|
1021
|
+
deepStrictEqual(jsonLd.attributedTo, {
|
|
1022
|
+
"@list": ["ap+ef61://did:key:z6Mkabc/actor"],
|
|
1023
|
+
});
|
|
1024
|
+
deepStrictEqual(jsonLd.to, "ap+ef61://did:key:z6Mkabc/followers");
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
test("fromJsonLd() formats portable IRIs hidden behind JSON-LD aliases", async () => {
|
|
1028
|
+
const activity = await Activity.fromJsonLd({
|
|
1029
|
+
"@context": [
|
|
1030
|
+
"https://www.w3.org/ns/activitystreams",
|
|
1031
|
+
{
|
|
1032
|
+
as: {
|
|
1033
|
+
"@id": "https://www.w3.org/ns/activitystreams#",
|
|
1034
|
+
"@prefix": true,
|
|
1035
|
+
},
|
|
1036
|
+
extra: "https://example.com/ns#extra",
|
|
1037
|
+
extraRef: {
|
|
1038
|
+
"@id": "https://example.com/ns#extraRef",
|
|
1039
|
+
"@type": "@id",
|
|
1040
|
+
},
|
|
1041
|
+
actorRef: {
|
|
1042
|
+
"@id": "as:actor",
|
|
1043
|
+
"@type": "@id",
|
|
1044
|
+
},
|
|
1045
|
+
targetRef: {
|
|
1046
|
+
"@id": "as:target",
|
|
1047
|
+
"@type": "@id",
|
|
1048
|
+
},
|
|
1049
|
+
},
|
|
1050
|
+
],
|
|
1051
|
+
type: "Create",
|
|
1052
|
+
actorRef: "ap://did:key:z6Mkabc/actor",
|
|
1053
|
+
object: "https://example.com/objects/1",
|
|
1054
|
+
targetRef: "ap://did:key:z6Mkabc/target",
|
|
1055
|
+
extra: "This extension property should stay cached.",
|
|
1056
|
+
extraRef: "ap://did:key:z6Mkabc/extra",
|
|
1057
|
+
}, { documentLoader: mockDocumentLoader, contextLoader: mockDocumentLoader });
|
|
1058
|
+
|
|
1059
|
+
deepStrictEqual(
|
|
1060
|
+
activity.actorId,
|
|
1061
|
+
new URL("ap+ef61://did%3Akey%3Az6Mkabc/actor"),
|
|
1062
|
+
);
|
|
1063
|
+
deepStrictEqual(
|
|
1064
|
+
activity.targetId,
|
|
1065
|
+
new URL("ap+ef61://did%3Akey%3Az6Mkabc/target"),
|
|
1066
|
+
);
|
|
1067
|
+
const jsonLd = await activity.toJsonLd({
|
|
1068
|
+
contextLoader: mockDocumentLoader,
|
|
1069
|
+
}) as Record<string, unknown>;
|
|
1070
|
+
deepStrictEqual(jsonLd.actor, "ap+ef61://did:key:z6Mkabc/actor");
|
|
1071
|
+
deepStrictEqual("actorRef" in jsonLd, false);
|
|
1072
|
+
deepStrictEqual(jsonLd.target, "ap+ef61://did:key:z6Mkabc/target");
|
|
1073
|
+
deepStrictEqual("targetRef" in jsonLd, false);
|
|
1074
|
+
deepStrictEqual(jsonLd.extra, "This extension property should stay cached.");
|
|
1075
|
+
deepStrictEqual(jsonLd.extraRef, "ap+ef61://did:key:z6Mkabc/extra");
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
test("fromJsonLd() preserves portable IRIs in @id extension terms", async () => {
|
|
1079
|
+
const note = await Note.fromJsonLd({
|
|
1080
|
+
"@context": [
|
|
1081
|
+
"https://www.w3.org/ns/activitystreams",
|
|
1082
|
+
{
|
|
1083
|
+
extraRef: {
|
|
1084
|
+
"@id": "https://example.com/ns#extraRef",
|
|
1085
|
+
"@type": "@id",
|
|
1086
|
+
},
|
|
1087
|
+
},
|
|
1088
|
+
],
|
|
1089
|
+
type: "Note",
|
|
1090
|
+
id: "https://example.com/notes/1",
|
|
1091
|
+
extraRef: "ap://did:key:z6Mkabc/extra",
|
|
1092
|
+
}, { documentLoader: mockDocumentLoader, contextLoader: mockDocumentLoader });
|
|
1093
|
+
|
|
1094
|
+
const jsonLd = await note.toJsonLd({
|
|
1095
|
+
contextLoader: mockDocumentLoader,
|
|
1096
|
+
}) as Record<string, unknown>;
|
|
1097
|
+
deepStrictEqual(jsonLd.extraRef, "ap+ef61://did:key:z6Mkabc/extra");
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
test("fromJsonLd() ignores malformed portable IRIs in extension cache terms", async () => {
|
|
1101
|
+
const noteJson = {
|
|
1102
|
+
"@context": [
|
|
1103
|
+
"https://www.w3.org/ns/activitystreams",
|
|
1104
|
+
{
|
|
1105
|
+
extraRef: {
|
|
1106
|
+
"@id": "https://example.com/ns#extraRef",
|
|
1107
|
+
"@type": "@id",
|
|
1108
|
+
},
|
|
1109
|
+
},
|
|
1110
|
+
],
|
|
1111
|
+
type: "Note",
|
|
1112
|
+
id: "https://example.com/notes/1",
|
|
1113
|
+
extraRef: "ap://example.com/not-portable",
|
|
1114
|
+
};
|
|
1115
|
+
|
|
1116
|
+
const note = await Note.fromJsonLd(noteJson, {
|
|
1117
|
+
documentLoader: mockDocumentLoader,
|
|
1118
|
+
contextLoader: mockDocumentLoader,
|
|
1119
|
+
});
|
|
1120
|
+
|
|
1121
|
+
deepStrictEqual(await note.toJsonLd(), noteJson);
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
test("fromJsonLd() preserves portable IRIs in @id typed terms", async () => {
|
|
1125
|
+
const note = await Note.fromJsonLd({
|
|
1126
|
+
"@context": [
|
|
1127
|
+
"https://www.w3.org/ns/activitystreams",
|
|
1128
|
+
{
|
|
1129
|
+
"@vocab": "https://example.com/ns#",
|
|
1130
|
+
extraRef: { "@type": "@id" },
|
|
1131
|
+
},
|
|
1132
|
+
],
|
|
1133
|
+
type: "Note",
|
|
1134
|
+
id: "https://example.com/notes/1",
|
|
1135
|
+
content: "This text mentions ap://did:key:z6Mkabc/text.",
|
|
1136
|
+
extraRef: "ap://did:key:z6Mkabc/extra",
|
|
1137
|
+
}, { documentLoader: mockDocumentLoader, contextLoader: mockDocumentLoader });
|
|
1138
|
+
|
|
1139
|
+
const jsonLd = await note.toJsonLd({
|
|
1140
|
+
contextLoader: mockDocumentLoader,
|
|
1141
|
+
}) as Record<string, unknown>;
|
|
1142
|
+
deepStrictEqual(
|
|
1143
|
+
jsonLd.content,
|
|
1144
|
+
"This text mentions ap://did:key:z6Mkabc/text.",
|
|
1145
|
+
);
|
|
1146
|
+
deepStrictEqual(jsonLd.extraRef, "ap+ef61://did:key:z6Mkabc/extra");
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1149
|
+
test("fromJsonLd() preserves portable IRIs hidden behind remote contexts", async () => {
|
|
1150
|
+
const contextUrl = "https://example.com/contexts/portable-iris";
|
|
1151
|
+
const contextLoader: DocumentLoader = async (
|
|
1152
|
+
resource: string,
|
|
1153
|
+
options,
|
|
1154
|
+
): Promise<RemoteDocument> => {
|
|
1155
|
+
if (resource === contextUrl) {
|
|
1156
|
+
return {
|
|
1157
|
+
contextUrl: null,
|
|
1158
|
+
documentUrl: resource,
|
|
1159
|
+
document: {
|
|
1160
|
+
"@context": [
|
|
1161
|
+
"https://www.w3.org/ns/activitystreams",
|
|
1162
|
+
{
|
|
1163
|
+
"@vocab": "https://example.com/ns#",
|
|
1164
|
+
extra: "https://example.com/ns#extra",
|
|
1165
|
+
extraRef: { "@type": "@id" },
|
|
1166
|
+
},
|
|
1167
|
+
],
|
|
1168
|
+
},
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
return await mockDocumentLoader(resource, options);
|
|
1172
|
+
};
|
|
1173
|
+
const note = await Note.fromJsonLd({
|
|
1174
|
+
"@context": contextUrl,
|
|
1175
|
+
type: "Note",
|
|
1176
|
+
id: "https://example.com/notes/1",
|
|
1177
|
+
content: "This text mentions ap://did:key:z6Mkabc/text.",
|
|
1178
|
+
extra: "This extension property should stay cached.",
|
|
1179
|
+
extraRef: "ap://did:key:z6Mkabc/extra",
|
|
1180
|
+
}, { documentLoader: mockDocumentLoader, contextLoader });
|
|
1181
|
+
|
|
1182
|
+
const jsonLd = await note.toJsonLd({ contextLoader }) as Record<
|
|
1183
|
+
string,
|
|
1184
|
+
unknown
|
|
1185
|
+
>;
|
|
1186
|
+
deepStrictEqual(
|
|
1187
|
+
jsonLd.content,
|
|
1188
|
+
"This text mentions ap://did:key:z6Mkabc/text.",
|
|
1189
|
+
);
|
|
1190
|
+
deepStrictEqual(jsonLd.extra, "This extension property should stay cached.");
|
|
1191
|
+
deepStrictEqual(jsonLd.extraRef, "ap+ef61://did:key:z6Mkabc/extra");
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
test("fromJsonLd() formats portable IRIs hidden behind nested remote contexts", async () => {
|
|
1195
|
+
const rootContextUrl = "https://example.com/contexts/nested-portable-iris";
|
|
1196
|
+
const nestedContextUrl = "https://example.com/contexts/nested-portable-ref";
|
|
1197
|
+
const contextLoader: DocumentLoader = async (
|
|
1198
|
+
resource: string,
|
|
1199
|
+
options,
|
|
1200
|
+
): Promise<RemoteDocument> => {
|
|
1201
|
+
if (resource === rootContextUrl) {
|
|
1202
|
+
return {
|
|
1203
|
+
contextUrl: null,
|
|
1204
|
+
documentUrl: resource,
|
|
1205
|
+
document: {
|
|
1206
|
+
"@context": [
|
|
1207
|
+
"https://www.w3.org/ns/activitystreams",
|
|
1208
|
+
{
|
|
1209
|
+
"@vocab": "https://example.com/ns#",
|
|
1210
|
+
extraContainer: "https://example.com/ns#extraContainer",
|
|
1211
|
+
},
|
|
1212
|
+
],
|
|
1213
|
+
},
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
if (resource === nestedContextUrl) {
|
|
1217
|
+
return {
|
|
1218
|
+
contextUrl: null,
|
|
1219
|
+
documentUrl: resource,
|
|
1220
|
+
document: {
|
|
1221
|
+
"@context": {
|
|
1222
|
+
"@vocab": "https://example.com/ns#",
|
|
1223
|
+
extra: "https://example.com/ns#extra",
|
|
1224
|
+
extraRef: { "@type": "@id" },
|
|
1225
|
+
},
|
|
1226
|
+
},
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
return await mockDocumentLoader(resource, options);
|
|
1230
|
+
};
|
|
1231
|
+
const note = await Note.fromJsonLd({
|
|
1232
|
+
"@context": rootContextUrl,
|
|
1233
|
+
type: "Note",
|
|
1234
|
+
id: "https://example.com/notes/1",
|
|
1235
|
+
extraContainer: {
|
|
1236
|
+
"@context": nestedContextUrl,
|
|
1237
|
+
content: "This text mentions ap://did:key:z6Mkabc/text.",
|
|
1238
|
+
extra: "This nested extension object should stay cached.",
|
|
1239
|
+
extraRef: "ap://did:key:z6Mkabc/extra",
|
|
1240
|
+
},
|
|
1241
|
+
}, { documentLoader: mockDocumentLoader, contextLoader });
|
|
1242
|
+
|
|
1243
|
+
const jsonLd = await note.toJsonLd({ contextLoader }) as Record<
|
|
1244
|
+
string,
|
|
1245
|
+
unknown
|
|
1246
|
+
>;
|
|
1247
|
+
deepStrictEqual(jsonLd.extraContainer, {
|
|
1248
|
+
"@context": nestedContextUrl,
|
|
1249
|
+
content: "This text mentions ap://did:key:z6Mkabc/text.",
|
|
1250
|
+
extra: "This nested extension object should stay cached.",
|
|
1251
|
+
extraRef: { id: "ap+ef61://did:key:z6Mkabc/extra" },
|
|
1252
|
+
});
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1255
|
+
test("fromJsonLd() formats portable IRIs in sibling remote-context objects", async () => {
|
|
1256
|
+
const rootContextUrl = "https://example.com/contexts/sibling-containers";
|
|
1257
|
+
const nestedContextUrl = "https://example.com/contexts/sibling-extra-ref";
|
|
1258
|
+
const contextLoader: DocumentLoader = async (
|
|
1259
|
+
resource: string,
|
|
1260
|
+
options,
|
|
1261
|
+
): Promise<RemoteDocument> => {
|
|
1262
|
+
if (resource === rootContextUrl) {
|
|
1263
|
+
return {
|
|
1264
|
+
contextUrl: null,
|
|
1265
|
+
documentUrl: resource,
|
|
1266
|
+
document: {
|
|
1267
|
+
"@context": [
|
|
1268
|
+
"https://www.w3.org/ns/activitystreams",
|
|
1269
|
+
{
|
|
1270
|
+
"@vocab": "https://example.com/ns#",
|
|
1271
|
+
firstExtraContainer: "https://example.com/ns#firstExtraContainer",
|
|
1272
|
+
secondExtraContainer:
|
|
1273
|
+
"https://example.com/ns#secondExtraContainer",
|
|
1274
|
+
},
|
|
1275
|
+
],
|
|
1276
|
+
},
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
if (resource === nestedContextUrl) {
|
|
1280
|
+
return {
|
|
1281
|
+
contextUrl: null,
|
|
1282
|
+
documentUrl: resource,
|
|
1283
|
+
document: {
|
|
1284
|
+
"@context": {
|
|
1285
|
+
"@vocab": "https://example.com/ns#",
|
|
1286
|
+
extraRef: { "@type": "@id" },
|
|
1287
|
+
},
|
|
1288
|
+
},
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
return await mockDocumentLoader(resource, options);
|
|
1292
|
+
};
|
|
1293
|
+
const note = await Note.fromJsonLd({
|
|
1294
|
+
"@context": rootContextUrl,
|
|
1295
|
+
type: "Note",
|
|
1296
|
+
id: "https://example.com/notes/1",
|
|
1297
|
+
firstExtraContainer: {
|
|
1298
|
+
"@context": nestedContextUrl,
|
|
1299
|
+
content: "No portable IRI here.",
|
|
1300
|
+
},
|
|
1301
|
+
secondExtraContainer: {
|
|
1302
|
+
"@context": nestedContextUrl,
|
|
1303
|
+
extraRef: "ap://did:key:z6Mkabc/second",
|
|
1304
|
+
},
|
|
1305
|
+
}, { documentLoader: mockDocumentLoader, contextLoader });
|
|
1306
|
+
|
|
1307
|
+
const jsonLd = await note.toJsonLd({ contextLoader }) as Record<
|
|
1308
|
+
string,
|
|
1309
|
+
unknown
|
|
1310
|
+
>;
|
|
1311
|
+
deepStrictEqual(jsonLd.firstExtraContainer, {
|
|
1312
|
+
"@context": nestedContextUrl,
|
|
1313
|
+
content: "No portable IRI here.",
|
|
1314
|
+
});
|
|
1315
|
+
deepStrictEqual(jsonLd.secondExtraContainer, {
|
|
1316
|
+
"@context": nestedContextUrl,
|
|
1317
|
+
extraRef: { id: "ap+ef61://did:key:z6Mkabc/second" },
|
|
1318
|
+
});
|
|
1319
|
+
});
|
|
1320
|
+
|
|
1321
|
+
test("fromJsonLd() batches unmapped portable IRI term checks", async () => {
|
|
1322
|
+
const contextUrl = "https://example.com/contexts/batched-portable-aliases";
|
|
1323
|
+
let contextLoads = 0;
|
|
1324
|
+
const contextLoader: DocumentLoader = async (
|
|
1325
|
+
resource: string,
|
|
1326
|
+
options,
|
|
1327
|
+
): Promise<RemoteDocument> => {
|
|
1328
|
+
if (resource === contextUrl) {
|
|
1329
|
+
contextLoads++;
|
|
1330
|
+
return {
|
|
1331
|
+
contextUrl: null,
|
|
1332
|
+
documentUrl: resource,
|
|
1333
|
+
document: {
|
|
1334
|
+
"@context": [
|
|
1335
|
+
"https://www.w3.org/ns/activitystreams",
|
|
1336
|
+
{
|
|
1337
|
+
as: {
|
|
1338
|
+
"@id": "https://www.w3.org/ns/activitystreams#",
|
|
1339
|
+
"@prefix": true,
|
|
1340
|
+
},
|
|
1341
|
+
actorRef0: { "@id": "as:actor", "@type": "@id" },
|
|
1342
|
+
actorRef1: { "@id": "as:actor", "@type": "@id" },
|
|
1343
|
+
targetRef0: { "@id": "as:target", "@type": "@id" },
|
|
1344
|
+
targetRef1: { "@id": "as:target", "@type": "@id" },
|
|
1345
|
+
objectRef0: { "@id": "as:object", "@type": "@id" },
|
|
1346
|
+
objectRef1: { "@id": "as:object", "@type": "@id" },
|
|
1347
|
+
},
|
|
1348
|
+
],
|
|
1349
|
+
},
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
return await mockDocumentLoader(resource, options);
|
|
1353
|
+
};
|
|
1354
|
+
|
|
1355
|
+
const activity = await Activity.fromJsonLd({
|
|
1356
|
+
"@context": contextUrl,
|
|
1357
|
+
type: "Create",
|
|
1358
|
+
actorRef0: "ap://did:key:z6Mkabc/actor0",
|
|
1359
|
+
actorRef1: "ap://did:key:z6Mkabc/actor1",
|
|
1360
|
+
targetRef0: "ap://did:key:z6Mkabc/target0",
|
|
1361
|
+
targetRef1: "ap://did:key:z6Mkabc/target1",
|
|
1362
|
+
objectRef0: "https://example.com/objects/0",
|
|
1363
|
+
objectRef1: "https://example.com/objects/1",
|
|
1364
|
+
}, { documentLoader: mockDocumentLoader, contextLoader });
|
|
1365
|
+
|
|
1366
|
+
await activity.toJsonLd({ contextLoader });
|
|
1367
|
+
|
|
1368
|
+
ok(contextLoads <= 5);
|
|
1369
|
+
});
|
|
1370
|
+
|
|
1371
|
+
test("fromJsonLd() falls back when portable IRI cache merge fails", async () => {
|
|
1372
|
+
const contextUrl = "https://example.com/contexts/failing-cache-merge";
|
|
1373
|
+
let contextLoads = 0;
|
|
1374
|
+
const contextLoader: DocumentLoader = async (
|
|
1375
|
+
resource: string,
|
|
1376
|
+
options,
|
|
1377
|
+
): Promise<RemoteDocument> => {
|
|
1378
|
+
if (resource === contextUrl) {
|
|
1379
|
+
contextLoads++;
|
|
1380
|
+
if (contextLoads > 1) throw new Error("merge context unavailable");
|
|
1381
|
+
return {
|
|
1382
|
+
contextUrl: null,
|
|
1383
|
+
documentUrl: resource,
|
|
1384
|
+
document: {
|
|
1385
|
+
"@context": [
|
|
1386
|
+
"https://www.w3.org/ns/activitystreams",
|
|
1387
|
+
{
|
|
1388
|
+
"@vocab": "https://example.com/ns#",
|
|
1389
|
+
extraRef: { "@type": "@id" },
|
|
1390
|
+
},
|
|
1391
|
+
],
|
|
1392
|
+
},
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
return await mockDocumentLoader(resource, options);
|
|
1396
|
+
};
|
|
1397
|
+
|
|
1398
|
+
const note = await Note.fromJsonLd({
|
|
1399
|
+
"@context": contextUrl,
|
|
1400
|
+
type: "Note",
|
|
1401
|
+
id: "ap://did:key:z6Mkabc/objects/1",
|
|
1402
|
+
extraRef: "ap://did:key:z6Mkabc/extra",
|
|
1403
|
+
}, { documentLoader: mockDocumentLoader, contextLoader });
|
|
1404
|
+
|
|
1405
|
+
const jsonLd = await note.toJsonLd({
|
|
1406
|
+
contextLoader: mockDocumentLoader,
|
|
1407
|
+
}) as Record<string, unknown>;
|
|
1408
|
+
|
|
1409
|
+
deepStrictEqual(jsonLd.type, "Note");
|
|
1410
|
+
deepStrictEqual(jsonLd.id, "ap+ef61://did:key:z6Mkabc/objects/1");
|
|
1411
|
+
});
|
|
1412
|
+
|
|
1413
|
+
test("fromJsonLd() formats portable IRIs in scalar URL values", async () => {
|
|
1414
|
+
const note = await Note.fromJsonLd({
|
|
1415
|
+
"@context": [
|
|
1416
|
+
"https://www.w3.org/ns/activitystreams",
|
|
1417
|
+
"https://gotosocial.org/ns",
|
|
1418
|
+
{ quoteUrl: "as:quoteUrl" },
|
|
1419
|
+
],
|
|
1420
|
+
type: "Note",
|
|
1421
|
+
id: "https://example.com/notes/1",
|
|
1422
|
+
quoteUrl: "ap://did:key:z6Mkabc/objects/1",
|
|
1423
|
+
}, { documentLoader: mockDocumentLoader, contextLoader: mockDocumentLoader });
|
|
1424
|
+
|
|
1425
|
+
deepStrictEqual(
|
|
1426
|
+
note.quoteUrl,
|
|
1427
|
+
new URL("ap+ef61://did%3Akey%3Az6Mkabc/objects/1"),
|
|
1428
|
+
);
|
|
1429
|
+
const jsonLd = await note.toJsonLd({
|
|
1430
|
+
contextLoader: mockDocumentLoader,
|
|
1431
|
+
}) as Record<string, unknown>;
|
|
1432
|
+
deepStrictEqual(jsonLd.quoteUrl, "ap+ef61://did:key:z6Mkabc/objects/1");
|
|
1433
|
+
});
|
|
1434
|
+
|
|
1435
|
+
test("fromJsonLd() formats portable IRIs in URL value lists", async () => {
|
|
1436
|
+
const note = await Note.fromJsonLd({
|
|
1437
|
+
"@context": [
|
|
1438
|
+
"https://www.w3.org/ns/activitystreams",
|
|
1439
|
+
"https://gotosocial.org/ns",
|
|
1440
|
+
{ quoteUrl: "as:quoteUrl" },
|
|
1441
|
+
],
|
|
1442
|
+
type: "Note",
|
|
1443
|
+
id: "https://example.com/notes/1",
|
|
1444
|
+
quoteUrl: {
|
|
1445
|
+
"@list": [
|
|
1446
|
+
{ "@value": "ap://did:key:z6Mkabc/objects/1" },
|
|
1447
|
+
],
|
|
1448
|
+
},
|
|
1449
|
+
}, { documentLoader: mockDocumentLoader, contextLoader: mockDocumentLoader });
|
|
1450
|
+
|
|
1451
|
+
deepStrictEqual(
|
|
1452
|
+
note.quoteUrl,
|
|
1453
|
+
new URL("ap+ef61://did%3Akey%3Az6Mkabc/objects/1"),
|
|
479
1454
|
);
|
|
1455
|
+
const jsonLd = await note.toJsonLd({
|
|
1456
|
+
contextLoader: mockDocumentLoader,
|
|
1457
|
+
}) as Record<string, unknown>;
|
|
1458
|
+
deepStrictEqual(jsonLd.quoteUrl, {
|
|
1459
|
+
"@list": [
|
|
1460
|
+
"ap+ef61://did:key:z6Mkabc/objects/1",
|
|
1461
|
+
],
|
|
1462
|
+
});
|
|
480
1463
|
});
|
|
481
1464
|
|
|
482
1465
|
test({
|
|
@@ -544,6 +1527,37 @@ test({
|
|
|
544
1527
|
},
|
|
545
1528
|
});
|
|
546
1529
|
|
|
1530
|
+
test("Activity.getObject() fetches canonical portable IRIs", async () => {
|
|
1531
|
+
const fetchedUrls: string[] = [];
|
|
1532
|
+
// deno-lint-ignore require-await
|
|
1533
|
+
const documentLoader: DocumentLoader = async (url) => {
|
|
1534
|
+
fetchedUrls.push(url);
|
|
1535
|
+
return {
|
|
1536
|
+
contextUrl: null,
|
|
1537
|
+
documentUrl: url,
|
|
1538
|
+
document: {
|
|
1539
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1540
|
+
id: url,
|
|
1541
|
+
type: "Note",
|
|
1542
|
+
content: "Fetched portable object",
|
|
1543
|
+
},
|
|
1544
|
+
};
|
|
1545
|
+
};
|
|
1546
|
+
const activity = await Activity.fromJsonLd({
|
|
1547
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1548
|
+
type: "Create",
|
|
1549
|
+
object: "ap://did:key:z6Mkabc/objects/1",
|
|
1550
|
+
}, { documentLoader, contextLoader: mockDocumentLoader });
|
|
1551
|
+
|
|
1552
|
+
const object = await activity.getObject({
|
|
1553
|
+
documentLoader,
|
|
1554
|
+
contextLoader: mockDocumentLoader,
|
|
1555
|
+
});
|
|
1556
|
+
|
|
1557
|
+
assertInstanceOf(object, Note);
|
|
1558
|
+
deepStrictEqual(fetchedUrls, ["ap+ef61://did:key:z6Mkabc/objects/1"]);
|
|
1559
|
+
});
|
|
1560
|
+
|
|
547
1561
|
test({
|
|
548
1562
|
name: "Activity.getObjects()",
|
|
549
1563
|
permissions: { env: true, read: true },
|
|
@@ -741,6 +1755,7 @@ test("Person.toJsonLd()", async () => {
|
|
|
741
1755
|
"https://www.w3.org/ns/did/v1",
|
|
742
1756
|
"https://w3id.org/security/multikey/v1",
|
|
743
1757
|
"https://gotosocial.org/ns",
|
|
1758
|
+
"https://w3id.org/fep/7aa9",
|
|
744
1759
|
{
|
|
745
1760
|
PropertyValue: "schema:PropertyValue",
|
|
746
1761
|
alsoKnownAs: {
|
|
@@ -756,6 +1771,10 @@ test("Person.toJsonLd()", async () => {
|
|
|
756
1771
|
"@id": "toot:featured",
|
|
757
1772
|
"@type": "@id",
|
|
758
1773
|
},
|
|
1774
|
+
featuredCollections: {
|
|
1775
|
+
"@id": "https://w3id.org/fep/7aa9#featuredCollections",
|
|
1776
|
+
"@type": "@id",
|
|
1777
|
+
},
|
|
759
1778
|
featuredTags: {
|
|
760
1779
|
"@id": "toot:featuredTags",
|
|
761
1780
|
"@type": "@id",
|
|
@@ -1298,164 +2317,424 @@ test("Collection.fromJsonLd()", async () => {
|
|
|
1298
2317
|
"inboxOf": "https://example.com/person/bup9a8eqm",
|
|
1299
2318
|
});
|
|
1300
2319
|
deepStrictEqual(
|
|
1301
|
-
collection.id,
|
|
1302
|
-
new URL("https://example.com/collection/jzc50wc28l"),
|
|
2320
|
+
collection.id,
|
|
2321
|
+
new URL("https://example.com/collection/jzc50wc28l"),
|
|
2322
|
+
);
|
|
2323
|
+
deepStrictEqual(
|
|
2324
|
+
collection.inboxOfId,
|
|
2325
|
+
new URL("https://example.com/person/bup9a8eqm"),
|
|
2326
|
+
);
|
|
2327
|
+
});
|
|
2328
|
+
|
|
2329
|
+
test("Note.quoteUrl", async () => {
|
|
2330
|
+
const note = new Note({
|
|
2331
|
+
quoteUrl: new URL("https://example.com/object"),
|
|
2332
|
+
});
|
|
2333
|
+
const expected = {
|
|
2334
|
+
"@context": NOTE_QUOTE_CONTEXT,
|
|
2335
|
+
_misskey_quote: "https://example.com/object",
|
|
2336
|
+
quoteUri: "https://example.com/object",
|
|
2337
|
+
quoteUrl: "https://example.com/object",
|
|
2338
|
+
type: "Note",
|
|
2339
|
+
};
|
|
2340
|
+
deepStrictEqual(await note.toJsonLd(), expected);
|
|
2341
|
+
deepStrictEqual(await note.toJsonLd({ format: "compact" }), expected);
|
|
2342
|
+
|
|
2343
|
+
const jsonLd: Record<string, unknown> = {
|
|
2344
|
+
"@context": [
|
|
2345
|
+
"https://www.w3.org/ns/activitystreams",
|
|
2346
|
+
{
|
|
2347
|
+
_misskey_quote: "misskey:_misskey_quote",
|
|
2348
|
+
fedibird: "http://fedibird.com/ns#",
|
|
2349
|
+
misskey: "https://misskey-hub.net/ns#",
|
|
2350
|
+
quoteUri: "fedibird:quoteUri",
|
|
2351
|
+
quoteUrl: "as:quoteUrl",
|
|
2352
|
+
},
|
|
2353
|
+
],
|
|
2354
|
+
type: "Note",
|
|
2355
|
+
quoteUrl: "https://example.com/object",
|
|
2356
|
+
_misskey_quote: "https://example.com/object2",
|
|
2357
|
+
quoteUri: "https://example.com/object3",
|
|
2358
|
+
};
|
|
2359
|
+
const loaded = await Note.fromJsonLd(jsonLd);
|
|
2360
|
+
deepStrictEqual(loaded.quoteUrl, new URL("https://example.com/object"));
|
|
2361
|
+
|
|
2362
|
+
delete jsonLd.quoteUrl;
|
|
2363
|
+
const loaded2 = await Note.fromJsonLd(jsonLd);
|
|
2364
|
+
deepStrictEqual(loaded2.quoteUrl, new URL("https://example.com/object2"));
|
|
2365
|
+
|
|
2366
|
+
delete jsonLd._misskey_quote;
|
|
2367
|
+
const loaded3 = await Note.fromJsonLd(jsonLd);
|
|
2368
|
+
deepStrictEqual(loaded3.quoteUrl, new URL("https://example.com/object3"));
|
|
2369
|
+
});
|
|
2370
|
+
|
|
2371
|
+
test("Note.quote", async () => {
|
|
2372
|
+
const note = new Note({
|
|
2373
|
+
quote: new URL("https://example.com/object"),
|
|
2374
|
+
});
|
|
2375
|
+
const expected = {
|
|
2376
|
+
"@context": NOTE_QUOTE_CONTEXT,
|
|
2377
|
+
quote: "https://example.com/object",
|
|
2378
|
+
type: "Note",
|
|
2379
|
+
};
|
|
2380
|
+
deepStrictEqual(await note.toJsonLd(), expected);
|
|
2381
|
+
deepStrictEqual(await note.toJsonLd({ format: "compact" }), expected);
|
|
2382
|
+
|
|
2383
|
+
const loaded = await Note.fromJsonLd({
|
|
2384
|
+
"@context": [
|
|
2385
|
+
"https://www.w3.org/ns/activitystreams",
|
|
2386
|
+
{
|
|
2387
|
+
quote: {
|
|
2388
|
+
"@id": "https://w3id.org/fep/044f#quote",
|
|
2389
|
+
"@type": "@id",
|
|
2390
|
+
},
|
|
2391
|
+
},
|
|
2392
|
+
],
|
|
2393
|
+
type: "Note",
|
|
2394
|
+
quote: "https://example.com/object",
|
|
2395
|
+
});
|
|
2396
|
+
deepStrictEqual(loaded.quoteId, new URL("https://example.com/object"));
|
|
2397
|
+
});
|
|
2398
|
+
|
|
2399
|
+
test("Note.quoteAuthorization", async () => {
|
|
2400
|
+
const note = new Note({
|
|
2401
|
+
quoteAuthorization: new URL("https://example.com/authorizations/1"),
|
|
2402
|
+
});
|
|
2403
|
+
const expected = {
|
|
2404
|
+
"@context": NOTE_QUOTE_CONTEXT,
|
|
2405
|
+
quoteAuthorization: "https://example.com/authorizations/1",
|
|
2406
|
+
type: "Note",
|
|
2407
|
+
};
|
|
2408
|
+
deepStrictEqual(await note.toJsonLd(), expected);
|
|
2409
|
+
deepStrictEqual(await note.toJsonLd({ format: "compact" }), expected);
|
|
2410
|
+
|
|
2411
|
+
const loaded = await Note.fromJsonLd({
|
|
2412
|
+
"@context": [
|
|
2413
|
+
"https://www.w3.org/ns/activitystreams",
|
|
2414
|
+
{
|
|
2415
|
+
QuoteAuthorization: "https://w3id.org/fep/044f#QuoteAuthorization",
|
|
2416
|
+
quoteAuthorization: {
|
|
2417
|
+
"@id": "https://w3id.org/fep/044f#quoteAuthorization",
|
|
2418
|
+
"@type": "@id",
|
|
2419
|
+
},
|
|
2420
|
+
},
|
|
2421
|
+
],
|
|
2422
|
+
type: "Note",
|
|
2423
|
+
quoteAuthorization: "https://example.com/authorizations/1",
|
|
2424
|
+
});
|
|
2425
|
+
deepStrictEqual(
|
|
2426
|
+
loaded.quoteAuthorizationId,
|
|
2427
|
+
new URL("https://example.com/authorizations/1"),
|
|
2428
|
+
);
|
|
2429
|
+
|
|
2430
|
+
const loadedFromGoToSocialContext = await Note.fromJsonLd({
|
|
2431
|
+
"@context": [
|
|
2432
|
+
"https://www.w3.org/ns/activitystreams",
|
|
2433
|
+
"https://gotosocial.org/ns",
|
|
2434
|
+
],
|
|
2435
|
+
type: "Note",
|
|
2436
|
+
quoteAuthorization: "https://example.com/authorizations/2",
|
|
2437
|
+
}, {
|
|
2438
|
+
documentLoader: mockDocumentLoader,
|
|
2439
|
+
contextLoader: mockDocumentLoader,
|
|
2440
|
+
});
|
|
2441
|
+
deepStrictEqual(
|
|
2442
|
+
loadedFromGoToSocialContext.quoteAuthorizationId,
|
|
2443
|
+
new URL("https://example.com/authorizations/2"),
|
|
2444
|
+
);
|
|
2445
|
+
});
|
|
2446
|
+
|
|
2447
|
+
test("InteractionPolicy.canQuote", async () => {
|
|
2448
|
+
const note = new Note({
|
|
2449
|
+
interactionPolicy: new InteractionPolicy({
|
|
2450
|
+
canQuote: new InteractionRule({
|
|
2451
|
+
automaticApproval: new URL(
|
|
2452
|
+
"https://www.w3.org/ns/activitystreams#Public",
|
|
2453
|
+
),
|
|
2454
|
+
}),
|
|
2455
|
+
}),
|
|
2456
|
+
});
|
|
2457
|
+
const expected = {
|
|
2458
|
+
"@context": NOTE_QUOTE_CONTEXT,
|
|
2459
|
+
interactionPolicy: {
|
|
2460
|
+
canQuote: {
|
|
2461
|
+
automaticApproval: "as:Public",
|
|
2462
|
+
},
|
|
2463
|
+
},
|
|
2464
|
+
type: "Note",
|
|
2465
|
+
};
|
|
2466
|
+
deepStrictEqual(
|
|
2467
|
+
await note.toJsonLd({ contextLoader: mockDocumentLoader }),
|
|
2468
|
+
expected,
|
|
2469
|
+
);
|
|
2470
|
+
|
|
2471
|
+
const loaded = await Note.fromJsonLd(expected, {
|
|
2472
|
+
documentLoader: mockDocumentLoader,
|
|
2473
|
+
contextLoader: mockDocumentLoader,
|
|
2474
|
+
});
|
|
2475
|
+
deepStrictEqual(
|
|
2476
|
+
await loaded.toJsonLd({ contextLoader: mockDocumentLoader }),
|
|
2477
|
+
expected,
|
|
2478
|
+
);
|
|
2479
|
+
});
|
|
2480
|
+
|
|
2481
|
+
test("InteractionPolicy.canFeature", async () => {
|
|
2482
|
+
const person = new Person({
|
|
2483
|
+
id: new URL("https://example.com/users/alice"),
|
|
2484
|
+
featuredCollections: new URL(
|
|
2485
|
+
"https://example.com/users/alice/featured_collections",
|
|
2486
|
+
),
|
|
2487
|
+
interactionPolicy: new InteractionPolicy({
|
|
2488
|
+
canFeature: new InteractionRule({
|
|
2489
|
+
automaticApproval: new URL(
|
|
2490
|
+
"https://www.w3.org/ns/activitystreams#Public",
|
|
2491
|
+
),
|
|
2492
|
+
}),
|
|
2493
|
+
}),
|
|
2494
|
+
});
|
|
2495
|
+
const expected = {
|
|
2496
|
+
"@context": [
|
|
2497
|
+
"https://www.w3.org/ns/activitystreams",
|
|
2498
|
+
"https://w3id.org/security/v1",
|
|
2499
|
+
"https://w3id.org/security/data-integrity/v1",
|
|
2500
|
+
"https://www.w3.org/ns/did/v1",
|
|
2501
|
+
"https://w3id.org/security/multikey/v1",
|
|
2502
|
+
"https://gotosocial.org/ns",
|
|
2503
|
+
"https://w3id.org/fep/7aa9",
|
|
2504
|
+
{
|
|
2505
|
+
Emoji: "toot:Emoji",
|
|
2506
|
+
PropertyValue: "schema:PropertyValue",
|
|
2507
|
+
_misskey_followedMessage: "misskey:_misskey_followedMessage",
|
|
2508
|
+
alsoKnownAs: {
|
|
2509
|
+
"@id": "as:alsoKnownAs",
|
|
2510
|
+
"@type": "@id",
|
|
2511
|
+
},
|
|
2512
|
+
discoverable: "toot:discoverable",
|
|
2513
|
+
featured: {
|
|
2514
|
+
"@id": "toot:featured",
|
|
2515
|
+
"@type": "@id",
|
|
2516
|
+
},
|
|
2517
|
+
featuredCollections: {
|
|
2518
|
+
"@id": "https://w3id.org/fep/7aa9#featuredCollections",
|
|
2519
|
+
"@type": "@id",
|
|
2520
|
+
},
|
|
2521
|
+
featuredTags: {
|
|
2522
|
+
"@id": "toot:featuredTags",
|
|
2523
|
+
"@type": "@id",
|
|
2524
|
+
},
|
|
2525
|
+
indexable: "toot:indexable",
|
|
2526
|
+
isCat: "misskey:isCat",
|
|
2527
|
+
manuallyApprovesFollowers: "as:manuallyApprovesFollowers",
|
|
2528
|
+
memorial: "toot:memorial",
|
|
2529
|
+
misskey: "https://misskey-hub.net/ns#",
|
|
2530
|
+
movedTo: {
|
|
2531
|
+
"@id": "as:movedTo",
|
|
2532
|
+
"@type": "@id",
|
|
2533
|
+
},
|
|
2534
|
+
schema: "http://schema.org#",
|
|
2535
|
+
suspended: "toot:suspended",
|
|
2536
|
+
toot: "http://joinmastodon.org/ns#",
|
|
2537
|
+
value: "schema:value",
|
|
2538
|
+
},
|
|
2539
|
+
],
|
|
2540
|
+
type: "Person",
|
|
2541
|
+
id: "https://example.com/users/alice",
|
|
2542
|
+
featuredCollections: "https://example.com/users/alice/featured_collections",
|
|
2543
|
+
interactionPolicy: {
|
|
2544
|
+
canFeature: {
|
|
2545
|
+
automaticApproval: "as:Public",
|
|
2546
|
+
},
|
|
2547
|
+
},
|
|
2548
|
+
};
|
|
2549
|
+
deepStrictEqual(
|
|
2550
|
+
await person.toJsonLd({ contextLoader: mockDocumentLoader }),
|
|
2551
|
+
expected,
|
|
2552
|
+
);
|
|
2553
|
+
|
|
2554
|
+
const loaded = await Person.fromJsonLd(expected, {
|
|
2555
|
+
documentLoader: mockDocumentLoader,
|
|
2556
|
+
contextLoader: mockDocumentLoader,
|
|
2557
|
+
});
|
|
2558
|
+
deepStrictEqual(
|
|
2559
|
+
loaded.featuredCollectionsId,
|
|
2560
|
+
new URL("https://example.com/users/alice/featured_collections"),
|
|
2561
|
+
);
|
|
2562
|
+
assertInstanceOf(loaded.interactionPolicy, InteractionPolicy);
|
|
2563
|
+
assertInstanceOf(loaded.interactionPolicy.canFeature, InteractionRule);
|
|
2564
|
+
deepStrictEqual(
|
|
2565
|
+
loaded.interactionPolicy.canFeature.automaticApproval,
|
|
2566
|
+
new URL("https://www.w3.org/ns/activitystreams#Public"),
|
|
1303
2567
|
);
|
|
1304
2568
|
deepStrictEqual(
|
|
1305
|
-
|
|
1306
|
-
|
|
2569
|
+
await loaded.toJsonLd({ contextLoader: mockDocumentLoader }),
|
|
2570
|
+
expected,
|
|
1307
2571
|
);
|
|
1308
|
-
});
|
|
1309
|
-
|
|
1310
|
-
test("Note.quoteUrl", async () => {
|
|
1311
|
-
const note = new Note({
|
|
1312
|
-
quoteUrl: new URL("https://example.com/object"),
|
|
1313
|
-
});
|
|
1314
|
-
const expected = {
|
|
1315
|
-
"@context": NOTE_QUOTE_CONTEXT,
|
|
1316
|
-
_misskey_quote: "https://example.com/object",
|
|
1317
|
-
quoteUri: "https://example.com/object",
|
|
1318
|
-
quoteUrl: "https://example.com/object",
|
|
1319
|
-
type: "Note",
|
|
1320
|
-
};
|
|
1321
|
-
deepStrictEqual(await note.toJsonLd(), expected);
|
|
1322
|
-
deepStrictEqual(await note.toJsonLd({ format: "compact" }), expected);
|
|
1323
2572
|
|
|
1324
|
-
const
|
|
2573
|
+
const loadedFromFepContext = await Person.fromJsonLd({
|
|
1325
2574
|
"@context": [
|
|
1326
2575
|
"https://www.w3.org/ns/activitystreams",
|
|
2576
|
+
"https://gotosocial.org/ns",
|
|
2577
|
+
"https://w3id.org/fep/7aa9",
|
|
1327
2578
|
{
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
quoteUrl: "as:quoteUrl",
|
|
2579
|
+
featuredCollections: {
|
|
2580
|
+
"@id": "https://w3id.org/fep/7aa9#featuredCollections",
|
|
2581
|
+
"@type": "@id",
|
|
2582
|
+
},
|
|
1333
2583
|
},
|
|
1334
2584
|
],
|
|
1335
|
-
type: "
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
delete jsonLd._misskey_quote;
|
|
1348
|
-
const loaded3 = await Note.fromJsonLd(jsonLd);
|
|
1349
|
-
deepStrictEqual(loaded3.quoteUrl, new URL("https://example.com/object3"));
|
|
2585
|
+
type: "Person",
|
|
2586
|
+
id: "https://example.com/users/alice",
|
|
2587
|
+
featuredCollections: "https://example.com/users/alice/featured_collections",
|
|
2588
|
+
}, {
|
|
2589
|
+
documentLoader: mockDocumentLoader,
|
|
2590
|
+
contextLoader: mockDocumentLoader,
|
|
2591
|
+
});
|
|
2592
|
+
deepStrictEqual(
|
|
2593
|
+
loadedFromFepContext.featuredCollectionsId,
|
|
2594
|
+
new URL("https://example.com/users/alice/featured_collections"),
|
|
2595
|
+
);
|
|
1350
2596
|
});
|
|
1351
2597
|
|
|
1352
|
-
test("
|
|
1353
|
-
const
|
|
1354
|
-
|
|
2598
|
+
test("FeaturedCollection.toJsonLd()", async () => {
|
|
2599
|
+
const collection = new FeaturedCollection({
|
|
2600
|
+
id: new URL("https://example.com/users/alice/featured/1"),
|
|
2601
|
+
name: "Cute cats",
|
|
2602
|
+
attribution: new URL("https://example.com/users/alice"),
|
|
2603
|
+
topic: new Hashtag({ name: "#cats" }),
|
|
2604
|
+
discoverable: false,
|
|
2605
|
+
totalItems: 1,
|
|
1355
2606
|
});
|
|
1356
2607
|
const expected = {
|
|
1357
|
-
"@context":
|
|
1358
|
-
|
|
1359
|
-
|
|
2608
|
+
"@context": FEATURED_COLLECTION_CONTEXT,
|
|
2609
|
+
type: "FeaturedCollection",
|
|
2610
|
+
id: "https://example.com/users/alice/featured/1",
|
|
2611
|
+
attributedTo: "https://example.com/users/alice",
|
|
2612
|
+
name: "Cute cats",
|
|
2613
|
+
totalItems: 1,
|
|
2614
|
+
topic: {
|
|
2615
|
+
type: "Hashtag",
|
|
2616
|
+
name: "#cats",
|
|
2617
|
+
},
|
|
2618
|
+
discoverable: false,
|
|
1360
2619
|
};
|
|
1361
|
-
deepStrictEqual(
|
|
1362
|
-
|
|
2620
|
+
deepStrictEqual(
|
|
2621
|
+
await collection.toJsonLd({ contextLoader: mockDocumentLoader }),
|
|
2622
|
+
expected,
|
|
2623
|
+
);
|
|
1363
2624
|
|
|
1364
|
-
const loaded = await
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
{
|
|
1368
|
-
quote: {
|
|
1369
|
-
"@id": "https://w3id.org/fep/044f#quote",
|
|
1370
|
-
"@type": "@id",
|
|
1371
|
-
},
|
|
1372
|
-
},
|
|
1373
|
-
],
|
|
1374
|
-
type: "Note",
|
|
1375
|
-
quote: "https://example.com/object",
|
|
2625
|
+
const loaded = await FeaturedCollection.fromJsonLd(expected, {
|
|
2626
|
+
documentLoader: mockDocumentLoader,
|
|
2627
|
+
contextLoader: mockDocumentLoader,
|
|
1376
2628
|
});
|
|
1377
|
-
|
|
2629
|
+
assertInstanceOf(loaded, FeaturedCollection);
|
|
2630
|
+
deepStrictEqual(loaded.name, "Cute cats");
|
|
2631
|
+
deepStrictEqual(
|
|
2632
|
+
loaded.attributionId,
|
|
2633
|
+
new URL("https://example.com/users/alice"),
|
|
2634
|
+
);
|
|
2635
|
+
assertInstanceOf(loaded.topic, Hashtag);
|
|
2636
|
+
deepStrictEqual(loaded.topic.name, "#cats");
|
|
2637
|
+
deepStrictEqual(loaded.discoverable, false);
|
|
2638
|
+
deepStrictEqual(loaded.totalItems, 1);
|
|
2639
|
+
deepStrictEqual(
|
|
2640
|
+
await loaded.toJsonLd({ contextLoader: mockDocumentLoader }),
|
|
2641
|
+
expected,
|
|
2642
|
+
);
|
|
1378
2643
|
});
|
|
1379
2644
|
|
|
1380
|
-
test("
|
|
1381
|
-
const
|
|
1382
|
-
|
|
2645
|
+
test("FeaturedItem.toJsonLd()", async () => {
|
|
2646
|
+
const item = new FeaturedItem({
|
|
2647
|
+
id: new URL("https://example.com/users/alice/featured/1/items/1"),
|
|
2648
|
+
featuredObject: new URL("https://example.com/users/bob"),
|
|
2649
|
+
featureAuthorization: new URL("https://example.com/users/bob/stamps/1"),
|
|
1383
2650
|
});
|
|
1384
2651
|
const expected = {
|
|
1385
|
-
"@context":
|
|
1386
|
-
|
|
1387
|
-
|
|
2652
|
+
"@context": FEATURE_CONTEXT,
|
|
2653
|
+
type: "FeaturedItem",
|
|
2654
|
+
id: "https://example.com/users/alice/featured/1/items/1",
|
|
2655
|
+
featuredObject: "https://example.com/users/bob",
|
|
2656
|
+
featureAuthorization: "https://example.com/users/bob/stamps/1",
|
|
1388
2657
|
};
|
|
1389
|
-
deepStrictEqual(
|
|
1390
|
-
|
|
2658
|
+
deepStrictEqual(
|
|
2659
|
+
await item.toJsonLd({ contextLoader: mockDocumentLoader }),
|
|
2660
|
+
expected,
|
|
2661
|
+
);
|
|
1391
2662
|
|
|
1392
|
-
const loaded = await
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
{
|
|
1396
|
-
QuoteAuthorization: "https://w3id.org/fep/044f#QuoteAuthorization",
|
|
1397
|
-
quoteAuthorization: {
|
|
1398
|
-
"@id": "https://w3id.org/fep/044f#quoteAuthorization",
|
|
1399
|
-
"@type": "@id",
|
|
1400
|
-
},
|
|
1401
|
-
},
|
|
1402
|
-
],
|
|
1403
|
-
type: "Note",
|
|
1404
|
-
quoteAuthorization: "https://example.com/authorizations/1",
|
|
2663
|
+
const loaded = await FeaturedItem.fromJsonLd(expected, {
|
|
2664
|
+
documentLoader: mockDocumentLoader,
|
|
2665
|
+
contextLoader: mockDocumentLoader,
|
|
1405
2666
|
});
|
|
2667
|
+
assertInstanceOf(loaded, FeaturedItem);
|
|
1406
2668
|
deepStrictEqual(
|
|
1407
|
-
loaded.
|
|
1408
|
-
new URL("https://example.com/
|
|
2669
|
+
loaded.featuredObjectId,
|
|
2670
|
+
new URL("https://example.com/users/bob"),
|
|
2671
|
+
);
|
|
2672
|
+
deepStrictEqual(
|
|
2673
|
+
loaded.featureAuthorizationId,
|
|
2674
|
+
new URL("https://example.com/users/bob/stamps/1"),
|
|
1409
2675
|
);
|
|
2676
|
+
});
|
|
1410
2677
|
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
}
|
|
2678
|
+
test("FeatureAuthorization.fromJsonLd()", async () => {
|
|
2679
|
+
const jsonLd = {
|
|
2680
|
+
"@context": FEATURE_CONTEXT,
|
|
2681
|
+
type: "FeatureAuthorization",
|
|
2682
|
+
id: "https://example.com/users/bob/stamps/1",
|
|
2683
|
+
interactingObject: "https://example.com/users/alice/featured/1",
|
|
2684
|
+
interactionTarget: "https://example.com/users/bob",
|
|
2685
|
+
};
|
|
2686
|
+
const authorization = await FeatureAuthorization.fromJsonLd(jsonLd, {
|
|
1419
2687
|
documentLoader: mockDocumentLoader,
|
|
1420
2688
|
contextLoader: mockDocumentLoader,
|
|
1421
2689
|
});
|
|
2690
|
+
assertInstanceOf(authorization, FeatureAuthorization);
|
|
1422
2691
|
deepStrictEqual(
|
|
1423
|
-
|
|
1424
|
-
new URL("https://example.com/
|
|
2692
|
+
authorization.interactingObjectId,
|
|
2693
|
+
new URL("https://example.com/users/alice/featured/1"),
|
|
2694
|
+
);
|
|
2695
|
+
deepStrictEqual(
|
|
2696
|
+
authorization.interactionTargetId,
|
|
2697
|
+
new URL("https://example.com/users/bob"),
|
|
2698
|
+
);
|
|
2699
|
+
deepStrictEqual(
|
|
2700
|
+
await authorization.toJsonLd({ contextLoader: mockDocumentLoader }),
|
|
2701
|
+
jsonLd,
|
|
1425
2702
|
);
|
|
1426
2703
|
});
|
|
1427
2704
|
|
|
1428
|
-
test("
|
|
1429
|
-
const
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
"https://www.w3.org/ns/activitystreams#Public",
|
|
1434
|
-
),
|
|
1435
|
-
}),
|
|
1436
|
-
}),
|
|
2705
|
+
test("FeatureRequest.toJsonLd()", async () => {
|
|
2706
|
+
const request = new FeatureRequest({
|
|
2707
|
+
id: new URL("https://example.com/users/alice/featured/1/requests/1"),
|
|
2708
|
+
object: new URL("https://example.com/users/bob"),
|
|
2709
|
+
instrument: new URL("https://example.com/users/alice/featured/1"),
|
|
1437
2710
|
});
|
|
1438
2711
|
const expected = {
|
|
1439
|
-
"@context":
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
2712
|
+
"@context": [
|
|
2713
|
+
"https://w3id.org/identity/v1",
|
|
2714
|
+
...FEATURE_CONTEXT,
|
|
2715
|
+
],
|
|
2716
|
+
type: "FeatureRequest",
|
|
2717
|
+
id: "https://example.com/users/alice/featured/1/requests/1",
|
|
2718
|
+
object: "https://example.com/users/bob",
|
|
2719
|
+
instrument: "https://example.com/users/alice/featured/1",
|
|
1446
2720
|
};
|
|
1447
2721
|
deepStrictEqual(
|
|
1448
|
-
await
|
|
2722
|
+
await request.toJsonLd({ contextLoader: mockDocumentLoader }),
|
|
1449
2723
|
expected,
|
|
1450
2724
|
);
|
|
1451
2725
|
|
|
1452
|
-
const loaded = await
|
|
2726
|
+
const loaded = await FeatureRequest.fromJsonLd(expected, {
|
|
1453
2727
|
documentLoader: mockDocumentLoader,
|
|
1454
2728
|
contextLoader: mockDocumentLoader,
|
|
1455
2729
|
});
|
|
2730
|
+
assertInstanceOf(loaded, FeatureRequest);
|
|
1456
2731
|
deepStrictEqual(
|
|
1457
|
-
|
|
1458
|
-
|
|
2732
|
+
loaded.objectId,
|
|
2733
|
+
new URL("https://example.com/users/bob"),
|
|
2734
|
+
);
|
|
2735
|
+
deepStrictEqual(
|
|
2736
|
+
loaded.instrumentId,
|
|
2737
|
+
new URL("https://example.com/users/alice/featured/1"),
|
|
1459
2738
|
);
|
|
1460
2739
|
});
|
|
1461
2740
|
|
|
@@ -2462,6 +3741,31 @@ test("FEP-fe34: crossOrigin trust behavior", async () => {
|
|
|
2462
3741
|
deepStrictEqual(result?.content, "This is a spoofed note");
|
|
2463
3742
|
});
|
|
2464
3743
|
|
|
3744
|
+
test("FEP-fe34: id-less owners honor crossOrigin trust", async () => {
|
|
3745
|
+
const create = await Create.fromJsonLd({
|
|
3746
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
3747
|
+
"@type": "Create",
|
|
3748
|
+
"actor": "https://example.com/actor",
|
|
3749
|
+
"object": {
|
|
3750
|
+
"@type": "Note",
|
|
3751
|
+
"@id": "https://different-origin.com/note",
|
|
3752
|
+
"content": "Embedded note",
|
|
3753
|
+
},
|
|
3754
|
+
});
|
|
3755
|
+
|
|
3756
|
+
const result = await create.getObject({
|
|
3757
|
+
crossOrigin: "trust",
|
|
3758
|
+
// deno-lint-ignore require-await
|
|
3759
|
+
documentLoader: async (url) => {
|
|
3760
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
3761
|
+
},
|
|
3762
|
+
});
|
|
3763
|
+
|
|
3764
|
+
assertInstanceOf(result, Note);
|
|
3765
|
+
deepStrictEqual(result.id, new URL("https://different-origin.com/note"));
|
|
3766
|
+
deepStrictEqual(result.content, "Embedded note");
|
|
3767
|
+
});
|
|
3768
|
+
|
|
2465
3769
|
test("FEP-fe34: Same origin objects are trusted", async () => {
|
|
2466
3770
|
// deno-lint-ignore require-await
|
|
2467
3771
|
const sameOriginDocumentLoader = async (url: string) => {
|
|
@@ -2609,6 +3913,44 @@ test("FEP-fe34: Constructor vs JSON-LD parsing trust difference", async () => {
|
|
|
2609
3913
|
deepStrictEqual(jsonLdResult?.content, "Fetched from origin");
|
|
2610
3914
|
});
|
|
2611
3915
|
|
|
3916
|
+
test(
|
|
3917
|
+
"FEP-fe34: Portable DID authorities are cross-origin boundaries",
|
|
3918
|
+
async () => {
|
|
3919
|
+
const create = await Create.fromJsonLd({
|
|
3920
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
3921
|
+
"@type": "Create",
|
|
3922
|
+
"@id": "ap://did:key:z6MkOwner/create",
|
|
3923
|
+
"actor": "ap://did:key:z6MkOwner/actor",
|
|
3924
|
+
"object": {
|
|
3925
|
+
"@type": "Note",
|
|
3926
|
+
"@id": "ap://did:key:z6MkOther/note",
|
|
3927
|
+
"content": "Embedded portable note",
|
|
3928
|
+
},
|
|
3929
|
+
});
|
|
3930
|
+
|
|
3931
|
+
// deno-lint-ignore require-await
|
|
3932
|
+
const documentLoader = async (url: string) => {
|
|
3933
|
+
if (url === "ap+ef61://did:key:z6MkOther/note") {
|
|
3934
|
+
return {
|
|
3935
|
+
documentUrl: url,
|
|
3936
|
+
contextUrl: null,
|
|
3937
|
+
document: {
|
|
3938
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
3939
|
+
"@type": "Note",
|
|
3940
|
+
"@id": "ap://did:key:z6MkOther/note",
|
|
3941
|
+
"content": "Fetched portable note",
|
|
3942
|
+
},
|
|
3943
|
+
};
|
|
3944
|
+
}
|
|
3945
|
+
throw new Error("Document not found");
|
|
3946
|
+
};
|
|
3947
|
+
|
|
3948
|
+
const result = await create.getObject({ documentLoader });
|
|
3949
|
+
assertInstanceOf(result, Note);
|
|
3950
|
+
deepStrictEqual(result.content, "Fetched portable note");
|
|
3951
|
+
},
|
|
3952
|
+
);
|
|
3953
|
+
|
|
2612
3954
|
test("FEP-fe34: Array properties respect cross-origin policy", async () => {
|
|
2613
3955
|
// deno-lint-ignore require-await
|
|
2614
3956
|
const crossOriginDocumentLoader = async (url: string) => {
|
|
@@ -2716,6 +4058,82 @@ test("FEP-fe34: Array properties with crossOrigin trust option", async () => {
|
|
|
2716
4058
|
deepStrictEqual((items[1] as Note).content, "Legitimate note 2");
|
|
2717
4059
|
});
|
|
2718
4060
|
|
|
4061
|
+
test("FEP-fe34: id-less arrays honor crossOrigin trust", async () => {
|
|
4062
|
+
const collection = await Collection.fromJsonLd({
|
|
4063
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
4064
|
+
"@type": "Collection",
|
|
4065
|
+
"items": [
|
|
4066
|
+
{
|
|
4067
|
+
"@type": "Note",
|
|
4068
|
+
"@id": "https://different-origin.com/note1",
|
|
4069
|
+
"content": "Embedded note 1",
|
|
4070
|
+
},
|
|
4071
|
+
{
|
|
4072
|
+
"@type": "Note",
|
|
4073
|
+
"@id": "https://different-origin.com/note2",
|
|
4074
|
+
"content": "Embedded note 2",
|
|
4075
|
+
},
|
|
4076
|
+
],
|
|
4077
|
+
});
|
|
4078
|
+
|
|
4079
|
+
const items = [];
|
|
4080
|
+
for await (
|
|
4081
|
+
const item of collection.getItems({
|
|
4082
|
+
crossOrigin: "trust",
|
|
4083
|
+
// deno-lint-ignore require-await
|
|
4084
|
+
documentLoader: async (url) => {
|
|
4085
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
4086
|
+
},
|
|
4087
|
+
})
|
|
4088
|
+
) {
|
|
4089
|
+
items.push(item);
|
|
4090
|
+
}
|
|
4091
|
+
|
|
4092
|
+
deepStrictEqual(items.length, 2);
|
|
4093
|
+
assertInstanceOf(items[0], Note);
|
|
4094
|
+
assertInstanceOf(items[1], Note);
|
|
4095
|
+
deepStrictEqual((items[0] as Note).content, "Embedded note 1");
|
|
4096
|
+
deepStrictEqual((items[1] as Note).content, "Embedded note 2");
|
|
4097
|
+
});
|
|
4098
|
+
|
|
4099
|
+
test("FEP-fe34: Array properties track trust per item", async () => {
|
|
4100
|
+
const collection = new Collection({
|
|
4101
|
+
id: new URL("https://example.com/collection"),
|
|
4102
|
+
items: [
|
|
4103
|
+
new Note({
|
|
4104
|
+
id: new URL("https://malicious.com/fake-note1"),
|
|
4105
|
+
content: "Trusted constructor note 1",
|
|
4106
|
+
}),
|
|
4107
|
+
new URL("https://different-origin.com/note2"),
|
|
4108
|
+
],
|
|
4109
|
+
});
|
|
4110
|
+
// deno-lint-ignore require-await
|
|
4111
|
+
const documentLoader = async (url: string) => {
|
|
4112
|
+
if (url === "https://different-origin.com/note2") {
|
|
4113
|
+
return {
|
|
4114
|
+
documentUrl: url,
|
|
4115
|
+
contextUrl: null,
|
|
4116
|
+
document: {
|
|
4117
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
4118
|
+
"@type": "Note",
|
|
4119
|
+
"@id": "https://malicious.com/fake-note2",
|
|
4120
|
+
"content": "Untrusted fetched note 2",
|
|
4121
|
+
},
|
|
4122
|
+
};
|
|
4123
|
+
}
|
|
4124
|
+
throw new Error("Document not found");
|
|
4125
|
+
};
|
|
4126
|
+
|
|
4127
|
+
const items = [];
|
|
4128
|
+
for await (const item of collection.getItems({ documentLoader })) {
|
|
4129
|
+
items.push(item);
|
|
4130
|
+
}
|
|
4131
|
+
|
|
4132
|
+
deepStrictEqual(items.length, 1);
|
|
4133
|
+
assertInstanceOf(items[0], Note);
|
|
4134
|
+
deepStrictEqual((items[0] as Note).content, "Trusted constructor note 1");
|
|
4135
|
+
});
|
|
4136
|
+
|
|
2719
4137
|
test(
|
|
2720
4138
|
"FEP-fe34: Embedded objects in arrays from JSON-LD respect cross-origin policy",
|
|
2721
4139
|
async () => {
|