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

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/deno.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab",
3
- "version": "2.4.0-dev.1599+7b19967f",
3
+ "version": "2.4.0-dev.1618+58858243",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./src/mod.ts"
package/dist/mod.cjs CHANGED
@@ -34,7 +34,7 @@ let _fedify_vocab_runtime_temporal = require("@fedify/vocab-runtime/temporal");
34
34
  let es_toolkit = require("es-toolkit");
35
35
  //#region deno.json
36
36
  var name = "@fedify/vocab";
37
- var version = "2.4.0-dev.1599+7b19967f";
37
+ var version = "2.4.0-dev.1618+58858243";
38
38
  //#endregion
39
39
  //#region src/type.ts
40
40
  function getTypeId(object) {
@@ -3755,6 +3755,7 @@ const PORTABLE_IRI_KEYS = new Set([
3755
3755
  "https://www.w3.org/ns/activitystreams#target",
3756
3756
  "https://www.w3.org/ns/activitystreams#to",
3757
3757
  "https://www.w3.org/ns/activitystreams#units",
3758
+ "https://www.w3.org/ns/activitystreams#uploadMedia",
3758
3759
  "https://www.w3.org/ns/activitystreams#url",
3759
3760
  "https://www.w3.org/ns/did#service",
3760
3761
  "https://www.w3.org/ns/did#serviceEndpoint",
@@ -3816,6 +3817,7 @@ const PORTABLE_IRI_KEYS = new Set([
3816
3817
  "target",
3817
3818
  "to",
3818
3819
  "units",
3820
+ "uploadMedia",
3819
3821
  "url"
3820
3822
  ]);
3821
3823
  /** Describes an object of any kind. The Object type serves as the base type for
@@ -32208,6 +32210,7 @@ var Endpoints = class {
32208
32210
  #_8Bx9qN8oU7Bpt2xi6khaxWp1gMr_provideClientKey = [];
32209
32211
  #_3dU7PMVQZJpsCpo2F4RQXxBXdPmS_signClientKey = [];
32210
32212
  #_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox = [];
32213
+ #_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia = [];
32211
32214
  /**
32212
32215
  * Constructs a new instance of Endpoints with the given values.
32213
32216
  * @param values The values to initialize the instance with.
@@ -32234,6 +32237,8 @@ var Endpoints = class {
32234
32237
  else throw new TypeError("The signClientKey must be of type URL.");
32235
32238
  if ("sharedInbox" in values && values.sharedInbox != null) if (values.sharedInbox instanceof URL) this.#_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox = [values.sharedInbox];
32236
32239
  else throw new TypeError("The sharedInbox must be of type URL.");
32240
+ if ("uploadMedia" in values && values.uploadMedia != null) if (values.uploadMedia instanceof URL) this.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia = [values.uploadMedia];
32241
+ else throw new TypeError("The uploadMedia must be of type URL.");
32237
32242
  }
32238
32243
  /**
32239
32244
  * Clones this instance, optionally updating it with the given values.
@@ -32268,6 +32273,9 @@ var Endpoints = class {
32268
32273
  clone.#_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox = this.#_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox;
32269
32274
  if ("sharedInbox" in values && values.sharedInbox != null) if (values.sharedInbox instanceof URL) clone.#_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox = [values.sharedInbox];
32270
32275
  else throw new TypeError("The sharedInbox must be of type URL.");
32276
+ clone.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia = this.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia;
32277
+ if ("uploadMedia" in values && values.uploadMedia != null) if (values.uploadMedia instanceof URL) clone.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia = [values.uploadMedia];
32278
+ else throw new TypeError("The uploadMedia must be of type URL.");
32271
32279
  return clone;
32272
32280
  }
32273
32281
  /** Endpoint URI so this actor's clients may access remote ActivityStreams
@@ -32335,6 +32343,18 @@ var Endpoints = class {
32335
32343
  if (this.#_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox.length < 1) return null;
32336
32344
  return this.#_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox[0];
32337
32345
  }
32346
+ /** Upload endpoint URI for this user for binary data, such as an image or
32347
+ * video, as described in the [ActivityPub Media
32348
+ * Upload](https://www.w3.org/wiki/SocialCG/ActivityPub/MediaUpload) extension.
32349
+ * Clients `POST` a `multipart/form-data` request containing a `file` part
32350
+ * (the binary payload) and an `object` part (an ActivityStreams object shell)
32351
+ * to this endpoint.
32352
+ */
32353
+ get uploadMedia() {
32354
+ if (this._warning != null) (0, _logtape_logtape.getLogger)(this._warning.category).warn(this._warning.message, this._warning.values);
32355
+ if (this.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia.length < 1) return null;
32356
+ return this.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia[0];
32357
+ }
32338
32358
  /**
32339
32359
  * Converts this object to a JSON-LD structure.
32340
32360
  * @param options The options to use.
@@ -32390,6 +32410,12 @@ var Endpoints = class {
32390
32410
  compactItems.push(item);
32391
32411
  }
32392
32412
  if (compactItems.length > 0) result["sharedInbox"] = compactItems.length > 1 ? compactItems : compactItems[0];
32413
+ compactItems = [];
32414
+ for (const v of this.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia) {
32415
+ const item = (0, _fedify_vocab_runtime.formatIri)(v);
32416
+ compactItems.push(item);
32417
+ }
32418
+ if (compactItems.length > 0) result["uploadMedia"] = compactItems.length > 1 ? compactItems : compactItems[0];
32393
32419
  if (this.id != null) result["id"] = (0, _fedify_vocab_runtime.formatIri)(this.id);
32394
32420
  result["@context"] = "https://www.w3.org/ns/activitystreams";
32395
32421
  return result;
@@ -32432,6 +32458,12 @@ var Endpoints = class {
32432
32458
  array.push(element);
32433
32459
  }
32434
32460
  if (array.length > 0) values["https://www.w3.org/ns/activitystreams#sharedInbox"] = array;
32461
+ array = [];
32462
+ for (const v of this.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia) {
32463
+ const element = { "@id": (0, _fedify_vocab_runtime.formatIri)(v) };
32464
+ array.push(element);
32465
+ }
32466
+ if (array.length > 0) values["https://www.w3.org/ns/activitystreams#uploadMedia"] = array;
32435
32467
  if (this.id != null) values["@id"] = (0, _fedify_vocab_runtime.formatIri)(this.id);
32436
32468
  if (options.format === "expand") return await _fedify_vocab_runtime_jsonld.default.expand(values, { documentLoader: options.contextLoader });
32437
32469
  const docContext = options.context ?? "https://www.w3.org/ns/activitystreams";
@@ -32557,6 +32589,15 @@ var Endpoints = class {
32557
32589
  _3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox.push(decoded);
32558
32590
  }
32559
32591
  instance.#_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox = _3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox;
32592
+ const _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia = [];
32593
+ let _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia__array = values["https://www.w3.org/ns/activitystreams#uploadMedia"];
32594
+ for (const v of _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia__array == null ? [] : _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia__array.length === 1 && "@list" in _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia__array[0] ? _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia__array[0]["@list"] : _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia__array) {
32595
+ if (v == null) continue;
32596
+ const decoded = (0, _fedify_vocab_runtime.parseIri)(v["@id"], options.baseUrl);
32597
+ if (typeof decoded === "undefined") continue;
32598
+ _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia.push(decoded);
32599
+ }
32600
+ instance.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia = _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia;
32560
32601
  if (!("_fromSubclass" in options) || !options._fromSubclass) try {
32561
32602
  if (cacheJsonLd != null && cacheJsonLd !== expanded) {
32562
32603
  const compactArray = Array.isArray(json) && json.length === 1;
@@ -32605,6 +32646,11 @@ var Endpoints = class {
32605
32646
  [Symbol.for("nodejs.util.inspect.custom")]: (_depth, options, inspect) => "URL " + inspect(v.href, options)
32606
32647
  } : v);
32607
32648
  if (_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox.length == 1) proxy.sharedInbox = _3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox[0];
32649
+ const _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia = this.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia.map((v) => v instanceof URL ? {
32650
+ [Symbol.for("Deno.customInspect")]: (inspect, options) => "URL " + inspect(v.href, options),
32651
+ [Symbol.for("nodejs.util.inspect.custom")]: (_depth, options, inspect) => "URL " + inspect(v.href, options)
32652
+ } : v);
32653
+ if (_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia.length == 1) proxy.uploadMedia = _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia[0];
32608
32654
  return proxy;
32609
32655
  }
32610
32656
  };
@@ -56787,7 +56833,7 @@ async function lookupObjectInternal(identifier, options = {}) {
56787
56833
  }
56788
56834
  throw error;
56789
56835
  }
56790
- if (options.crossOrigin !== "trust" && object.id != null && !(0, _fedify_vocab_runtime.haveSameIriOrigin)(object.id, documentUrl)) {
56836
+ if (options.crossOrigin !== "trust" && object.id != null && !(0, _fedify_vocab_runtime.haveSameIriOrigin)(object.id, documentUrl) && !(0, _fedify_vocab_runtime.haveSameFe34Origin)(object.id, documentUrl)) {
56791
56837
  if (options.crossOrigin === "throw") throw new Error(`The object's @id (${object.id.href}) has a different origin than the document URL (${remoteDoc.documentUrl}); refusing to return the object. If you want to bypass this check and are aware of the security implications, set the crossOrigin option to "trust".`);
56792
56838
  logger.warn("The object's @id ({objectId}) has a different origin than the document URL ({documentUrl}); refusing to return the object. If you want to bypass this check and are aware of the security implications, set the crossOrigin option to \"trust\".", {
56793
56839
  ...remoteDoc,
package/dist/mod.d.cts CHANGED
@@ -10269,6 +10269,7 @@ declare class Endpoints {
10269
10269
  provideClientKey?: URL | null;
10270
10270
  signClientKey?: URL | null;
10271
10271
  sharedInbox?: URL | null;
10272
+ uploadMedia?: URL | null;
10272
10273
  }, options?: {
10273
10274
  documentLoader?: DocumentLoader$1;
10274
10275
  contextLoader?: DocumentLoader$1;
@@ -10288,6 +10289,7 @@ declare class Endpoints {
10288
10289
  provideClientKey?: URL | null;
10289
10290
  signClientKey?: URL | null;
10290
10291
  sharedInbox?: URL | null;
10292
+ uploadMedia?: URL | null;
10291
10293
  }, options?: {
10292
10294
  documentLoader?: DocumentLoader$1;
10293
10295
  contextLoader?: DocumentLoader$1;
@@ -10333,6 +10335,14 @@ declare class Endpoints {
10333
10335
  * objects which are not addressed to the `Public`` endpoint.
10334
10336
  */
10335
10337
  get sharedInbox(): URL | null;
10338
+ /** Upload endpoint URI for this user for binary data, such as an image or
10339
+ * video, as described in the [ActivityPub Media
10340
+ * Upload](https://www.w3.org/wiki/SocialCG/ActivityPub/MediaUpload) extension.
10341
+ * Clients `POST` a `multipart/form-data` request containing a `file` part
10342
+ * (the binary payload) and an `object` part (an ActivityStreams object shell)
10343
+ * to this endpoint.
10344
+ */
10345
+ get uploadMedia(): URL | null;
10336
10346
  /**
10337
10347
  * Converts this object to a JSON-LD structure.
10338
10348
  * @param options The options to use.
package/dist/mod.d.ts CHANGED
@@ -10269,6 +10269,7 @@ declare class Endpoints {
10269
10269
  provideClientKey?: URL | null;
10270
10270
  signClientKey?: URL | null;
10271
10271
  sharedInbox?: URL | null;
10272
+ uploadMedia?: URL | null;
10272
10273
  }, options?: {
10273
10274
  documentLoader?: DocumentLoader$1;
10274
10275
  contextLoader?: DocumentLoader$1;
@@ -10288,6 +10289,7 @@ declare class Endpoints {
10288
10289
  provideClientKey?: URL | null;
10289
10290
  signClientKey?: URL | null;
10290
10291
  sharedInbox?: URL | null;
10292
+ uploadMedia?: URL | null;
10291
10293
  }, options?: {
10292
10294
  documentLoader?: DocumentLoader$1;
10293
10295
  contextLoader?: DocumentLoader$1;
@@ -10333,6 +10335,14 @@ declare class Endpoints {
10333
10335
  * objects which are not addressed to the `Public`` endpoint.
10334
10336
  */
10335
10337
  get sharedInbox(): URL | null;
10338
+ /** Upload endpoint URI for this user for binary data, such as an image or
10339
+ * video, as described in the [ActivityPub Media
10340
+ * Upload](https://www.w3.org/wiki/SocialCG/ActivityPub/MediaUpload) extension.
10341
+ * Clients `POST` a `multipart/form-data` request containing a `file` part
10342
+ * (the binary payload) and an `object` part (an ActivityStreams object shell)
10343
+ * to this endpoint.
10344
+ */
10345
+ get uploadMedia(): URL | null;
10336
10346
  /**
10337
10347
  * Converts this object to a JSON-LD structure.
10338
10348
  * @param options The options to use.
package/dist/mod.js CHANGED
@@ -4,13 +4,13 @@ import { SpanStatusCode, trace } from "@opentelemetry/api";
4
4
  import { domainToASCII, domainToUnicode } from "node:url";
5
5
  import jsonld from "@fedify/vocab-runtime/jsonld";
6
6
  import { getLogger } from "@logtape/logtape";
7
- import { LanguageString, LanguageString as LanguageString$1, decodeMultibase, encodeMultibase, exportMultibaseKey, exportSpki, formatIri, getDocumentLoader, haveSameIriOrigin, importMultibaseKey, importPem, isDecimal, parseDecimal, parseIri, parseJsonLdId } from "@fedify/vocab-runtime";
7
+ import { LanguageString, LanguageString as LanguageString$1, decodeMultibase, encodeMultibase, exportMultibaseKey, exportSpki, formatIri, getDocumentLoader, haveSameFe34Origin, haveSameIriOrigin, importMultibaseKey, importPem, isDecimal, parseDecimal, parseIri, parseJsonLdId } from "@fedify/vocab-runtime";
8
8
  import { compactJsonLdCache, isTrustedIriOrigin, normalizeJsonLdIris } from "@fedify/vocab-runtime/internal/jsonld-cache";
9
9
  import { isTemporalDuration, isTemporalInstant } from "@fedify/vocab-runtime/temporal";
10
10
  import { delay } from "es-toolkit";
11
11
  //#region deno.json
12
12
  var name = "@fedify/vocab";
13
- var version = "2.4.0-dev.1599+7b19967f";
13
+ var version = "2.4.0-dev.1618+58858243";
14
14
  //#endregion
15
15
  //#region src/type.ts
16
16
  function getTypeId(object) {
@@ -172,6 +172,7 @@ const PORTABLE_IRI_KEYS = new Set([
172
172
  "https://www.w3.org/ns/activitystreams#target",
173
173
  "https://www.w3.org/ns/activitystreams#to",
174
174
  "https://www.w3.org/ns/activitystreams#units",
175
+ "https://www.w3.org/ns/activitystreams#uploadMedia",
175
176
  "https://www.w3.org/ns/activitystreams#url",
176
177
  "https://www.w3.org/ns/did#service",
177
178
  "https://www.w3.org/ns/did#serviceEndpoint",
@@ -233,6 +234,7 @@ const PORTABLE_IRI_KEYS = new Set([
233
234
  "target",
234
235
  "to",
235
236
  "units",
237
+ "uploadMedia",
236
238
  "url"
237
239
  ]);
238
240
  /** Describes an object of any kind. The Object type serves as the base type for
@@ -28625,6 +28627,7 @@ var Endpoints = class {
28625
28627
  #_8Bx9qN8oU7Bpt2xi6khaxWp1gMr_provideClientKey = [];
28626
28628
  #_3dU7PMVQZJpsCpo2F4RQXxBXdPmS_signClientKey = [];
28627
28629
  #_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox = [];
28630
+ #_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia = [];
28628
28631
  /**
28629
28632
  * Constructs a new instance of Endpoints with the given values.
28630
28633
  * @param values The values to initialize the instance with.
@@ -28651,6 +28654,8 @@ var Endpoints = class {
28651
28654
  else throw new TypeError("The signClientKey must be of type URL.");
28652
28655
  if ("sharedInbox" in values && values.sharedInbox != null) if (values.sharedInbox instanceof URL) this.#_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox = [values.sharedInbox];
28653
28656
  else throw new TypeError("The sharedInbox must be of type URL.");
28657
+ if ("uploadMedia" in values && values.uploadMedia != null) if (values.uploadMedia instanceof URL) this.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia = [values.uploadMedia];
28658
+ else throw new TypeError("The uploadMedia must be of type URL.");
28654
28659
  }
28655
28660
  /**
28656
28661
  * Clones this instance, optionally updating it with the given values.
@@ -28685,6 +28690,9 @@ var Endpoints = class {
28685
28690
  clone.#_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox = this.#_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox;
28686
28691
  if ("sharedInbox" in values && values.sharedInbox != null) if (values.sharedInbox instanceof URL) clone.#_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox = [values.sharedInbox];
28687
28692
  else throw new TypeError("The sharedInbox must be of type URL.");
28693
+ clone.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia = this.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia;
28694
+ if ("uploadMedia" in values && values.uploadMedia != null) if (values.uploadMedia instanceof URL) clone.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia = [values.uploadMedia];
28695
+ else throw new TypeError("The uploadMedia must be of type URL.");
28688
28696
  return clone;
28689
28697
  }
28690
28698
  /** Endpoint URI so this actor's clients may access remote ActivityStreams
@@ -28752,6 +28760,18 @@ var Endpoints = class {
28752
28760
  if (this.#_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox.length < 1) return null;
28753
28761
  return this.#_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox[0];
28754
28762
  }
28763
+ /** Upload endpoint URI for this user for binary data, such as an image or
28764
+ * video, as described in the [ActivityPub Media
28765
+ * Upload](https://www.w3.org/wiki/SocialCG/ActivityPub/MediaUpload) extension.
28766
+ * Clients `POST` a `multipart/form-data` request containing a `file` part
28767
+ * (the binary payload) and an `object` part (an ActivityStreams object shell)
28768
+ * to this endpoint.
28769
+ */
28770
+ get uploadMedia() {
28771
+ if (this._warning != null) getLogger(this._warning.category).warn(this._warning.message, this._warning.values);
28772
+ if (this.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia.length < 1) return null;
28773
+ return this.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia[0];
28774
+ }
28755
28775
  /**
28756
28776
  * Converts this object to a JSON-LD structure.
28757
28777
  * @param options The options to use.
@@ -28807,6 +28827,12 @@ var Endpoints = class {
28807
28827
  compactItems.push(item);
28808
28828
  }
28809
28829
  if (compactItems.length > 0) result["sharedInbox"] = compactItems.length > 1 ? compactItems : compactItems[0];
28830
+ compactItems = [];
28831
+ for (const v of this.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia) {
28832
+ const item = formatIri(v);
28833
+ compactItems.push(item);
28834
+ }
28835
+ if (compactItems.length > 0) result["uploadMedia"] = compactItems.length > 1 ? compactItems : compactItems[0];
28810
28836
  if (this.id != null) result["id"] = formatIri(this.id);
28811
28837
  result["@context"] = "https://www.w3.org/ns/activitystreams";
28812
28838
  return result;
@@ -28849,6 +28875,12 @@ var Endpoints = class {
28849
28875
  array.push(element);
28850
28876
  }
28851
28877
  if (array.length > 0) values["https://www.w3.org/ns/activitystreams#sharedInbox"] = array;
28878
+ array = [];
28879
+ for (const v of this.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia) {
28880
+ const element = { "@id": formatIri(v) };
28881
+ array.push(element);
28882
+ }
28883
+ if (array.length > 0) values["https://www.w3.org/ns/activitystreams#uploadMedia"] = array;
28852
28884
  if (this.id != null) values["@id"] = formatIri(this.id);
28853
28885
  if (options.format === "expand") return await jsonld.expand(values, { documentLoader: options.contextLoader });
28854
28886
  const docContext = options.context ?? "https://www.w3.org/ns/activitystreams";
@@ -28974,6 +29006,15 @@ var Endpoints = class {
28974
29006
  _3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox.push(decoded);
28975
29007
  }
28976
29008
  instance.#_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox = _3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox;
29009
+ const _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia = [];
29010
+ let _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia__array = values["https://www.w3.org/ns/activitystreams#uploadMedia"];
29011
+ for (const v of _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia__array == null ? [] : _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia__array.length === 1 && "@list" in _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia__array[0] ? _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia__array[0]["@list"] : _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia__array) {
29012
+ if (v == null) continue;
29013
+ const decoded = parseIri(v["@id"], options.baseUrl);
29014
+ if (typeof decoded === "undefined") continue;
29015
+ _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia.push(decoded);
29016
+ }
29017
+ instance.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia = _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia;
28977
29018
  if (!("_fromSubclass" in options) || !options._fromSubclass) try {
28978
29019
  if (cacheJsonLd != null && cacheJsonLd !== expanded) {
28979
29020
  const compactArray = Array.isArray(json) && json.length === 1;
@@ -29022,6 +29063,11 @@ var Endpoints = class {
29022
29063
  [Symbol.for("nodejs.util.inspect.custom")]: (_depth, options, inspect) => "URL " + inspect(v.href, options)
29023
29064
  } : v);
29024
29065
  if (_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox.length == 1) proxy.sharedInbox = _3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox[0];
29066
+ const _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia = this.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia.map((v) => v instanceof URL ? {
29067
+ [Symbol.for("Deno.customInspect")]: (inspect, options) => "URL " + inspect(v.href, options),
29068
+ [Symbol.for("nodejs.util.inspect.custom")]: (_depth, options, inspect) => "URL " + inspect(v.href, options)
29069
+ } : v);
29070
+ if (_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia.length == 1) proxy.uploadMedia = _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia[0];
29025
29071
  return proxy;
29026
29072
  }
29027
29073
  };
@@ -53203,7 +53249,7 @@ async function lookupObjectInternal(identifier, options = {}) {
53203
53249
  }
53204
53250
  throw error;
53205
53251
  }
53206
- if (options.crossOrigin !== "trust" && object.id != null && !haveSameIriOrigin(object.id, documentUrl)) {
53252
+ if (options.crossOrigin !== "trust" && object.id != null && !haveSameIriOrigin(object.id, documentUrl) && !haveSameFe34Origin(object.id, documentUrl)) {
53207
53253
  if (options.crossOrigin === "throw") throw new Error(`The object's @id (${object.id.href}) has a different origin than the document URL (${remoteDoc.documentUrl}); refusing to return the object. If you want to bypass this check and are aware of the security implications, set the crossOrigin option to "trust".`);
53208
53254
  logger.warn("The object's @id ({objectId}) has a different origin than the document URL ({documentUrl}); refusing to return the object. If you want to bypass this check and are aware of the security implications, set the crossOrigin option to \"trust\".", {
53209
53255
  ...remoteDoc,
@@ -1,6 +1,6 @@
1
1
  import "temporal-polyfill";
2
2
  globalThis.addEventListener = () => {};
3
- import { A as Person, G as __toESM, L as Service, U as __commonJSMin, a as Application, k as Organization, v as Group } from "./vocab-BQ8y6QS7.mjs";
3
+ import { A as Organization, K as __toESM, R as Service, W as __commonJSMin, a as Application, j as Person, v as Group } from "./vocab-OU5GYbNg.mjs";
4
4
  import { t as getTypeId } from "./type-_BlsfT3S.mjs";
5
5
  import { lookupWebFinger } from "@fedify/webfinger";
6
6
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -1206,7 +1206,7 @@ var esm_default = new class FetchMock {
1206
1206
  //#endregion
1207
1207
  //#region deno.json
1208
1208
  var name = "@fedify/vocab";
1209
- var version = "2.4.0-dev.1599+7b19967f";
1209
+ var version = "2.4.0-dev.1618+58858243";
1210
1210
  //#endregion
1211
1211
  //#region src/actor.ts
1212
1212
  const ACTOR_DISCOVERY_HISTOGRAM_BUCKETS = [
@@ -1,7 +1,7 @@
1
1
  import "temporal-polyfill";
2
2
  globalThis.addEventListener = () => {};
3
- import { A as Person, L as Service, W as __exportAll, a as Application, k as Organization, v as Group } from "./vocab-BQ8y6QS7.mjs";
4
- import { a as normalizeActorHandle, c as esm_default, i as isActor, n as getActorHandle, r as getActorTypeName, t as getActorClassByTypeName } from "./actor-NhcklZK0.mjs";
3
+ import { A as Organization, G as __exportAll, R as Service, a as Application, j as Person, v as Group } from "./vocab-OU5GYbNg.mjs";
4
+ import { a as normalizeActorHandle, c as esm_default, i as isActor, n as getActorHandle, r as getActorTypeName, t as getActorClassByTypeName } from "./actor-COnJgcOw.mjs";
5
5
  import { createTestMeterProvider, test } from "@fedify/fixture";
6
6
  import { deepStrictEqual, ok, rejects, strictEqual, throws } from "node:assert/strict";
7
7
  //#region ../../node_modules/.pnpm/fast-check@3.23.2/node_modules/fast-check/lib/esm/check/precondition/PreconditionFailure.js
@@ -84,3 +84,17 @@ properties:
84
84
  objects which are not addressed to the `Public`` endpoint.
85
85
  range:
86
86
  - "http://www.w3.org/2001/XMLSchema#anyURI"
87
+
88
+ - singularName: uploadMedia
89
+ functional: true
90
+ compactName: uploadMedia
91
+ uri: "https://www.w3.org/ns/activitystreams#uploadMedia"
92
+ description: |
93
+ Upload endpoint URI for this user for binary data, such as an image or
94
+ video, as described in the [ActivityPub Media
95
+ Upload](https://www.w3.org/wiki/SocialCG/ActivityPub/MediaUpload) extension.
96
+ Clients `POST` a `multipart/form-data` request containing a `file` part
97
+ (the binary payload) and an `object` part (an ActivityStreams object shell)
98
+ to this endpoint.
99
+ range:
100
+ - "http://www.w3.org/2001/XMLSchema#anyURI"
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  globalThis.addEventListener = () => {};
3
- import { A as Person, E as Object$1, T as Note, o as Collection } from "./vocab-BQ8y6QS7.mjs";
4
- import { c as esm_default, i as isActor, o as name, s as version } from "./actor-NhcklZK0.mjs";
3
+ import { D as Object$1, E as Note, j as Person, o as Collection } from "./vocab-OU5GYbNg.mjs";
4
+ import { c as esm_default, i as isActor, o as name, s as version } from "./actor-COnJgcOw.mjs";
5
5
  import { t as getTypeId } from "./type-_BlsfT3S.mjs";
6
6
  import { t as assertInstanceOf } from "./utils-CQjrpUkt.mjs";
7
7
  import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
@@ -9,7 +9,7 @@ import { deepStrictEqual, equal, ok, rejects } from "node:assert/strict";
9
9
  import { lookupWebFinger } from "@fedify/webfinger";
10
10
  import { SpanStatusCode, trace } from "@opentelemetry/api";
11
11
  import { getLogger } from "@logtape/logtape";
12
- import { getDocumentLoader, haveSameIriOrigin, parseIri } from "@fedify/vocab-runtime";
12
+ import { getDocumentLoader, haveSameFe34Origin, haveSameIriOrigin, parseIri } from "@fedify/vocab-runtime";
13
13
  import { delay } from "es-toolkit";
14
14
  //#region src/handle.ts
15
15
  /**
@@ -223,7 +223,7 @@ async function lookupObjectInternal(identifier, options = {}) {
223
223
  }
224
224
  throw error;
225
225
  }
226
- if (options.crossOrigin !== "trust" && object.id != null && !haveSameIriOrigin(object.id, documentUrl)) {
226
+ if (options.crossOrigin !== "trust" && object.id != null && !haveSameIriOrigin(object.id, documentUrl) && !haveSameFe34Origin(object.id, documentUrl)) {
227
227
  if (options.crossOrigin === "throw") throw new Error(`The object's @id (${object.id.href}) has a different origin than the document URL (${remoteDoc.documentUrl}); refusing to return the object. If you want to bypass this check and are aware of the security implications, set the crossOrigin option to "trust".`);
228
228
  logger.warn("The object's @id ({objectId}) has a different origin than the document URL ({documentUrl}); refusing to return the object. If you want to bypass this check and are aware of the security implications, set the crossOrigin option to \"trust\".", {
229
229
  ...remoteDoc,
@@ -538,6 +538,73 @@ test("FEP-fe34: lookupObject() cross-origin security", {
538
538
  deepStrictEqual(result.id, new URL("https://example.com/note"));
539
539
  deepStrictEqual(result.content, "This is a legitimate note from the same origin");
540
540
  });
541
+ await t.step("same-authority non-FE34 IRIs are still trusted", async () => {
542
+ const atProtoDocumentLoader = async (url) => {
543
+ if (url === "https://gateway.example/at-item") return {
544
+ documentUrl: "at://did%3Aplc%3Aexample/collection/item",
545
+ contextUrl: null,
546
+ document: {
547
+ "@context": "https://www.w3.org/ns/activitystreams",
548
+ type: "Note",
549
+ id: "at://did:plc:example/collection/reply",
550
+ content: "AT Protocol note"
551
+ }
552
+ };
553
+ throw new Error(`Unexpected URL: ${url}`);
554
+ };
555
+ const result = await lookupObject("https://gateway.example/at-item", {
556
+ documentLoader: atProtoDocumentLoader,
557
+ contextLoader: mockDocumentLoader
558
+ });
559
+ assertInstanceOf(result, Note);
560
+ deepStrictEqual(result.id, new URL("at://did%3Aplc%3Aexample/collection/reply"));
561
+ deepStrictEqual(result.content, "AT Protocol note");
562
+ });
563
+ await t.step("portable object IDs are trusted by DID cryptographic origin", async () => {
564
+ const portableDocumentLoader = async (url) => {
565
+ if (url === "https://gateway.example/.well-known/apgateway/did:key:z6Mkabc/note") return {
566
+ documentUrl: "ap+ef61://did:key:z6Mkabc/note",
567
+ contextUrl: null,
568
+ document: {
569
+ "@context": "https://www.w3.org/ns/activitystreams",
570
+ type: "Note",
571
+ id: "ap://did:key:z6Mkabc/note",
572
+ content: "Portable note"
573
+ }
574
+ };
575
+ throw new Error(`Unexpected URL: ${url}`);
576
+ };
577
+ const result = await lookupObject("https://gateway.example/.well-known/apgateway/did:key:z6Mkabc/note", {
578
+ documentLoader: portableDocumentLoader,
579
+ contextLoader: mockDocumentLoader
580
+ });
581
+ assertInstanceOf(result, Note);
582
+ deepStrictEqual(result.content, "Portable note");
583
+ });
584
+ await t.step("portable object IDs reject mismatched cryptographic origins", async () => {
585
+ const portableDocumentLoader = async (url) => {
586
+ if (url === "https://gateway.example/.well-known/apgateway/did:key:z6Mkabc/note") return {
587
+ documentUrl: "ap+ef61://did:key:z6Mkabc/note",
588
+ contextUrl: null,
589
+ document: {
590
+ "@context": "https://www.w3.org/ns/activitystreams",
591
+ type: "Note",
592
+ id: "ap://did:key:z6Mkdef/note",
593
+ content: "Spoofed portable note"
594
+ }
595
+ };
596
+ throw new Error(`Unexpected URL: ${url}`);
597
+ };
598
+ deepStrictEqual(await lookupObject("https://gateway.example/.well-known/apgateway/did:key:z6Mkabc/note", {
599
+ documentLoader: portableDocumentLoader,
600
+ contextLoader: mockDocumentLoader
601
+ }), null);
602
+ await rejects(() => lookupObject("https://gateway.example/.well-known/apgateway/did:key:z6Mkabc/note", {
603
+ documentLoader: portableDocumentLoader,
604
+ contextLoader: mockDocumentLoader,
605
+ crossOrigin: "throw"
606
+ }), Error, "The object's @id (ap+ef61://did%3Akey%3Az6Mkdef/note) has a different origin than the document URL (ap+ef61://did:key:z6Mkabc/note)");
607
+ });
541
608
  await t.step("objects without @id are trusted", async () => {
542
609
  const noIdDocumentLoader = async (url) => {
543
610
  if (url === "https://example.com/note") return {
@@ -1,6 +1,6 @@
1
1
  import "temporal-polyfill";
2
2
  globalThis.addEventListener = () => {};
3
- import { A as Person, B as getEntityTypeById, C as Link, V as isEntityType } from "./vocab-BQ8y6QS7.mjs";
3
+ import { C as Link, H as isEntityType, V as getEntityTypeById, j as Person } from "./vocab-OU5GYbNg.mjs";
4
4
  import { t as getTypeId } from "./type-_BlsfT3S.mjs";
5
5
  import { test } from "@fedify/fixture";
6
6
  import { deepStrictEqual } from "node:assert/strict";
@@ -282,6 +282,7 @@ const PORTABLE_IRI_KEYS = new Set([
282
282
  "https://www.w3.org/ns/activitystreams#target",
283
283
  "https://www.w3.org/ns/activitystreams#to",
284
284
  "https://www.w3.org/ns/activitystreams#units",
285
+ "https://www.w3.org/ns/activitystreams#uploadMedia",
285
286
  "https://www.w3.org/ns/activitystreams#url",
286
287
  "https://www.w3.org/ns/did#service",
287
288
  "https://www.w3.org/ns/did#serviceEndpoint",
@@ -343,6 +344,7 @@ const PORTABLE_IRI_KEYS = new Set([
343
344
  "target",
344
345
  "to",
345
346
  "units",
347
+ "uploadMedia",
346
348
  "url"
347
349
  ]);
348
350
  /** Describes an object of any kind. The Object type serves as the base type for
@@ -28735,6 +28737,7 @@ var Endpoints = class {
28735
28737
  #_8Bx9qN8oU7Bpt2xi6khaxWp1gMr_provideClientKey = [];
28736
28738
  #_3dU7PMVQZJpsCpo2F4RQXxBXdPmS_signClientKey = [];
28737
28739
  #_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox = [];
28740
+ #_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia = [];
28738
28741
  /**
28739
28742
  * Constructs a new instance of Endpoints with the given values.
28740
28743
  * @param values The values to initialize the instance with.
@@ -28761,6 +28764,8 @@ var Endpoints = class {
28761
28764
  else throw new TypeError("The signClientKey must be of type URL.");
28762
28765
  if ("sharedInbox" in values && values.sharedInbox != null) if (values.sharedInbox instanceof URL) this.#_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox = [values.sharedInbox];
28763
28766
  else throw new TypeError("The sharedInbox must be of type URL.");
28767
+ if ("uploadMedia" in values && values.uploadMedia != null) if (values.uploadMedia instanceof URL) this.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia = [values.uploadMedia];
28768
+ else throw new TypeError("The uploadMedia must be of type URL.");
28764
28769
  }
28765
28770
  /**
28766
28771
  * Clones this instance, optionally updating it with the given values.
@@ -28795,6 +28800,9 @@ var Endpoints = class {
28795
28800
  clone.#_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox = this.#_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox;
28796
28801
  if ("sharedInbox" in values && values.sharedInbox != null) if (values.sharedInbox instanceof URL) clone.#_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox = [values.sharedInbox];
28797
28802
  else throw new TypeError("The sharedInbox must be of type URL.");
28803
+ clone.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia = this.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia;
28804
+ if ("uploadMedia" in values && values.uploadMedia != null) if (values.uploadMedia instanceof URL) clone.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia = [values.uploadMedia];
28805
+ else throw new TypeError("The uploadMedia must be of type URL.");
28798
28806
  return clone;
28799
28807
  }
28800
28808
  /** Endpoint URI so this actor's clients may access remote ActivityStreams
@@ -28862,6 +28870,18 @@ var Endpoints = class {
28862
28870
  if (this.#_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox.length < 1) return null;
28863
28871
  return this.#_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox[0];
28864
28872
  }
28873
+ /** Upload endpoint URI for this user for binary data, such as an image or
28874
+ * video, as described in the [ActivityPub Media
28875
+ * Upload](https://www.w3.org/wiki/SocialCG/ActivityPub/MediaUpload) extension.
28876
+ * Clients `POST` a `multipart/form-data` request containing a `file` part
28877
+ * (the binary payload) and an `object` part (an ActivityStreams object shell)
28878
+ * to this endpoint.
28879
+ */
28880
+ get uploadMedia() {
28881
+ if (this._warning != null) getLogger(this._warning.category).warn(this._warning.message, this._warning.values);
28882
+ if (this.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia.length < 1) return null;
28883
+ return this.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia[0];
28884
+ }
28865
28885
  /**
28866
28886
  * Converts this object to a JSON-LD structure.
28867
28887
  * @param options The options to use.
@@ -28917,6 +28937,12 @@ var Endpoints = class {
28917
28937
  compactItems.push(item);
28918
28938
  }
28919
28939
  if (compactItems.length > 0) result["sharedInbox"] = compactItems.length > 1 ? compactItems : compactItems[0];
28940
+ compactItems = [];
28941
+ for (const v of this.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia) {
28942
+ const item = formatIri(v);
28943
+ compactItems.push(item);
28944
+ }
28945
+ if (compactItems.length > 0) result["uploadMedia"] = compactItems.length > 1 ? compactItems : compactItems[0];
28920
28946
  if (this.id != null) result["id"] = formatIri(this.id);
28921
28947
  result["@context"] = "https://www.w3.org/ns/activitystreams";
28922
28948
  return result;
@@ -28959,6 +28985,12 @@ var Endpoints = class {
28959
28985
  array.push(element);
28960
28986
  }
28961
28987
  if (array.length > 0) values["https://www.w3.org/ns/activitystreams#sharedInbox"] = array;
28988
+ array = [];
28989
+ for (const v of this.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia) {
28990
+ const element = { "@id": formatIri(v) };
28991
+ array.push(element);
28992
+ }
28993
+ if (array.length > 0) values["https://www.w3.org/ns/activitystreams#uploadMedia"] = array;
28962
28994
  if (this.id != null) values["@id"] = formatIri(this.id);
28963
28995
  if (options.format === "expand") return await jsonld.expand(values, { documentLoader: options.contextLoader });
28964
28996
  const docContext = options.context ?? "https://www.w3.org/ns/activitystreams";
@@ -29084,6 +29116,15 @@ var Endpoints = class {
29084
29116
  _3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox.push(decoded);
29085
29117
  }
29086
29118
  instance.#_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox = _3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox;
29119
+ const _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia = [];
29120
+ let _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia__array = values["https://www.w3.org/ns/activitystreams#uploadMedia"];
29121
+ for (const v of _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia__array == null ? [] : _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia__array.length === 1 && "@list" in _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia__array[0] ? _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia__array[0]["@list"] : _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia__array) {
29122
+ if (v == null) continue;
29123
+ const decoded = parseIri(v["@id"], options.baseUrl);
29124
+ if (typeof decoded === "undefined") continue;
29125
+ _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia.push(decoded);
29126
+ }
29127
+ instance.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia = _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia;
29087
29128
  if (!("_fromSubclass" in options) || !options._fromSubclass) try {
29088
29129
  if (cacheJsonLd != null && cacheJsonLd !== expanded) {
29089
29130
  const compactArray = Array.isArray(json) && json.length === 1;
@@ -29132,6 +29173,11 @@ var Endpoints = class {
29132
29173
  [Symbol.for("nodejs.util.inspect.custom")]: (_depth, options, inspect) => "URL " + inspect(v.href, options)
29133
29174
  } : v);
29134
29175
  if (_3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox.length == 1) proxy.sharedInbox = _3JprUSDLVqqX4dwHRi37qGZZCRCc_sharedInbox[0];
29176
+ const _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia = this.#_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia.map((v) => v instanceof URL ? {
29177
+ [Symbol.for("Deno.customInspect")]: (inspect, options) => "URL " + inspect(v.href, options),
29178
+ [Symbol.for("nodejs.util.inspect.custom")]: (_depth, options, inspect) => "URL " + inspect(v.href, options)
29179
+ } : v);
29180
+ if (_KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia.length == 1) proxy.uploadMedia = _KukVKGqFRETxAxXA15YCYAA7XAT_uploadMedia[0];
29135
29181
  return proxy;
29136
29182
  }
29137
29183
  };
@@ -52874,4 +52920,4 @@ function getEntityTypeById(id) {
52874
52920
  return entityTypeIds.get(typeof id === "string" ? id : id?.href);
52875
52921
  }
52876
52922
  //#endregion
52877
- export { Person as A, getEntityTypeById as B, Link as C, Offer as D, Object$1 as E, QuoteRequest as F, __toESM as G, vocab_exports as H, Reject as I, Service as L, Proposal as M, Question as N, OrderedCollectionPage as O, QuoteAuthorization as P, Source as R, InteractionRule as S, Note as T, __commonJSMin as U, isEntityType as V, __exportAll as W, Follow as _, Application as a, Intent as b, Create as c, Document as d, Endpoints as f, FeaturedItem as g, FeaturedCollection as h, Announce as i, Place as j, Organization as k, CryptographicKey as l, FeatureRequest as m, Activity as n, Collection as o, FeatureAuthorization as p, Agreement as r, Commitment as s, Accept as t, Delete as u, Group as v, Measure as w, InteractionPolicy as x, Hashtag as y, Tombstone as z };
52923
+ export { Organization as A, Tombstone as B, Link as C, Object$1 as D, Note as E, QuoteAuthorization as F, __exportAll as G, isEntityType as H, QuoteRequest as I, __toESM as K, Reject as L, Place as M, Proposal as N, Offer as O, Question as P, Service as R, InteractionRule as S, Multikey as T, vocab_exports as U, getEntityTypeById as V, __commonJSMin as W, Follow as _, Application as a, Intent as b, Create as c, Document as d, Endpoints as f, FeaturedItem as g, FeaturedCollection as h, Announce as i, Person as j, OrderedCollectionPage as k, CryptographicKey as l, FeatureRequest as m, Activity as n, Collection as o, FeatureAuthorization as p, Agreement as r, Commitment as s, Accept as t, Delete as u, Group as v, Measure as w, InteractionPolicy as x, Hashtag as y, Source as z };
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  globalThis.addEventListener = () => {};
3
- import { A as Person, C as Link, D as Offer, E as Object$1, F as QuoteRequest, H as vocab_exports, I as Reject, M as Proposal, N as Question, O as OrderedCollectionPage, P as QuoteAuthorization, R as Source, S as InteractionRule, T as Note, _ as Follow, b as Intent, c as Create, d as Document, f as Endpoints, g as FeaturedItem, h as FeaturedCollection, i as Announce, j as Place, l as CryptographicKey, m as FeatureRequest, n as Activity, o as Collection, p as FeatureAuthorization, r as Agreement, s as Commitment, t as Accept, u as Delete, w as Measure, x as InteractionPolicy, y as Hashtag, z as Tombstone } from "./vocab-BQ8y6QS7.mjs";
3
+ import { B as Tombstone, C as Link, D as Object$1, E as Note, F as QuoteAuthorization, I as QuoteRequest, L as Reject, M as Place, N as Proposal, O as Offer, P as Question, S as InteractionRule, T as Multikey, U as vocab_exports, _ as Follow, b as Intent, c as Create, d as Document, f as Endpoints, g as FeaturedItem, h as FeaturedCollection, i as Announce, j as Person, k as OrderedCollectionPage, l as CryptographicKey, m as FeatureRequest, n as Activity, o as Collection, p as FeatureAuthorization, r as Agreement, s as Commitment, t as Accept, u as Delete, w as Measure, x as InteractionPolicy, y as Hashtag, z as Source } from "./vocab-OU5GYbNg.mjs";
4
4
  import { t as assertInstanceOf } from "./utils-CQjrpUkt.mjs";
5
5
  import { mockDocumentLoader, test } from "@fedify/fixture";
6
6
  import { deepStrictEqual, notDeepStrictEqual, ok, rejects, throws } from "node:assert/strict";
@@ -1311,6 +1311,25 @@ test("Endpoints.toJsonLd() omits type", async () => {
1311
1311
  contextLoader: mockDocumentLoader
1312
1312
  }), ep);
1313
1313
  });
1314
+ test("Endpoints.uploadMedia round-trips", async () => {
1315
+ const ep = new Endpoints({ uploadMedia: new URL("https://example.com/users/alice/media") });
1316
+ deepStrictEqual(ep.uploadMedia?.href, "https://example.com/users/alice/media");
1317
+ deepStrictEqual((await ep.toJsonLd())["uploadMedia"], "https://example.com/users/alice/media");
1318
+ for (const format of [
1319
+ void 0,
1320
+ "compact",
1321
+ "expand"
1322
+ ]) {
1323
+ const jsonLd = await ep.toJsonLd({
1324
+ format,
1325
+ contextLoader: mockDocumentLoader
1326
+ });
1327
+ deepStrictEqual((await Endpoints.fromJsonLd(jsonLd, {
1328
+ documentLoader: mockDocumentLoader,
1329
+ contextLoader: mockDocumentLoader
1330
+ })).uploadMedia?.href, "https://example.com/users/alice/media", `round-trip failed for format=${format ?? "heuristic"}`);
1331
+ }
1332
+ });
1314
1333
  test("Source.toJsonLd() omits type", async () => {
1315
1334
  const src = new Source({
1316
1335
  content: "Hello, world!",
@@ -1361,7 +1380,8 @@ test("Endpoints with all properties set omits type", async () => {
1361
1380
  oauthTokenEndpoint: new URL("https://example.com/oauth/token"),
1362
1381
  provideClientKey: new URL("https://example.com/provide-key"),
1363
1382
  signClientKey: new URL("https://example.com/sign-key"),
1364
- sharedInbox: new URL("https://example.com/inbox")
1383
+ sharedInbox: new URL("https://example.com/inbox"),
1384
+ uploadMedia: new URL("https://example.com/upload-media")
1365
1385
  });
1366
1386
  const compact = await ep.toJsonLd();
1367
1387
  ok(!("type" in compact), "compact output should not have 'type'");
@@ -1371,6 +1391,7 @@ test("Endpoints with all properties set omits type", async () => {
1371
1391
  deepStrictEqual(compact["provideClientKey"], "https://example.com/provide-key");
1372
1392
  deepStrictEqual(compact["signClientKey"], "https://example.com/sign-key");
1373
1393
  deepStrictEqual(compact["sharedInbox"], "https://example.com/inbox");
1394
+ deepStrictEqual(compact["uploadMedia"], "https://example.com/upload-media");
1374
1395
  for (const format of [
1375
1396
  void 0,
1376
1397
  "compact",
@@ -2555,6 +2576,23 @@ test("FEP-fe34: Same origin objects are trusted", async () => {
2555
2576
  deepStrictEqual(result?.id, new URL("https://example.com/note"));
2556
2577
  deepStrictEqual(result?.content, "This is a legitimate note");
2557
2578
  });
2579
+ test("FEP-fe34: Same-authority non-FE34 embedded objects are trusted", async () => {
2580
+ const result = await (await Create.fromJsonLd({
2581
+ "@context": "https://www.w3.org/ns/activitystreams",
2582
+ "@type": "Create",
2583
+ "@id": "at://did:plc:example/collection/item",
2584
+ "actor": "at://did:plc:example/actor/self",
2585
+ "object": {
2586
+ "@type": "Note",
2587
+ "@id": "at://did:plc:example/collection/reply",
2588
+ "content": "Embedded AT Protocol note"
2589
+ }
2590
+ })).getObject({ documentLoader: async (url) => {
2591
+ throw new Error(`Unexpected fetch: ${url}`);
2592
+ } });
2593
+ assertInstanceOf(result, Note);
2594
+ deepStrictEqual(result.content, "Embedded AT Protocol note");
2595
+ });
2558
2596
  test("FEP-fe34: Embedded cross-origin objects from JSON-LD are ignored by default", async () => {
2559
2597
  const createDocumentLoader = async (url) => {
2560
2598
  if (url === "https://example.com/create") return {
@@ -2656,6 +2694,47 @@ test("FEP-fe34: Portable DID authorities are cross-origin boundaries", async ()
2656
2694
  assertInstanceOf(result, Note);
2657
2695
  deepStrictEqual(result.content, "Fetched portable note");
2658
2696
  });
2697
+ test("FEP-fe34: DID verification methods share portable actor cryptographic origin", async () => {
2698
+ const person = await Person.fromJsonLd({
2699
+ "@id": "ap://did:key:z6MkOwner/actor",
2700
+ "@type": ["https://www.w3.org/ns/activitystreams#Person"],
2701
+ "https://w3id.org/security#assertionMethod": [{
2702
+ "@id": "did:key:z6MkOwner#z6MkOwner",
2703
+ "@type": ["https://w3id.org/security#Multikey"],
2704
+ "https://w3id.org/security#controller": [{ "@id": "did:key:z6MkOwner" }]
2705
+ }]
2706
+ });
2707
+ const documentLoader = async (url) => {
2708
+ throw new Error(`Unexpected fetch: ${url}`);
2709
+ };
2710
+ const methods = [];
2711
+ for await (const method of person.getAssertionMethods({ documentLoader })) methods.push(method);
2712
+ deepStrictEqual(methods.length, 1);
2713
+ assertInstanceOf(methods[0], Multikey);
2714
+ deepStrictEqual(methods[0].id, new URL("did:key:z6MkOwner#z6MkOwner"));
2715
+ });
2716
+ test("FEP-fe34: DID verification methods from another cryptographic origin are untrusted", async () => {
2717
+ const person = await Person.fromJsonLd({
2718
+ "@id": "ap://did:key:z6MkOwner/actor",
2719
+ "@type": ["https://www.w3.org/ns/activitystreams#Person"],
2720
+ "https://w3id.org/security#assertionMethod": [{
2721
+ "@id": "did:key:z6MkOther#z6MkOther",
2722
+ "@type": ["https://w3id.org/security#Multikey"],
2723
+ "https://w3id.org/security#controller": [{ "@id": "did:key:z6MkOther" }]
2724
+ }]
2725
+ });
2726
+ let fetches = 0;
2727
+ const methods = [];
2728
+ for await (const method of person.getAssertionMethods({
2729
+ suppressError: true,
2730
+ documentLoader: async (url) => {
2731
+ fetches++;
2732
+ throw new Error(`Unexpected fetch: ${url}`);
2733
+ }
2734
+ })) methods.push(method);
2735
+ deepStrictEqual(methods, []);
2736
+ deepStrictEqual(fetches, 1);
2737
+ });
2659
2738
  test("FEP-fe34: Array properties respect cross-origin policy", async () => {
2660
2739
  const crossOriginDocumentLoader = async (url) => {
2661
2740
  if (url === "https://different-origin.com/note1") return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab",
3
- "version": "2.4.0-dev.1599+7b19967f",
3
+ "version": "2.4.0-dev.1618+58858243",
4
4
  "homepage": "https://fedify.dev/",
5
5
  "repository": {
6
6
  "type": "git",
@@ -43,9 +43,9 @@
43
43
  "jsonld": "^9.0.0",
44
44
  "pkijs": "^3.3.3",
45
45
  "temporal-polyfill": "^1.0.1",
46
- "@fedify/vocab-tools": "2.4.0-dev.1599+7b19967f",
47
- "@fedify/vocab-runtime": "2.4.0-dev.1599+7b19967f",
48
- "@fedify/webfinger": "2.4.0-dev.1599+7b19967f"
46
+ "@fedify/vocab-tools": "2.4.0-dev.1618+58858243",
47
+ "@fedify/webfinger": "2.4.0-dev.1618+58858243",
48
+ "@fedify/vocab-runtime": "2.4.0-dev.1618+58858243"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/node": "^22.17.0",
@@ -5003,7 +5003,8 @@ snapshot[`Deno.inspect(Endpoints) [auto] 1`] = `
5003
5003
  oauthTokenEndpoint: URL "https://example.com/",
5004
5004
  provideClientKey: URL "https://example.com/",
5005
5005
  signClientKey: URL "https://example.com/",
5006
- sharedInbox: URL "https://example.com/"
5006
+ sharedInbox: URL "https://example.com/",
5007
+ uploadMedia: URL "https://example.com/"
5007
5008
  }'
5008
5009
  `;
5009
5010
 
@@ -5015,7 +5016,8 @@ snapshot[`Deno.inspect(Endpoints) [auto] 2`] = `
5015
5016
  oauthTokenEndpoint: URL "https://example.com/",
5016
5017
  provideClientKey: URL "https://example.com/",
5017
5018
  signClientKey: URL "https://example.com/",
5018
- sharedInbox: URL "https://example.com/"
5019
+ sharedInbox: URL "https://example.com/",
5020
+ uploadMedia: URL "https://example.com/"
5019
5021
  }'
5020
5022
  `;
5021
5023
 
@@ -5027,7 +5029,8 @@ snapshot[`Deno.inspect(Endpoints) [auto] 3`] = `
5027
5029
  oauthTokenEndpoint: URL "https://example.com/",
5028
5030
  provideClientKey: URL "https://example.com/",
5029
5031
  signClientKey: URL "https://example.com/",
5030
- sharedInbox: URL "https://example.com/"
5032
+ sharedInbox: URL "https://example.com/",
5033
+ uploadMedia: URL "https://example.com/"
5031
5034
  }'
5032
5035
  `;
5033
5036
 
@@ -84,3 +84,17 @@ properties:
84
84
  objects which are not addressed to the `Public`` endpoint.
85
85
  range:
86
86
  - "http://www.w3.org/2001/XMLSchema#anyURI"
87
+
88
+ - singularName: uploadMedia
89
+ functional: true
90
+ compactName: uploadMedia
91
+ uri: "https://www.w3.org/ns/activitystreams#uploadMedia"
92
+ description: |
93
+ Upload endpoint URI for this user for binary data, such as an image or
94
+ video, as described in the [ActivityPub Media
95
+ Upload](https://www.w3.org/wiki/SocialCG/ActivityPub/MediaUpload) extension.
96
+ Clients `POST` a `multipart/form-data` request containing a `file` part
97
+ (the binary payload) and an `object` part (an ActivityStreams object shell)
98
+ to this endpoint.
99
+ range:
100
+ - "http://www.w3.org/2001/XMLSchema#anyURI"
@@ -416,6 +416,127 @@ test("FEP-fe34: lookupObject() cross-origin security", {
416
416
  );
417
417
  });
418
418
 
419
+ await t.step(
420
+ "same-authority non-FE34 IRIs are still trusted",
421
+ async () => {
422
+ // deno-lint-ignore require-await
423
+ const atProtoDocumentLoader = async (url: string) => {
424
+ if (url === "https://gateway.example/at-item") {
425
+ return {
426
+ documentUrl: "at://did%3Aplc%3Aexample/collection/item",
427
+ contextUrl: null,
428
+ document: {
429
+ "@context": "https://www.w3.org/ns/activitystreams",
430
+ type: "Note",
431
+ id: "at://did:plc:example/collection/reply",
432
+ content: "AT Protocol note",
433
+ },
434
+ };
435
+ }
436
+ throw new Error(`Unexpected URL: ${url}`);
437
+ };
438
+
439
+ const result = await lookupObject(
440
+ "https://gateway.example/at-item",
441
+ {
442
+ documentLoader: atProtoDocumentLoader,
443
+ contextLoader: mockDocumentLoader,
444
+ },
445
+ );
446
+
447
+ assertInstanceOf(result, Note);
448
+ deepStrictEqual(
449
+ result.id,
450
+ new URL("at://did%3Aplc%3Aexample/collection/reply"),
451
+ );
452
+ deepStrictEqual(result.content, "AT Protocol note");
453
+ },
454
+ );
455
+
456
+ await t.step(
457
+ "portable object IDs are trusted by DID cryptographic origin",
458
+ async () => {
459
+ // deno-lint-ignore require-await
460
+ const portableDocumentLoader = async (url: string) => {
461
+ if (
462
+ url ===
463
+ "https://gateway.example/.well-known/apgateway/did:key:z6Mkabc/note"
464
+ ) {
465
+ return {
466
+ documentUrl: "ap+ef61://did:key:z6Mkabc/note",
467
+ contextUrl: null,
468
+ document: {
469
+ "@context": "https://www.w3.org/ns/activitystreams",
470
+ type: "Note",
471
+ id: "ap://did:key:z6Mkabc/note",
472
+ content: "Portable note",
473
+ },
474
+ };
475
+ }
476
+ throw new Error(`Unexpected URL: ${url}`);
477
+ };
478
+
479
+ const result = await lookupObject(
480
+ "https://gateway.example/.well-known/apgateway/did:key:z6Mkabc/note",
481
+ {
482
+ documentLoader: portableDocumentLoader,
483
+ contextLoader: mockDocumentLoader,
484
+ },
485
+ );
486
+
487
+ assertInstanceOf(result, Note);
488
+ deepStrictEqual(result.content, "Portable note");
489
+ },
490
+ );
491
+
492
+ await t.step(
493
+ "portable object IDs reject mismatched cryptographic origins",
494
+ async () => {
495
+ // deno-lint-ignore require-await
496
+ const portableDocumentLoader = async (url: string) => {
497
+ if (
498
+ url ===
499
+ "https://gateway.example/.well-known/apgateway/did:key:z6Mkabc/note"
500
+ ) {
501
+ return {
502
+ documentUrl: "ap+ef61://did:key:z6Mkabc/note",
503
+ contextUrl: null,
504
+ document: {
505
+ "@context": "https://www.w3.org/ns/activitystreams",
506
+ type: "Note",
507
+ id: "ap://did:key:z6Mkdef/note",
508
+ content: "Spoofed portable note",
509
+ },
510
+ };
511
+ }
512
+ throw new Error(`Unexpected URL: ${url}`);
513
+ };
514
+
515
+ const result = await lookupObject(
516
+ "https://gateway.example/.well-known/apgateway/did:key:z6Mkabc/note",
517
+ {
518
+ documentLoader: portableDocumentLoader,
519
+ contextLoader: mockDocumentLoader,
520
+ },
521
+ );
522
+
523
+ deepStrictEqual(result, null);
524
+ await rejects(
525
+ () =>
526
+ lookupObject(
527
+ "https://gateway.example/.well-known/apgateway/did:key:z6Mkabc/note",
528
+ {
529
+ documentLoader: portableDocumentLoader,
530
+ contextLoader: mockDocumentLoader,
531
+ crossOrigin: "throw",
532
+ },
533
+ ),
534
+ Error,
535
+ "The object's @id (ap+ef61://did%3Akey%3Az6Mkdef/note) has a different origin than the document URL (ap+ef61://did:key:z6Mkabc/note)",
536
+ );
537
+ },
538
+ );
539
+
419
540
  await t.step("objects without @id are trusted", async () => {
420
541
  // deno-lint-ignore require-await
421
542
  const noIdDocumentLoader = async (url: string) => {
package/src/lookup.ts CHANGED
@@ -2,6 +2,7 @@ import type { GetUserAgentOptions } from "@fedify/vocab-runtime";
2
2
  import {
3
3
  type DocumentLoader,
4
4
  getDocumentLoader,
5
+ haveSameFe34Origin,
5
6
  haveSameIriOrigin,
6
7
  parseIri,
7
8
  type RemoteDocument,
@@ -335,7 +336,8 @@ async function lookupObjectInternal(
335
336
  }
336
337
  if (
337
338
  options.crossOrigin !== "trust" && object.id != null &&
338
- !haveSameIriOrigin(object.id, documentUrl)
339
+ !haveSameIriOrigin(object.id, documentUrl) &&
340
+ !haveSameFe34Origin(object.id, documentUrl)
339
341
  ) {
340
342
  if (options.crossOrigin === "throw") {
341
343
  throw new Error(
package/src/vocab.test.ts CHANGED
@@ -47,6 +47,7 @@ import {
47
47
  InteractionRule,
48
48
  Link,
49
49
  Measure,
50
+ Multikey,
50
51
  Note,
51
52
  Object,
52
53
  Offer,
@@ -1963,6 +1964,39 @@ test("Endpoints.toJsonLd() omits type", async () => {
1963
1964
  deepStrictEqual(restored, ep);
1964
1965
  });
1965
1966
 
1967
+ test("Endpoints.uploadMedia round-trips", async () => {
1968
+ const ep = new Endpoints({
1969
+ uploadMedia: new URL("https://example.com/users/alice/media"),
1970
+ });
1971
+ deepStrictEqual(
1972
+ ep.uploadMedia?.href,
1973
+ "https://example.com/users/alice/media",
1974
+ );
1975
+
1976
+ const compact = await ep.toJsonLd() as Record<string, unknown>;
1977
+ deepStrictEqual(
1978
+ compact["uploadMedia"],
1979
+ "https://example.com/users/alice/media",
1980
+ );
1981
+
1982
+ // Round-trip through every format under the standard AS term.
1983
+ for (const format of [undefined, "compact" as const, "expand" as const]) {
1984
+ const jsonLd = await ep.toJsonLd({
1985
+ format,
1986
+ contextLoader: mockDocumentLoader,
1987
+ });
1988
+ const restored = await Endpoints.fromJsonLd(jsonLd, {
1989
+ documentLoader: mockDocumentLoader,
1990
+ contextLoader: mockDocumentLoader,
1991
+ });
1992
+ deepStrictEqual(
1993
+ restored.uploadMedia?.href,
1994
+ "https://example.com/users/alice/media",
1995
+ `round-trip failed for format=${format ?? "heuristic"}`,
1996
+ );
1997
+ }
1998
+ });
1999
+
1966
2000
  test("Source.toJsonLd() omits type", async () => {
1967
2001
  const src = new Source({
1968
2002
  content: "Hello, world!",
@@ -2029,6 +2063,7 @@ test("Endpoints with all properties set omits type", async () => {
2029
2063
  provideClientKey: new URL("https://example.com/provide-key"),
2030
2064
  signClientKey: new URL("https://example.com/sign-key"),
2031
2065
  sharedInbox: new URL("https://example.com/inbox"),
2066
+ uploadMedia: new URL("https://example.com/upload-media"),
2032
2067
  });
2033
2068
 
2034
2069
  // Compact heuristic path
@@ -2049,6 +2084,7 @@ test("Endpoints with all properties set omits type", async () => {
2049
2084
  );
2050
2085
  deepStrictEqual(compact["signClientKey"], "https://example.com/sign-key");
2051
2086
  deepStrictEqual(compact["sharedInbox"], "https://example.com/inbox");
2087
+ deepStrictEqual(compact["uploadMedia"], "https://example.com/upload-media");
2052
2088
 
2053
2089
  // Round-trip all three formats
2054
2090
  for (
@@ -3800,6 +3836,30 @@ test("FEP-fe34: Same origin objects are trusted", async () => {
3800
3836
  deepStrictEqual(result?.content, "This is a legitimate note");
3801
3837
  });
3802
3838
 
3839
+ test("FEP-fe34: Same-authority non-FE34 embedded objects are trusted", async () => {
3840
+ const create = await Create.fromJsonLd({
3841
+ "@context": "https://www.w3.org/ns/activitystreams",
3842
+ "@type": "Create",
3843
+ "@id": "at://did:plc:example/collection/item",
3844
+ "actor": "at://did:plc:example/actor/self",
3845
+ "object": {
3846
+ "@type": "Note",
3847
+ "@id": "at://did:plc:example/collection/reply",
3848
+ "content": "Embedded AT Protocol note",
3849
+ },
3850
+ });
3851
+
3852
+ const result = await create.getObject({
3853
+ // deno-lint-ignore require-await
3854
+ documentLoader: async (url) => {
3855
+ throw new Error(`Unexpected fetch: ${url}`);
3856
+ },
3857
+ });
3858
+
3859
+ assertInstanceOf(result, Note);
3860
+ deepStrictEqual(result.content, "Embedded AT Protocol note");
3861
+ });
3862
+
3803
3863
  test(
3804
3864
  "FEP-fe34: Embedded cross-origin objects from JSON-LD are ignored by default",
3805
3865
  async () => {
@@ -3951,6 +4011,74 @@ test(
3951
4011
  },
3952
4012
  );
3953
4013
 
4014
+ test(
4015
+ "FEP-fe34: DID verification methods share portable actor cryptographic origin",
4016
+ async () => {
4017
+ const person = await Person.fromJsonLd({
4018
+ "@id": "ap://did:key:z6MkOwner/actor",
4019
+ "@type": ["https://www.w3.org/ns/activitystreams#Person"],
4020
+ "https://w3id.org/security#assertionMethod": [{
4021
+ "@id": "did:key:z6MkOwner#z6MkOwner",
4022
+ "@type": ["https://w3id.org/security#Multikey"],
4023
+ "https://w3id.org/security#controller": [{
4024
+ "@id": "did:key:z6MkOwner",
4025
+ }],
4026
+ }],
4027
+ });
4028
+
4029
+ // deno-lint-ignore require-await
4030
+ const documentLoader = async (url: string) => {
4031
+ throw new Error(`Unexpected fetch: ${url}`);
4032
+ };
4033
+
4034
+ const methods = [];
4035
+ for await (const method of person.getAssertionMethods({ documentLoader })) {
4036
+ methods.push(method);
4037
+ }
4038
+
4039
+ deepStrictEqual(methods.length, 1);
4040
+ assertInstanceOf(methods[0], Multikey);
4041
+ deepStrictEqual(
4042
+ methods[0].id,
4043
+ new URL("did:key:z6MkOwner#z6MkOwner"),
4044
+ );
4045
+ },
4046
+ );
4047
+
4048
+ test(
4049
+ "FEP-fe34: DID verification methods from another cryptographic origin are untrusted",
4050
+ async () => {
4051
+ const person = await Person.fromJsonLd({
4052
+ "@id": "ap://did:key:z6MkOwner/actor",
4053
+ "@type": ["https://www.w3.org/ns/activitystreams#Person"],
4054
+ "https://w3id.org/security#assertionMethod": [{
4055
+ "@id": "did:key:z6MkOther#z6MkOther",
4056
+ "@type": ["https://w3id.org/security#Multikey"],
4057
+ "https://w3id.org/security#controller": [{
4058
+ "@id": "did:key:z6MkOther",
4059
+ }],
4060
+ }],
4061
+ });
4062
+
4063
+ let fetches = 0;
4064
+ const methods = [];
4065
+ for await (
4066
+ const method of person.getAssertionMethods({
4067
+ suppressError: true,
4068
+ // deno-lint-ignore require-await
4069
+ documentLoader: async (url) => {
4070
+ fetches++;
4071
+ throw new Error(`Unexpected fetch: ${url}`);
4072
+ },
4073
+ })
4074
+ ) {
4075
+ methods.push(method);
4076
+ }
4077
+ deepStrictEqual(methods, []);
4078
+ deepStrictEqual(fetches, 1);
4079
+ },
4080
+ );
4081
+
3954
4082
  test("FEP-fe34: Array properties respect cross-origin policy", async () => {
3955
4083
  // deno-lint-ignore require-await
3956
4084
  const crossOriginDocumentLoader = async (url: string) => {