@fedify/vocab-tools 2.4.0-pr.936.41 → 2.4.1
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/README.md +39 -0
- package/deno.json +1 -1
- package/dist/mod.cjs +570 -123
- package/dist/mod.d.cts +14 -0
- package/dist/mod.d.ts +14 -0
- package/dist/mod.js +570 -123
- package/dist/schema.yaml +15 -0
- package/package.json +2 -2
- package/src/__snapshots__/class.test.ts.deno.snap +54490 -12168
- package/src/__snapshots__/class.test.ts.node.snap +54490 -12168
- package/src/__snapshots__/class.test.ts.snap +54490 -12168
- package/src/class.ts +116 -0
- package/src/codec.ts +133 -34
- package/src/constructor.ts +16 -4
- package/src/inspector.ts +4 -4
- package/src/property.ts +343 -70
- package/src/schema.ts +16 -0
- package/src/schema.yaml +15 -0
- package/src/type.ts +20 -8
package/dist/mod.cjs
CHANGED
|
@@ -8,7 +8,7 @@ let yaml = require("yaml");
|
|
|
8
8
|
let es_toolkit = require("es-toolkit");
|
|
9
9
|
//#region deno.json
|
|
10
10
|
var name = "@fedify/vocab-tools";
|
|
11
|
-
var version = "2.4.
|
|
11
|
+
var version = "2.4.1";
|
|
12
12
|
//#endregion
|
|
13
13
|
//#region src/type.ts
|
|
14
14
|
const HEURISTICS_CONTEXTS = [
|
|
@@ -186,7 +186,8 @@ const scalarTypes = {
|
|
|
186
186
|
dataCheck(v) {
|
|
187
187
|
return `typeof ${v} === "object" && "@language" in ${v} && "@value" in ${v}
|
|
188
188
|
&& typeof ${v}["@language"] === "string"
|
|
189
|
-
&& typeof ${v}["@value"] === "string"
|
|
189
|
+
&& typeof ${v}["@value"] === "string"
|
|
190
|
+
&& isValidLanguageTag(${v}["@language"])`;
|
|
190
191
|
},
|
|
191
192
|
decoder(v) {
|
|
192
193
|
return `new LanguageString(${v}["@value"], ${v}["@language"])`;
|
|
@@ -302,7 +303,7 @@ const scalarTypes = {
|
|
|
302
303
|
&& typeof ${v}["@value"] === "string" && !("@language" in ${v})`;
|
|
303
304
|
},
|
|
304
305
|
decoder(v) {
|
|
305
|
-
return `new Intl.Locale(${v}["@value"])`;
|
|
306
|
+
return `new Intl.Locale(normalizeLanguageTag(${v}["@value"]))`;
|
|
306
307
|
}
|
|
307
308
|
},
|
|
308
309
|
"fedify:url": {
|
|
@@ -317,13 +318,14 @@ const scalarTypes = {
|
|
|
317
318
|
return `formatIri(${v})`;
|
|
318
319
|
},
|
|
319
320
|
dataCheck(v) {
|
|
320
|
-
return `typeof ${v} === "object" &&
|
|
321
|
-
|
|
322
|
-
|
|
321
|
+
return `typeof ${v} === "object" &&
|
|
322
|
+
((typeof ${v}["@id"] === "string" && canDecodeIri(${v}["@id"])) ||
|
|
323
|
+
(typeof ${v}["@value"] === "string" && canDecodeIri(${v}["@value"])))`;
|
|
323
324
|
},
|
|
324
325
|
decoder(v) {
|
|
325
|
-
return `
|
|
326
|
-
}
|
|
326
|
+
return `decodeIri(typeof ${v}["@id"] === "string" ? ${v}["@id"] : ${v}["@value"])`;
|
|
327
|
+
},
|
|
328
|
+
skipUnparsable: true
|
|
327
329
|
},
|
|
328
330
|
"fedify:gatewayUrl": {
|
|
329
331
|
name: "URL",
|
|
@@ -331,10 +333,10 @@ const scalarTypes = {
|
|
|
331
333
|
return `${v} instanceof URL && isGatewayUrl(${v})`;
|
|
332
334
|
},
|
|
333
335
|
encoder(v) {
|
|
334
|
-
return `{ "@id":
|
|
336
|
+
return `{ "@id": ${v}.origin }`;
|
|
335
337
|
},
|
|
336
338
|
compactEncoder(v) {
|
|
337
|
-
return
|
|
339
|
+
return `${v}.origin`;
|
|
338
340
|
},
|
|
339
341
|
dataCheck(v) {
|
|
340
342
|
return `${v} != null && typeof ${v} === "object" &&
|
|
@@ -557,6 +559,9 @@ function getDecoder(typeUri, types, variable, optionsVariable, baseUrlVariable)
|
|
|
557
559
|
)`;
|
|
558
560
|
throw new Error(`Unknown type: ${typeUri}`);
|
|
559
561
|
}
|
|
562
|
+
function skipsUnparsable(typeUri) {
|
|
563
|
+
return scalarTypes[typeUri]?.skipUnparsable ?? false;
|
|
564
|
+
}
|
|
560
565
|
function getDataCheck(typeUri, types, variable) {
|
|
561
566
|
if (typeUri in scalarTypes) return scalarTypes[typeUri].dataCheck(variable);
|
|
562
567
|
if (typeUri in types) {
|
|
@@ -736,6 +741,7 @@ function* generatePreprocessorBlock(property, rangeTypeName, variable, baseUrlEx
|
|
|
736
741
|
documentLoader: options.documentLoader,
|
|
737
742
|
contextLoader: options.contextLoader,
|
|
738
743
|
tracerProvider: options.tracerProvider,
|
|
744
|
+
verifyPortableObject: options.verifyPortableObject,
|
|
739
745
|
baseUrl: ${baseUrlExpr},
|
|
740
746
|
});
|
|
741
747
|
if (_result instanceof Error) throw _result;
|
|
@@ -784,6 +790,10 @@ async function* generateEncoder(typeUri, types) {
|
|
|
784
790
|
...options,
|
|
785
791
|
contextLoader: options.contextLoader ?? getDocumentLoader(),
|
|
786
792
|
};
|
|
793
|
+
// Joins the enclosing frame's signed-value scope, or starts one when this
|
|
794
|
+
// frame is the outermost. Only the owning frame puts retained signed
|
|
795
|
+
// child documents back in place of their placeholders.
|
|
796
|
+
const signedValues = enterSignedValueScope(options);
|
|
787
797
|
`;
|
|
788
798
|
if (isCompactableType(typeUri, types)) {
|
|
789
799
|
yield `
|
|
@@ -814,9 +824,13 @@ async function* generateEncoder(typeUri, types) {
|
|
|
814
824
|
for (const v of this.${await getFieldName(property.uri)}) {
|
|
815
825
|
const item = (
|
|
816
826
|
`;
|
|
817
|
-
if (!areAllScalarTypes(property.range, types))
|
|
827
|
+
if (!areAllScalarTypes(property.range, types)) {
|
|
828
|
+
yield "retainedSignedValueRef(v, signedValues.scope) ?? (";
|
|
829
|
+
yield "v instanceof URL ? formatIri(v) : ";
|
|
830
|
+
}
|
|
818
831
|
const encoders = getEncoders(property.range, types, "v", "options", true);
|
|
819
832
|
for (const code of encoders) yield code;
|
|
833
|
+
if (!areAllScalarTypes(property.range, types)) yield ")";
|
|
820
834
|
yield `
|
|
821
835
|
);
|
|
822
836
|
compactItems.push(item);
|
|
@@ -847,7 +861,9 @@ async function* generateEncoder(typeUri, types) {
|
|
|
847
861
|
${type.typeless ? "" : `result["type"] = ${JSON.stringify(type.compactName ?? type.uri)};`}
|
|
848
862
|
if (this.id != null) result["id"] = formatIri(this.id);
|
|
849
863
|
result["@context"] = ${JSON.stringify(type.defaultContext)};
|
|
850
|
-
return
|
|
864
|
+
return signedValues.owner
|
|
865
|
+
? resolveSignedValues(result, signedValues.scope)
|
|
866
|
+
: result;
|
|
851
867
|
}
|
|
852
868
|
`;
|
|
853
869
|
}
|
|
@@ -871,12 +887,21 @@ async function* generateEncoder(typeUri, types) {
|
|
|
871
887
|
yield `
|
|
872
888
|
array = [];
|
|
873
889
|
for (const v of this.${await getFieldName(property.uri)}) {
|
|
874
|
-
|
|
890
|
+
let element = (
|
|
875
891
|
`;
|
|
876
|
-
if (!areAllScalarTypes(property.range, types))
|
|
892
|
+
if (!areAllScalarTypes(property.range, types)) {
|
|
893
|
+
yield "retainedSignedValueRef(v, signedValues.scope) ?? (";
|
|
894
|
+
yield "v instanceof URL ? { \"@id\": formatIri(v) } : ";
|
|
895
|
+
}
|
|
877
896
|
for (const code of getEncoders(property.range, types, "v", "options")) yield code;
|
|
897
|
+
if (!areAllScalarTypes(property.range, types)) yield ")";
|
|
878
898
|
yield `
|
|
879
899
|
);
|
|
900
|
+
if (Array.isArray(element)) {
|
|
901
|
+
if (element.length < 1) continue;
|
|
902
|
+
if (element.length == 1) element = element[0];
|
|
903
|
+
}
|
|
904
|
+
if (typeof element === "undefined") continue;
|
|
880
905
|
`;
|
|
881
906
|
if (isNonFunctionalProperty(property) && property.container === "graph") yield `array.push({ "@graph": element });`;
|
|
882
907
|
else yield `array.push(element);`;
|
|
@@ -904,16 +929,35 @@ async function* generateEncoder(typeUri, types) {
|
|
|
904
929
|
if (options.format === "expand") {
|
|
905
930
|
return await jsonld.expand(
|
|
906
931
|
values,
|
|
907
|
-
{
|
|
932
|
+
{
|
|
933
|
+
documentLoader: options.contextLoader,
|
|
934
|
+
keepFreeFloatingNodes: true,
|
|
935
|
+
},
|
|
908
936
|
);
|
|
909
937
|
}
|
|
910
|
-
|
|
938
|
+
let docContext: Parameters<typeof jsonld.compact>[1] = options.context ??
|
|
911
939
|
${JSON.stringify(type.defaultContext)};
|
|
912
|
-
|
|
940
|
+
let compacted = await jsonld.compact(
|
|
913
941
|
values,
|
|
914
942
|
docContext,
|
|
915
943
|
{ documentLoader: options.contextLoader },
|
|
916
944
|
);
|
|
945
|
+
`;
|
|
946
|
+
if (Object.values(types).some((t) => t.properties.some((p) => p.extraContext != null))) yield `
|
|
947
|
+
if (options.context == null) {
|
|
948
|
+
const currentContexts = Array.isArray(docContext) ? docContext : [docContext];
|
|
949
|
+
const additionalContexts = getExtraContexts(compacted).filter(
|
|
950
|
+
context => !currentContexts.includes(context)
|
|
951
|
+
);
|
|
952
|
+
if (additionalContexts.length > 0) {
|
|
953
|
+
docContext = [...currentContexts, ...additionalContexts];
|
|
954
|
+
compacted = await jsonld.compact(values, docContext, {
|
|
955
|
+
documentLoader: options.contextLoader,
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
`;
|
|
960
|
+
yield `
|
|
917
961
|
if (docContext != null) {
|
|
918
962
|
// Embed context
|
|
919
963
|
`;
|
|
@@ -937,12 +981,14 @@ async function* generateEncoder(typeUri, types) {
|
|
|
937
981
|
}
|
|
938
982
|
yield `
|
|
939
983
|
}
|
|
940
|
-
return
|
|
984
|
+
return signedValues.owner
|
|
985
|
+
? resolveSignedValues(compacted, signedValues.scope)
|
|
986
|
+
: compacted;
|
|
941
987
|
}
|
|
942
988
|
|
|
943
989
|
protected ${emitOverride$1(typeUri, types)} isCompactable(): boolean {
|
|
944
990
|
`;
|
|
945
|
-
for (const property of type.properties) if (!property.range.every((r) => isCompactableType(r, types))) yield `
|
|
991
|
+
for (const property of type.properties) if (property.extraContext != null || !property.range.every((r) => isCompactableType(r, types))) yield `
|
|
946
992
|
if (
|
|
947
993
|
this.${await getFieldName(property.uri)} != null &&
|
|
948
994
|
this.${await getFieldName(property.uri)}.length > 0
|
|
@@ -964,6 +1010,10 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
|
|
|
964
1010
|
* - \`contextLoader\`: The loader for remote JSON-LD contexts.
|
|
965
1011
|
* - \`tracerProvider\`: The OpenTelemetry tracer provider to use.
|
|
966
1012
|
* If omitted, the global tracer provider is used.
|
|
1013
|
+
* - \`verifyPortableObject\`: The default FEP-ef61 portable
|
|
1014
|
+
* object verifier for the property accessors of the
|
|
1015
|
+
* returned object and the objects obtained from it. It
|
|
1016
|
+
* does not verify the given \`json\` itself.
|
|
967
1017
|
* @returns The object of this type.
|
|
968
1018
|
* @throws {TypeError} If the given \`json\` is invalid.
|
|
969
1019
|
*/
|
|
@@ -973,6 +1023,7 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
|
|
|
973
1023
|
documentLoader?: DocumentLoader,
|
|
974
1024
|
contextLoader?: DocumentLoader,
|
|
975
1025
|
tracerProvider?: TracerProvider,
|
|
1026
|
+
verifyPortableObject?: PortableObjectVerifier,
|
|
976
1027
|
baseUrl?: URL,
|
|
977
1028
|
} = {},
|
|
978
1029
|
): Promise<${type.name}> {
|
|
@@ -1011,6 +1062,7 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
|
|
|
1011
1062
|
documentLoader?: DocumentLoader,
|
|
1012
1063
|
contextLoader?: DocumentLoader,
|
|
1013
1064
|
tracerProvider?: TracerProvider,
|
|
1065
|
+
verifyPortableObject?: PortableObjectVerifier,
|
|
1014
1066
|
baseUrl?: URL,
|
|
1015
1067
|
} = {},
|
|
1016
1068
|
): Promise<${type.name}> {
|
|
@@ -1027,7 +1079,17 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
|
|
|
1027
1079
|
// deno-lint-ignore no-explicit-any
|
|
1028
1080
|
let values: Record<string, any[]> & { "@id"?: string };
|
|
1029
1081
|
let expanded: unknown[];
|
|
1030
|
-
if (
|
|
1082
|
+
if (
|
|
1083
|
+
"_fromSubclass" in options &&
|
|
1084
|
+
options._fromSubclass &&
|
|
1085
|
+
typeof json === "object" &&
|
|
1086
|
+
!Array.isArray(json)
|
|
1087
|
+
) {
|
|
1088
|
+
values =
|
|
1089
|
+
// deno-lint-ignore no-explicit-any
|
|
1090
|
+
json as (Record<string, any[]> & { "@id"?: string });
|
|
1091
|
+
expanded = [values];
|
|
1092
|
+
} else if (globalThis.Object.keys(json).length == 0) {
|
|
1031
1093
|
values = {};
|
|
1032
1094
|
expanded = [values];
|
|
1033
1095
|
} else {
|
|
@@ -1084,8 +1146,9 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
|
|
|
1084
1146
|
);
|
|
1085
1147
|
`;
|
|
1086
1148
|
else yield `
|
|
1087
|
-
|
|
1088
|
-
|
|
1149
|
+
const superValues = structuredClone(values);
|
|
1150
|
+
delete superValues["@type"];
|
|
1151
|
+
const instance = await super.fromJsonLd(superValues, {
|
|
1089
1152
|
...options,
|
|
1090
1153
|
// @ts-ignore: an internal option
|
|
1091
1154
|
_fromSubclass: true,
|
|
@@ -1094,25 +1157,35 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
|
|
|
1094
1157
|
throw new TypeError("Unexpected type: " + instance.constructor.name);
|
|
1095
1158
|
}
|
|
1096
1159
|
`;
|
|
1160
|
+
yield `
|
|
1161
|
+
let shouldCacheJsonLd = instance._shouldCacheJsonLd;
|
|
1162
|
+
`;
|
|
1097
1163
|
for (const property of type.properties) {
|
|
1098
1164
|
const variable = await getFieldName(property.uri, "");
|
|
1099
1165
|
yield await generateField(property, types, "const ");
|
|
1100
1166
|
const arrayVariable = `${variable}__array`;
|
|
1101
1167
|
yield `
|
|
1102
|
-
let ${arrayVariable} = values[${JSON.stringify(property.uri)}];
|
|
1168
|
+
let ${arrayVariable} = ${property.uri === "https://w3id.org/fep/ef61/gateways" ? "getPortableActorGateways(values) as typeof values[string]" : `values[${JSON.stringify(property.uri)}]`};
|
|
1103
1169
|
`;
|
|
1104
1170
|
if (property.functional && property.redundantProperties != null) for (const prop of property.redundantProperties) yield `
|
|
1105
1171
|
if (${arrayVariable} == null || ${arrayVariable}.length < 1) {
|
|
1106
1172
|
${arrayVariable} = values[${JSON.stringify(prop.uri)}];
|
|
1107
1173
|
}
|
|
1108
1174
|
`;
|
|
1175
|
+
let itemsExpression = `${arrayVariable}.length === 1 && "@list" in ${arrayVariable}[0]
|
|
1176
|
+
? ${arrayVariable}[0]["@list"]
|
|
1177
|
+
: ${arrayVariable}`;
|
|
1178
|
+
if (isNonFunctionalProperty(property) && property.container === "graph") itemsExpression = `(${itemsExpression}).flatMap((item: unknown) =>
|
|
1179
|
+
item != null && typeof item === "object" && "@graph" in item &&
|
|
1180
|
+
Array.isArray((item as { "@graph": unknown })["@graph"])
|
|
1181
|
+
? (item as { "@graph": unknown[] })["@graph"]
|
|
1182
|
+
: [item]
|
|
1183
|
+
)`;
|
|
1109
1184
|
yield `
|
|
1110
1185
|
for (
|
|
1111
1186
|
const v of ${arrayVariable} == null
|
|
1112
1187
|
? []
|
|
1113
|
-
: ${
|
|
1114
|
-
? ${arrayVariable}[0]["@list"]
|
|
1115
|
-
: ${arrayVariable}
|
|
1188
|
+
: ${itemsExpression}
|
|
1116
1189
|
) {
|
|
1117
1190
|
if (v == null) continue;
|
|
1118
1191
|
`;
|
|
@@ -1126,29 +1199,47 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
|
|
|
1126
1199
|
continue;
|
|
1127
1200
|
}
|
|
1128
1201
|
`;
|
|
1129
|
-
|
|
1130
|
-
const decoded = ${getDecoder(property.range[0], types, "v", "options", propertyBaseUrl)};
|
|
1131
|
-
if (typeof decoded === "undefined") continue;
|
|
1132
|
-
${variable}.push(decoded);`;
|
|
1133
|
-
else {
|
|
1134
|
-
yield `
|
|
1202
|
+
yield `
|
|
1135
1203
|
const decoded =
|
|
1136
|
-
|
|
1204
|
+
`;
|
|
1205
|
+
const lenient = property.range.length == 1 && skipsUnparsable(property.range[0]);
|
|
1206
|
+
if (property.range.length == 1) {
|
|
1207
|
+
if (lenient) {
|
|
1208
|
+
yield getDataCheck(property.range[0], types, "v");
|
|
1209
|
+
yield " ? ";
|
|
1210
|
+
}
|
|
1211
|
+
yield getDecoder(property.range[0], types, "v", "options", propertyBaseUrl);
|
|
1212
|
+
if (lenient) yield " : undefined";
|
|
1213
|
+
} else {
|
|
1137
1214
|
const decoders = getDecoders(property.range, types, "v", "options", propertyBaseUrl);
|
|
1138
1215
|
for (const code of decoders) yield code;
|
|
1139
|
-
|
|
1216
|
+
}
|
|
1217
|
+
yield `
|
|
1140
1218
|
;
|
|
1141
|
-
|
|
1219
|
+
`;
|
|
1220
|
+
yield `
|
|
1221
|
+
if (typeof decoded === "undefined") {
|
|
1222
|
+
shouldCacheJsonLd = false;
|
|
1223
|
+
continue;
|
|
1224
|
+
}
|
|
1225
|
+
`;
|
|
1226
|
+
yield `
|
|
1227
|
+
if (!this._shouldCacheDecodedJsonLd(decoded)) {
|
|
1228
|
+
shouldCacheJsonLd = false;
|
|
1229
|
+
}
|
|
1142
1230
|
${variable}.push(decoded);
|
|
1143
|
-
|
|
1144
|
-
}
|
|
1231
|
+
`;
|
|
1145
1232
|
yield `
|
|
1146
1233
|
}
|
|
1147
1234
|
instance.${await getFieldName(property.uri)} = ${variable};
|
|
1148
1235
|
`;
|
|
1149
1236
|
}
|
|
1150
1237
|
yield `
|
|
1151
|
-
|
|
1238
|
+
instance._shouldCacheJsonLd = shouldCacheJsonLd;
|
|
1239
|
+
if (
|
|
1240
|
+
shouldCacheJsonLd &&
|
|
1241
|
+
(!("_fromSubclass" in options) || !options._fromSubclass)
|
|
1242
|
+
) {
|
|
1152
1243
|
try {
|
|
1153
1244
|
if (cacheJsonLd != null && cacheJsonLd !== expanded) {
|
|
1154
1245
|
const compactArray = Array.isArray(json) && json.length === 1;
|
|
@@ -1214,6 +1305,7 @@ async function* generateConstructor(typeUri, types) {
|
|
|
1214
1305
|
documentLoader?: DocumentLoader,
|
|
1215
1306
|
contextLoader?: DocumentLoader,
|
|
1216
1307
|
tracerProvider?: TracerProvider,
|
|
1308
|
+
verifyPortableObject?: PortableObjectVerifier,
|
|
1217
1309
|
} = {},
|
|
1218
1310
|
) {
|
|
1219
1311
|
`;
|
|
@@ -1221,6 +1313,7 @@ async function* generateConstructor(typeUri, types) {
|
|
|
1221
1313
|
this.#documentLoader = options.documentLoader;
|
|
1222
1314
|
this.#contextLoader = options.contextLoader;
|
|
1223
1315
|
this.#tracerProvider = options.tracerProvider;
|
|
1316
|
+
this.#verifyPortableObject = options.verifyPortableObject;
|
|
1224
1317
|
const baseUrl = (options as { baseUrl?: URL }).baseUrl;
|
|
1225
1318
|
this.#_baseUrl = baseUrl == null ? undefined : new URL(baseUrl.href);
|
|
1226
1319
|
if ("$warning" in options) {
|
|
@@ -1290,7 +1383,7 @@ async function* generateConstructor(typeUri, types) {
|
|
|
1290
1383
|
if (Array.isArray(values.${property.pluralName}) &&
|
|
1291
1384
|
values.${property.pluralName}.every(v => ${typeGuards})) {
|
|
1292
1385
|
// @ts-ignore: type is checked above.
|
|
1293
|
-
this.${fieldName} = values.${property.pluralName};
|
|
1386
|
+
this.${fieldName} = values.${property.pluralName}.slice();
|
|
1294
1387
|
`;
|
|
1295
1388
|
if (!allScalarTypes) yield `
|
|
1296
1389
|
for (let i = 0; i < values.${property.pluralName}.length; i++) {
|
|
@@ -1315,9 +1408,17 @@ async function* generateCloner(typeUri, types) {
|
|
|
1315
1408
|
yield `
|
|
1316
1409
|
/**
|
|
1317
1410
|
* Clones this instance, optionally updating it with the given values.
|
|
1411
|
+
*
|
|
1412
|
+
* A clone never inherits a signed JSON-LD representation retained by
|
|
1413
|
+
* \`signObject()\`: the clone may differ from the document that the proof
|
|
1414
|
+
* covers, so embedding the original secured JSON in a parent document
|
|
1415
|
+
* would be wrong. Sign the clone again if it has to be embedded as a
|
|
1416
|
+
* secured child.
|
|
1417
|
+
*
|
|
1318
1418
|
* @param values The values to update the clone with.
|
|
1319
1419
|
* @param options The options to use for cloning.
|
|
1320
|
-
* @returns The cloned instance.
|
|
1420
|
+
* @returns The cloned instance with its own property arrays. Nested objects
|
|
1421
|
+
* and URLs are shared with this instance.
|
|
1321
1422
|
*/
|
|
1322
1423
|
${emitOverride$1(typeUri, types)} clone(
|
|
1323
1424
|
values:
|
|
@@ -1328,6 +1429,7 @@ async function* generateCloner(typeUri, types) {
|
|
|
1328
1429
|
options: {
|
|
1329
1430
|
documentLoader?: DocumentLoader,
|
|
1330
1431
|
contextLoader?: DocumentLoader,
|
|
1432
|
+
verifyPortableObject?: PortableObjectVerifier,
|
|
1331
1433
|
} = {}
|
|
1332
1434
|
): ${type.name} {
|
|
1333
1435
|
if (this._warning != null) {
|
|
@@ -1345,13 +1447,14 @@ async function* generateCloner(typeUri, types) {
|
|
|
1345
1447
|
{ id: values.id ?? this.id },
|
|
1346
1448
|
options
|
|
1347
1449
|
);
|
|
1450
|
+
copyPortableProvenance(this, clone);
|
|
1348
1451
|
`;
|
|
1349
1452
|
else yield `const clone = super.clone(values, options) as unknown as ${type.name};`;
|
|
1350
1453
|
for (const property of type.properties) {
|
|
1351
1454
|
const fieldName = await getFieldName(property.uri);
|
|
1352
1455
|
const trustFieldName = await getFieldName(property.uri, "#_trust");
|
|
1353
1456
|
const allScalarTypes = areAllScalarTypes(property.range, types);
|
|
1354
|
-
yield `clone.${fieldName} = this.${fieldName};`;
|
|
1457
|
+
yield `clone.${fieldName} = this.${fieldName}.slice();`;
|
|
1355
1458
|
if (!allScalarTypes) yield `clone.${trustFieldName} = new Set(this.${trustFieldName});`;
|
|
1356
1459
|
if (hasSingularAccessor(property)) {
|
|
1357
1460
|
let typeGuards = getTypeGuards(property.range, types, `values.${property.singularName}`);
|
|
@@ -1402,7 +1505,7 @@ async function* generateCloner(typeUri, types) {
|
|
|
1402
1505
|
if (Array.isArray(values.${property.pluralName}) &&
|
|
1403
1506
|
values.${property.pluralName}.every(v => ${typeGuards})) {
|
|
1404
1507
|
// @ts-ignore: type is checked above.
|
|
1405
|
-
clone.${fieldName} = values.${property.pluralName};
|
|
1508
|
+
clone.${fieldName} = values.${property.pluralName}.slice();
|
|
1406
1509
|
`;
|
|
1407
1510
|
if (!allScalarTypes) yield `
|
|
1408
1511
|
clone.${trustFieldName} = new Set();
|
|
@@ -1440,12 +1543,12 @@ async function* generateInspector(typeUri, types) {
|
|
|
1440
1543
|
[Symbol.for("Deno.customInspect")]: (
|
|
1441
1544
|
inspect: typeof Deno.inspect,
|
|
1442
1545
|
options: Deno.InspectOptions,
|
|
1443
|
-
): string => "URL " + inspect(this.id
|
|
1546
|
+
): string => "URL " + inspect(inspectIri(this.id!), options),
|
|
1444
1547
|
[Symbol.for("nodejs.util.inspect.custom")]: (
|
|
1445
1548
|
_depth: number,
|
|
1446
1549
|
options: unknown,
|
|
1447
1550
|
inspect: (value: unknown, options: unknown) => string,
|
|
1448
|
-
): string => "URL " + inspect(this.id
|
|
1551
|
+
): string => "URL " + inspect(inspectIri(this.id!), options),
|
|
1449
1552
|
};
|
|
1450
1553
|
}
|
|
1451
1554
|
`;
|
|
@@ -1461,12 +1564,12 @@ async function* generateInspector(typeUri, types) {
|
|
|
1461
1564
|
[Symbol.for("Deno.customInspect")]: (
|
|
1462
1565
|
inspect: typeof Deno.inspect,
|
|
1463
1566
|
options: Deno.InspectOptions,
|
|
1464
|
-
): string => "URL " + inspect(v
|
|
1567
|
+
): string => "URL " + inspect(inspectIri(v), options),
|
|
1465
1568
|
[Symbol.for("nodejs.util.inspect.custom")]: (
|
|
1466
1569
|
_depth: number,
|
|
1467
1570
|
options: unknown,
|
|
1468
1571
|
inspect: (value: unknown, options: unknown) => string,
|
|
1469
|
-
): string => "URL " + inspect(v
|
|
1572
|
+
): string => "URL " + inspect(inspectIri(v), options),
|
|
1470
1573
|
}
|
|
1471
1574
|
: v);
|
|
1472
1575
|
`;
|
|
@@ -1536,6 +1639,7 @@ function emitOverride(typeUri, types, property) {
|
|
|
1536
1639
|
}
|
|
1537
1640
|
async function* generateProperty(type, property, types, moduleVarNames) {
|
|
1538
1641
|
const override = emitOverride(type.uri, types, property);
|
|
1642
|
+
const trustOwner = type.trustEmbeddedObjects === false ? "null" : "this.id";
|
|
1539
1643
|
const doc = `\n/** ${property.description.replaceAll("\n", "\n * ")}\n */\n`;
|
|
1540
1644
|
const cachedPropertyBaseUrl = `(options as { baseUrl?: URL }).baseUrl ??
|
|
1541
1645
|
this._baseUrl ??
|
|
@@ -1576,6 +1680,9 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1576
1680
|
suppressError?: boolean,
|
|
1577
1681
|
tracerProvider?: TracerProvider,
|
|
1578
1682
|
crossOrigin?: "ignore" | "throw" | "trust";
|
|
1683
|
+
gateways?: readonly (string | URL)[];
|
|
1684
|
+
verifyPortableObject?: PortableObjectVerifier;
|
|
1685
|
+
inheritPortableObjectVerifier?: boolean;
|
|
1579
1686
|
} = {},
|
|
1580
1687
|
): Promise<${getTypeNames(property.range, types)} | null> {
|
|
1581
1688
|
const documentLoader =
|
|
@@ -1588,11 +1695,96 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1588
1695
|
${JSON.stringify(name)},
|
|
1589
1696
|
${JSON.stringify(version)},
|
|
1590
1697
|
);
|
|
1698
|
+
// The fetched object uses the verifier given to this call (which falls
|
|
1699
|
+
// back to the parent's own default) for its accessors by default,
|
|
1700
|
+
// unless the call keeps its verifier to itself:
|
|
1701
|
+
const childVerifier = options.inheritPortableObjectVerifier === false
|
|
1702
|
+
? this._verifyPortableObject
|
|
1703
|
+
: options.verifyPortableObject;
|
|
1591
1704
|
return await tracer.startActiveSpan("activitypub.lookup_object", async (span) => {
|
|
1705
|
+
const dereferencePortable = async (
|
|
1706
|
+
portableUrl: URL,
|
|
1707
|
+
extra: {
|
|
1708
|
+
inferredGateways?: readonly URL[];
|
|
1709
|
+
response?: RemoteDocument;
|
|
1710
|
+
contextLoader?: DocumentLoader;
|
|
1711
|
+
} = {},
|
|
1712
|
+
) => {
|
|
1713
|
+
try {
|
|
1714
|
+
const obj = await dereferencePortableIri(portableUrl, {
|
|
1715
|
+
documentLoader,
|
|
1716
|
+
contextLoader: extra.contextLoader ?? contextLoader,
|
|
1717
|
+
tracerProvider,
|
|
1718
|
+
gateways: options.gateways,
|
|
1719
|
+
inferredGateways: extra.inferredGateways,
|
|
1720
|
+
response: extra.response,
|
|
1721
|
+
verifyPortableObject: options.verifyPortableObject,
|
|
1722
|
+
referrer: {
|
|
1723
|
+
object: this,
|
|
1724
|
+
property: ${JSON.stringify(property.uri)},
|
|
1725
|
+
},
|
|
1726
|
+
suppressError: options.suppressError,
|
|
1727
|
+
crossOrigin: options.crossOrigin,
|
|
1728
|
+
span,
|
|
1729
|
+
parse: (document, parseOptions) =>
|
|
1730
|
+
this.#${property.singularName}_fromJsonLd(document, {
|
|
1731
|
+
documentLoader,
|
|
1732
|
+
contextLoader: parseOptions.contextLoader,
|
|
1733
|
+
tracerProvider,
|
|
1734
|
+
verifyPortableObject: childVerifier,
|
|
1735
|
+
baseUrl: parseOptions.baseUrl,
|
|
1736
|
+
}),
|
|
1737
|
+
});
|
|
1738
|
+
if (obj != null) {
|
|
1739
|
+
span.setAttribute(
|
|
1740
|
+
"activitypub.object.id",
|
|
1741
|
+
(obj.id ?? portableUrl).href,
|
|
1742
|
+
);
|
|
1743
|
+
span.setAttribute(
|
|
1744
|
+
"activitypub.object.type",
|
|
1745
|
+
// @ts-ignore: obj.constructor always has a typeId.
|
|
1746
|
+
obj.constructor.typeId.href
|
|
1747
|
+
);
|
|
1748
|
+
}
|
|
1749
|
+
return obj;
|
|
1750
|
+
} catch (e) {
|
|
1751
|
+
span.setStatus({
|
|
1752
|
+
code: SpanStatusCode.ERROR,
|
|
1753
|
+
message: String(e),
|
|
1754
|
+
});
|
|
1755
|
+
throw e;
|
|
1756
|
+
} finally {
|
|
1757
|
+
span.end();
|
|
1758
|
+
}
|
|
1759
|
+
};
|
|
1760
|
+
if (isPortableIri(url)) {
|
|
1761
|
+
// A reference in a portable actor's own document, e.g., its outbox,
|
|
1762
|
+
// has no location hints, so the actor's gateways are used instead:
|
|
1763
|
+
return await dereferencePortable(
|
|
1764
|
+
url,
|
|
1765
|
+
options.gateways == null
|
|
1766
|
+
? { inferredGateways: getReferrerGateways(this, url) }
|
|
1767
|
+
: {},
|
|
1768
|
+
);
|
|
1769
|
+
}
|
|
1592
1770
|
const lookupUrl = formatIri(url);
|
|
1771
|
+
const portableMode = isPortableMode(this, options);
|
|
1772
|
+
if (portableMode) {
|
|
1773
|
+
const compatible = parseCompatibleEf61Reference(url);
|
|
1774
|
+
if (compatible === null) {
|
|
1775
|
+
span.end();
|
|
1776
|
+
return rejectMalformedCompatibleReference(lookupUrl, options);
|
|
1777
|
+
} else if (compatible != null) {
|
|
1778
|
+
return await dereferencePortable(compatible.id, {
|
|
1779
|
+
inferredGateways: [compatible.gateway],
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1593
1783
|
let fetchResult: RemoteDocument;
|
|
1594
1784
|
try {
|
|
1595
|
-
fetchResult = await documentLoader(lookupUrl
|
|
1785
|
+
fetchResult = await documentLoader(lookupUrl, {
|
|
1786
|
+
suppressError: options.suppressError,
|
|
1787
|
+
});
|
|
1596
1788
|
} catch (error) {
|
|
1597
1789
|
span.setStatus({
|
|
1598
1790
|
code: SpanStatusCode.ERROR,
|
|
@@ -1600,7 +1792,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1600
1792
|
});
|
|
1601
1793
|
span.end();
|
|
1602
1794
|
if (options.suppressError) {
|
|
1603
|
-
getLogger(["fedify", "vocab"]).
|
|
1795
|
+
getLogger(["fedify", "vocab"]).warn(
|
|
1604
1796
|
"Failed to fetch {url}: {error}",
|
|
1605
1797
|
{ error, url: lookupUrl }
|
|
1606
1798
|
);
|
|
@@ -1609,52 +1801,96 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1609
1801
|
throw error;
|
|
1610
1802
|
}
|
|
1611
1803
|
const { document, documentUrl } = fetchResult;
|
|
1612
|
-
|
|
1804
|
+
// In portable mode, the document may turn out to be a portable object,
|
|
1805
|
+
// which has to be verified with the same context documents:
|
|
1806
|
+
const snapshot = portableMode
|
|
1807
|
+
? createSnapshotContextLoader(contextLoader, options.suppressError)
|
|
1808
|
+
: null;
|
|
1809
|
+
const scopedContext = createScopedContextLoader(
|
|
1810
|
+
snapshot?.loader ?? contextLoader,
|
|
1811
|
+
snapshot == null && options.suppressError,
|
|
1812
|
+
);
|
|
1613
1813
|
try {
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1814
|
+
let claim: PortableResponseClaim | null | undefined;
|
|
1815
|
+
try {
|
|
1816
|
+
const baseUrl = parseIri(documentUrl);
|
|
1817
|
+
const obj = await this.#${property.singularName}_fromJsonLd(
|
|
1818
|
+
document,
|
|
1819
|
+
{
|
|
1820
|
+
documentLoader,
|
|
1821
|
+
contextLoader: scopedContext.loader,
|
|
1822
|
+
tracerProvider,
|
|
1823
|
+
verifyPortableObject: childVerifier,
|
|
1824
|
+
baseUrl,
|
|
1825
|
+
}
|
|
1826
|
+
);
|
|
1827
|
+
if (snapshot == null) {
|
|
1828
|
+
markUnverifiedPortableClaim(obj, url, documentUrl);
|
|
1829
|
+
} else {
|
|
1830
|
+
claim = getPortableResponseClaim(documentUrl, obj.id);
|
|
1831
|
+
}
|
|
1832
|
+
if (claim === undefined) {
|
|
1833
|
+
if (
|
|
1834
|
+
obj?.id != null &&
|
|
1835
|
+
!isTrustedIriOrigin(options, obj.id, baseUrl)
|
|
1836
|
+
) {
|
|
1837
|
+
if (options.crossOrigin === "throw") {
|
|
1838
|
+
throw new Error(
|
|
1839
|
+
"The object's @id (" + obj.id.href + ") has a different origin " +
|
|
1840
|
+
"than the document URL (" + baseUrl.href + "); refusing to return " +
|
|
1841
|
+
"the object. If you want to bypass this check and are aware of" +
|
|
1842
|
+
'the security implications, set the crossOrigin option to "trust".'
|
|
1843
|
+
);
|
|
1844
|
+
}
|
|
1845
|
+
getLogger(["fedify", "vocab"]).warn(
|
|
1846
|
+
"The object's @id ({objectId}) has a different origin than the document " +
|
|
1847
|
+
"URL ({documentUrl}); refusing to return the object. If you want to " +
|
|
1848
|
+
"bypass this check and are aware of the security implications, " +
|
|
1849
|
+
'set the crossOrigin option to "trust".',
|
|
1850
|
+
{ ...fetchResult, objectId: obj.id.href },
|
|
1851
|
+
);
|
|
1852
|
+
span.end();
|
|
1853
|
+
return null;
|
|
1854
|
+
}
|
|
1855
|
+
span.setAttribute("activitypub.object.id", (obj.id ?? url).href);
|
|
1856
|
+
span.setAttribute(
|
|
1857
|
+
"activitypub.object.type",
|
|
1858
|
+
// @ts-ignore: obj.constructor always has a typeId.
|
|
1859
|
+
obj.constructor.typeId.href
|
|
1625
1860
|
);
|
|
1861
|
+
span.end();
|
|
1862
|
+
return obj;
|
|
1626
1863
|
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
"
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1864
|
+
} catch (e) {
|
|
1865
|
+
if (options.suppressError) {
|
|
1866
|
+
getLogger(["fedify", "vocab"]).warn(
|
|
1867
|
+
"Failed to parse {url}: {error}",
|
|
1868
|
+
{ error: e, url: lookupUrl }
|
|
1869
|
+
);
|
|
1870
|
+
span.end();
|
|
1871
|
+
return null;
|
|
1872
|
+
}
|
|
1873
|
+
span.setStatus({
|
|
1874
|
+
code: SpanStatusCode.ERROR,
|
|
1875
|
+
message: String(e),
|
|
1876
|
+
});
|
|
1877
|
+
span.end();
|
|
1878
|
+
throw e;
|
|
1635
1879
|
}
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
);
|
|
1642
|
-
return obj;
|
|
1643
|
-
} catch (e) {
|
|
1644
|
-
if (options.suppressError) {
|
|
1645
|
-
getLogger(["fedify", "vocab"]).error(
|
|
1646
|
-
"Failed to parse {url}: {error}",
|
|
1647
|
-
{ error: e, url: lookupUrl }
|
|
1648
|
-
);
|
|
1649
|
-
return null;
|
|
1880
|
+
// The document stands for a portable object, so it is verified as
|
|
1881
|
+
// one instead of being trusted because of where it came from:
|
|
1882
|
+
if (claim === null) {
|
|
1883
|
+
span.end();
|
|
1884
|
+
return rejectMalformedCompatibleReference(documentUrl, options);
|
|
1650
1885
|
}
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1886
|
+
return await dereferencePortable(claim.id, {
|
|
1887
|
+
inferredGateways: claim.inferredGateways,
|
|
1888
|
+
response: fetchResult,
|
|
1889
|
+
contextLoader: snapshot?.loader,
|
|
1654
1890
|
});
|
|
1655
|
-
throw e;
|
|
1656
1891
|
} finally {
|
|
1657
|
-
|
|
1892
|
+
scopedContext.release();
|
|
1893
|
+
snapshot?.release();
|
|
1658
1894
|
}
|
|
1659
1895
|
});
|
|
1660
1896
|
}
|
|
@@ -1665,6 +1901,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1665
1901
|
documentLoader?: DocumentLoader,
|
|
1666
1902
|
contextLoader?: DocumentLoader,
|
|
1667
1903
|
tracerProvider?: TracerProvider,
|
|
1904
|
+
verifyPortableObject?: PortableObjectVerifier,
|
|
1668
1905
|
baseUrl?: URL
|
|
1669
1906
|
}
|
|
1670
1907
|
): Promise<${getTypeNames(property.range, types)}> {
|
|
@@ -1675,6 +1912,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1675
1912
|
const tracerProvider = options.tracerProvider ??
|
|
1676
1913
|
this._tracerProvider ?? trace.getTracerProvider();
|
|
1677
1914
|
const baseUrl = options.baseUrl;
|
|
1915
|
+
const verifyPortableObject = options.verifyPortableObject;
|
|
1678
1916
|
`;
|
|
1679
1917
|
if (property.preprocessors != null && property.preprocessors.length > 0) {
|
|
1680
1918
|
yield `
|
|
@@ -1694,6 +1932,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1694
1932
|
documentLoader,
|
|
1695
1933
|
contextLoader,
|
|
1696
1934
|
tracerProvider,
|
|
1935
|
+
verifyPortableObject,
|
|
1697
1936
|
baseUrl,
|
|
1698
1937
|
});
|
|
1699
1938
|
if (_result instanceof Error) throw _result;
|
|
@@ -1712,7 +1951,13 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1712
1951
|
try {
|
|
1713
1952
|
return await ${types[range].name}.fromJsonLd(
|
|
1714
1953
|
jsonLd,
|
|
1715
|
-
{
|
|
1954
|
+
{
|
|
1955
|
+
documentLoader,
|
|
1956
|
+
contextLoader,
|
|
1957
|
+
tracerProvider,
|
|
1958
|
+
verifyPortableObject,
|
|
1959
|
+
baseUrl,
|
|
1960
|
+
},
|
|
1716
1961
|
);
|
|
1717
1962
|
} catch (e) {
|
|
1718
1963
|
if (!(e instanceof TypeError)) throw e;
|
|
@@ -1754,6 +1999,21 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1754
1999
|
suppressError?: boolean,
|
|
1755
2000
|
tracerProvider?: TracerProvider,
|
|
1756
2001
|
crossOrigin?: "ignore" | "throw" | "trust";
|
|
2002
|
+
gateways?: readonly (string | URL)[];
|
|
2003
|
+
verifyPortableObject?: PortableObjectVerifier;
|
|
2004
|
+
/**
|
|
2005
|
+
* Whether objects that this call newly fetches use the
|
|
2006
|
+
* \`verifyPortableObject\` option of this call as their default
|
|
2007
|
+
* verifier. Defaults to \`true\`. If \`false\`, the option
|
|
2008
|
+
* applies to this call only, and fetched objects use this object's
|
|
2009
|
+
* own default verifier, if any, as their default instead. Pass
|
|
2010
|
+
* \`false\` for a temporary verifier, e.g., one that accepts
|
|
2011
|
+
* everything. Objects that were already cached in this object or
|
|
2012
|
+
* embedded in it keep their defaults either way, and this option
|
|
2013
|
+
* itself is not passed on.
|
|
2014
|
+
* @since 2.4.0
|
|
2015
|
+
*/
|
|
2016
|
+
inheritPortableObjectVerifier?: boolean;
|
|
1757
2017
|
} = {}
|
|
1758
2018
|
): Promise<${getTypeNames(property.range, types)} | null> {
|
|
1759
2019
|
if (this._warning != null) {
|
|
@@ -1763,25 +2023,46 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1763
2023
|
);
|
|
1764
2024
|
}
|
|
1765
2025
|
if (this.${await getFieldName(property.uri)}.length < 1) return null;
|
|
2026
|
+
if (
|
|
2027
|
+
options.verifyPortableObject == null &&
|
|
2028
|
+
this._verifyPortableObject != null
|
|
2029
|
+
) {
|
|
2030
|
+
options = {
|
|
2031
|
+
...options,
|
|
2032
|
+
verifyPortableObject: this._verifyPortableObject,
|
|
2033
|
+
};
|
|
2034
|
+
}
|
|
1766
2035
|
let v = this.${await getFieldName(property.uri)}[0];
|
|
1767
2036
|
if (!(v instanceof URL) &&
|
|
1768
|
-
v.id != null &&
|
|
1769
|
-
!isTrustedIriOrigin(options, v.id, this.id) &&
|
|
1770
2037
|
!this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
|
|
1771
|
-
|
|
2038
|
+
if (v.id == null) {
|
|
2039
|
+
if (isUnsecuredPortableObject(this)) {
|
|
2040
|
+
warnUnverifiableEmbeddedObject(this, ${JSON.stringify(property.uri)});
|
|
2041
|
+
return null;
|
|
2042
|
+
}
|
|
2043
|
+
} else if (
|
|
2044
|
+
!isTrustedIriOrigin(options, v.id, ${trustOwner}) ||
|
|
2045
|
+
isUnsecuredPortableObject(this) ||
|
|
2046
|
+
mustDereferencePortableObject(this, ${trustOwner}, v.id, options)
|
|
2047
|
+
) {
|
|
2048
|
+
v = v.id;
|
|
2049
|
+
}
|
|
1772
2050
|
}
|
|
1773
2051
|
if (v instanceof URL) {
|
|
1774
2052
|
const fetched =
|
|
1775
2053
|
await this.#fetch${(0, es_toolkit.pascalCase)(property.singularName)}(v, options);
|
|
1776
2054
|
if (fetched == null) return null;
|
|
2055
|
+
// Not cached, so that it is verified when the portable object
|
|
2056
|
+
// policy applies later:
|
|
2057
|
+
if (isUnverifiedPortableClaim(fetched)) return fetched;
|
|
1777
2058
|
this.${await getFieldName(property.uri)}[0] = fetched;
|
|
1778
2059
|
this.${await getFieldName(property.uri, "#_trust")}.add(0);
|
|
1779
2060
|
this._cachedJsonLd = undefined;
|
|
2061
|
+
recordPortableReferrer(this, fetched, ${JSON.stringify(property.uri)});
|
|
1780
2062
|
return fetched;
|
|
1781
2063
|
}
|
|
1782
2064
|
`;
|
|
1783
|
-
if (property.compactName != null)
|
|
1784
|
-
yield `
|
|
2065
|
+
if (property.compactName != null) yield `
|
|
1785
2066
|
if (
|
|
1786
2067
|
this._cachedJsonLd != null &&
|
|
1787
2068
|
typeof this._cachedJsonLd === "object" &&
|
|
@@ -1792,26 +2073,36 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1792
2073
|
${JSON.stringify(property.compactName)}];
|
|
1793
2074
|
const doc = Array.isArray(prop) ? prop[0] : prop;
|
|
1794
2075
|
if (doc != null && typeof doc === "object" && "@context" in doc) {
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
2076
|
+
try {
|
|
2077
|
+
v = await this.#${property.singularName}_fromJsonLd(doc, {
|
|
2078
|
+
...options,
|
|
2079
|
+
// Keep the parent default for embedded objects.
|
|
2080
|
+
verifyPortableObject: this._verifyPortableObject,
|
|
2081
|
+
baseUrl: ${cachedPropertyBaseUrl},
|
|
2082
|
+
});
|
|
2083
|
+
} catch (error) {
|
|
2084
|
+
if (!options.suppressError) throw error;
|
|
2085
|
+
getLogger(["fedify", "vocab"]).debug(
|
|
2086
|
+
"Failed to parse embedded value of {property}: {error}",
|
|
2087
|
+
{ property: ${JSON.stringify(property.uri)}, error },
|
|
2088
|
+
);
|
|
2089
|
+
return null;
|
|
2090
|
+
}
|
|
1803
2091
|
}
|
|
1804
2092
|
}
|
|
1805
2093
|
`;
|
|
1806
|
-
}
|
|
1807
2094
|
yield `
|
|
1808
2095
|
if (v?.id != null &&
|
|
1809
|
-
this.id != null && !isTrustedIriOrigin(options, v.id,
|
|
2096
|
+
((${type.trustEmbeddedObjects === false ? "" : "this.id != null && "}!isTrustedIriOrigin(options, v.id, ${trustOwner})) ||
|
|
2097
|
+
isUnsecuredPortableObject(this) ||
|
|
2098
|
+
mustDereferencePortableObject(
|
|
2099
|
+
this, ${trustOwner}, v.id, options,
|
|
2100
|
+
)) &&
|
|
1810
2101
|
!this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
|
|
1811
2102
|
if (options.crossOrigin === "throw") {
|
|
1812
2103
|
throw new Error(
|
|
1813
2104
|
"The property object's @id (" + v.id.href + ") has a different " +
|
|
1814
|
-
"origin than the property owner's @id (" + this.id
|
|
2105
|
+
"origin than the property owner's @id (" + ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "(this.id?.href ?? null)"} + "); " +
|
|
1815
2106
|
"refusing to return the object. If you want to bypass this " +
|
|
1816
2107
|
"check and are aware of the security implications, set the " +
|
|
1817
2108
|
'crossOrigin option to "trust".'
|
|
@@ -1823,10 +2114,11 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1823
2114
|
"return the object. If you want to bypass this check and are " +
|
|
1824
2115
|
"aware of the security implications, set the crossOrigin option " +
|
|
1825
2116
|
'to "trust".',
|
|
1826
|
-
{ objectId: v.id.href, parentObjectId: this.id
|
|
2117
|
+
{ objectId: v.id.href, parentObjectId: ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "(this.id?.href ?? null)"} },
|
|
1827
2118
|
);
|
|
1828
2119
|
return null;
|
|
1829
2120
|
}
|
|
2121
|
+
recordPortableReferrer(this, v, ${JSON.stringify(property.uri)});
|
|
1830
2122
|
return v;
|
|
1831
2123
|
}
|
|
1832
2124
|
`;
|
|
@@ -1859,6 +2151,21 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1859
2151
|
suppressError?: boolean,
|
|
1860
2152
|
tracerProvider?: TracerProvider,
|
|
1861
2153
|
crossOrigin?: "ignore" | "throw" | "trust";
|
|
2154
|
+
gateways?: readonly (string | URL)[];
|
|
2155
|
+
verifyPortableObject?: PortableObjectVerifier;
|
|
2156
|
+
/**
|
|
2157
|
+
* Whether objects that this call newly fetches use the
|
|
2158
|
+
* \`verifyPortableObject\` option of this call as their default
|
|
2159
|
+
* verifier. Defaults to \`true\`. If \`false\`, the option
|
|
2160
|
+
* applies to this call only, and fetched objects use this object's
|
|
2161
|
+
* own default verifier, if any, as their default instead. Pass
|
|
2162
|
+
* \`false\` for a temporary verifier, e.g., one that accepts
|
|
2163
|
+
* everything. Objects that were already cached in this object or
|
|
2164
|
+
* embedded in it keep their defaults either way, and this option
|
|
2165
|
+
* itself is not passed on.
|
|
2166
|
+
* @since 2.4.0
|
|
2167
|
+
*/
|
|
2168
|
+
inheritPortableObjectVerifier?: boolean;
|
|
1862
2169
|
} = {}
|
|
1863
2170
|
): AsyncIterable<${getTypeNames(property.range, types)}> {
|
|
1864
2171
|
if (this._warning != null) {
|
|
@@ -1867,28 +2174,52 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1867
2174
|
this._warning.values
|
|
1868
2175
|
);
|
|
1869
2176
|
}
|
|
2177
|
+
if (
|
|
2178
|
+
options.verifyPortableObject == null &&
|
|
2179
|
+
this._verifyPortableObject != null
|
|
2180
|
+
) {
|
|
2181
|
+
options = {
|
|
2182
|
+
...options,
|
|
2183
|
+
verifyPortableObject: this._verifyPortableObject,
|
|
2184
|
+
};
|
|
2185
|
+
}
|
|
1870
2186
|
const vs = this.${await getFieldName(property.uri)};
|
|
1871
2187
|
for (let i = 0; i < vs.length; i++) {
|
|
1872
2188
|
let v = vs[i];
|
|
1873
2189
|
if (!(v instanceof URL) &&
|
|
1874
|
-
v.id != null &&
|
|
1875
|
-
!isTrustedIriOrigin(options, v.id, this.id) &&
|
|
1876
2190
|
!this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
|
|
1877
|
-
|
|
2191
|
+
if (v.id == null) {
|
|
2192
|
+
if (isUnsecuredPortableObject(this)) {
|
|
2193
|
+
warnUnverifiableEmbeddedObject(this, ${JSON.stringify(property.uri)});
|
|
2194
|
+
continue;
|
|
2195
|
+
}
|
|
2196
|
+
} else if (
|
|
2197
|
+
!isTrustedIriOrigin(options, v.id, ${trustOwner}) ||
|
|
2198
|
+
isUnsecuredPortableObject(this) ||
|
|
2199
|
+
mustDereferencePortableObject(this, ${trustOwner}, v.id, options)
|
|
2200
|
+
) {
|
|
2201
|
+
v = v.id;
|
|
2202
|
+
}
|
|
1878
2203
|
}
|
|
1879
2204
|
if (v instanceof URL) {
|
|
1880
2205
|
const fetched =
|
|
1881
2206
|
await this.#fetch${(0, es_toolkit.pascalCase)(property.singularName)}(v, options);
|
|
1882
2207
|
if (fetched == null) continue;
|
|
2208
|
+
// Not cached, so that it is verified when the portable object
|
|
2209
|
+
// policy applies later:
|
|
2210
|
+
if (isUnverifiedPortableClaim(fetched)) {
|
|
2211
|
+
yield fetched;
|
|
2212
|
+
continue;
|
|
2213
|
+
}
|
|
1883
2214
|
vs[i] = fetched;
|
|
1884
2215
|
this.${await getFieldName(property.uri, "#_trust")}.add(i);
|
|
1885
2216
|
this._cachedJsonLd = undefined;
|
|
2217
|
+
recordPortableReferrer(this, fetched, ${JSON.stringify(property.uri)});
|
|
1886
2218
|
yield fetched;
|
|
1887
2219
|
continue;
|
|
1888
2220
|
}
|
|
1889
2221
|
`;
|
|
1890
|
-
if (property.compactName != null)
|
|
1891
|
-
yield `
|
|
2222
|
+
if (property.compactName != null) yield `
|
|
1892
2223
|
if (
|
|
1893
2224
|
this._cachedJsonLd != null &&
|
|
1894
2225
|
typeof this._cachedJsonLd === "object" &&
|
|
@@ -1899,26 +2230,36 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1899
2230
|
${JSON.stringify(property.compactName)}];
|
|
1900
2231
|
const obj = Array.isArray(prop) ? prop[i] : prop;
|
|
1901
2232
|
if (obj != null && typeof obj === "object" && "@context" in obj) {
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
2233
|
+
try {
|
|
2234
|
+
v = await this.#${property.singularName}_fromJsonLd(obj, {
|
|
2235
|
+
...options,
|
|
2236
|
+
// Keep the parent default for embedded objects.
|
|
2237
|
+
verifyPortableObject: this._verifyPortableObject,
|
|
2238
|
+
baseUrl: ${cachedPropertyBaseUrl},
|
|
2239
|
+
});
|
|
2240
|
+
} catch (error) {
|
|
2241
|
+
if (!options.suppressError) throw error;
|
|
2242
|
+
getLogger(["fedify", "vocab"]).debug(
|
|
2243
|
+
"Failed to parse embedded value of {property} at index {index}: {error}",
|
|
2244
|
+
{ property: ${JSON.stringify(property.uri)}, index: i, error },
|
|
2245
|
+
);
|
|
2246
|
+
continue;
|
|
2247
|
+
}
|
|
1910
2248
|
}
|
|
1911
2249
|
}
|
|
1912
2250
|
`;
|
|
1913
|
-
}
|
|
1914
2251
|
yield `
|
|
1915
2252
|
if (v?.id != null &&
|
|
1916
|
-
this.id != null && !isTrustedIriOrigin(options, v.id,
|
|
2253
|
+
((${type.trustEmbeddedObjects === false ? "" : "this.id != null && "}!isTrustedIriOrigin(options, v.id, ${trustOwner})) ||
|
|
2254
|
+
isUnsecuredPortableObject(this) ||
|
|
2255
|
+
mustDereferencePortableObject(
|
|
2256
|
+
this, ${trustOwner}, v.id, options,
|
|
2257
|
+
)) &&
|
|
1917
2258
|
!this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
|
|
1918
2259
|
if (options.crossOrigin === "throw") {
|
|
1919
2260
|
throw new Error(
|
|
1920
2261
|
"The property object's @id (" + v.id.href + ") has a different " +
|
|
1921
|
-
"origin than the property owner's @id (" + this.id
|
|
2262
|
+
"origin than the property owner's @id (" + ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "(this.id?.href ?? null)"} + "); " +
|
|
1922
2263
|
"refusing to return the object. If you want to bypass this " +
|
|
1923
2264
|
"check and are aware of the security implications, set the " +
|
|
1924
2265
|
'crossOrigin option to "trust".'
|
|
@@ -1930,10 +2271,11 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1930
2271
|
"return the object. If you want to bypass this check and are " +
|
|
1931
2272
|
"aware of the security implications, set the crossOrigin " +
|
|
1932
2273
|
'option to "trust".',
|
|
1933
|
-
{ objectId: v.id.href, parentObjectId: this.id
|
|
2274
|
+
{ objectId: v.id.href, parentObjectId: ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "(this.id?.href ?? null)"} },
|
|
1934
2275
|
);
|
|
1935
2276
|
continue;
|
|
1936
2277
|
}
|
|
2278
|
+
recordPortableReferrer(this, v, ${JSON.stringify(property.uri)});
|
|
1937
2279
|
yield v;
|
|
1938
2280
|
}
|
|
1939
2281
|
}
|
|
@@ -1951,10 +2293,35 @@ const XSD_ANY_URI = "http://www.w3.org/2001/XMLSchema#anyURI";
|
|
|
1951
2293
|
const FEDIFY_URL = "fedify:url";
|
|
1952
2294
|
const INTERNAL_RUNTIME_IMPORTS = [
|
|
1953
2295
|
"compactJsonLdCache",
|
|
2296
|
+
"createScopedContextLoader",
|
|
1954
2297
|
"getJsonLdContext",
|
|
2298
|
+
"getPortableActorGateways",
|
|
1955
2299
|
"isTrustedIriOrigin",
|
|
1956
2300
|
"normalizeJsonLdIris"
|
|
1957
2301
|
].join(",\n ");
|
|
2302
|
+
const PORTABLE_DEREFERENCE_IMPORTS = [
|
|
2303
|
+
"copyPortableProvenance",
|
|
2304
|
+
"createSnapshotContextLoader",
|
|
2305
|
+
"dereferencePortableIri",
|
|
2306
|
+
"getPortableResponseClaim",
|
|
2307
|
+
"getReferrerGateways",
|
|
2308
|
+
"isPortableIri",
|
|
2309
|
+
"isPortableMode",
|
|
2310
|
+
"isUnsecuredPortableObject",
|
|
2311
|
+
"isUnverifiedPortableClaim",
|
|
2312
|
+
"markUnverifiedPortableClaim",
|
|
2313
|
+
"mustDereferencePortableObject",
|
|
2314
|
+
"parseCompatibleEf61Reference",
|
|
2315
|
+
"type PortableResponseClaim",
|
|
2316
|
+
"recordPortableReferrer",
|
|
2317
|
+
"rejectMalformedCompatibleReference",
|
|
2318
|
+
"warnUnverifiableEmbeddedObject"
|
|
2319
|
+
].join(",\n ");
|
|
2320
|
+
const SIGNED_REPRESENTATION_IMPORTS = [
|
|
2321
|
+
"enterSignedValueScope",
|
|
2322
|
+
"resolveSignedValues",
|
|
2323
|
+
"retainedSignedValueRef"
|
|
2324
|
+
].join(",\n ");
|
|
1958
2325
|
const RUNTIME_IMPORTS = [
|
|
1959
2326
|
"canParseDecimal",
|
|
1960
2327
|
"decodeMultibase",
|
|
@@ -1970,10 +2337,12 @@ const RUNTIME_IMPORTS = [
|
|
|
1970
2337
|
"isDecimal",
|
|
1971
2338
|
"isGatewayUrl",
|
|
1972
2339
|
"LanguageString",
|
|
2340
|
+
"normalizeLanguageTag",
|
|
1973
2341
|
"parseDecimal",
|
|
1974
2342
|
"parseGatewayUrl",
|
|
1975
2343
|
"parseIri",
|
|
1976
2344
|
"parseJsonLdId",
|
|
2345
|
+
"type PortableObjectVerifier",
|
|
1977
2346
|
"type RemoteDocument"
|
|
1978
2347
|
].join(",\n ");
|
|
1979
2348
|
/**
|
|
@@ -2009,6 +2378,7 @@ async function* generateClass(typeUri, types, moduleVarNames) {
|
|
|
2009
2378
|
readonly #documentLoader?: DocumentLoader;
|
|
2010
2379
|
readonly #contextLoader?: DocumentLoader;
|
|
2011
2380
|
readonly #tracerProvider?: TracerProvider;
|
|
2381
|
+
readonly #verifyPortableObject?: PortableObjectVerifier;
|
|
2012
2382
|
readonly #warning?: {
|
|
2013
2383
|
category: string[];
|
|
2014
2384
|
message: string;
|
|
@@ -2016,6 +2386,7 @@ async function* generateClass(typeUri, types, moduleVarNames) {
|
|
|
2016
2386
|
};
|
|
2017
2387
|
#cachedJsonLd?: unknown;
|
|
2018
2388
|
readonly #_baseUrl?: URL;
|
|
2389
|
+
#shouldCacheJsonLd = true;
|
|
2019
2390
|
readonly id: URL | null;
|
|
2020
2391
|
|
|
2021
2392
|
protected get _documentLoader(): DocumentLoader | undefined {
|
|
@@ -2030,6 +2401,10 @@ async function* generateClass(typeUri, types, moduleVarNames) {
|
|
|
2030
2401
|
return this.#tracerProvider;
|
|
2031
2402
|
}
|
|
2032
2403
|
|
|
2404
|
+
protected get _verifyPortableObject(): PortableObjectVerifier | undefined {
|
|
2405
|
+
return this.#verifyPortableObject;
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2033
2408
|
protected get _warning(): {
|
|
2034
2409
|
category: string[];
|
|
2035
2410
|
message: string;
|
|
@@ -2049,6 +2424,20 @@ async function* generateClass(typeUri, types, moduleVarNames) {
|
|
|
2049
2424
|
protected get _baseUrl(): URL | undefined {
|
|
2050
2425
|
return this.#_baseUrl;
|
|
2051
2426
|
}
|
|
2427
|
+
|
|
2428
|
+
protected get _shouldCacheJsonLd(): boolean {
|
|
2429
|
+
return this.#shouldCacheJsonLd;
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
protected set _shouldCacheJsonLd(value: boolean) {
|
|
2433
|
+
this.#shouldCacheJsonLd = value;
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
protected static _shouldCacheDecodedJsonLd(value: unknown): boolean {
|
|
2437
|
+
if (value == null || typeof value !== "object") return true;
|
|
2438
|
+
if (!("_shouldCacheJsonLd" in value)) return true;
|
|
2439
|
+
return (value as { _shouldCacheJsonLd: boolean })._shouldCacheJsonLd;
|
|
2440
|
+
}
|
|
2052
2441
|
`;
|
|
2053
2442
|
yield `
|
|
2054
2443
|
/**
|
|
@@ -2135,11 +2524,69 @@ async function* generateClasses(types) {
|
|
|
2135
2524
|
from "@opentelemetry/api";\n`;
|
|
2136
2525
|
yield `import {\n ${RUNTIME_IMPORTS}\n} from "@fedify/vocab-runtime";\n`;
|
|
2137
2526
|
yield `import {\n ${INTERNAL_RUNTIME_IMPORTS}\n} from "@fedify/vocab-runtime/internal/jsonld-cache";\n`;
|
|
2527
|
+
yield `import {\n ${PORTABLE_DEREFERENCE_IMPORTS}\n} from "@fedify/vocab-runtime/internal/portable-dereference";\n`;
|
|
2528
|
+
yield `import {\n ${SIGNED_REPRESENTATION_IMPORTS}\n} from "@fedify/vocab-runtime/internal/signed-representation";\n`;
|
|
2138
2529
|
yield `import {
|
|
2139
2530
|
isTemporalDuration,
|
|
2140
2531
|
isTemporalInstant,
|
|
2141
2532
|
} from "@fedify/vocab-runtime/temporal";\n`;
|
|
2142
|
-
|
|
2533
|
+
yield `
|
|
2534
|
+
function canDecodeIri(iri: string): boolean {
|
|
2535
|
+
try { parseIri(iri); return true; } catch { return iri.startsWith("at://"); }
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
function decodeIri(iri: string): URL {
|
|
2539
|
+
return !URL.canParse(iri) && iri.startsWith("at://")
|
|
2540
|
+
? new URL("at://" + encodeURIComponent(iri.substring(5)))
|
|
2541
|
+
: parseIri(iri);
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
function inspectIri(iri: URL): string {
|
|
2545
|
+
// Inspecting an object must not throw, even for a URL that cannot be
|
|
2546
|
+
// formatted as an IRI, e.g., a malformed portable ID:
|
|
2547
|
+
try { return formatIri(iri); } catch { return iri.href; }
|
|
2548
|
+
}
|
|
2549
|
+
`;
|
|
2550
|
+
yield `
|
|
2551
|
+
function isValidLanguageTag(language: string): boolean {
|
|
2552
|
+
try {
|
|
2553
|
+
new Intl.Locale(normalizeLanguageTag(language));
|
|
2554
|
+
return true;
|
|
2555
|
+
} catch (error) {
|
|
2556
|
+
if (error instanceof RangeError) return false;
|
|
2557
|
+
throw error;
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
`;
|
|
2561
|
+
const extraContexts = Object.fromEntries(Object.values(types).flatMap((type) => type.properties.flatMap((property) => property.extraContext == null ? [] : [[property.uri, property.extraContext]])));
|
|
2562
|
+
if (Object.keys(extraContexts).length > 0) yield `
|
|
2563
|
+
const extraPropertyContexts: Readonly<Record<string, string>> =
|
|
2564
|
+
${JSON.stringify(extraContexts)};
|
|
2565
|
+
|
|
2566
|
+
function getExtraContexts(document: unknown): string[] {
|
|
2567
|
+
const contexts = new Set<string>();
|
|
2568
|
+
const pending: unknown[] = [document];
|
|
2569
|
+
while (pending.length > 0) {
|
|
2570
|
+
const value = pending.pop();
|
|
2571
|
+
if (value == null || typeof value !== "object") continue;
|
|
2572
|
+
if (Array.isArray(value)) {
|
|
2573
|
+
for (const item of value) pending.push(item);
|
|
2574
|
+
continue;
|
|
2575
|
+
}
|
|
2576
|
+
if ("@value" in value) continue;
|
|
2577
|
+
for (const [key, child] of globalThis.Object.entries(value)) {
|
|
2578
|
+
if (key === "@context") continue;
|
|
2579
|
+
if (globalThis.Object.hasOwn(extraPropertyContexts, key)) {
|
|
2580
|
+
contexts.add(extraPropertyContexts[key]);
|
|
2581
|
+
}
|
|
2582
|
+
pending.push(child);
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
return [...contexts];
|
|
2586
|
+
}
|
|
2587
|
+
`;
|
|
2588
|
+
yield "\n\n";
|
|
2589
|
+
const portableIriKeys = /* @__PURE__ */ new Set(["@id", "id"]);
|
|
2143
2590
|
for (const type of Object.values(types)) for (const property of type.properties) addPortableIriKeys(portableIriKeys, property, types);
|
|
2144
2591
|
yield "const PORTABLE_IRI_PATTERN = /^ap(?:\\+ef61)?:\\/\\//i;\n";
|
|
2145
2592
|
yield `const PORTABLE_IRI_KEYS: ReadonlySet<string> = new Set(${JSON.stringify([...portableIriKeys].sort())});\n\n`;
|