@fedify/vocab 2.4.0-dev.1599 → 2.4.0-dev.1634

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/deno.json +1 -1
  2. package/dist/mod.cjs +472 -16
  3. package/dist/mod.d.cts +119 -1
  4. package/dist/mod.d.ts +119 -1
  5. package/dist/mod.js +473 -17
  6. package/dist-tests/{actor-NhcklZK0.mjs → actor-BQd8S953.mjs} +2 -2
  7. package/dist-tests/actor.test.mjs +2 -2
  8. package/dist-tests/application.yaml +15 -0
  9. package/dist-tests/audio.yaml +2 -1
  10. package/dist-tests/document.yaml +14 -1
  11. package/dist-tests/endpoints.yaml +14 -0
  12. package/dist-tests/group.yaml +15 -0
  13. package/dist-tests/image.yaml +2 -1
  14. package/dist-tests/link.yaml +14 -1
  15. package/dist-tests/lookup.test.mjs +71 -4
  16. package/dist-tests/organization.yaml +15 -0
  17. package/dist-tests/page.yaml +2 -1
  18. package/dist-tests/person.yaml +15 -0
  19. package/dist-tests/service.yaml +15 -0
  20. package/dist-tests/type.test.mjs +1 -1
  21. package/dist-tests/video.yaml +2 -1
  22. package/dist-tests/{vocab-BQ8y6QS7.mjs → vocab-Bjwctrxn.mjs} +472 -16
  23. package/dist-tests/vocab.test.mjs +232 -2
  24. package/package.json +4 -4
  25. package/src/__snapshots__/vocab.test.ts.snap +60 -18
  26. package/src/application.yaml +15 -0
  27. package/src/audio.yaml +2 -1
  28. package/src/document.yaml +14 -1
  29. package/src/endpoints.yaml +14 -0
  30. package/src/group.yaml +15 -0
  31. package/src/image.yaml +2 -1
  32. package/src/link.yaml +14 -1
  33. package/src/lookup.test.ts +121 -0
  34. package/src/lookup.ts +3 -1
  35. package/src/organization.yaml +15 -0
  36. package/src/page.yaml +2 -1
  37. package/src/person.yaml +15 -0
  38. package/src/preprocessors.ts +3 -1
  39. package/src/service.yaml +15 -0
  40. package/src/video.yaml +2 -1
  41. package/src/vocab.test.ts +372 -0
package/src/vocab.test.ts CHANGED
@@ -28,6 +28,7 @@ import {
28
28
  Activity,
29
29
  Agreement,
30
30
  Announce,
31
+ Application,
31
32
  Collection,
32
33
  Commitment,
33
34
  Create,
@@ -41,16 +42,20 @@ import {
41
42
  FeaturedItem,
42
43
  FeatureRequest,
43
44
  Follow,
45
+ Group,
44
46
  Hashtag,
47
+ Image,
45
48
  Intent,
46
49
  InteractionPolicy,
47
50
  InteractionRule,
48
51
  Link,
49
52
  Measure,
53
+ Multikey,
50
54
  Note,
51
55
  Object,
52
56
  Offer,
53
57
  OrderedCollectionPage,
58
+ Organization,
54
59
  Person,
55
60
  Place,
56
61
  Proposal,
@@ -58,6 +63,7 @@ import {
58
63
  QuoteAuthorization,
59
64
  QuoteRequest,
60
65
  Reject,
66
+ Service,
61
67
  Source,
62
68
  Tombstone,
63
69
  } from "./vocab.ts";
@@ -622,6 +628,242 @@ test("fromJsonLd() handles portable ActivityPub IRIs", async () => {
622
628
  );
623
629
  });
624
630
 
631
+ test("FEP-ef61: actor gateways round-trip as an ordered URI list", async () => {
632
+ const actorClasses = [Application, Group, Organization, Person, Service];
633
+ const gateways = [
634
+ new URL("https://server1.example/"),
635
+ new URL("https://server2.example/"),
636
+ ];
637
+
638
+ for (const ActorClass of actorClasses) {
639
+ const actor = await ActorClass.fromJsonLd({
640
+ "@context": [
641
+ "https://www.w3.org/ns/activitystreams",
642
+ "https://w3id.org/fep/ef61",
643
+ ],
644
+ type: ActorClass.name,
645
+ id: "ap+ef61://did:key:z6Mkabc/actor",
646
+ gateways: gateways.map((gateway) => gateway.href),
647
+ });
648
+ deepStrictEqual(actor.gateways, gateways);
649
+
650
+ const jsonLd = await actor.toJsonLd() as Record<string, unknown>;
651
+ deepStrictEqual(jsonLd.type, ActorClass.name);
652
+ deepStrictEqual(jsonLd.gateways, gateways.map((gateway) => gateway.href));
653
+
654
+ const restored = await ActorClass.fromJsonLd(jsonLd);
655
+ deepStrictEqual(restored.gateways, gateways);
656
+ }
657
+ });
658
+
659
+ test("FEP-ef61: actor gateways preserve single, empty, and invalid cases", async () => {
660
+ const singleGateway = new Person({
661
+ id: new URL("ap+ef61://did%3Akey%3Az6Mkabc/actor"),
662
+ gateways: [new URL("https://server.example/")],
663
+ });
664
+ deepStrictEqual(
665
+ (await singleGateway.toJsonLd() as Record<string, unknown>).gateways,
666
+ ["https://server.example/"],
667
+ );
668
+
669
+ const noGateways = new Person({
670
+ id: new URL("ap+ef61://did%3Akey%3Az6Mkabc/actor"),
671
+ gateways: [],
672
+ });
673
+ ok(!("gateways" in (await noGateways.toJsonLd() as Record<string, unknown>)));
674
+
675
+ await rejects(
676
+ () =>
677
+ Person.fromJsonLd({
678
+ "@context": [
679
+ "https://www.w3.org/ns/activitystreams",
680
+ "https://w3id.org/fep/ef61",
681
+ ],
682
+ type: "Person",
683
+ gateways: ["not a uri"],
684
+ }),
685
+ TypeError,
686
+ );
687
+ });
688
+
689
+ test("FEP-ef61: actor gateways accept @id typed JSON-LD references", async () => {
690
+ const actor = await Person.fromJsonLd({
691
+ "@context": [
692
+ "https://www.w3.org/ns/activitystreams",
693
+ {
694
+ gateways: {
695
+ "@id": "https://w3id.org/fep/ef61/gateways",
696
+ "@type": "@id",
697
+ "@container": "@list",
698
+ },
699
+ },
700
+ ],
701
+ type: "Person",
702
+ id: "ap+ef61://did:key:z6Mkabc/actor",
703
+ gateways: ["https://gateway.example/"],
704
+ });
705
+
706
+ deepStrictEqual(actor.gateways, [new URL("https://gateway.example/")]);
707
+ });
708
+
709
+ test("FEP-ef61: actor gateways must be HTTP(S) base URIs", async () => {
710
+ const validGateways = [
711
+ new URL("https://server.example/"),
712
+ new URL("http://server.example/"),
713
+ ];
714
+ const actor = new Person({
715
+ id: new URL("ap+ef61://did%3Akey%3Az6Mkabc/actor"),
716
+ gateways: validGateways,
717
+ });
718
+ deepStrictEqual(actor.gateways, validGateways);
719
+
720
+ for (
721
+ const gateway of [
722
+ "ftp://server.example/",
723
+ "https://user:pass@server.example/",
724
+ "https://user@server.example/",
725
+ "https://server.example/path",
726
+ "https://server.example/?x=1",
727
+ "https://server.example/#fragment",
728
+ ]
729
+ ) {
730
+ throws(
731
+ () =>
732
+ new Person({
733
+ id: new URL("ap+ef61://did%3Akey%3Az6Mkabc/actor"),
734
+ gateways: [new URL(gateway)],
735
+ }),
736
+ TypeError,
737
+ );
738
+
739
+ await rejects(
740
+ () =>
741
+ Person.fromJsonLd({
742
+ "@context": [
743
+ "https://www.w3.org/ns/activitystreams",
744
+ "https://w3id.org/fep/ef61",
745
+ ],
746
+ type: "Person",
747
+ gateways: [gateway],
748
+ }),
749
+ TypeError,
750
+ );
751
+ }
752
+ });
753
+
754
+ test("FEP-ef61: digestMultibase round-trips on links and media objects", async () => {
755
+ const digestMultibase = "zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n";
756
+
757
+ const link = await Link.fromJsonLd({
758
+ "@context": [
759
+ "https://www.w3.org/ns/activitystreams",
760
+ "https://w3id.org/fep/ef61",
761
+ ],
762
+ type: "Link",
763
+ href: "hl:zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n",
764
+ digestMultibase,
765
+ });
766
+ deepStrictEqual(link.digestMultibase, digestMultibase);
767
+ deepStrictEqual(
768
+ (await link.toJsonLd() as Record<string, unknown>).digestMultibase,
769
+ digestMultibase,
770
+ );
771
+
772
+ for (const cls of [Document, Image]) {
773
+ const media = await cls.fromJsonLd({
774
+ "@context": [
775
+ "https://www.w3.org/ns/activitystreams",
776
+ "https://w3id.org/fep/ef61",
777
+ ],
778
+ type: cls.name,
779
+ url: "hl:zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n",
780
+ mediaType: "image/png",
781
+ digestMultibase,
782
+ });
783
+ deepStrictEqual(media.digestMultibase, digestMultibase);
784
+ const jsonLd = await media.toJsonLd() as Record<string, unknown>;
785
+ deepStrictEqual(jsonLd.type, cls.name);
786
+ deepStrictEqual(jsonLd.digestMultibase, digestMultibase);
787
+ }
788
+ });
789
+
790
+ test("FEP-ef61: digestMultibase avoids Data Integrity context conflicts", async () => {
791
+ const digestMultibase = "zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n";
792
+ const image = new Image({
793
+ url: new URL("hl:zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n"),
794
+ mediaType: "image/png",
795
+ digestMultibase,
796
+ });
797
+
798
+ const expanded = await image.toJsonLd({ format: "expand" });
799
+ deepStrictEqual(
800
+ (expanded as Record<string, unknown>[])[0][
801
+ "https://www.w3.org/ns/credentials/v2#digestMultibase"
802
+ ],
803
+ [{ "@value": digestMultibase }],
804
+ );
805
+ ok(
806
+ !(
807
+ "https://w3id.org/security#digestMultibase" in
808
+ (expanded as Record<string, unknown>[])[0]
809
+ ),
810
+ );
811
+
812
+ const compact = await image.toJsonLd() as Record<string, unknown>;
813
+ deepStrictEqual(compact.digestMultibase, digestMultibase);
814
+ ok(
815
+ !(compact["@context"] as unknown[]).includes("https://w3id.org/fep/ef61"),
816
+ );
817
+ ok(
818
+ (compact["@context"] as unknown[]).some((context) =>
819
+ context != null && typeof context === "object" &&
820
+ (context as Record<string, unknown>).digestMultibase ===
821
+ "https://www.w3.org/ns/credentials/v2#digestMultibase"
822
+ ),
823
+ );
824
+ });
825
+
826
+ test("FEP-ef61: digestMultibase parses after Data Integrity v1 contexts", async () => {
827
+ const digestMultibase = "zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n";
828
+ const image = await Image.fromJsonLd({
829
+ "@context": [
830
+ "https://www.w3.org/ns/activitystreams",
831
+ "https://w3id.org/security/data-integrity/v1",
832
+ "https://w3id.org/fep/ef61",
833
+ ],
834
+ type: "Image",
835
+ url: "hl:zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n",
836
+ mediaType: "image/png",
837
+ digestMultibase,
838
+ });
839
+
840
+ deepStrictEqual(image.digestMultibase, digestMultibase);
841
+ });
842
+
843
+ test("FEP-ef61: Link image normalization preserves digestMultibase", async () => {
844
+ const digestMultibase = "zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n";
845
+ const obj = await Object.fromJsonLd({
846
+ "@context": [
847
+ "https://www.w3.org/ns/activitystreams",
848
+ "https://w3id.org/security/data-integrity/v1",
849
+ "https://w3id.org/fep/ef61",
850
+ ],
851
+ type: "Note",
852
+ image: {
853
+ type: "Link",
854
+ href: "hl:zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n",
855
+ mediaType: "image/png",
856
+ digestMultibase,
857
+ },
858
+ });
859
+ const images = [];
860
+ for await (const img of obj.getImages()) {
861
+ images.push(img);
862
+ }
863
+
864
+ deepStrictEqual(images[0]?.digestMultibase, digestMultibase);
865
+ });
866
+
625
867
  test("fromJsonLd() caches text that mentions portable ActivityPub IRIs", async () => {
626
868
  const noteJson = {
627
869
  "@context": [
@@ -1750,6 +1992,7 @@ test("Person.toJsonLd()", async () => {
1750
1992
  deepStrictEqual(await person.toJsonLd(), {
1751
1993
  "@context": [
1752
1994
  "https://www.w3.org/ns/activitystreams",
1995
+ "https://w3id.org/fep/ef61",
1753
1996
  "https://w3id.org/security/v1",
1754
1997
  "https://w3id.org/security/data-integrity/v1",
1755
1998
  "https://www.w3.org/ns/did/v1",
@@ -1963,6 +2206,39 @@ test("Endpoints.toJsonLd() omits type", async () => {
1963
2206
  deepStrictEqual(restored, ep);
1964
2207
  });
1965
2208
 
2209
+ test("Endpoints.uploadMedia round-trips", async () => {
2210
+ const ep = new Endpoints({
2211
+ uploadMedia: new URL("https://example.com/users/alice/media"),
2212
+ });
2213
+ deepStrictEqual(
2214
+ ep.uploadMedia?.href,
2215
+ "https://example.com/users/alice/media",
2216
+ );
2217
+
2218
+ const compact = await ep.toJsonLd() as Record<string, unknown>;
2219
+ deepStrictEqual(
2220
+ compact["uploadMedia"],
2221
+ "https://example.com/users/alice/media",
2222
+ );
2223
+
2224
+ // Round-trip through every format under the standard AS term.
2225
+ for (const format of [undefined, "compact" as const, "expand" as const]) {
2226
+ const jsonLd = await ep.toJsonLd({
2227
+ format,
2228
+ contextLoader: mockDocumentLoader,
2229
+ });
2230
+ const restored = await Endpoints.fromJsonLd(jsonLd, {
2231
+ documentLoader: mockDocumentLoader,
2232
+ contextLoader: mockDocumentLoader,
2233
+ });
2234
+ deepStrictEqual(
2235
+ restored.uploadMedia?.href,
2236
+ "https://example.com/users/alice/media",
2237
+ `round-trip failed for format=${format ?? "heuristic"}`,
2238
+ );
2239
+ }
2240
+ });
2241
+
1966
2242
  test("Source.toJsonLd() omits type", async () => {
1967
2243
  const src = new Source({
1968
2244
  content: "Hello, world!",
@@ -2029,6 +2305,7 @@ test("Endpoints with all properties set omits type", async () => {
2029
2305
  provideClientKey: new URL("https://example.com/provide-key"),
2030
2306
  signClientKey: new URL("https://example.com/sign-key"),
2031
2307
  sharedInbox: new URL("https://example.com/inbox"),
2308
+ uploadMedia: new URL("https://example.com/upload-media"),
2032
2309
  });
2033
2310
 
2034
2311
  // Compact heuristic path
@@ -2049,6 +2326,7 @@ test("Endpoints with all properties set omits type", async () => {
2049
2326
  );
2050
2327
  deepStrictEqual(compact["signClientKey"], "https://example.com/sign-key");
2051
2328
  deepStrictEqual(compact["sharedInbox"], "https://example.com/inbox");
2329
+ deepStrictEqual(compact["uploadMedia"], "https://example.com/upload-media");
2052
2330
 
2053
2331
  // Round-trip all three formats
2054
2332
  for (
@@ -2495,6 +2773,7 @@ test("InteractionPolicy.canFeature", async () => {
2495
2773
  const expected = {
2496
2774
  "@context": [
2497
2775
  "https://www.w3.org/ns/activitystreams",
2776
+ "https://w3id.org/fep/ef61",
2498
2777
  "https://w3id.org/security/v1",
2499
2778
  "https://w3id.org/security/data-integrity/v1",
2500
2779
  "https://www.w3.org/ns/did/v1",
@@ -3800,6 +4079,30 @@ test("FEP-fe34: Same origin objects are trusted", async () => {
3800
4079
  deepStrictEqual(result?.content, "This is a legitimate note");
3801
4080
  });
3802
4081
 
4082
+ test("FEP-fe34: Same-authority non-FE34 embedded objects are trusted", async () => {
4083
+ const create = await Create.fromJsonLd({
4084
+ "@context": "https://www.w3.org/ns/activitystreams",
4085
+ "@type": "Create",
4086
+ "@id": "at://did:plc:example/collection/item",
4087
+ "actor": "at://did:plc:example/actor/self",
4088
+ "object": {
4089
+ "@type": "Note",
4090
+ "@id": "at://did:plc:example/collection/reply",
4091
+ "content": "Embedded AT Protocol note",
4092
+ },
4093
+ });
4094
+
4095
+ const result = await create.getObject({
4096
+ // deno-lint-ignore require-await
4097
+ documentLoader: async (url) => {
4098
+ throw new Error(`Unexpected fetch: ${url}`);
4099
+ },
4100
+ });
4101
+
4102
+ assertInstanceOf(result, Note);
4103
+ deepStrictEqual(result.content, "Embedded AT Protocol note");
4104
+ });
4105
+
3803
4106
  test(
3804
4107
  "FEP-fe34: Embedded cross-origin objects from JSON-LD are ignored by default",
3805
4108
  async () => {
@@ -3951,6 +4254,74 @@ test(
3951
4254
  },
3952
4255
  );
3953
4256
 
4257
+ test(
4258
+ "FEP-fe34: DID verification methods share portable actor cryptographic origin",
4259
+ async () => {
4260
+ const person = await Person.fromJsonLd({
4261
+ "@id": "ap://did:key:z6MkOwner/actor",
4262
+ "@type": ["https://www.w3.org/ns/activitystreams#Person"],
4263
+ "https://w3id.org/security#assertionMethod": [{
4264
+ "@id": "did:key:z6MkOwner#z6MkOwner",
4265
+ "@type": ["https://w3id.org/security#Multikey"],
4266
+ "https://w3id.org/security#controller": [{
4267
+ "@id": "did:key:z6MkOwner",
4268
+ }],
4269
+ }],
4270
+ });
4271
+
4272
+ // deno-lint-ignore require-await
4273
+ const documentLoader = async (url: string) => {
4274
+ throw new Error(`Unexpected fetch: ${url}`);
4275
+ };
4276
+
4277
+ const methods = [];
4278
+ for await (const method of person.getAssertionMethods({ documentLoader })) {
4279
+ methods.push(method);
4280
+ }
4281
+
4282
+ deepStrictEqual(methods.length, 1);
4283
+ assertInstanceOf(methods[0], Multikey);
4284
+ deepStrictEqual(
4285
+ methods[0].id,
4286
+ new URL("did:key:z6MkOwner#z6MkOwner"),
4287
+ );
4288
+ },
4289
+ );
4290
+
4291
+ test(
4292
+ "FEP-fe34: DID verification methods from another cryptographic origin are untrusted",
4293
+ async () => {
4294
+ const person = await Person.fromJsonLd({
4295
+ "@id": "ap://did:key:z6MkOwner/actor",
4296
+ "@type": ["https://www.w3.org/ns/activitystreams#Person"],
4297
+ "https://w3id.org/security#assertionMethod": [{
4298
+ "@id": "did:key:z6MkOther#z6MkOther",
4299
+ "@type": ["https://w3id.org/security#Multikey"],
4300
+ "https://w3id.org/security#controller": [{
4301
+ "@id": "did:key:z6MkOther",
4302
+ }],
4303
+ }],
4304
+ });
4305
+
4306
+ let fetches = 0;
4307
+ const methods = [];
4308
+ for await (
4309
+ const method of person.getAssertionMethods({
4310
+ suppressError: true,
4311
+ // deno-lint-ignore require-await
4312
+ documentLoader: async (url) => {
4313
+ fetches++;
4314
+ throw new Error(`Unexpected fetch: ${url}`);
4315
+ },
4316
+ })
4317
+ ) {
4318
+ methods.push(method);
4319
+ }
4320
+ deepStrictEqual(methods, []);
4321
+ deepStrictEqual(fetches, 1);
4322
+ },
4323
+ );
4324
+
3954
4325
  test("FEP-fe34: Array properties respect cross-origin policy", async () => {
3955
4326
  // deno-lint-ignore require-await
3956
4327
  const crossOriginDocumentLoader = async (url: string) => {
@@ -4652,6 +5023,7 @@ const sampleValues: Record<string, any> = {
4652
5023
  ]),
4653
5024
  "fedify:langTag": new Intl.Locale("en-Latn-US"),
4654
5025
  "fedify:url": new URL("https://fedify.dev/"),
5026
+ "fedify:gatewayUrl": new URL("https://gateway.example/"),
4655
5027
  "fedify:publicKey": rsaPublicKey.publicKey,
4656
5028
  "fedify:multibaseKey": ed25519PublicKey.publicKey,
4657
5029
  "fedify:proofPurpose": "assertionMethod",