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