@fedify/vocab 2.4.0-dev.2063 → 2.4.0-dev.2071

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.
@@ -1,6 +1,6 @@
1
1
  import "temporal-polyfill";
2
2
  globalThis.addEventListener = () => {};
3
- import { F as Person, P as Organization, U as Service, a as Application, b as Group } from "./vocab-DZq1JW3s.mjs";
3
+ import { F as Person, P as Organization, U as Service, a as Application, b as Group } from "./vocab-BHtBRusa.mjs";
4
4
  import { t as getTypeId } from "./type-BMdjm4Eg.mjs";
5
5
  import { canonicalizePortableUri, formatIri, fromCompatibleEf61Id, isGatewayUrl } from "@fedify/vocab-runtime";
6
6
  import { lookupWebFinger } from "@fedify/webfinger";
@@ -8,7 +8,7 @@ import { SpanStatusCode, trace } from "@opentelemetry/api";
8
8
  import { domainToASCII, domainToUnicode } from "node:url";
9
9
  //#region deno.json
10
10
  var name = "@fedify/vocab";
11
- var version = "2.4.0-dev.2063+366c0362";
11
+ var version = "2.4.0-dev.2071+8b9dccb9";
12
12
  //#endregion
13
13
  //#region src/actor.ts
14
14
  const ACTOR_DISCOVERY_HISTOGRAM_BUCKETS = [
@@ -1,8 +1,8 @@
1
1
  import "temporal-polyfill";
2
2
  globalThis.addEventListener = () => {};
3
- import { F as Person, P as Organization, Q as __exportAll, U as Service, a as Application, b as Group } from "./vocab-DZq1JW3s.mjs";
4
- import { t as esm_default } from "./esm-CuilqPDu.mjs";
5
- import { a as normalizeActorHandle, i as isActor, n as getActorHandle, r as getActorTypeName, t as getActorClassByTypeName } from "./actor-hpNVHQvY.mjs";
3
+ import { F as Person, P as Organization, Q as __exportAll, U as Service, a as Application, b as Group } from "./vocab-BHtBRusa.mjs";
4
+ import { t as esm_default } from "./esm-C9nxP5_e.mjs";
5
+ import { a as normalizeActorHandle, i as isActor, n as getActorHandle, r as getActorTypeName, t as getActorClassByTypeName } from "./actor-CPbEFVm1.mjs";
6
6
  import { createTestMeterProvider, test } from "@fedify/fixture";
7
7
  import { deepStrictEqual, ok, rejects, strictEqual, throws } from "node:assert/strict";
8
8
  //#region ../../node_modules/.pnpm/fast-check@3.23.2/node_modules/fast-check/lib/esm/check/precondition/PreconditionFailure.js
@@ -1,6 +1,6 @@
1
1
  import "temporal-polyfill";
2
2
  globalThis.addEventListener = () => {};
3
- import { $ as __toESM, Z as __commonJSMin } from "./vocab-DZq1JW3s.mjs";
3
+ import { $ as __toESM, Z as __commonJSMin } from "./vocab-BHtBRusa.mjs";
4
4
  //#endregion
5
5
  //#region ../../node_modules/.pnpm/regexparam@3.0.0/node_modules/regexparam/dist/index.mjs
6
6
  var import_glob_to_regexp = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
@@ -1,12 +1,12 @@
1
1
  import "temporal-polyfill";
2
2
  globalThis.addEventListener = () => {};
3
- import { F as Person, M as OrderedCollection, c as CollectionPage, k as Note, s as Collection, u as Create } from "./vocab-DZq1JW3s.mjs";
4
- import { t as esm_default } from "./esm-CuilqPDu.mjs";
5
- import { n as traverseCollection } from "./lookup-Bi6dOTm_.mjs";
3
+ import { F as Person, M as OrderedCollection, c as CollectionPage, k as Note, s as Collection, u as Create } from "./vocab-BHtBRusa.mjs";
4
+ import { t as esm_default } from "./esm-C9nxP5_e.mjs";
5
+ import { n as traverseCollection } from "./lookup-e0QSMZGC.mjs";
6
6
  import { t as assertInstanceOf } from "./utils-Df2odC27.mjs";
7
7
  import { createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
8
8
  import { deepStrictEqual, ok, rejects } from "node:assert/strict";
9
- import { FetchError, getDocumentLoader } from "@fedify/vocab-runtime";
9
+ import { FetchError, getDocumentLoader, parseIri } from "@fedify/vocab-runtime";
10
10
  //#region src/gateway.test.ts
11
11
  const did = "did:key:z6Mkabc";
12
12
  const objectId = `ap+ef61://${did}/objects/1`;
@@ -507,11 +507,16 @@ test("getObject() shares context documents within a dereference", async (t) => {
507
507
  const loaded = [];
508
508
  const contextLoader = async (url) => {
509
509
  loaded.push(url);
510
+ if (url === contextUrl) return {
511
+ contextUrl: null,
512
+ documentUrl: url,
513
+ document: { "@context": {} }
514
+ };
510
515
  return await mockDocumentLoader(url);
511
516
  };
512
517
  const object = await (await createActivity()).getObject({
513
518
  documentLoader: createLoader({ [`https://gw1.example${gatewayPath}`]: {
514
- ...note(),
519
+ ...document,
515
520
  attributedTo: "https://example.com/person"
516
521
  } }),
517
522
  contextLoader,
@@ -519,13 +524,13 @@ test("getObject() shares context documents within a dereference", async (t) => {
519
524
  verifyPortableObject: createVerifier()
520
525
  });
521
526
  assertInstanceOf(object, Note);
522
- deepStrictEqual(loaded, []);
527
+ deepStrictEqual(loaded, [contextUrl]);
523
528
  deepStrictEqual((await object.getAttribution({ documentLoader: createLoader({ "https://example.com/person": {
524
- "@context": "https://www.w3.org/ns/activitystreams",
529
+ "@context": ["https://www.w3.org/ns/activitystreams", contextUrl],
525
530
  id: "https://example.com/person",
526
531
  type: "Person"
527
532
  } }) }))?.id, new URL("https://example.com/person"));
528
- ok(loaded.includes("https://www.w3.org/ns/activitystreams"));
533
+ deepStrictEqual(loaded, [contextUrl, contextUrl]);
529
534
  });
530
535
  await t.step("failed context loads are retried", async () => {
531
536
  let failures = 1;
@@ -763,14 +768,222 @@ test("accessors do not trust objects embedded in unsecured collections", async (
763
768
  suppressError: true
764
769
  })), []);
765
770
  });
766
- test("accessors reject compatible identifiers from portable objects", async () => {
767
- const compatibleId = gatewayUrl("https://evil.example", "/actor/outbox");
768
- const collection = (id) => ({
771
+ function createRedirectingLoader(responses) {
772
+ const fetched = [];
773
+ const loader = async (url) => {
774
+ fetched.push(url);
775
+ const response = responses[url];
776
+ if (response == null) throw new FetchError(url, "HTTP 404", new globalThis.Response(null, { status: 404 }));
777
+ return {
778
+ contextUrl: null,
779
+ ...response
780
+ };
781
+ };
782
+ return Object.assign(loader, { fetched });
783
+ }
784
+ function createHttpActivity(object) {
785
+ return Create.fromJsonLd({
769
786
  "@context": "https://www.w3.org/ns/activitystreams",
770
- id,
771
- type: "OrderedCollection",
772
- totalItems: 0
787
+ type: "Create",
788
+ id: "https://example.com/activities/1",
789
+ object
790
+ }, { contextLoader: mockDocumentLoader });
791
+ }
792
+ const compatibleNoteId = gatewayUrl("https://gw.example", "/objects/1");
793
+ test("accessors dereference compatible identifiers through their gateways", async (t) => {
794
+ await t.step("through the named gateway", async () => {
795
+ const documentLoader = createLoader({ [compatibleNoteId]: note() });
796
+ const verifier = createRecordingVerifier();
797
+ const activity = await createHttpActivity(compatibleNoteId);
798
+ const object = await activity.getObject({
799
+ documentLoader,
800
+ contextLoader: mockDocumentLoader,
801
+ verifyPortableObject: verifier
802
+ });
803
+ assertInstanceOf(object, Note);
804
+ deepStrictEqual(object.id, parseIri(objectId));
805
+ deepStrictEqual(documentLoader.fetched, [compatibleNoteId]);
806
+ deepStrictEqual(verifier.calls.length, 1);
807
+ const { options } = verifier.calls[0];
808
+ deepStrictEqual(options.documentUrl, new URL(compatibleNoteId));
809
+ deepStrictEqual(options.gateways, void 0);
810
+ deepStrictEqual(options.gatewayHints, [new URL("https://gw.example")]);
811
+ ok(options.referrer?.object === activity);
812
+ ok(await activity.getObject({ documentLoader: createLoader({}) }) === object);
813
+ });
814
+ await t.step("then through explicit gateways", async () => {
815
+ const documentLoader = createLoader({ [gatewayUrl("https://gw2.example", "/objects/1")]: note() });
816
+ const verifier = createRecordingVerifier();
817
+ assertInstanceOf(await (await createHttpActivity(compatibleNoteId)).getObject({
818
+ documentLoader,
819
+ contextLoader: mockDocumentLoader,
820
+ gateways: ["https://gw2.example", "https://gw.example"],
821
+ verifyPortableObject: verifier
822
+ }), Note);
823
+ deepStrictEqual(documentLoader.fetched, [compatibleNoteId, gatewayUrl("https://gw2.example", "/objects/1")]);
824
+ deepStrictEqual(verifier.calls[0].options.gateways, [new URL("https://gw2.example"), new URL("https://gw.example")]);
825
+ deepStrictEqual(verifier.calls[0].options.gatewayHints, void 0);
826
+ const documentLoader2 = createLoader({ [compatibleNoteId]: note() });
827
+ assertInstanceOf(await (await createHttpActivity(compatibleNoteId)).getObject({
828
+ documentLoader: documentLoader2,
829
+ contextLoader: mockDocumentLoader,
830
+ gateways: [],
831
+ verifyPortableObject: createVerifier()
832
+ }), Note);
833
+ deepStrictEqual(documentLoader2.fetched, [compatibleNoteId]);
834
+ });
835
+ await t.step("in plural accessors", async () => {
836
+ const documentLoader = createLoader({ [compatibleNoteId]: note() });
837
+ deepStrictEqual((await Array.fromAsync((await createHttpActivity([compatibleNoteId])).getObjects({
838
+ documentLoader,
839
+ contextLoader: mockDocumentLoader,
840
+ verifyPortableObject: createVerifier()
841
+ }))).map((o) => o.id), [parseIri(objectId)]);
842
+ deepStrictEqual(documentLoader.fetched, [compatibleNoteId]);
773
843
  });
844
+ });
845
+ test("accessors reject forged objects at compatible identifiers", async () => {
846
+ const forgedId = gatewayUrl("https://evil.example", "/objects/1");
847
+ const forged = {
848
+ "@context": "https://www.w3.org/ns/activitystreams",
849
+ id: forgedId,
850
+ type: "Note",
851
+ attributedTo: gatewayUrl("https://evil.example", "/actor"),
852
+ content: "Forged"
853
+ };
854
+ for (const crossOrigin of [
855
+ void 0,
856
+ "ignore",
857
+ "trust"
858
+ ]) {
859
+ const verifier = createRecordingVerifier();
860
+ deepStrictEqual(await (await createHttpActivity(forgedId)).getObject({
861
+ documentLoader: createLoader({ [forgedId]: forged }),
862
+ contextLoader: mockDocumentLoader,
863
+ verifyPortableObject: verifier,
864
+ crossOrigin
865
+ }), null);
866
+ deepStrictEqual(verifier.calls, []);
867
+ deepStrictEqual(await (await createHttpActivity(forgedId)).getObject({
868
+ documentLoader: createLoader({ [forgedId]: note() }),
869
+ contextLoader: mockDocumentLoader,
870
+ verifyPortableObject: createVerifier(false),
871
+ crossOrigin
872
+ }), null);
873
+ }
874
+ await rejects(async () => await (await createHttpActivity(forgedId)).getObject({
875
+ documentLoader: createLoader({ [forgedId]: note() }),
876
+ contextLoader: mockDocumentLoader,
877
+ verifyPortableObject: createVerifier(false),
878
+ crossOrigin: "throw"
879
+ }), /No gateway returned a valid portable object/);
880
+ });
881
+ test("accessors reject malformed compatible identifiers", async () => {
882
+ const malformed = [`${compatibleNoteId}?@gateway=https%3A%2F%2Fgw2.example`, compatibleNoteId.replace("https://", "https://user:pass@")];
883
+ for (const url of malformed) {
884
+ const documentLoader = createLoader({ [url]: note() });
885
+ const verifier = createRecordingVerifier();
886
+ const activity = await createHttpActivity(url);
887
+ deepStrictEqual(await activity.getObject({
888
+ documentLoader,
889
+ contextLoader: mockDocumentLoader,
890
+ verifyPortableObject: verifier
891
+ }), null);
892
+ deepStrictEqual(await activity.getObject({
893
+ documentLoader,
894
+ contextLoader: mockDocumentLoader,
895
+ verifyPortableObject: verifier,
896
+ crossOrigin: "throw",
897
+ suppressError: true
898
+ }), null);
899
+ await rejects(async () => await activity.getObject({
900
+ documentLoader,
901
+ contextLoader: mockDocumentLoader,
902
+ verifyPortableObject: verifier,
903
+ crossOrigin: "throw"
904
+ }), /malformed FEP-ef61 compatible identifier/);
905
+ deepStrictEqual(documentLoader.fetched, []);
906
+ deepStrictEqual(verifier.calls, []);
907
+ }
908
+ });
909
+ test("accessors keep fetching compatible identifiers as HTTP(S) URLs without verifyPortableObject", async (t) => {
910
+ const document = {
911
+ ...note(),
912
+ id: compatibleNoteId
913
+ };
914
+ await t.step("singular accessors", async () => {
915
+ const documentLoader = createLoader({ [compatibleNoteId]: document });
916
+ const activity = await createHttpActivity(compatibleNoteId);
917
+ const object = await activity.getObject({
918
+ documentLoader,
919
+ contextLoader: mockDocumentLoader
920
+ });
921
+ assertInstanceOf(object, Note);
922
+ deepStrictEqual(object.id, new URL(compatibleNoteId));
923
+ deepStrictEqual(await activity.getObject({
924
+ documentLoader,
925
+ contextLoader: mockDocumentLoader,
926
+ verifyPortableObject: createVerifier()
927
+ }), null);
928
+ deepStrictEqual(documentLoader.fetched, [compatibleNoteId, compatibleNoteId]);
929
+ });
930
+ await t.step("plural accessors", async () => {
931
+ const documentLoader = createLoader({ [compatibleNoteId]: document });
932
+ const activity = await createHttpActivity([compatibleNoteId]);
933
+ deepStrictEqual((await Array.fromAsync(activity.getObjects({
934
+ documentLoader,
935
+ contextLoader: mockDocumentLoader
936
+ }))).map((o) => o.id), [new URL(compatibleNoteId)]);
937
+ deepStrictEqual(await Array.fromAsync(activity.getObjects({
938
+ documentLoader,
939
+ contextLoader: mockDocumentLoader,
940
+ verifyPortableObject: createVerifier()
941
+ })), []);
942
+ });
943
+ await t.step("redirects to ordinary objects", async () => {
944
+ const plainUrl = "https://gw.example/notes/1";
945
+ const documentLoader = createRedirectingLoader({ [compatibleNoteId]: {
946
+ documentUrl: plainUrl,
947
+ document: {
948
+ ...note(),
949
+ id: plainUrl
950
+ }
951
+ } });
952
+ for (const activity of [await createHttpActivity(compatibleNoteId)]) {
953
+ deepStrictEqual((await activity.getObject({
954
+ documentLoader,
955
+ contextLoader: mockDocumentLoader
956
+ }))?.id, new URL(plainUrl));
957
+ deepStrictEqual(await activity.getObject({
958
+ documentLoader,
959
+ contextLoader: mockDocumentLoader,
960
+ verifyPortableObject: createVerifier()
961
+ }), null);
962
+ }
963
+ const activity = await createHttpActivity([compatibleNoteId]);
964
+ deepStrictEqual((await Array.fromAsync(activity.getObjects({
965
+ documentLoader,
966
+ contextLoader: mockDocumentLoader
967
+ }))).length, 1);
968
+ deepStrictEqual(await Array.fromAsync(activity.getObjects({
969
+ documentLoader,
970
+ contextLoader: mockDocumentLoader,
971
+ verifyPortableObject: createVerifier()
972
+ })), []);
973
+ });
974
+ await t.step("embedded objects", async () => {
975
+ deepStrictEqual((await (await createHttpActivity({
976
+ type: "Note",
977
+ id: gatewayUrl("https://example.com", "/objects/1"),
978
+ content: "Embedded"
979
+ })).getObject({
980
+ documentLoader: createLoader({}),
981
+ contextLoader: mockDocumentLoader
982
+ }))?.id, new URL(gatewayUrl("https://example.com", "/objects/1")));
983
+ });
984
+ });
985
+ test("accessors dereference compatible identifiers from portable objects", async () => {
986
+ const compatibleOutbox = gatewayUrl("https://gw.example", "/actor/outbox");
774
987
  const person = (outbox) => Person.fromJsonLd({
775
988
  "@context": "https://www.w3.org/ns/activitystreams",
776
989
  id: `ap://${did}/actor`,
@@ -778,37 +991,275 @@ test("accessors reject compatible identifiers from portable objects", async () =
778
991
  inbox: `ap://${did}/actor/inbox`,
779
992
  outbox
780
993
  }, { contextLoader: mockDocumentLoader });
781
- const redirectingLoader = (url) => Promise.resolve({
782
- contextUrl: null,
783
- documentUrl: url === "https://example.com/outbox" ? compatibleId : url,
784
- document: collection(url === "https://example.com/outbox" ? compatibleId : url)
994
+ const documentLoader = createRedirectingLoader({
995
+ [compatibleOutbox]: {
996
+ documentUrl: compatibleOutbox,
997
+ document: portableCollection("/actor/outbox")
998
+ },
999
+ "https://example.com/outbox": {
1000
+ documentUrl: compatibleOutbox,
1001
+ document: portableCollection("/actor/outbox")
1002
+ }
785
1003
  });
786
- for (const crossOrigin of [void 0, "trust"]) {
787
- const options = {
788
- documentLoader: redirectingLoader,
1004
+ for (const crossOrigin of [void 0, "trust"]) for (const outbox of [
1005
+ compatibleOutbox,
1006
+ "https://example.com/outbox",
1007
+ {
1008
+ ...portableCollection("/actor/outbox"),
1009
+ id: compatibleOutbox
1010
+ }
1011
+ ]) {
1012
+ const verifier = createRecordingVerifier();
1013
+ const collection = await (await person(outbox)).getOutbox({
1014
+ documentLoader,
789
1015
  contextLoader: mockDocumentLoader,
790
- verifyPortableObject: createRecordingVerifier(),
1016
+ verifyPortableObject: verifier,
791
1017
  crossOrigin
1018
+ });
1019
+ assertInstanceOf(collection, OrderedCollection);
1020
+ deepStrictEqual(collection.id, parseIri(`ap://${did}/actor/outbox`));
1021
+ deepStrictEqual(verifier.calls.length, 1);
1022
+ deepStrictEqual(verifier.calls[0].options.documentUrl, new URL(compatibleOutbox));
1023
+ deepStrictEqual(verifier.calls[0].options.gatewayHints, [new URL("https://gw.example")]);
1024
+ }
1025
+ await rejects(async () => await (await person(compatibleOutbox)).getOutbox({
1026
+ documentLoader,
1027
+ contextLoader: mockDocumentLoader
1028
+ }), TypeError);
1029
+ deepStrictEqual(await (await person(compatibleOutbox)).getOutbox({
1030
+ documentLoader,
1031
+ contextLoader: mockDocumentLoader,
1032
+ suppressError: true
1033
+ }), null);
1034
+ });
1035
+ test("accessors verify fetched documents that stand for portable objects", async (t) => {
1036
+ const plainUrl = "https://example.com/notes/1";
1037
+ await t.step("a redirect to a compatible identifier", async () => {
1038
+ const documentLoader = createRedirectingLoader({ [plainUrl]: {
1039
+ documentUrl: compatibleNoteId,
1040
+ document: note()
1041
+ } });
1042
+ const verifier = createRecordingVerifier();
1043
+ const object = await (await createHttpActivity(plainUrl)).getObject({
1044
+ documentLoader,
1045
+ contextLoader: mockDocumentLoader,
1046
+ verifyPortableObject: verifier
1047
+ });
1048
+ assertInstanceOf(object, Note);
1049
+ deepStrictEqual(object.id, parseIri(objectId));
1050
+ deepStrictEqual(documentLoader.fetched, [plainUrl]);
1051
+ deepStrictEqual(verifier.calls.length, 1);
1052
+ deepStrictEqual(verifier.calls[0].options.documentUrl, new URL(compatibleNoteId));
1053
+ deepStrictEqual(verifier.calls[0].options.gatewayHints, [new URL("https://gw.example")]);
1054
+ deepStrictEqual(await (await createHttpActivity(plainUrl)).getObject({
1055
+ documentLoader,
1056
+ contextLoader: mockDocumentLoader,
1057
+ verifyPortableObject: createVerifier(false)
1058
+ }), null);
1059
+ await rejects(async () => await (await createHttpActivity(plainUrl)).getObject({
1060
+ documentLoader,
1061
+ contextLoader: mockDocumentLoader,
1062
+ verifyPortableObject: createVerifier(false),
1063
+ crossOrigin: "throw"
1064
+ }), /No gateway returned a valid portable object/);
1065
+ });
1066
+ await t.step("a document that claims another object", async () => {
1067
+ for (const id of [
1068
+ `ap://${did}/objects/2`,
1069
+ compatibleNoteId,
1070
+ "https://gw.example/notes/1"
1071
+ ]) {
1072
+ const verifier = createRecordingVerifier();
1073
+ deepStrictEqual(await (await createHttpActivity(plainUrl)).getObject({
1074
+ documentLoader: createRedirectingLoader({ [plainUrl]: {
1075
+ documentUrl: compatibleNoteId,
1076
+ document: note(id)
1077
+ } }),
1078
+ contextLoader: mockDocumentLoader,
1079
+ verifyPortableObject: verifier
1080
+ }), null);
1081
+ deepStrictEqual(verifier.calls, []);
1082
+ }
1083
+ });
1084
+ await t.step("a portable @id", async () => {
1085
+ const verifier = createRecordingVerifier();
1086
+ assertInstanceOf(await (await createHttpActivity(plainUrl)).getObject({
1087
+ documentLoader: createLoader({ [plainUrl]: note() }),
1088
+ contextLoader: mockDocumentLoader,
1089
+ verifyPortableObject: verifier,
1090
+ crossOrigin: "trust"
1091
+ }), Note);
1092
+ deepStrictEqual(verifier.calls.length, 1);
1093
+ deepStrictEqual(verifier.calls[0].options.documentUrl, new URL(plainUrl));
1094
+ deepStrictEqual(await (await createHttpActivity(plainUrl)).getObject({
1095
+ documentLoader: createLoader({ [plainUrl]: note() }),
1096
+ contextLoader: mockDocumentLoader,
1097
+ verifyPortableObject: createVerifier(false),
1098
+ crossOrigin: "trust"
1099
+ }), null);
1100
+ });
1101
+ await t.step("a compatible @id", async () => {
1102
+ const verifier = createRecordingVerifier();
1103
+ const url = "https://gw.example/notes/1";
1104
+ deepStrictEqual(await (await createHttpActivity(url)).getObject({
1105
+ documentLoader: createLoader({ [url]: note(compatibleNoteId) }),
1106
+ contextLoader: mockDocumentLoader,
1107
+ verifyPortableObject: verifier
1108
+ }), null);
1109
+ deepStrictEqual(verifier.calls, []);
1110
+ });
1111
+ await t.step("a portable final URL", async () => {
1112
+ for (const [id, expected] of [[objectId, true], [`ap://${did}/objects/2`, false]]) deepStrictEqual(await (await createHttpActivity(plainUrl)).getObject({
1113
+ documentLoader: createRedirectingLoader({ [plainUrl]: {
1114
+ documentUrl: `ap://${did}/objects/1`,
1115
+ document: note(id)
1116
+ } }),
1117
+ contextLoader: mockDocumentLoader,
1118
+ verifyPortableObject: createVerifier()
1119
+ }) instanceof Note, expected);
1120
+ deepStrictEqual(await (await createHttpActivity(plainUrl)).getObject({
1121
+ documentLoader: createRedirectingLoader({ [plainUrl]: {
1122
+ documentUrl: `ap://${did}/objects/x/../1`,
1123
+ document: note()
1124
+ } }),
1125
+ contextLoader: mockDocumentLoader,
1126
+ verifyPortableObject: createVerifier()
1127
+ }), null);
1128
+ });
1129
+ await t.step("a malformed compatible final URL", async () => {
1130
+ const malformed = `${compatibleNoteId}?@gateway=https%3A%2F%2Fgw2.example`;
1131
+ const documentLoader = createRedirectingLoader({ [plainUrl]: {
1132
+ documentUrl: malformed,
1133
+ document: note()
1134
+ } });
1135
+ deepStrictEqual(await (await createHttpActivity(plainUrl)).getObject({
1136
+ documentLoader,
1137
+ contextLoader: mockDocumentLoader,
1138
+ verifyPortableObject: createVerifier()
1139
+ }), null);
1140
+ await rejects(async () => await (await createHttpActivity(plainUrl)).getObject({
1141
+ documentLoader,
1142
+ contextLoader: mockDocumentLoader,
1143
+ verifyPortableObject: createVerifier(),
1144
+ crossOrigin: "throw"
1145
+ }), /malformed FEP-ef61 compatible identifier/);
1146
+ });
1147
+ await t.step("context documents are shared", async () => {
1148
+ const contextUrl = "https://example.com/portable-context";
1149
+ let version = 0;
1150
+ const contextLoader = async (url) => {
1151
+ if (url === contextUrl) {
1152
+ version++;
1153
+ return {
1154
+ contextUrl: null,
1155
+ documentUrl: url,
1156
+ document: { "@context": { version: `urn:version:${version}` } }
1157
+ };
1158
+ }
1159
+ return await mockDocumentLoader(url);
792
1160
  };
793
- deepStrictEqual(await (await person(compatibleId)).getOutbox(options), null);
794
- deepStrictEqual(await (await person("https://example.com/outbox")).getOutbox(options), null);
795
- deepStrictEqual(await (await person(collection(compatibleId))).getOutbox(options), null);
1161
+ const contexts = [];
1162
+ assertInstanceOf(await (await createHttpActivity(plainUrl)).getObject({
1163
+ documentLoader: createRedirectingLoader({ [plainUrl]: {
1164
+ documentUrl: compatibleNoteId,
1165
+ document: {
1166
+ ...note(),
1167
+ "@context": ["https://www.w3.org/ns/activitystreams", contextUrl]
1168
+ }
1169
+ } }),
1170
+ contextLoader,
1171
+ verifyPortableObject: async (_, { contextLoader }) => {
1172
+ contexts.push((await contextLoader(contextUrl)).document);
1173
+ return { verified: true };
1174
+ }
1175
+ }), Note);
1176
+ deepStrictEqual(version, 1);
1177
+ deepStrictEqual(contexts, [{ "@context": { version: "urn:version:1" } }]);
1178
+ });
1179
+ await t.step("without verifyPortableObject", async () => {
1180
+ deepStrictEqual(await (await createHttpActivity(plainUrl)).getObject({
1181
+ documentLoader: createLoader({ [plainUrl]: note() }),
1182
+ contextLoader: mockDocumentLoader
1183
+ }), null);
1184
+ assertInstanceOf(await (await createHttpActivity(plainUrl)).getObject({
1185
+ documentLoader: createRedirectingLoader({ [plainUrl]: {
1186
+ documentUrl: gatewayUrl("https://example.com", "/objects/1"),
1187
+ document: note(gatewayUrl("https://example.com", "/objects/1"))
1188
+ } }),
1189
+ contextLoader: mockDocumentLoader
1190
+ }), Note);
1191
+ });
1192
+ });
1193
+ test("accessors verify embedded objects that stand for portable objects", async () => {
1194
+ const forgedId = gatewayUrl("https://example.com", "/objects/1");
1195
+ const forged = {
1196
+ type: "Note",
1197
+ id: forgedId,
1198
+ content: "Forged"
1199
+ };
1200
+ for (const crossOrigin of [void 0, "trust"]) {
1201
+ const documentLoader = createLoader({ [forgedId]: note() });
1202
+ const verifier = createRecordingVerifier(() => ({ verified: false }));
1203
+ deepStrictEqual(await (await createHttpActivity(forged)).getObject({
1204
+ documentLoader,
1205
+ contextLoader: mockDocumentLoader,
1206
+ verifyPortableObject: verifier,
1207
+ crossOrigin
1208
+ }), null);
1209
+ deepStrictEqual(documentLoader.fetched, [forgedId]);
1210
+ deepStrictEqual(verifier.calls.length, 1);
1211
+ deepStrictEqual(await Array.fromAsync((await createHttpActivity([forged])).getObjects({
1212
+ documentLoader,
1213
+ contextLoader: mockDocumentLoader,
1214
+ verifyPortableObject: verifier,
1215
+ crossOrigin
1216
+ })), []);
1217
+ const documentLoader2 = createLoader({ [gatewayUrl("https://gw.example", "/objects/1")]: note() });
1218
+ const verifier2 = createRecordingVerifier();
1219
+ const object = await (await createHttpActivity({
1220
+ type: "Note",
1221
+ id: objectId,
1222
+ content: "Forged"
1223
+ })).getObject({
1224
+ documentLoader: documentLoader2,
1225
+ contextLoader: mockDocumentLoader,
1226
+ gateways: ["https://gw.example"],
1227
+ verifyPortableObject: verifier2,
1228
+ crossOrigin
1229
+ });
1230
+ assertInstanceOf(object, Note);
1231
+ deepStrictEqual(object.content, "Portable note");
1232
+ deepStrictEqual(verifier2.calls.length, 1);
796
1233
  }
797
- await rejects(async () => await (await person(compatibleId)).getOutbox({
798
- documentLoader: redirectingLoader,
1234
+ const otherDid = "did:key:z6Mkother";
1235
+ const otherId = `ap://${otherDid}/objects/1`;
1236
+ const documentLoader = createLoader({ [`https://gw.example/.well-known/apgateway/${otherDid}/objects/1`]: note(otherId) });
1237
+ const verifier = createRecordingVerifier();
1238
+ const crossDid = await (await createActivity({
1239
+ type: "Note",
1240
+ id: otherId,
1241
+ content: "Forged"
1242
+ })).getObject({
1243
+ documentLoader,
799
1244
  contextLoader: mockDocumentLoader,
800
- crossOrigin: "throw"
801
- }), /portable object/);
802
- assertInstanceOf(await (await Person.fromJsonLd({
803
- "@context": "https://www.w3.org/ns/activitystreams",
804
- id: "https://evil.example/actor",
805
- type: "Person",
806
- inbox: "https://evil.example/actor/inbox",
807
- outbox: compatibleId
808
- }, { contextLoader: mockDocumentLoader })).getOutbox({
809
- documentLoader: redirectingLoader,
810
- contextLoader: mockDocumentLoader
811
- }), OrderedCollection);
1245
+ gateways: ["https://gw.example"],
1246
+ verifyPortableObject: verifier,
1247
+ crossOrigin: "trust"
1248
+ });
1249
+ assertInstanceOf(crossDid, Note);
1250
+ deepStrictEqual(crossDid.content, "Portable note");
1251
+ deepStrictEqual(verifier.calls.length, 1);
1252
+ const object = await (await createActivity({
1253
+ type: "Note",
1254
+ id: objectId,
1255
+ content: "Embedded"
1256
+ })).getObject({
1257
+ documentLoader: createLoader({}),
1258
+ contextLoader: mockDocumentLoader,
1259
+ verifyPortableObject: createVerifier(false)
1260
+ });
1261
+ assertInstanceOf(object, Note);
1262
+ deepStrictEqual(object.content, "Embedded");
812
1263
  });
813
1264
  test("singular accessors drop anonymous objects embedded in unsecured collections", async () => {
814
1265
  const options = {