@fedify/vocab-tools 2.4.0-pr.934.40 → 2.4.0

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/dist/mod.js CHANGED
@@ -7,15 +7,17 @@ import { parse } from "yaml";
7
7
  import { pascalCase } from "es-toolkit";
8
8
  //#region deno.json
9
9
  var name = "@fedify/vocab-tools";
10
- var version = "2.4.0-pr.934.40+3331d302";
10
+ var version = "2.4.0";
11
11
  //#endregion
12
12
  //#region src/type.ts
13
13
  const HEURISTICS_CONTEXTS = [
14
14
  "https://www.w3.org/ns/activitystreams",
15
15
  "https://w3id.org/security/v1",
16
16
  "https://w3id.org/security/data-integrity/v1",
17
+ "https://w3id.org/security/data-integrity/v2",
17
18
  "https://www.w3.org/ns/did/v1",
18
- "https://w3id.org/security/multikey/v1"
19
+ "https://w3id.org/security/multikey/v1",
20
+ "https://w3id.org/fep/ef61"
19
21
  ];
20
22
  const scalarTypes = {
21
23
  "http://www.w3.org/2001/XMLSchema#boolean": {
@@ -183,7 +185,8 @@ const scalarTypes = {
183
185
  dataCheck(v) {
184
186
  return `typeof ${v} === "object" && "@language" in ${v} && "@value" in ${v}
185
187
  && typeof ${v}["@language"] === "string"
186
- && typeof ${v}["@value"] === "string"`;
188
+ && typeof ${v}["@value"] === "string"
189
+ && isValidLanguageTag(${v}["@language"])`;
187
190
  },
188
191
  decoder(v) {
189
192
  return `new LanguageString(${v}["@value"], ${v}["@language"])`;
@@ -314,12 +317,35 @@ const scalarTypes = {
314
317
  return `formatIri(${v})`;
315
318
  },
316
319
  dataCheck(v) {
317
- return `typeof ${v} === "object" && "@value" in ${v}
318
- && typeof ${v}["@value"] === "string"
319
- && ${v}["@value"] !== "" && ${v}["@value"] !== "/"`;
320
+ return `typeof ${v} === "object" &&
321
+ ((typeof ${v}["@id"] === "string" && canDecodeIri(${v}["@id"])) ||
322
+ (typeof ${v}["@value"] === "string" && canDecodeIri(${v}["@value"])))`;
323
+ },
324
+ decoder(v) {
325
+ return `decodeIri(typeof ${v}["@id"] === "string" ? ${v}["@id"] : ${v}["@value"])`;
326
+ },
327
+ skipUnparsable: true
328
+ },
329
+ "fedify:gatewayUrl": {
330
+ name: "URL",
331
+ typeGuard(v) {
332
+ return `${v} instanceof URL && isGatewayUrl(${v})`;
333
+ },
334
+ encoder(v) {
335
+ return `{ "@id": ${v}.origin }`;
336
+ },
337
+ compactEncoder(v) {
338
+ return `${v}.origin`;
339
+ },
340
+ dataCheck(v) {
341
+ return `${v} != null && typeof ${v} === "object" &&
342
+ (("@id" in ${v} && typeof ${v}["@id"] === "string" &&
343
+ ${v}["@id"] !== "" && ${v}["@id"] !== "/") ||
344
+ ("@value" in ${v} && typeof ${v}["@value"] === "string" &&
345
+ ${v}["@value"] !== "" && ${v}["@value"] !== "/"))`;
320
346
  },
321
347
  decoder(v) {
322
- return `parseIri(${v}["@value"])`;
348
+ return `parseGatewayUrl(typeof ${v}["@id"] === "string" ? ${v}["@id"] : ${v}["@value"])`;
323
349
  }
324
350
  },
325
351
  "fedify:publicKey": {
@@ -532,6 +558,9 @@ function getDecoder(typeUri, types, variable, optionsVariable, baseUrlVariable)
532
558
  )`;
533
559
  throw new Error(`Unknown type: ${typeUri}`);
534
560
  }
561
+ function skipsUnparsable(typeUri) {
562
+ return scalarTypes[typeUri]?.skipUnparsable ?? false;
563
+ }
535
564
  function getDataCheck(typeUri, types, variable) {
536
565
  if (typeUri in scalarTypes) return scalarTypes[typeUri].dataCheck(variable);
537
566
  if (typeUri in types) {
@@ -711,6 +740,7 @@ function* generatePreprocessorBlock(property, rangeTypeName, variable, baseUrlEx
711
740
  documentLoader: options.documentLoader,
712
741
  contextLoader: options.contextLoader,
713
742
  tracerProvider: options.tracerProvider,
743
+ verifyPortableObject: options.verifyPortableObject,
714
744
  baseUrl: ${baseUrlExpr},
715
745
  });
716
746
  if (_result instanceof Error) throw _result;
@@ -759,6 +789,10 @@ async function* generateEncoder(typeUri, types) {
759
789
  ...options,
760
790
  contextLoader: options.contextLoader ?? getDocumentLoader(),
761
791
  };
792
+ // Joins the enclosing frame's signed-value scope, or starts one when this
793
+ // frame is the outermost. Only the owning frame puts retained signed
794
+ // child documents back in place of their placeholders.
795
+ const signedValues = enterSignedValueScope(options);
762
796
  `;
763
797
  if (isCompactableType(typeUri, types)) {
764
798
  yield `
@@ -789,9 +823,13 @@ async function* generateEncoder(typeUri, types) {
789
823
  for (const v of this.${await getFieldName(property.uri)}) {
790
824
  const item = (
791
825
  `;
792
- if (!areAllScalarTypes(property.range, types)) yield "v instanceof URL ? formatIri(v) : ";
826
+ if (!areAllScalarTypes(property.range, types)) {
827
+ yield "retainedSignedValueRef(v, signedValues.scope) ?? (";
828
+ yield "v instanceof URL ? formatIri(v) : ";
829
+ }
793
830
  const encoders = getEncoders(property.range, types, "v", "options", true);
794
831
  for (const code of encoders) yield code;
832
+ if (!areAllScalarTypes(property.range, types)) yield ")";
795
833
  yield `
796
834
  );
797
835
  compactItems.push(item);
@@ -822,7 +860,9 @@ async function* generateEncoder(typeUri, types) {
822
860
  ${type.typeless ? "" : `result["type"] = ${JSON.stringify(type.compactName ?? type.uri)};`}
823
861
  if (this.id != null) result["id"] = formatIri(this.id);
824
862
  result["@context"] = ${JSON.stringify(type.defaultContext)};
825
- return result;
863
+ return signedValues.owner
864
+ ? resolveSignedValues(result, signedValues.scope)
865
+ : result;
826
866
  }
827
867
  `;
828
868
  }
@@ -846,12 +886,21 @@ async function* generateEncoder(typeUri, types) {
846
886
  yield `
847
887
  array = [];
848
888
  for (const v of this.${await getFieldName(property.uri)}) {
849
- const element = (
889
+ let element = (
850
890
  `;
851
- if (!areAllScalarTypes(property.range, types)) yield "v instanceof URL ? { \"@id\": formatIri(v) } : ";
891
+ if (!areAllScalarTypes(property.range, types)) {
892
+ yield "retainedSignedValueRef(v, signedValues.scope) ?? (";
893
+ yield "v instanceof URL ? { \"@id\": formatIri(v) } : ";
894
+ }
852
895
  for (const code of getEncoders(property.range, types, "v", "options")) yield code;
896
+ if (!areAllScalarTypes(property.range, types)) yield ")";
853
897
  yield `
854
898
  );
899
+ if (Array.isArray(element)) {
900
+ if (element.length < 1) continue;
901
+ if (element.length == 1) element = element[0];
902
+ }
903
+ if (typeof element === "undefined") continue;
855
904
  `;
856
905
  if (isNonFunctionalProperty(property) && property.container === "graph") yield `array.push({ "@graph": element });`;
857
906
  else yield `array.push(element);`;
@@ -879,16 +928,35 @@ async function* generateEncoder(typeUri, types) {
879
928
  if (options.format === "expand") {
880
929
  return await jsonld.expand(
881
930
  values,
882
- { documentLoader: options.contextLoader },
931
+ {
932
+ documentLoader: options.contextLoader,
933
+ keepFreeFloatingNodes: true,
934
+ },
883
935
  );
884
936
  }
885
- const docContext = options.context ??
937
+ let docContext: Parameters<typeof jsonld.compact>[1] = options.context ??
886
938
  ${JSON.stringify(type.defaultContext)};
887
- const compacted = await jsonld.compact(
939
+ let compacted = await jsonld.compact(
888
940
  values,
889
941
  docContext,
890
942
  { documentLoader: options.contextLoader },
891
943
  );
944
+ `;
945
+ if (Object.values(types).some((t) => t.properties.some((p) => p.extraContext != null))) yield `
946
+ if (options.context == null) {
947
+ const currentContexts = Array.isArray(docContext) ? docContext : [docContext];
948
+ const additionalContexts = getExtraContexts(compacted).filter(
949
+ context => !currentContexts.includes(context)
950
+ );
951
+ if (additionalContexts.length > 0) {
952
+ docContext = [...currentContexts, ...additionalContexts];
953
+ compacted = await jsonld.compact(values, docContext, {
954
+ documentLoader: options.contextLoader,
955
+ });
956
+ }
957
+ }
958
+ `;
959
+ yield `
892
960
  if (docContext != null) {
893
961
  // Embed context
894
962
  `;
@@ -912,12 +980,14 @@ async function* generateEncoder(typeUri, types) {
912
980
  }
913
981
  yield `
914
982
  }
915
- return compacted;
983
+ return signedValues.owner
984
+ ? resolveSignedValues(compacted, signedValues.scope)
985
+ : compacted;
916
986
  }
917
987
 
918
988
  protected ${emitOverride$1(typeUri, types)} isCompactable(): boolean {
919
989
  `;
920
- for (const property of type.properties) if (!property.range.every((r) => isCompactableType(r, types))) yield `
990
+ for (const property of type.properties) if (property.extraContext != null || !property.range.every((r) => isCompactableType(r, types))) yield `
921
991
  if (
922
992
  this.${await getFieldName(property.uri)} != null &&
923
993
  this.${await getFieldName(property.uri)}.length > 0
@@ -939,6 +1009,10 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
939
1009
  * - \`contextLoader\`: The loader for remote JSON-LD contexts.
940
1010
  * - \`tracerProvider\`: The OpenTelemetry tracer provider to use.
941
1011
  * If omitted, the global tracer provider is used.
1012
+ * - \`verifyPortableObject\`: The default FEP-ef61 portable
1013
+ * object verifier for the property accessors of the
1014
+ * returned object and the objects obtained from it. It
1015
+ * does not verify the given \`json\` itself.
942
1016
  * @returns The object of this type.
943
1017
  * @throws {TypeError} If the given \`json\` is invalid.
944
1018
  */
@@ -948,6 +1022,7 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
948
1022
  documentLoader?: DocumentLoader,
949
1023
  contextLoader?: DocumentLoader,
950
1024
  tracerProvider?: TracerProvider,
1025
+ verifyPortableObject?: PortableObjectVerifier,
951
1026
  baseUrl?: URL,
952
1027
  } = {},
953
1028
  ): Promise<${type.name}> {
@@ -986,6 +1061,7 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
986
1061
  documentLoader?: DocumentLoader,
987
1062
  contextLoader?: DocumentLoader,
988
1063
  tracerProvider?: TracerProvider,
1064
+ verifyPortableObject?: PortableObjectVerifier,
989
1065
  baseUrl?: URL,
990
1066
  } = {},
991
1067
  ): Promise<${type.name}> {
@@ -1002,7 +1078,17 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
1002
1078
  // deno-lint-ignore no-explicit-any
1003
1079
  let values: Record<string, any[]> & { "@id"?: string };
1004
1080
  let expanded: unknown[];
1005
- if (globalThis.Object.keys(json).length == 0) {
1081
+ if (
1082
+ "_fromSubclass" in options &&
1083
+ options._fromSubclass &&
1084
+ typeof json === "object" &&
1085
+ !Array.isArray(json)
1086
+ ) {
1087
+ values =
1088
+ // deno-lint-ignore no-explicit-any
1089
+ json as (Record<string, any[]> & { "@id"?: string });
1090
+ expanded = [values];
1091
+ } else if (globalThis.Object.keys(json).length == 0) {
1006
1092
  values = {};
1007
1093
  expanded = [values];
1008
1094
  } else {
@@ -1059,8 +1145,9 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
1059
1145
  );
1060
1146
  `;
1061
1147
  else yield `
1062
- delete values["@type"];
1063
- const instance = await super.fromJsonLd(values, {
1148
+ const superValues = structuredClone(values);
1149
+ delete superValues["@type"];
1150
+ const instance = await super.fromJsonLd(superValues, {
1064
1151
  ...options,
1065
1152
  // @ts-ignore: an internal option
1066
1153
  _fromSubclass: true,
@@ -1069,25 +1156,35 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
1069
1156
  throw new TypeError("Unexpected type: " + instance.constructor.name);
1070
1157
  }
1071
1158
  `;
1159
+ yield `
1160
+ let shouldCacheJsonLd = instance._shouldCacheJsonLd;
1161
+ `;
1072
1162
  for (const property of type.properties) {
1073
1163
  const variable = await getFieldName(property.uri, "");
1074
1164
  yield await generateField(property, types, "const ");
1075
1165
  const arrayVariable = `${variable}__array`;
1076
1166
  yield `
1077
- let ${arrayVariable} = values[${JSON.stringify(property.uri)}];
1167
+ let ${arrayVariable} = ${property.uri === "https://w3id.org/fep/ef61/gateways" ? "getPortableActorGateways(values) as typeof values[string]" : `values[${JSON.stringify(property.uri)}]`};
1078
1168
  `;
1079
1169
  if (property.functional && property.redundantProperties != null) for (const prop of property.redundantProperties) yield `
1080
1170
  if (${arrayVariable} == null || ${arrayVariable}.length < 1) {
1081
1171
  ${arrayVariable} = values[${JSON.stringify(prop.uri)}];
1082
1172
  }
1083
1173
  `;
1174
+ let itemsExpression = `${arrayVariable}.length === 1 && "@list" in ${arrayVariable}[0]
1175
+ ? ${arrayVariable}[0]["@list"]
1176
+ : ${arrayVariable}`;
1177
+ if (isNonFunctionalProperty(property) && property.container === "graph") itemsExpression = `(${itemsExpression}).flatMap((item: unknown) =>
1178
+ item != null && typeof item === "object" && "@graph" in item &&
1179
+ Array.isArray((item as { "@graph": unknown })["@graph"])
1180
+ ? (item as { "@graph": unknown[] })["@graph"]
1181
+ : [item]
1182
+ )`;
1084
1183
  yield `
1085
1184
  for (
1086
1185
  const v of ${arrayVariable} == null
1087
1186
  ? []
1088
- : ${arrayVariable}.length === 1 && "@list" in ${arrayVariable}[0]
1089
- ? ${arrayVariable}[0]["@list"]
1090
- : ${arrayVariable}
1187
+ : ${itemsExpression}
1091
1188
  ) {
1092
1189
  if (v == null) continue;
1093
1190
  `;
@@ -1101,29 +1198,47 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
1101
1198
  continue;
1102
1199
  }
1103
1200
  `;
1104
- if (property.range.length == 1) yield `
1105
- const decoded = ${getDecoder(property.range[0], types, "v", "options", propertyBaseUrl)};
1106
- if (typeof decoded === "undefined") continue;
1107
- ${variable}.push(decoded);`;
1108
- else {
1109
- yield `
1201
+ yield `
1110
1202
  const decoded =
1111
- `;
1203
+ `;
1204
+ const lenient = property.range.length == 1 && skipsUnparsable(property.range[0]);
1205
+ if (property.range.length == 1) {
1206
+ if (lenient) {
1207
+ yield getDataCheck(property.range[0], types, "v");
1208
+ yield " ? ";
1209
+ }
1210
+ yield getDecoder(property.range[0], types, "v", "options", propertyBaseUrl);
1211
+ if (lenient) yield " : undefined";
1212
+ } else {
1112
1213
  const decoders = getDecoders(property.range, types, "v", "options", propertyBaseUrl);
1113
1214
  for (const code of decoders) yield code;
1114
- yield `
1215
+ }
1216
+ yield `
1115
1217
  ;
1116
- if (typeof decoded === "undefined") continue;
1218
+ `;
1219
+ yield `
1220
+ if (typeof decoded === "undefined") {
1221
+ shouldCacheJsonLd = false;
1222
+ continue;
1223
+ }
1224
+ `;
1225
+ yield `
1226
+ if (!this._shouldCacheDecodedJsonLd(decoded)) {
1227
+ shouldCacheJsonLd = false;
1228
+ }
1117
1229
  ${variable}.push(decoded);
1118
- `;
1119
- }
1230
+ `;
1120
1231
  yield `
1121
1232
  }
1122
1233
  instance.${await getFieldName(property.uri)} = ${variable};
1123
1234
  `;
1124
1235
  }
1125
1236
  yield `
1126
- if (!("_fromSubclass" in options) || !options._fromSubclass) {
1237
+ instance._shouldCacheJsonLd = shouldCacheJsonLd;
1238
+ if (
1239
+ shouldCacheJsonLd &&
1240
+ (!("_fromSubclass" in options) || !options._fromSubclass)
1241
+ ) {
1127
1242
  try {
1128
1243
  if (cacheJsonLd != null && cacheJsonLd !== expanded) {
1129
1244
  const compactArray = Array.isArray(json) && json.length === 1;
@@ -1189,6 +1304,7 @@ async function* generateConstructor(typeUri, types) {
1189
1304
  documentLoader?: DocumentLoader,
1190
1305
  contextLoader?: DocumentLoader,
1191
1306
  tracerProvider?: TracerProvider,
1307
+ verifyPortableObject?: PortableObjectVerifier,
1192
1308
  } = {},
1193
1309
  ) {
1194
1310
  `;
@@ -1196,6 +1312,7 @@ async function* generateConstructor(typeUri, types) {
1196
1312
  this.#documentLoader = options.documentLoader;
1197
1313
  this.#contextLoader = options.contextLoader;
1198
1314
  this.#tracerProvider = options.tracerProvider;
1315
+ this.#verifyPortableObject = options.verifyPortableObject;
1199
1316
  const baseUrl = (options as { baseUrl?: URL }).baseUrl;
1200
1317
  this.#_baseUrl = baseUrl == null ? undefined : new URL(baseUrl.href);
1201
1318
  if ("$warning" in options) {
@@ -1290,6 +1407,13 @@ async function* generateCloner(typeUri, types) {
1290
1407
  yield `
1291
1408
  /**
1292
1409
  * Clones this instance, optionally updating it with the given values.
1410
+ *
1411
+ * A clone never inherits a signed JSON-LD representation retained by
1412
+ * \`signObject()\`: the clone may differ from the document that the proof
1413
+ * covers, so embedding the original secured JSON in a parent document
1414
+ * would be wrong. Sign the clone again if it has to be embedded as a
1415
+ * secured child.
1416
+ *
1293
1417
  * @param values The values to update the clone with.
1294
1418
  * @param options The options to use for cloning.
1295
1419
  * @returns The cloned instance.
@@ -1303,6 +1427,7 @@ async function* generateCloner(typeUri, types) {
1303
1427
  options: {
1304
1428
  documentLoader?: DocumentLoader,
1305
1429
  contextLoader?: DocumentLoader,
1430
+ verifyPortableObject?: PortableObjectVerifier,
1306
1431
  } = {}
1307
1432
  ): ${type.name} {
1308
1433
  if (this._warning != null) {
@@ -1320,6 +1445,7 @@ async function* generateCloner(typeUri, types) {
1320
1445
  { id: values.id ?? this.id },
1321
1446
  options
1322
1447
  );
1448
+ copyPortableProvenance(this, clone);
1323
1449
  `;
1324
1450
  else yield `const clone = super.clone(values, options) as unknown as ${type.name};`;
1325
1451
  for (const property of type.properties) {
@@ -1415,12 +1541,12 @@ async function* generateInspector(typeUri, types) {
1415
1541
  [Symbol.for("Deno.customInspect")]: (
1416
1542
  inspect: typeof Deno.inspect,
1417
1543
  options: Deno.InspectOptions,
1418
- ): string => "URL " + inspect(this.id!.href, options),
1544
+ ): string => "URL " + inspect(inspectIri(this.id!), options),
1419
1545
  [Symbol.for("nodejs.util.inspect.custom")]: (
1420
1546
  _depth: number,
1421
1547
  options: unknown,
1422
1548
  inspect: (value: unknown, options: unknown) => string,
1423
- ): string => "URL " + inspect(this.id!.href, options),
1549
+ ): string => "URL " + inspect(inspectIri(this.id!), options),
1424
1550
  };
1425
1551
  }
1426
1552
  `;
@@ -1436,12 +1562,12 @@ async function* generateInspector(typeUri, types) {
1436
1562
  [Symbol.for("Deno.customInspect")]: (
1437
1563
  inspect: typeof Deno.inspect,
1438
1564
  options: Deno.InspectOptions,
1439
- ): string => "URL " + inspect(v.href, options),
1565
+ ): string => "URL " + inspect(inspectIri(v), options),
1440
1566
  [Symbol.for("nodejs.util.inspect.custom")]: (
1441
1567
  _depth: number,
1442
1568
  options: unknown,
1443
1569
  inspect: (value: unknown, options: unknown) => string,
1444
- ): string => "URL " + inspect(v.href, options),
1570
+ ): string => "URL " + inspect(inspectIri(v), options),
1445
1571
  }
1446
1572
  : v);
1447
1573
  `;
@@ -1511,6 +1637,7 @@ function emitOverride(typeUri, types, property) {
1511
1637
  }
1512
1638
  async function* generateProperty(type, property, types, moduleVarNames) {
1513
1639
  const override = emitOverride(type.uri, types, property);
1640
+ const trustOwner = type.trustEmbeddedObjects === false ? "null" : "this.id";
1514
1641
  const doc = `\n/** ${property.description.replaceAll("\n", "\n * ")}\n */\n`;
1515
1642
  const cachedPropertyBaseUrl = `(options as { baseUrl?: URL }).baseUrl ??
1516
1643
  this._baseUrl ??
@@ -1551,6 +1678,9 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1551
1678
  suppressError?: boolean,
1552
1679
  tracerProvider?: TracerProvider,
1553
1680
  crossOrigin?: "ignore" | "throw" | "trust";
1681
+ gateways?: readonly (string | URL)[];
1682
+ verifyPortableObject?: PortableObjectVerifier;
1683
+ inheritPortableObjectVerifier?: boolean;
1554
1684
  } = {},
1555
1685
  ): Promise<${getTypeNames(property.range, types)} | null> {
1556
1686
  const documentLoader =
@@ -1563,11 +1693,96 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1563
1693
  ${JSON.stringify(name)},
1564
1694
  ${JSON.stringify(version)},
1565
1695
  );
1696
+ // The fetched object uses the verifier given to this call (which falls
1697
+ // back to the parent's own default) for its accessors by default,
1698
+ // unless the call keeps its verifier to itself:
1699
+ const childVerifier = options.inheritPortableObjectVerifier === false
1700
+ ? this._verifyPortableObject
1701
+ : options.verifyPortableObject;
1566
1702
  return await tracer.startActiveSpan("activitypub.lookup_object", async (span) => {
1703
+ const dereferencePortable = async (
1704
+ portableUrl: URL,
1705
+ extra: {
1706
+ inferredGateways?: readonly URL[];
1707
+ response?: RemoteDocument;
1708
+ contextLoader?: DocumentLoader;
1709
+ } = {},
1710
+ ) => {
1711
+ try {
1712
+ const obj = await dereferencePortableIri(portableUrl, {
1713
+ documentLoader,
1714
+ contextLoader: extra.contextLoader ?? contextLoader,
1715
+ tracerProvider,
1716
+ gateways: options.gateways,
1717
+ inferredGateways: extra.inferredGateways,
1718
+ response: extra.response,
1719
+ verifyPortableObject: options.verifyPortableObject,
1720
+ referrer: {
1721
+ object: this,
1722
+ property: ${JSON.stringify(property.uri)},
1723
+ },
1724
+ suppressError: options.suppressError,
1725
+ crossOrigin: options.crossOrigin,
1726
+ span,
1727
+ parse: (document, parseOptions) =>
1728
+ this.#${property.singularName}_fromJsonLd(document, {
1729
+ documentLoader,
1730
+ contextLoader: parseOptions.contextLoader,
1731
+ tracerProvider,
1732
+ verifyPortableObject: childVerifier,
1733
+ baseUrl: parseOptions.baseUrl,
1734
+ }),
1735
+ });
1736
+ if (obj != null) {
1737
+ span.setAttribute(
1738
+ "activitypub.object.id",
1739
+ (obj.id ?? portableUrl).href,
1740
+ );
1741
+ span.setAttribute(
1742
+ "activitypub.object.type",
1743
+ // @ts-ignore: obj.constructor always has a typeId.
1744
+ obj.constructor.typeId.href
1745
+ );
1746
+ }
1747
+ return obj;
1748
+ } catch (e) {
1749
+ span.setStatus({
1750
+ code: SpanStatusCode.ERROR,
1751
+ message: String(e),
1752
+ });
1753
+ throw e;
1754
+ } finally {
1755
+ span.end();
1756
+ }
1757
+ };
1758
+ if (isPortableIri(url)) {
1759
+ // A reference in a portable actor's own document, e.g., its outbox,
1760
+ // has no location hints, so the actor's gateways are used instead:
1761
+ return await dereferencePortable(
1762
+ url,
1763
+ options.gateways == null
1764
+ ? { inferredGateways: getReferrerGateways(this, url) }
1765
+ : {},
1766
+ );
1767
+ }
1567
1768
  const lookupUrl = formatIri(url);
1769
+ const portableMode = isPortableMode(this, options);
1770
+ if (portableMode) {
1771
+ const compatible = parseCompatibleEf61Reference(url);
1772
+ if (compatible === null) {
1773
+ span.end();
1774
+ return rejectMalformedCompatibleReference(lookupUrl, options);
1775
+ } else if (compatible != null) {
1776
+ return await dereferencePortable(compatible.id, {
1777
+ inferredGateways: [compatible.gateway],
1778
+ });
1779
+ }
1780
+ }
1568
1781
  let fetchResult: RemoteDocument;
1569
1782
  try {
1570
- fetchResult = await documentLoader(lookupUrl);
1783
+ fetchResult = await documentLoader(lookupUrl, {
1784
+ suppressError: options.suppressError,
1785
+ });
1571
1786
  } catch (error) {
1572
1787
  span.setStatus({
1573
1788
  code: SpanStatusCode.ERROR,
@@ -1575,7 +1790,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1575
1790
  });
1576
1791
  span.end();
1577
1792
  if (options.suppressError) {
1578
- getLogger(["fedify", "vocab"]).error(
1793
+ getLogger(["fedify", "vocab"]).warn(
1579
1794
  "Failed to fetch {url}: {error}",
1580
1795
  { error, url: lookupUrl }
1581
1796
  );
@@ -1584,52 +1799,96 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1584
1799
  throw error;
1585
1800
  }
1586
1801
  const { document, documentUrl } = fetchResult;
1587
- const baseUrl = parseIri(documentUrl);
1802
+ // In portable mode, the document may turn out to be a portable object,
1803
+ // which has to be verified with the same context documents:
1804
+ const snapshot = portableMode
1805
+ ? createSnapshotContextLoader(contextLoader, options.suppressError)
1806
+ : null;
1807
+ const scopedContext = createScopedContextLoader(
1808
+ snapshot?.loader ?? contextLoader,
1809
+ snapshot == null && options.suppressError,
1810
+ );
1588
1811
  try {
1589
- const obj = await this.#${property.singularName}_fromJsonLd(
1590
- document,
1591
- { documentLoader, contextLoader, tracerProvider, baseUrl }
1592
- );
1593
- if (obj?.id != null && !isTrustedIriOrigin(options, obj.id, baseUrl)) {
1594
- if (options.crossOrigin === "throw") {
1595
- throw new Error(
1596
- "The object's @id (" + obj.id.href + ") has a different origin " +
1597
- "than the document URL (" + baseUrl.href + "); refusing to return " +
1598
- "the object. If you want to bypass this check and are aware of" +
1599
- 'the security implications, set the crossOrigin option to "trust".'
1812
+ let claim: PortableResponseClaim | null | undefined;
1813
+ try {
1814
+ const baseUrl = parseIri(documentUrl);
1815
+ const obj = await this.#${property.singularName}_fromJsonLd(
1816
+ document,
1817
+ {
1818
+ documentLoader,
1819
+ contextLoader: scopedContext.loader,
1820
+ tracerProvider,
1821
+ verifyPortableObject: childVerifier,
1822
+ baseUrl,
1823
+ }
1824
+ );
1825
+ if (snapshot == null) {
1826
+ markUnverifiedPortableClaim(obj, url, documentUrl);
1827
+ } else {
1828
+ claim = getPortableResponseClaim(documentUrl, obj.id);
1829
+ }
1830
+ if (claim === undefined) {
1831
+ if (
1832
+ obj?.id != null &&
1833
+ !isTrustedIriOrigin(options, obj.id, baseUrl)
1834
+ ) {
1835
+ if (options.crossOrigin === "throw") {
1836
+ throw new Error(
1837
+ "The object's @id (" + obj.id.href + ") has a different origin " +
1838
+ "than the document URL (" + baseUrl.href + "); refusing to return " +
1839
+ "the object. If you want to bypass this check and are aware of" +
1840
+ 'the security implications, set the crossOrigin option to "trust".'
1841
+ );
1842
+ }
1843
+ getLogger(["fedify", "vocab"]).warn(
1844
+ "The object's @id ({objectId}) has a different origin than the document " +
1845
+ "URL ({documentUrl}); refusing to return the object. If you want to " +
1846
+ "bypass this check and are aware of the security implications, " +
1847
+ 'set the crossOrigin option to "trust".',
1848
+ { ...fetchResult, objectId: obj.id.href },
1849
+ );
1850
+ span.end();
1851
+ return null;
1852
+ }
1853
+ span.setAttribute("activitypub.object.id", (obj.id ?? url).href);
1854
+ span.setAttribute(
1855
+ "activitypub.object.type",
1856
+ // @ts-ignore: obj.constructor always has a typeId.
1857
+ obj.constructor.typeId.href
1600
1858
  );
1859
+ span.end();
1860
+ return obj;
1601
1861
  }
1602
- getLogger(["fedify", "vocab"]).warn(
1603
- "The object's @id ({objectId}) has a different origin than the document " +
1604
- "URL ({documentUrl}); refusing to return the object. If you want to " +
1605
- "bypass this check and are aware of the security implications, " +
1606
- 'set the crossOrigin option to "trust".',
1607
- { ...fetchResult, objectId: obj.id.href },
1608
- );
1609
- return null;
1862
+ } catch (e) {
1863
+ if (options.suppressError) {
1864
+ getLogger(["fedify", "vocab"]).warn(
1865
+ "Failed to parse {url}: {error}",
1866
+ { error: e, url: lookupUrl }
1867
+ );
1868
+ span.end();
1869
+ return null;
1870
+ }
1871
+ span.setStatus({
1872
+ code: SpanStatusCode.ERROR,
1873
+ message: String(e),
1874
+ });
1875
+ span.end();
1876
+ throw e;
1610
1877
  }
1611
- span.setAttribute("activitypub.object.id", (obj.id ?? url).href);
1612
- span.setAttribute(
1613
- "activitypub.object.type",
1614
- // @ts-ignore: obj.constructor always has a typeId.
1615
- obj.constructor.typeId.href
1616
- );
1617
- return obj;
1618
- } catch (e) {
1619
- if (options.suppressError) {
1620
- getLogger(["fedify", "vocab"]).error(
1621
- "Failed to parse {url}: {error}",
1622
- { error: e, url: lookupUrl }
1623
- );
1624
- return null;
1878
+ // The document stands for a portable object, so it is verified as
1879
+ // one instead of being trusted because of where it came from:
1880
+ if (claim === null) {
1881
+ span.end();
1882
+ return rejectMalformedCompatibleReference(documentUrl, options);
1625
1883
  }
1626
- span.setStatus({
1627
- code: SpanStatusCode.ERROR,
1628
- message: String(e),
1884
+ return await dereferencePortable(claim.id, {
1885
+ inferredGateways: claim.inferredGateways,
1886
+ response: fetchResult,
1887
+ contextLoader: snapshot?.loader,
1629
1888
  });
1630
- throw e;
1631
1889
  } finally {
1632
- span.end();
1890
+ scopedContext.release();
1891
+ snapshot?.release();
1633
1892
  }
1634
1893
  });
1635
1894
  }
@@ -1640,6 +1899,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1640
1899
  documentLoader?: DocumentLoader,
1641
1900
  contextLoader?: DocumentLoader,
1642
1901
  tracerProvider?: TracerProvider,
1902
+ verifyPortableObject?: PortableObjectVerifier,
1643
1903
  baseUrl?: URL
1644
1904
  }
1645
1905
  ): Promise<${getTypeNames(property.range, types)}> {
@@ -1650,6 +1910,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1650
1910
  const tracerProvider = options.tracerProvider ??
1651
1911
  this._tracerProvider ?? trace.getTracerProvider();
1652
1912
  const baseUrl = options.baseUrl;
1913
+ const verifyPortableObject = options.verifyPortableObject;
1653
1914
  `;
1654
1915
  if (property.preprocessors != null && property.preprocessors.length > 0) {
1655
1916
  yield `
@@ -1669,6 +1930,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1669
1930
  documentLoader,
1670
1931
  contextLoader,
1671
1932
  tracerProvider,
1933
+ verifyPortableObject,
1672
1934
  baseUrl,
1673
1935
  });
1674
1936
  if (_result instanceof Error) throw _result;
@@ -1687,7 +1949,13 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1687
1949
  try {
1688
1950
  return await ${types[range].name}.fromJsonLd(
1689
1951
  jsonLd,
1690
- { documentLoader, contextLoader, tracerProvider, baseUrl },
1952
+ {
1953
+ documentLoader,
1954
+ contextLoader,
1955
+ tracerProvider,
1956
+ verifyPortableObject,
1957
+ baseUrl,
1958
+ },
1691
1959
  );
1692
1960
  } catch (e) {
1693
1961
  if (!(e instanceof TypeError)) throw e;
@@ -1729,6 +1997,21 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1729
1997
  suppressError?: boolean,
1730
1998
  tracerProvider?: TracerProvider,
1731
1999
  crossOrigin?: "ignore" | "throw" | "trust";
2000
+ gateways?: readonly (string | URL)[];
2001
+ verifyPortableObject?: PortableObjectVerifier;
2002
+ /**
2003
+ * Whether objects that this call newly fetches use the
2004
+ * \`verifyPortableObject\` option of this call as their default
2005
+ * verifier. Defaults to \`true\`. If \`false\`, the option
2006
+ * applies to this call only, and fetched objects use this object's
2007
+ * own default verifier, if any, as their default instead. Pass
2008
+ * \`false\` for a temporary verifier, e.g., one that accepts
2009
+ * everything. Objects that were already cached in this object or
2010
+ * embedded in it keep their defaults either way, and this option
2011
+ * itself is not passed on.
2012
+ * @since 2.4.0
2013
+ */
2014
+ inheritPortableObjectVerifier?: boolean;
1732
2015
  } = {}
1733
2016
  ): Promise<${getTypeNames(property.range, types)} | null> {
1734
2017
  if (this._warning != null) {
@@ -1738,25 +2021,46 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1738
2021
  );
1739
2022
  }
1740
2023
  if (this.${await getFieldName(property.uri)}.length < 1) return null;
2024
+ if (
2025
+ options.verifyPortableObject == null &&
2026
+ this._verifyPortableObject != null
2027
+ ) {
2028
+ options = {
2029
+ ...options,
2030
+ verifyPortableObject: this._verifyPortableObject,
2031
+ };
2032
+ }
1741
2033
  let v = this.${await getFieldName(property.uri)}[0];
1742
2034
  if (!(v instanceof URL) &&
1743
- v.id != null &&
1744
- !isTrustedIriOrigin(options, v.id, this.id) &&
1745
2035
  !this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
1746
- v = v.id;
2036
+ if (v.id == null) {
2037
+ if (isUnsecuredPortableObject(this)) {
2038
+ warnUnverifiableEmbeddedObject(this, ${JSON.stringify(property.uri)});
2039
+ return null;
2040
+ }
2041
+ } else if (
2042
+ !isTrustedIriOrigin(options, v.id, ${trustOwner}) ||
2043
+ isUnsecuredPortableObject(this) ||
2044
+ mustDereferencePortableObject(this, ${trustOwner}, v.id, options)
2045
+ ) {
2046
+ v = v.id;
2047
+ }
1747
2048
  }
1748
2049
  if (v instanceof URL) {
1749
2050
  const fetched =
1750
2051
  await this.#fetch${pascalCase(property.singularName)}(v, options);
1751
2052
  if (fetched == null) return null;
2053
+ // Not cached, so that it is verified when the portable object
2054
+ // policy applies later:
2055
+ if (isUnverifiedPortableClaim(fetched)) return fetched;
1752
2056
  this.${await getFieldName(property.uri)}[0] = fetched;
1753
2057
  this.${await getFieldName(property.uri, "#_trust")}.add(0);
1754
2058
  this._cachedJsonLd = undefined;
2059
+ recordPortableReferrer(this, fetched, ${JSON.stringify(property.uri)});
1755
2060
  return fetched;
1756
2061
  }
1757
2062
  `;
1758
- if (property.compactName != null) {
1759
- yield `
2063
+ if (property.compactName != null) yield `
1760
2064
  if (
1761
2065
  this._cachedJsonLd != null &&
1762
2066
  typeof this._cachedJsonLd === "object" &&
@@ -1767,26 +2071,36 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1767
2071
  ${JSON.stringify(property.compactName)}];
1768
2072
  const doc = Array.isArray(prop) ? prop[0] : prop;
1769
2073
  if (doc != null && typeof doc === "object" && "@context" in doc) {
1770
- `;
1771
- yield `
1772
- v = await this.#${property.singularName}_fromJsonLd(doc, {
1773
- ...options,
1774
- baseUrl: ${cachedPropertyBaseUrl},
1775
- });
1776
- `;
1777
- yield `
2074
+ try {
2075
+ v = await this.#${property.singularName}_fromJsonLd(doc, {
2076
+ ...options,
2077
+ // Keep the parent default for embedded objects.
2078
+ verifyPortableObject: this._verifyPortableObject,
2079
+ baseUrl: ${cachedPropertyBaseUrl},
2080
+ });
2081
+ } catch (error) {
2082
+ if (!options.suppressError) throw error;
2083
+ getLogger(["fedify", "vocab"]).debug(
2084
+ "Failed to parse embedded value of {property}: {error}",
2085
+ { property: ${JSON.stringify(property.uri)}, error },
2086
+ );
2087
+ return null;
2088
+ }
1778
2089
  }
1779
2090
  }
1780
2091
  `;
1781
- }
1782
2092
  yield `
1783
2093
  if (v?.id != null &&
1784
- this.id != null && !isTrustedIriOrigin(options, v.id, this.id) &&
2094
+ ((${type.trustEmbeddedObjects === false ? "" : "this.id != null && "}!isTrustedIriOrigin(options, v.id, ${trustOwner})) ||
2095
+ isUnsecuredPortableObject(this) ||
2096
+ mustDereferencePortableObject(
2097
+ this, ${trustOwner}, v.id, options,
2098
+ )) &&
1785
2099
  !this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
1786
2100
  if (options.crossOrigin === "throw") {
1787
2101
  throw new Error(
1788
2102
  "The property object's @id (" + v.id.href + ") has a different " +
1789
- "origin than the property owner's @id (" + this.id.href + "); " +
2103
+ "origin than the property owner's @id (" + ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "(this.id?.href ?? null)"} + "); " +
1790
2104
  "refusing to return the object. If you want to bypass this " +
1791
2105
  "check and are aware of the security implications, set the " +
1792
2106
  'crossOrigin option to "trust".'
@@ -1798,10 +2112,11 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1798
2112
  "return the object. If you want to bypass this check and are " +
1799
2113
  "aware of the security implications, set the crossOrigin option " +
1800
2114
  'to "trust".',
1801
- { objectId: v.id.href, parentObjectId: this.id.href },
2115
+ { objectId: v.id.href, parentObjectId: ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "(this.id?.href ?? null)"} },
1802
2116
  );
1803
2117
  return null;
1804
2118
  }
2119
+ recordPortableReferrer(this, v, ${JSON.stringify(property.uri)});
1805
2120
  return v;
1806
2121
  }
1807
2122
  `;
@@ -1834,6 +2149,21 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1834
2149
  suppressError?: boolean,
1835
2150
  tracerProvider?: TracerProvider,
1836
2151
  crossOrigin?: "ignore" | "throw" | "trust";
2152
+ gateways?: readonly (string | URL)[];
2153
+ verifyPortableObject?: PortableObjectVerifier;
2154
+ /**
2155
+ * Whether objects that this call newly fetches use the
2156
+ * \`verifyPortableObject\` option of this call as their default
2157
+ * verifier. Defaults to \`true\`. If \`false\`, the option
2158
+ * applies to this call only, and fetched objects use this object's
2159
+ * own default verifier, if any, as their default instead. Pass
2160
+ * \`false\` for a temporary verifier, e.g., one that accepts
2161
+ * everything. Objects that were already cached in this object or
2162
+ * embedded in it keep their defaults either way, and this option
2163
+ * itself is not passed on.
2164
+ * @since 2.4.0
2165
+ */
2166
+ inheritPortableObjectVerifier?: boolean;
1837
2167
  } = {}
1838
2168
  ): AsyncIterable<${getTypeNames(property.range, types)}> {
1839
2169
  if (this._warning != null) {
@@ -1842,28 +2172,52 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1842
2172
  this._warning.values
1843
2173
  );
1844
2174
  }
2175
+ if (
2176
+ options.verifyPortableObject == null &&
2177
+ this._verifyPortableObject != null
2178
+ ) {
2179
+ options = {
2180
+ ...options,
2181
+ verifyPortableObject: this._verifyPortableObject,
2182
+ };
2183
+ }
1845
2184
  const vs = this.${await getFieldName(property.uri)};
1846
2185
  for (let i = 0; i < vs.length; i++) {
1847
2186
  let v = vs[i];
1848
2187
  if (!(v instanceof URL) &&
1849
- v.id != null &&
1850
- !isTrustedIriOrigin(options, v.id, this.id) &&
1851
2188
  !this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
1852
- v = v.id;
2189
+ if (v.id == null) {
2190
+ if (isUnsecuredPortableObject(this)) {
2191
+ warnUnverifiableEmbeddedObject(this, ${JSON.stringify(property.uri)});
2192
+ continue;
2193
+ }
2194
+ } else if (
2195
+ !isTrustedIriOrigin(options, v.id, ${trustOwner}) ||
2196
+ isUnsecuredPortableObject(this) ||
2197
+ mustDereferencePortableObject(this, ${trustOwner}, v.id, options)
2198
+ ) {
2199
+ v = v.id;
2200
+ }
1853
2201
  }
1854
2202
  if (v instanceof URL) {
1855
2203
  const fetched =
1856
2204
  await this.#fetch${pascalCase(property.singularName)}(v, options);
1857
2205
  if (fetched == null) continue;
2206
+ // Not cached, so that it is verified when the portable object
2207
+ // policy applies later:
2208
+ if (isUnverifiedPortableClaim(fetched)) {
2209
+ yield fetched;
2210
+ continue;
2211
+ }
1858
2212
  vs[i] = fetched;
1859
2213
  this.${await getFieldName(property.uri, "#_trust")}.add(i);
1860
2214
  this._cachedJsonLd = undefined;
2215
+ recordPortableReferrer(this, fetched, ${JSON.stringify(property.uri)});
1861
2216
  yield fetched;
1862
2217
  continue;
1863
2218
  }
1864
2219
  `;
1865
- if (property.compactName != null) {
1866
- yield `
2220
+ if (property.compactName != null) yield `
1867
2221
  if (
1868
2222
  this._cachedJsonLd != null &&
1869
2223
  typeof this._cachedJsonLd === "object" &&
@@ -1874,26 +2228,36 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1874
2228
  ${JSON.stringify(property.compactName)}];
1875
2229
  const obj = Array.isArray(prop) ? prop[i] : prop;
1876
2230
  if (obj != null && typeof obj === "object" && "@context" in obj) {
1877
- `;
1878
- yield `
1879
- v = await this.#${property.singularName}_fromJsonLd(obj, {
1880
- ...options,
1881
- baseUrl: ${cachedPropertyBaseUrl},
1882
- });
1883
- `;
1884
- yield `
2231
+ try {
2232
+ v = await this.#${property.singularName}_fromJsonLd(obj, {
2233
+ ...options,
2234
+ // Keep the parent default for embedded objects.
2235
+ verifyPortableObject: this._verifyPortableObject,
2236
+ baseUrl: ${cachedPropertyBaseUrl},
2237
+ });
2238
+ } catch (error) {
2239
+ if (!options.suppressError) throw error;
2240
+ getLogger(["fedify", "vocab"]).debug(
2241
+ "Failed to parse embedded value of {property} at index {index}: {error}",
2242
+ { property: ${JSON.stringify(property.uri)}, index: i, error },
2243
+ );
2244
+ continue;
2245
+ }
1885
2246
  }
1886
2247
  }
1887
2248
  `;
1888
- }
1889
2249
  yield `
1890
2250
  if (v?.id != null &&
1891
- this.id != null && !isTrustedIriOrigin(options, v.id, this.id) &&
2251
+ ((${type.trustEmbeddedObjects === false ? "" : "this.id != null && "}!isTrustedIriOrigin(options, v.id, ${trustOwner})) ||
2252
+ isUnsecuredPortableObject(this) ||
2253
+ mustDereferencePortableObject(
2254
+ this, ${trustOwner}, v.id, options,
2255
+ )) &&
1892
2256
  !this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
1893
2257
  if (options.crossOrigin === "throw") {
1894
2258
  throw new Error(
1895
2259
  "The property object's @id (" + v.id.href + ") has a different " +
1896
- "origin than the property owner's @id (" + this.id.href + "); " +
2260
+ "origin than the property owner's @id (" + ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "(this.id?.href ?? null)"} + "); " +
1897
2261
  "refusing to return the object. If you want to bypass this " +
1898
2262
  "check and are aware of the security implications, set the " +
1899
2263
  'crossOrigin option to "trust".'
@@ -1905,10 +2269,11 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1905
2269
  "return the object. If you want to bypass this check and are " +
1906
2270
  "aware of the security implications, set the crossOrigin " +
1907
2271
  'option to "trust".',
1908
- { objectId: v.id.href, parentObjectId: this.id.href },
2272
+ { objectId: v.id.href, parentObjectId: ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "(this.id?.href ?? null)"} },
1909
2273
  );
1910
2274
  continue;
1911
2275
  }
2276
+ recordPortableReferrer(this, v, ${JSON.stringify(property.uri)});
1912
2277
  yield v;
1913
2278
  }
1914
2279
  }
@@ -1926,10 +2291,35 @@ const XSD_ANY_URI = "http://www.w3.org/2001/XMLSchema#anyURI";
1926
2291
  const FEDIFY_URL = "fedify:url";
1927
2292
  const INTERNAL_RUNTIME_IMPORTS = [
1928
2293
  "compactJsonLdCache",
2294
+ "createScopedContextLoader",
1929
2295
  "getJsonLdContext",
2296
+ "getPortableActorGateways",
1930
2297
  "isTrustedIriOrigin",
1931
2298
  "normalizeJsonLdIris"
1932
2299
  ].join(",\n ");
2300
+ const PORTABLE_DEREFERENCE_IMPORTS = [
2301
+ "copyPortableProvenance",
2302
+ "createSnapshotContextLoader",
2303
+ "dereferencePortableIri",
2304
+ "getPortableResponseClaim",
2305
+ "getReferrerGateways",
2306
+ "isPortableIri",
2307
+ "isPortableMode",
2308
+ "isUnsecuredPortableObject",
2309
+ "isUnverifiedPortableClaim",
2310
+ "markUnverifiedPortableClaim",
2311
+ "mustDereferencePortableObject",
2312
+ "parseCompatibleEf61Reference",
2313
+ "type PortableResponseClaim",
2314
+ "recordPortableReferrer",
2315
+ "rejectMalformedCompatibleReference",
2316
+ "warnUnverifiableEmbeddedObject"
2317
+ ].join(",\n ");
2318
+ const SIGNED_REPRESENTATION_IMPORTS = [
2319
+ "enterSignedValueScope",
2320
+ "resolveSignedValues",
2321
+ "retainedSignedValueRef"
2322
+ ].join(",\n ");
1933
2323
  const RUNTIME_IMPORTS = [
1934
2324
  "canParseDecimal",
1935
2325
  "decodeMultibase",
@@ -1943,10 +2333,13 @@ const RUNTIME_IMPORTS = [
1943
2333
  "importMultibaseKey",
1944
2334
  "importPem",
1945
2335
  "isDecimal",
2336
+ "isGatewayUrl",
1946
2337
  "LanguageString",
1947
2338
  "parseDecimal",
2339
+ "parseGatewayUrl",
1948
2340
  "parseIri",
1949
2341
  "parseJsonLdId",
2342
+ "type PortableObjectVerifier",
1950
2343
  "type RemoteDocument"
1951
2344
  ].join(",\n ");
1952
2345
  /**
@@ -1982,6 +2375,7 @@ async function* generateClass(typeUri, types, moduleVarNames) {
1982
2375
  readonly #documentLoader?: DocumentLoader;
1983
2376
  readonly #contextLoader?: DocumentLoader;
1984
2377
  readonly #tracerProvider?: TracerProvider;
2378
+ readonly #verifyPortableObject?: PortableObjectVerifier;
1985
2379
  readonly #warning?: {
1986
2380
  category: string[];
1987
2381
  message: string;
@@ -1989,6 +2383,7 @@ async function* generateClass(typeUri, types, moduleVarNames) {
1989
2383
  };
1990
2384
  #cachedJsonLd?: unknown;
1991
2385
  readonly #_baseUrl?: URL;
2386
+ #shouldCacheJsonLd = true;
1992
2387
  readonly id: URL | null;
1993
2388
 
1994
2389
  protected get _documentLoader(): DocumentLoader | undefined {
@@ -2003,6 +2398,10 @@ async function* generateClass(typeUri, types, moduleVarNames) {
2003
2398
  return this.#tracerProvider;
2004
2399
  }
2005
2400
 
2401
+ protected get _verifyPortableObject(): PortableObjectVerifier | undefined {
2402
+ return this.#verifyPortableObject;
2403
+ }
2404
+
2006
2405
  protected get _warning(): {
2007
2406
  category: string[];
2008
2407
  message: string;
@@ -2022,6 +2421,20 @@ async function* generateClass(typeUri, types, moduleVarNames) {
2022
2421
  protected get _baseUrl(): URL | undefined {
2023
2422
  return this.#_baseUrl;
2024
2423
  }
2424
+
2425
+ protected get _shouldCacheJsonLd(): boolean {
2426
+ return this.#shouldCacheJsonLd;
2427
+ }
2428
+
2429
+ protected set _shouldCacheJsonLd(value: boolean) {
2430
+ this.#shouldCacheJsonLd = value;
2431
+ }
2432
+
2433
+ protected static _shouldCacheDecodedJsonLd(value: unknown): boolean {
2434
+ if (value == null || typeof value !== "object") return true;
2435
+ if (!("_shouldCacheJsonLd" in value)) return true;
2436
+ return (value as { _shouldCacheJsonLd: boolean })._shouldCacheJsonLd;
2437
+ }
2025
2438
  `;
2026
2439
  yield `
2027
2440
  /**
@@ -2108,11 +2521,69 @@ async function* generateClasses(types) {
2108
2521
  from "@opentelemetry/api";\n`;
2109
2522
  yield `import {\n ${RUNTIME_IMPORTS}\n} from "@fedify/vocab-runtime";\n`;
2110
2523
  yield `import {\n ${INTERNAL_RUNTIME_IMPORTS}\n} from "@fedify/vocab-runtime/internal/jsonld-cache";\n`;
2524
+ yield `import {\n ${PORTABLE_DEREFERENCE_IMPORTS}\n} from "@fedify/vocab-runtime/internal/portable-dereference";\n`;
2525
+ yield `import {\n ${SIGNED_REPRESENTATION_IMPORTS}\n} from "@fedify/vocab-runtime/internal/signed-representation";\n`;
2111
2526
  yield `import {
2112
2527
  isTemporalDuration,
2113
2528
  isTemporalInstant,
2114
2529
  } from "@fedify/vocab-runtime/temporal";\n`;
2115
- const portableIriKeys = new Set(["@id", "id"]);
2530
+ yield `
2531
+ function canDecodeIri(iri: string): boolean {
2532
+ try { parseIri(iri); return true; } catch { return iri.startsWith("at://"); }
2533
+ }
2534
+
2535
+ function decodeIri(iri: string): URL {
2536
+ return !URL.canParse(iri) && iri.startsWith("at://")
2537
+ ? new URL("at://" + encodeURIComponent(iri.substring(5)))
2538
+ : parseIri(iri);
2539
+ }
2540
+
2541
+ function inspectIri(iri: URL): string {
2542
+ // Inspecting an object must not throw, even for a URL that cannot be
2543
+ // formatted as an IRI, e.g., a malformed portable ID:
2544
+ try { return formatIri(iri); } catch { return iri.href; }
2545
+ }
2546
+ `;
2547
+ yield `
2548
+ function isValidLanguageTag(language: string): boolean {
2549
+ try {
2550
+ new Intl.Locale(language);
2551
+ return true;
2552
+ } catch (error) {
2553
+ if (error instanceof RangeError) return false;
2554
+ throw error;
2555
+ }
2556
+ }
2557
+ `;
2558
+ const extraContexts = Object.fromEntries(Object.values(types).flatMap((type) => type.properties.flatMap((property) => property.extraContext == null ? [] : [[property.uri, property.extraContext]])));
2559
+ if (Object.keys(extraContexts).length > 0) yield `
2560
+ const extraPropertyContexts: Readonly<Record<string, string>> =
2561
+ ${JSON.stringify(extraContexts)};
2562
+
2563
+ function getExtraContexts(document: unknown): string[] {
2564
+ const contexts = new Set<string>();
2565
+ const pending: unknown[] = [document];
2566
+ while (pending.length > 0) {
2567
+ const value = pending.pop();
2568
+ if (value == null || typeof value !== "object") continue;
2569
+ if (Array.isArray(value)) {
2570
+ for (const item of value) pending.push(item);
2571
+ continue;
2572
+ }
2573
+ if ("@value" in value) continue;
2574
+ for (const [key, child] of globalThis.Object.entries(value)) {
2575
+ if (key === "@context") continue;
2576
+ if (globalThis.Object.hasOwn(extraPropertyContexts, key)) {
2577
+ contexts.add(extraPropertyContexts[key]);
2578
+ }
2579
+ pending.push(child);
2580
+ }
2581
+ }
2582
+ return [...contexts];
2583
+ }
2584
+ `;
2585
+ yield "\n\n";
2586
+ const portableIriKeys = /* @__PURE__ */ new Set(["@id", "id"]);
2116
2587
  for (const type of Object.values(types)) for (const property of type.properties) addPortableIriKeys(portableIriKeys, property, types);
2117
2588
  yield "const PORTABLE_IRI_PATTERN = /^ap(?:\\+ef61)?:\\/\\//i;\n";
2118
2589
  yield `const PORTABLE_IRI_KEYS: ReadonlySet<string> = new Set(${JSON.stringify([...portableIriKeys].sort())});\n\n`;