@fedify/vocab-runtime 2.4.0-dev.2219 → 2.4.0-dev.2233

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.
Files changed (45) hide show
  1. package/deno.json +2 -2
  2. package/dist/internal/jsonld-cache.cjs +1 -1
  3. package/dist/internal/jsonld-cache.js +1 -1
  4. package/dist/internal/portable-dereference.cjs +4 -13
  5. package/dist/internal/portable-dereference.js +2 -11
  6. package/dist/mod.cjs +5 -2
  7. package/dist/mod.d.cts +95 -1
  8. package/dist/mod.d.ts +95 -1
  9. package/dist/mod.js +3 -3
  10. package/dist/tests/{body-BzhhuMI0.mjs → body-BEYUpKvX.mjs} +1 -1
  11. package/dist/tests/{body-GaHFfkaq.cjs → body-DQKTFMsH.cjs} +1 -1
  12. package/dist/tests/body.test.cjs +1 -1
  13. package/dist/tests/body.test.mjs +1 -1
  14. package/dist/tests/decimal.test.cjs +3 -3
  15. package/dist/tests/decimal.test.mjs +3 -3
  16. package/dist/tests/{docloader-sGr7kARm.cjs → docloader-DHCYdBXf.cjs} +3 -3
  17. package/dist/tests/{docloader-DiRcdiPp.mjs → docloader-ra1lbBfP.mjs} +3 -3
  18. package/dist/tests/docloader.test.cjs +3 -3
  19. package/dist/tests/docloader.test.mjs +3 -3
  20. package/dist/tests/internal/portable-dereference.test.cjs +16 -15
  21. package/dist/tests/internal/portable-dereference.test.mjs +14 -13
  22. package/dist/tests/{jsonld-cache-DUmuWRwp.mjs → jsonld-cache-BKrsWPCQ.mjs} +1 -1
  23. package/dist/tests/{jsonld-cache-BQMn_ILF.cjs → jsonld-cache-BTgizq9A.cjs} +1 -1
  24. package/dist/tests/jsonld-cache.test.cjs +2 -2
  25. package/dist/tests/jsonld-cache.test.mjs +2 -2
  26. package/dist/tests/{portable-media-BERS_tX_.mjs → portable-media-1NjUQari.mjs} +3 -3
  27. package/dist/tests/{portable-media-C_Q9GmqU.cjs → portable-media-DHaAEpaQ.cjs} +3 -3
  28. package/dist/tests/portable-media.test.cjs +1 -1
  29. package/dist/tests/portable-media.test.mjs +1 -1
  30. package/dist/tests/{request-qq1N2cl6.cjs → request-BQlkB8du.cjs} +1 -1
  31. package/dist/tests/{request-CWTG9_Xx.mjs → request-D3elVRyB.mjs} +1 -1
  32. package/dist/tests/request.test.cjs +1 -1
  33. package/dist/tests/request.test.mjs +1 -1
  34. package/dist/tests/{url-DfS-cvwr.mjs → url-Cdu4ASgJ.mjs} +165 -3
  35. package/dist/tests/{url-B8xyTwP-.cjs → url-CtHyzMR4.cjs} +194 -2
  36. package/dist/tests/url.test.cjs +133 -3
  37. package/dist/tests/url.test.mjs +133 -3
  38. package/dist/{url-DDVweCXr.cjs → url-BHfQuRdy.cjs} +194 -2
  39. package/dist/{url-Be3moMor.js → url-DqqDZY9i.js} +165 -3
  40. package/package.json +1 -1
  41. package/src/internal/portable-dereference.test.ts +16 -1
  42. package/src/internal/portable-dereference.ts +3 -20
  43. package/src/mod.ts +3 -0
  44. package/src/url.test.ts +311 -1
  45. package/src/url.ts +236 -8
package/deno.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab-runtime",
3
- "version": "2.4.0-dev.2219+eb70f060",
3
+ "version": "2.4.0-dev.2233+abb4a6b2",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./src/mod.ts",
@@ -36,6 +36,6 @@
36
36
  },
37
37
  "tasks": {
38
38
  "check": "deno fmt --check && deno lint && deno check src/*.ts",
39
- "test": "deno test"
39
+ "test": "deno test --allow-net"
40
40
  }
41
41
  }
@@ -1,6 +1,6 @@
1
1
 
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const require_url = require("../url-DDVweCXr.cjs");
3
+ const require_url = require("../url-BHfQuRdy.cjs");
4
4
  const require_jsonld = require("../jsonld.cjs");
5
5
  //#region src/internal/jsonld-cache.ts
6
6
  const noJsonLdContext = Symbol("noJsonLdContext");
@@ -1,5 +1,5 @@
1
1
 
2
- import { a as formatIri, c as haveSameFe34Origin, l as haveSameIriOrigin } from "../url-Be3moMor.js";
2
+ import { d as haveSameIriOrigin, o as formatIri, u as haveSameFe34Origin } from "../url-DqqDZY9i.js";
3
3
  import jsonld_default from "../jsonld.js";
4
4
  //#region src/internal/jsonld-cache.ts
5
5
  const noJsonLdContext = Symbol("noJsonLdContext");
@@ -1,7 +1,7 @@
1
1
 
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const require_contexts = require("../contexts-BK1CqBR5.cjs");
4
- const require_url = require("../url-DDVweCXr.cjs");
4
+ const require_url = require("../url-BHfQuRdy.cjs");
5
5
  const require_jsonld = require("../jsonld.cjs");
6
6
  const require_internal_jsonld_cache = require("./jsonld-cache.cjs");
7
7
  let _logtape_logtape = require("@logtape/logtape");
@@ -18,7 +18,6 @@ const logger = (0, _logtape_logtape.getLogger)([
18
18
  * a single accessor call from fanning out to many servers.
19
19
  */
20
20
  const MAX_GATEWAY_HINTS = 5;
21
- const LOCATION_HINT_PARAMETER = "@gateway";
22
21
  const BASELINE_CONTEXT_URLS = /* @__PURE__ */ new Set([
23
22
  "https://w3id.org/identity/v1",
24
23
  "https://www.w3.org/ns/activitystreams",
@@ -359,15 +358,15 @@ function getPortableGatewayCandidates(url, gateways) {
359
358
  };
360
359
  if (gateways != null) {
361
360
  for (const gateway of gateways) {
362
- const parsed = parseGatewayOrigin(gateway);
361
+ const parsed = require_url.parseGatewayOrigin(gateway);
363
362
  if (parsed == null) throw new TypeError("FEP-ef61 gateways must be HTTP(S) origins with no credentials, path, query, or fragment: " + String(gateway));
364
363
  add(parsed);
365
364
  }
366
365
  return candidates;
367
366
  }
368
- for (const hint of new URLSearchParams(url.search).getAll(LOCATION_HINT_PARAMETER)) {
367
+ for (const hint of new URLSearchParams(url.search).getAll(require_url.GATEWAY_HINT_PARAMETER)) {
369
368
  if (candidates.length >= MAX_GATEWAY_HINTS) break;
370
- const parsed = parseGatewayOrigin(hint);
369
+ const parsed = require_url.parseGatewayOrigin(hint);
371
370
  if (parsed == null) {
372
371
  logger.debug("Ignoring an invalid FEP-ef61 gateway hint {hint} in {url}.", {
373
372
  hint,
@@ -379,14 +378,6 @@ function getPortableGatewayCandidates(url, gateways) {
379
378
  }
380
379
  return candidates;
381
380
  }
382
- function parseGatewayOrigin(gateway) {
383
- let url;
384
- if (gateway instanceof URL) url = new URL(gateway.href);
385
- else if (typeof gateway === "string" && URL.canParse(gateway)) url = new URL(gateway);
386
- else return null;
387
- if (url.protocol !== "http:" && url.protocol !== "https:" || url.href !== `${url.origin}/`) return null;
388
- return url;
389
- }
390
381
  /**
391
382
  * Creates a context loader that returns the same context documents for the
392
383
  * whole dereference operation, so that the identity check, the proof
@@ -1,6 +1,6 @@
1
1
 
2
2
  import { t as preloadedContexts } from "../contexts-DPuJ4UYL.js";
3
- import { a as formatIri, c as haveSameFe34Origin, g as toCompatibleEf61Id, m as parseIri, o as fromCompatibleEf61Id, r as canonicalizePortableUri } from "../url-Be3moMor.js";
3
+ import { _ as parseIri, h as parseGatewayOrigin, i as canonicalizePortableUri, o as formatIri, s as fromCompatibleEf61Id, t as GATEWAY_HINT_PARAMETER, u as haveSameFe34Origin, y as toCompatibleEf61Id } from "../url-DqqDZY9i.js";
4
4
  import jsonld_default from "../jsonld.js";
5
5
  import { createScopedContextLoader, registerDocumentLoaderWrapper, unwrapReleasedDocumentLoader } from "./jsonld-cache.js";
6
6
  import { getLogger } from "@logtape/logtape";
@@ -17,7 +17,6 @@ const logger = getLogger([
17
17
  * a single accessor call from fanning out to many servers.
18
18
  */
19
19
  const MAX_GATEWAY_HINTS = 5;
20
- const LOCATION_HINT_PARAMETER = "@gateway";
21
20
  const BASELINE_CONTEXT_URLS = /* @__PURE__ */ new Set([
22
21
  "https://w3id.org/identity/v1",
23
22
  "https://www.w3.org/ns/activitystreams",
@@ -364,7 +363,7 @@ function getPortableGatewayCandidates(url, gateways) {
364
363
  }
365
364
  return candidates;
366
365
  }
367
- for (const hint of new URLSearchParams(url.search).getAll(LOCATION_HINT_PARAMETER)) {
366
+ for (const hint of new URLSearchParams(url.search).getAll(GATEWAY_HINT_PARAMETER)) {
368
367
  if (candidates.length >= MAX_GATEWAY_HINTS) break;
369
368
  const parsed = parseGatewayOrigin(hint);
370
369
  if (parsed == null) {
@@ -378,14 +377,6 @@ function getPortableGatewayCandidates(url, gateways) {
378
377
  }
379
378
  return candidates;
380
379
  }
381
- function parseGatewayOrigin(gateway) {
382
- let url;
383
- if (gateway instanceof URL) url = new URL(gateway.href);
384
- else if (typeof gateway === "string" && URL.canParse(gateway)) url = new URL(gateway);
385
- else return null;
386
- if (url.protocol !== "http:" && url.protocol !== "https:" || url.href !== `${url.origin}/`) return null;
387
- return url;
388
- }
389
380
  /**
390
381
  * Creates a context loader that returns the same context documents for the
391
382
  * whole dereference operation, so that the identity check, the proof
package/dist/mod.cjs CHANGED
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const require_rolldown_runtime = require("./rolldown-runtime-B7lfambq.cjs");
4
4
  const require_contexts = require("./contexts-BK1CqBR5.cjs");
5
- const require_url = require("./url-DDVweCXr.cjs");
5
+ const require_url = require("./url-BHfQuRdy.cjs");
6
6
  let _logtape_logtape = require("@logtape/logtape");
7
7
  let _opentelemetry_api = require("@opentelemetry/api");
8
8
  let node_process = require("node:process");
@@ -16,7 +16,7 @@ let _multiformats_base_x = require("@multiformats/base-x");
16
16
  _multiformats_base_x = require_rolldown_runtime.__toESM(_multiformats_base_x, 1);
17
17
  //#region deno.json
18
18
  var name = "@fedify/vocab-runtime";
19
- var version = "2.4.0-dev.2219+eb70f060";
19
+ var version = "2.4.0-dev.2233+abb4a6b2";
20
20
  //#endregion
21
21
  //#region src/request.ts
22
22
  /**
@@ -1745,6 +1745,7 @@ exports.formatIri = require_url.formatIri;
1745
1745
  exports.fromCompatibleEf61Id = require_url.fromCompatibleEf61Id;
1746
1746
  exports.getDocumentLoader = getDocumentLoader;
1747
1747
  exports.getFe34Origin = require_url.getFe34Origin;
1748
+ exports.getGatewayHints = require_url.getGatewayHints;
1748
1749
  exports.getRemoteDocument = getRemoteDocument;
1749
1750
  exports.getUserAgent = getUserAgent;
1750
1751
  exports.haveSameFe34Origin = require_url.haveSameFe34Origin;
@@ -1773,3 +1774,5 @@ exports.validatePublicUrl = require_url.validatePublicUrl;
1773
1774
  exports.verifyDigestMultibase = verifyDigestMultibase;
1774
1775
  exports.verifyHashlink = verifyHashlink;
1775
1776
  exports.withDocumentLoaderTimeout = withDocumentLoaderTimeout;
1777
+ exports.withGatewayHints = require_url.withGatewayHints;
1778
+ exports.withoutGatewayHints = require_url.withoutGatewayHints;
package/dist/mod.d.cts CHANGED
@@ -524,6 +524,7 @@ declare function haveSameFe34Origin(left: string | URL, right: string | URL): bo
524
524
  declare function haveSameIriOrigin(left: URL, right: URL): boolean;
525
525
  /**
526
526
  * Checks whether the URL is an FEP-ef61 gateway base URI.
527
+ * @since 2.4.0
527
528
  */
528
529
  declare function isGatewayUrl(url: URL): boolean;
529
530
  /**
@@ -532,6 +533,7 @@ declare function isGatewayUrl(url: URL): boolean;
532
533
  * with no credentials, path, query, or fragment. In the
533
534
  * latter case, the message starts with
534
535
  * `Invalid FEP-ef61 gateway:`.
536
+ * @since 2.4.0
535
537
  */
536
538
  declare function parseGatewayUrl(url: string): URL;
537
539
  /**
@@ -608,6 +610,98 @@ declare function fromCompatibleEf61Id(input: string | URL): URL | null;
608
610
  */
609
611
  declare function toCompatibleEf61Id(portableId: string | URL, gateway: string | URL): URL;
610
612
  /**
613
+ * Returns a copy of an [FEP-ef61] portable ActivityPub URI with `@gateway`
614
+ * location hints for the given gateways, which tell consumers where they can
615
+ * retrieve the object. Put hints on *references* to portable actors, e.g.,
616
+ * in `actor`, `attributedTo`, `to`, or `cc`, when constructing an object, as
617
+ * FEP-ef61 recommends:
618
+ *
619
+ * ~~~~ typescript
620
+ * withGatewayHints("ap://did:key:z6Mk.../actor", [
621
+ * "https://server1.example",
622
+ * "https://server2.example",
623
+ * ]);
624
+ * // ap+ef61://did:key:z6Mk.../actor?@gateway=https%3A%2F%2Fserver1.example&@gateway=https%3A%2F%2Fserver2.example
625
+ * ~~~~
626
+ *
627
+ * Do not put hints on an object's own `id`. Hints do not change the
628
+ * identity of a portable URI, since FEP-ef61 drops the query when comparing
629
+ * portable URIs, but implementations that do not canonicalize portable URIs
630
+ * would take a hinted ID for another object. Add hints before signing the
631
+ * object, since its Object Integrity Proof covers its references too.
632
+ *
633
+ * The hints that the URI already has, including the legacy `gateways`
634
+ * parameter, are replaced. Each gateway becomes a `@gateway` query
635
+ * parameter whose value is its URI-encoded origin, e.g.,
636
+ * `@gateway=https%3A%2F%2Fserver1.example`, in the given order after the
637
+ * other query parameters. Duplicate gateways are dropped, and an empty list
638
+ * removes the hints as {@link withoutGatewayHints} does. The other query
639
+ * parameters, their order, and the fragment are kept, but percent-encoding
640
+ * is normalized the same way as {@link canonicalizePortableUri} does it.
641
+ *
642
+ * Fedify follows at most five hints when dereferencing a portable URI
643
+ * (three for the key ID of an HTTP Signature), so list the preferred
644
+ * gateways first; there is no limit on the number of hints added here.
645
+ *
646
+ * [FEP-ef61]: https://w3id.org/fep/ef61
647
+ *
648
+ * @param portableId The `ap:` or `ap+ef61:` URI. Pass the raw string rather
649
+ * than a `URL` if its path may have `.` or `..` segments,
650
+ * because the `URL` class resolves them.
651
+ * @param gateways The gateways, e.g., the `gateways` of the actor that the
652
+ * URI refers to. Each has to be an HTTP(S) origin with no
653
+ * credentials, path, query, or fragment.
654
+ * @returns The portable URI with the hints, in the same internal `URL` form
655
+ * as {@link parseIri} returns. Use {@link formatIri} to get its
656
+ * canonical string.
657
+ * @throws {TypeError} If the portable ID is not a valid `ap:` or `ap+ef61:`
658
+ * URI, e.g., it is a compatible identifier, which must
659
+ * not have location hints; if its path has `.` or `..`
660
+ * segments, which the `URL` class cannot represent; or
661
+ * if a gateway is invalid.
662
+ * @since 2.4.0
663
+ */
664
+ declare function withGatewayHints(portableId: string | URL, gateways: Iterable<string | URL>): URL;
665
+ /**
666
+ * Returns a copy of an [FEP-ef61] portable ActivityPub URI without its
667
+ * location hints, i.e., `@gateway` query parameters and the legacy
668
+ * `gateways` parameter. The other query parameters, their order, and the
669
+ * fragment are kept, but percent-encoding is normalized the same way as
670
+ * {@link canonicalizePortableUri} does it.
671
+ *
672
+ * [FEP-ef61]: https://w3id.org/fep/ef61
673
+ *
674
+ * @param portableId The `ap:` or `ap+ef61:` URI. Pass the raw string rather
675
+ * than a `URL` if its path may have `.` or `..` segments,
676
+ * because the `URL` class resolves them.
677
+ * @returns The portable URI without the hints, in the same internal `URL`
678
+ * form as {@link parseIri} returns.
679
+ * @throws {TypeError} If the portable ID is not a valid `ap:` or `ap+ef61:`
680
+ * URI, or if its path has `.` or `..` segments, which
681
+ * the `URL` class cannot represent.
682
+ * @since 2.4.0
683
+ */
684
+ declare function withoutGatewayHints(portableId: string | URL): URL;
685
+ /**
686
+ * Gets the gateways in the `@gateway` location hints of an [FEP-ef61]
687
+ * portable ActivityPub URI, in order. Hints that are not valid gateways,
688
+ * i.e., HTTP(S) origins with no credentials, path, query, or fragment, are
689
+ * skipped, and so are duplicates. The legacy `gateways` parameter is not
690
+ * read.
691
+ *
692
+ * Unlike Fedify's dereferencing, which follows at most five hints, this
693
+ * returns all of them.
694
+ *
695
+ * [FEP-ef61]: https://w3id.org/fep/ef61
696
+ *
697
+ * @param portableId The `ap:` or `ap+ef61:` URI.
698
+ * @returns The gateways, e.g., `https://server1.example/`.
699
+ * @throws {TypeError} If the portable ID is not a valid `ap:` or `ap+ef61:`
700
+ * URI.
701
+ * @since 2.4.0
702
+ */
703
+ declare function getGatewayHints(portableId: string | URL): URL[];
704
+ /**
611
705
  * Validates a URL to prevent SSRF attacks.
612
706
  */
613
707
  declare function validatePublicUrl(url: string): Promise<void>;
@@ -615,4 +709,4 @@ declare function isValidPublicIPv4Address(address: string): boolean;
615
709
  declare function isValidPublicIPv6Address(address: string): boolean;
616
710
  declare function expandIPv6Address(address: string): string;
617
711
  //#endregion
618
- export { type AuthenticatedDocumentLoaderFactory, type CreateRequestOptions, type Decimal, type DidKeyVerificationMethod, type DocumentLoader, type DocumentLoaderFactory, type DocumentLoaderFactoryOptions, type DocumentLoaderOptions, FetchError, type FetchPortableMediaOptions, type GetDocumentLoaderOptions, type GetUserAgentOptions, type Json, LanguageString, type ParsedDigestMultibase, type ParsedHashlink, type PortableMedia, type PortableObjectReferrer, type PortableObjectVerification, type PortableObjectVerifier, type PortableObjectVerifierOptions, type PropertyPreprocessor, type PropertyPreprocessorContext, type RemoteDocument, UrlError, arePortableUrisEqual, canParseDecimal, canonicalizePortableUri, computeDigestMultibase, createActivityPubRequest, createHashlink, decodeMultibase, encodeMultibase, encodingFromBaseData, expandIPv6Address, exportDidKey, exportMultibaseKey, exportSpki, fetchPortableMedia, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, getRemoteDocument, getUserAgent, haveSameFe34Origin, haveSameIriOrigin, importDidKey, importMultibaseKey, importPem, importPkcs1, importSpki, isDecimal, isGatewayUrl, isValidPublicIPv4Address, isValidPublicIPv6Address, logRequest, parseDecimal, parseDidKeyVerificationMethod, parseDigestMultibase, parseGatewayUrl, parseHashlink, parseIri, parseJsonLdId, preloadedContexts, resolveDocumentLoaderTimeout, toCompatibleEf61Id, validatePublicUrl, verifyDigestMultibase, verifyHashlink, withDocumentLoaderTimeout };
712
+ export { type AuthenticatedDocumentLoaderFactory, type CreateRequestOptions, type Decimal, type DidKeyVerificationMethod, type DocumentLoader, type DocumentLoaderFactory, type DocumentLoaderFactoryOptions, type DocumentLoaderOptions, FetchError, type FetchPortableMediaOptions, type GetDocumentLoaderOptions, type GetUserAgentOptions, type Json, LanguageString, type ParsedDigestMultibase, type ParsedHashlink, type PortableMedia, type PortableObjectReferrer, type PortableObjectVerification, type PortableObjectVerifier, type PortableObjectVerifierOptions, type PropertyPreprocessor, type PropertyPreprocessorContext, type RemoteDocument, UrlError, arePortableUrisEqual, canParseDecimal, canonicalizePortableUri, computeDigestMultibase, createActivityPubRequest, createHashlink, decodeMultibase, encodeMultibase, encodingFromBaseData, expandIPv6Address, exportDidKey, exportMultibaseKey, exportSpki, fetchPortableMedia, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, getGatewayHints, getRemoteDocument, getUserAgent, haveSameFe34Origin, haveSameIriOrigin, importDidKey, importMultibaseKey, importPem, importPkcs1, importSpki, isDecimal, isGatewayUrl, isValidPublicIPv4Address, isValidPublicIPv6Address, logRequest, parseDecimal, parseDidKeyVerificationMethod, parseDigestMultibase, parseGatewayUrl, parseHashlink, parseIri, parseJsonLdId, preloadedContexts, resolveDocumentLoaderTimeout, toCompatibleEf61Id, validatePublicUrl, verifyDigestMultibase, verifyHashlink, withDocumentLoaderTimeout, withGatewayHints, withoutGatewayHints };
package/dist/mod.d.ts CHANGED
@@ -523,6 +523,7 @@ declare function haveSameFe34Origin(left: string | URL, right: string | URL): bo
523
523
  declare function haveSameIriOrigin(left: URL, right: URL): boolean;
524
524
  /**
525
525
  * Checks whether the URL is an FEP-ef61 gateway base URI.
526
+ * @since 2.4.0
526
527
  */
527
528
  declare function isGatewayUrl(url: URL): boolean;
528
529
  /**
@@ -531,6 +532,7 @@ declare function isGatewayUrl(url: URL): boolean;
531
532
  * with no credentials, path, query, or fragment. In the
532
533
  * latter case, the message starts with
533
534
  * `Invalid FEP-ef61 gateway:`.
535
+ * @since 2.4.0
534
536
  */
535
537
  declare function parseGatewayUrl(url: string): URL;
536
538
  /**
@@ -607,6 +609,98 @@ declare function fromCompatibleEf61Id(input: string | URL): URL | null;
607
609
  */
608
610
  declare function toCompatibleEf61Id(portableId: string | URL, gateway: string | URL): URL;
609
611
  /**
612
+ * Returns a copy of an [FEP-ef61] portable ActivityPub URI with `@gateway`
613
+ * location hints for the given gateways, which tell consumers where they can
614
+ * retrieve the object. Put hints on *references* to portable actors, e.g.,
615
+ * in `actor`, `attributedTo`, `to`, or `cc`, when constructing an object, as
616
+ * FEP-ef61 recommends:
617
+ *
618
+ * ~~~~ typescript
619
+ * withGatewayHints("ap://did:key:z6Mk.../actor", [
620
+ * "https://server1.example",
621
+ * "https://server2.example",
622
+ * ]);
623
+ * // ap+ef61://did:key:z6Mk.../actor?@gateway=https%3A%2F%2Fserver1.example&@gateway=https%3A%2F%2Fserver2.example
624
+ * ~~~~
625
+ *
626
+ * Do not put hints on an object's own `id`. Hints do not change the
627
+ * identity of a portable URI, since FEP-ef61 drops the query when comparing
628
+ * portable URIs, but implementations that do not canonicalize portable URIs
629
+ * would take a hinted ID for another object. Add hints before signing the
630
+ * object, since its Object Integrity Proof covers its references too.
631
+ *
632
+ * The hints that the URI already has, including the legacy `gateways`
633
+ * parameter, are replaced. Each gateway becomes a `@gateway` query
634
+ * parameter whose value is its URI-encoded origin, e.g.,
635
+ * `@gateway=https%3A%2F%2Fserver1.example`, in the given order after the
636
+ * other query parameters. Duplicate gateways are dropped, and an empty list
637
+ * removes the hints as {@link withoutGatewayHints} does. The other query
638
+ * parameters, their order, and the fragment are kept, but percent-encoding
639
+ * is normalized the same way as {@link canonicalizePortableUri} does it.
640
+ *
641
+ * Fedify follows at most five hints when dereferencing a portable URI
642
+ * (three for the key ID of an HTTP Signature), so list the preferred
643
+ * gateways first; there is no limit on the number of hints added here.
644
+ *
645
+ * [FEP-ef61]: https://w3id.org/fep/ef61
646
+ *
647
+ * @param portableId The `ap:` or `ap+ef61:` URI. Pass the raw string rather
648
+ * than a `URL` if its path may have `.` or `..` segments,
649
+ * because the `URL` class resolves them.
650
+ * @param gateways The gateways, e.g., the `gateways` of the actor that the
651
+ * URI refers to. Each has to be an HTTP(S) origin with no
652
+ * credentials, path, query, or fragment.
653
+ * @returns The portable URI with the hints, in the same internal `URL` form
654
+ * as {@link parseIri} returns. Use {@link formatIri} to get its
655
+ * canonical string.
656
+ * @throws {TypeError} If the portable ID is not a valid `ap:` or `ap+ef61:`
657
+ * URI, e.g., it is a compatible identifier, which must
658
+ * not have location hints; if its path has `.` or `..`
659
+ * segments, which the `URL` class cannot represent; or
660
+ * if a gateway is invalid.
661
+ * @since 2.4.0
662
+ */
663
+ declare function withGatewayHints(portableId: string | URL, gateways: Iterable<string | URL>): URL;
664
+ /**
665
+ * Returns a copy of an [FEP-ef61] portable ActivityPub URI without its
666
+ * location hints, i.e., `@gateway` query parameters and the legacy
667
+ * `gateways` parameter. The other query parameters, their order, and the
668
+ * fragment are kept, but percent-encoding is normalized the same way as
669
+ * {@link canonicalizePortableUri} does it.
670
+ *
671
+ * [FEP-ef61]: https://w3id.org/fep/ef61
672
+ *
673
+ * @param portableId The `ap:` or `ap+ef61:` URI. Pass the raw string rather
674
+ * than a `URL` if its path may have `.` or `..` segments,
675
+ * because the `URL` class resolves them.
676
+ * @returns The portable URI without the hints, in the same internal `URL`
677
+ * form as {@link parseIri} returns.
678
+ * @throws {TypeError} If the portable ID is not a valid `ap:` or `ap+ef61:`
679
+ * URI, or if its path has `.` or `..` segments, which
680
+ * the `URL` class cannot represent.
681
+ * @since 2.4.0
682
+ */
683
+ declare function withoutGatewayHints(portableId: string | URL): URL;
684
+ /**
685
+ * Gets the gateways in the `@gateway` location hints of an [FEP-ef61]
686
+ * portable ActivityPub URI, in order. Hints that are not valid gateways,
687
+ * i.e., HTTP(S) origins with no credentials, path, query, or fragment, are
688
+ * skipped, and so are duplicates. The legacy `gateways` parameter is not
689
+ * read.
690
+ *
691
+ * Unlike Fedify's dereferencing, which follows at most five hints, this
692
+ * returns all of them.
693
+ *
694
+ * [FEP-ef61]: https://w3id.org/fep/ef61
695
+ *
696
+ * @param portableId The `ap:` or `ap+ef61:` URI.
697
+ * @returns The gateways, e.g., `https://server1.example/`.
698
+ * @throws {TypeError} If the portable ID is not a valid `ap:` or `ap+ef61:`
699
+ * URI.
700
+ * @since 2.4.0
701
+ */
702
+ declare function getGatewayHints(portableId: string | URL): URL[];
703
+ /**
610
704
  * Validates a URL to prevent SSRF attacks.
611
705
  */
612
706
  declare function validatePublicUrl(url: string): Promise<void>;
@@ -614,4 +708,4 @@ declare function isValidPublicIPv4Address(address: string): boolean;
614
708
  declare function isValidPublicIPv6Address(address: string): boolean;
615
709
  declare function expandIPv6Address(address: string): string;
616
710
  //#endregion
617
- export { type AuthenticatedDocumentLoaderFactory, type CreateRequestOptions, type Decimal, type DidKeyVerificationMethod, type DocumentLoader, type DocumentLoaderFactory, type DocumentLoaderFactoryOptions, type DocumentLoaderOptions, FetchError, type FetchPortableMediaOptions, type GetDocumentLoaderOptions, type GetUserAgentOptions, type Json, LanguageString, type ParsedDigestMultibase, type ParsedHashlink, type PortableMedia, type PortableObjectReferrer, type PortableObjectVerification, type PortableObjectVerifier, type PortableObjectVerifierOptions, type PropertyPreprocessor, type PropertyPreprocessorContext, type RemoteDocument, UrlError, arePortableUrisEqual, canParseDecimal, canonicalizePortableUri, computeDigestMultibase, createActivityPubRequest, createHashlink, decodeMultibase, encodeMultibase, encodingFromBaseData, expandIPv6Address, exportDidKey, exportMultibaseKey, exportSpki, fetchPortableMedia, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, getRemoteDocument, getUserAgent, haveSameFe34Origin, haveSameIriOrigin, importDidKey, importMultibaseKey, importPem, importPkcs1, importSpki, isDecimal, isGatewayUrl, isValidPublicIPv4Address, isValidPublicIPv6Address, logRequest, parseDecimal, parseDidKeyVerificationMethod, parseDigestMultibase, parseGatewayUrl, parseHashlink, parseIri, parseJsonLdId, preloadedContexts, resolveDocumentLoaderTimeout, toCompatibleEf61Id, validatePublicUrl, verifyDigestMultibase, verifyHashlink, withDocumentLoaderTimeout };
711
+ export { type AuthenticatedDocumentLoaderFactory, type CreateRequestOptions, type Decimal, type DidKeyVerificationMethod, type DocumentLoader, type DocumentLoaderFactory, type DocumentLoaderFactoryOptions, type DocumentLoaderOptions, FetchError, type FetchPortableMediaOptions, type GetDocumentLoaderOptions, type GetUserAgentOptions, type Json, LanguageString, type ParsedDigestMultibase, type ParsedHashlink, type PortableMedia, type PortableObjectReferrer, type PortableObjectVerification, type PortableObjectVerifier, type PortableObjectVerifierOptions, type PropertyPreprocessor, type PropertyPreprocessorContext, type RemoteDocument, UrlError, arePortableUrisEqual, canParseDecimal, canonicalizePortableUri, computeDigestMultibase, createActivityPubRequest, createHashlink, decodeMultibase, encodeMultibase, encodingFromBaseData, expandIPv6Address, exportDidKey, exportMultibaseKey, exportSpki, fetchPortableMedia, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, getGatewayHints, getRemoteDocument, getUserAgent, haveSameFe34Origin, haveSameIriOrigin, importDidKey, importMultibaseKey, importPem, importPkcs1, importSpki, isDecimal, isGatewayUrl, isValidPublicIPv4Address, isValidPublicIPv6Address, logRequest, parseDecimal, parseDidKeyVerificationMethod, parseDigestMultibase, parseGatewayUrl, parseHashlink, parseIri, parseJsonLdId, preloadedContexts, resolveDocumentLoaderTimeout, toCompatibleEf61Id, validatePublicUrl, verifyDigestMultibase, verifyHashlink, withDocumentLoaderTimeout, withGatewayHints, withoutGatewayHints };
package/dist/mod.js CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
  import { t as preloadedContexts } from "./contexts-DPuJ4UYL.js";
3
- import { _ as validatePublicUrl, a as formatIri, c as haveSameFe34Origin, d as isValidPublicIPv4Address, f as isValidPublicIPv6Address, g as toCompatibleEf61Id, h as parseJsonLdId, i as expandIPv6Address, l as haveSameIriOrigin, m as parseIri, n as arePortableUrisEqual, o as fromCompatibleEf61Id, p as parseGatewayUrl, r as canonicalizePortableUri, s as getFe34Origin, t as UrlError, u as isGatewayUrl } from "./url-Be3moMor.js";
3
+ import { S as withoutGatewayHints, _ as parseIri, a as expandIPv6Address, b as validatePublicUrl, c as getFe34Origin, d as haveSameIriOrigin, f as isGatewayUrl, g as parseGatewayUrl, i as canonicalizePortableUri, l as getGatewayHints, m as isValidPublicIPv6Address, n as UrlError, o as formatIri, p as isValidPublicIPv4Address, r as arePortableUrisEqual, s as fromCompatibleEf61Id, u as haveSameFe34Origin, v as parseJsonLdId, x as withGatewayHints, y as toCompatibleEf61Id } from "./url-DqqDZY9i.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
6
6
  import process from "node:process";
@@ -12,7 +12,7 @@ import { PublicKeyInfo } from "pkijs";
12
12
  import baseX from "@multiformats/base-x";
13
13
  //#region deno.json
14
14
  var name = "@fedify/vocab-runtime";
15
- var version = "2.4.0-dev.2219+eb70f060";
15
+ var version = "2.4.0-dev.2233+abb4a6b2";
16
16
  //#endregion
17
17
  //#region src/request.ts
18
18
  /**
@@ -1720,4 +1720,4 @@ async function fetchPortableMedia(media, options = {}) {
1720
1720
  }, options.signal, timeout);
1721
1721
  }
1722
1722
  //#endregion
1723
- export { FetchError, LanguageString, UrlError, arePortableUrisEqual, canParseDecimal, canonicalizePortableUri, computeDigestMultibase, createActivityPubRequest, createHashlink, decodeMultibase, encodeMultibase, encodingFromBaseData, expandIPv6Address, exportDidKey, exportMultibaseKey, exportSpki, fetchPortableMedia, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, getRemoteDocument, getUserAgent, haveSameFe34Origin, haveSameIriOrigin, importDidKey, importMultibaseKey, importPem, importPkcs1, importSpki, isDecimal, isGatewayUrl, isValidPublicIPv4Address, isValidPublicIPv6Address, logRequest, parseDecimal, parseDidKeyVerificationMethod, parseDigestMultibase, parseGatewayUrl, parseHashlink, parseIri, parseJsonLdId, preloadedContexts, resolveDocumentLoaderTimeout, toCompatibleEf61Id, validatePublicUrl, verifyDigestMultibase, verifyHashlink, withDocumentLoaderTimeout };
1723
+ export { FetchError, LanguageString, UrlError, arePortableUrisEqual, canParseDecimal, canonicalizePortableUri, computeDigestMultibase, createActivityPubRequest, createHashlink, decodeMultibase, encodeMultibase, encodingFromBaseData, expandIPv6Address, exportDidKey, exportMultibaseKey, exportSpki, fetchPortableMedia, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, getGatewayHints, getRemoteDocument, getUserAgent, haveSameFe34Origin, haveSameIriOrigin, importDidKey, importMultibaseKey, importPem, importPkcs1, importSpki, isDecimal, isGatewayUrl, isValidPublicIPv4Address, isValidPublicIPv6Address, logRequest, parseDecimal, parseDidKeyVerificationMethod, parseDigestMultibase, parseGatewayUrl, parseHashlink, parseIri, parseJsonLdId, preloadedContexts, resolveDocumentLoaderTimeout, toCompatibleEf61Id, validatePublicUrl, verifyDigestMultibase, verifyHashlink, withDocumentLoaderTimeout, withGatewayHints, withoutGatewayHints };
@@ -1,4 +1,4 @@
1
- import { t as FetchError } from "./request-CWTG9_Xx.mjs";
1
+ import { t as FetchError } from "./request-D3elVRyB.mjs";
2
2
  import { getLogger } from "@logtape/logtape";
3
3
  //#region src/body.ts
4
4
  /** Decoded JSON body limit: 16 MiB. @internal */
@@ -1,4 +1,4 @@
1
- const require_request = require("./request-qq1N2cl6.cjs");
1
+ const require_request = require("./request-BQlkB8du.cjs");
2
2
  let _logtape_logtape = require("@logtape/logtape");
3
3
  //#region src/body.ts
4
4
  /** Decoded JSON body limit: 16 MiB. @internal */
@@ -1,4 +1,4 @@
1
- const require_body = require("./body-GaHFfkaq.cjs");
1
+ const require_body = require("./body-DQKTFMsH.cjs");
2
2
  let node_assert_strict = require("node:assert/strict");
3
3
  let node_test = require("node:test");
4
4
  let node_zlib = require("node:zlib");
@@ -1,4 +1,4 @@
1
- import { i as readBoundedText, t as BodyTooLargeError } from "./body-BzhhuMI0.mjs";
1
+ import { i as readBoundedText, t as BodyTooLargeError } from "./body-BEYUpKvX.mjs";
2
2
  import { deepStrictEqual, ok, rejects } from "node:assert/strict";
3
3
  import { test } from "node:test";
4
4
  import { gunzipSync, gzipSync } from "node:zlib";
@@ -1,10 +1,10 @@
1
- require("./url-B8xyTwP-.cjs");
2
- require("./docloader-sGr7kARm.cjs");
1
+ require("./url-CtHyzMR4.cjs");
2
+ require("./docloader-DHCYdBXf.cjs");
3
3
  require("./key-C-AYkdJJ.cjs");
4
4
  require("./multibase-B5Mea7Ip.cjs");
5
5
  require("./digest-3FeH2Y-Q.cjs");
6
6
  require("./langstr-C4Fl80ae.cjs");
7
- require("./portable-media-C_Q9GmqU.cjs");
7
+ require("./portable-media-DHaAEpaQ.cjs");
8
8
  let node_test = require("node:test");
9
9
  let node_assert = require("node:assert");
10
10
  //#region src/decimal.ts
@@ -1,10 +1,10 @@
1
- import "./url-DfS-cvwr.mjs";
2
- import "./docloader-DiRcdiPp.mjs";
1
+ import "./url-Cdu4ASgJ.mjs";
2
+ import "./docloader-ra1lbBfP.mjs";
3
3
  import "./key-C2Db_TAJ.mjs";
4
4
  import "./multibase-BPnF_L4e.mjs";
5
5
  import "./digest-COC7xDiQ.mjs";
6
6
  import "./langstr-CQ26J_L7.mjs";
7
- import "./portable-media-BERS_tX_.mjs";
7
+ import "./portable-media-1NjUQari.mjs";
8
8
  import { test } from "node:test";
9
9
  import { deepStrictEqual, throws } from "node:assert";
10
10
  //#region src/decimal.ts
@@ -1,8 +1,8 @@
1
- const require_request = require("./request-qq1N2cl6.cjs");
2
- const require_body = require("./body-GaHFfkaq.cjs");
1
+ const require_request = require("./request-BQlkB8du.cjs");
2
+ const require_body = require("./body-DQKTFMsH.cjs");
3
3
  const require_contexts = require("./contexts-DizzBjz4.cjs");
4
4
  const require_link = require("./link-DlKm8bEr.cjs");
5
- const require_url = require("./url-B8xyTwP-.cjs");
5
+ const require_url = require("./url-CtHyzMR4.cjs");
6
6
  let _logtape_logtape = require("@logtape/logtape");
7
7
  let _opentelemetry_api = require("@opentelemetry/api");
8
8
  //#region src/docloader.ts
@@ -1,8 +1,8 @@
1
- import { a as name, i as logRequest, n as createActivityPubRequest, o as version, t as FetchError } from "./request-CWTG9_Xx.mjs";
2
- import { i as readBoundedText, n as MAX_BODY_SIZE, r as readBoundedBytes, t as BodyTooLargeError } from "./body-BzhhuMI0.mjs";
1
+ import { a as name, i as logRequest, n as createActivityPubRequest, o as version, t as FetchError } from "./request-D3elVRyB.mjs";
2
+ import { i as readBoundedText, n as MAX_BODY_SIZE, r as readBoundedBytes, t as BodyTooLargeError } from "./body-BEYUpKvX.mjs";
3
3
  import { t as preloadedContexts } from "./contexts-CIKsin4e.mjs";
4
4
  import { t as HttpHeaderLink } from "./link-Cevmc87v.mjs";
5
- import { t as UrlError, v as validatePublicUrl } from "./url-DfS-cvwr.mjs";
5
+ import { n as UrlError, x as validatePublicUrl } from "./url-Cdu4ASgJ.mjs";
6
6
  import { getLogger } from "@logtape/logtape";
7
7
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
8
8
  //#region src/docloader.ts
@@ -1,8 +1,8 @@
1
1
  const require_rolldown_runtime = require("./rolldown-runtime-emK7D4bc.cjs");
2
- const require_request = require("./request-qq1N2cl6.cjs");
2
+ const require_request = require("./request-BQlkB8du.cjs");
3
3
  const require_contexts = require("./contexts-DizzBjz4.cjs");
4
- const require_url = require("./url-B8xyTwP-.cjs");
5
- const require_docloader = require("./docloader-sGr7kARm.cjs");
4
+ const require_url = require("./url-CtHyzMR4.cjs");
5
+ const require_docloader = require("./docloader-DHCYdBXf.cjs");
6
6
  let node_test = require("node:test");
7
7
  let node_zlib = require("node:zlib");
8
8
  let _logtape_logtape = require("@logtape/logtape");
@@ -1,7 +1,7 @@
1
- import { t as FetchError } from "./request-CWTG9_Xx.mjs";
1
+ import { t as FetchError } from "./request-D3elVRyB.mjs";
2
2
  import { n as cid_v1_default, t as preloadedContexts } from "./contexts-CIKsin4e.mjs";
3
- import { t as UrlError } from "./url-DfS-cvwr.mjs";
4
- import { i as withDocumentLoaderTimeout, n as getRemoteDocument, r as resolveDocumentLoaderTimeout, t as getDocumentLoader } from "./docloader-DiRcdiPp.mjs";
3
+ import { n as UrlError } from "./url-Cdu4ASgJ.mjs";
4
+ import { i as withDocumentLoaderTimeout, n as getRemoteDocument, r as resolveDocumentLoaderTimeout, t as getDocumentLoader } from "./docloader-ra1lbBfP.mjs";
5
5
  import { test } from "node:test";
6
6
  import { gzipSync } from "node:zlib";
7
7
  import { configure, reset } from "@logtape/logtape";
@@ -1,6 +1,6 @@
1
1
  const require_contexts = require("../contexts-DizzBjz4.cjs");
2
- const require_url = require("../url-B8xyTwP-.cjs");
3
- const require_jsonld_cache = require("../jsonld-cache-BQMn_ILF.cjs");
2
+ const require_url = require("../url-CtHyzMR4.cjs");
3
+ const require_jsonld_cache = require("../jsonld-cache-BTgizq9A.cjs");
4
4
  let node_assert_strict = require("node:assert/strict");
5
5
  let node_test = require("node:test");
6
6
  let _logtape_logtape = require("@logtape/logtape");
@@ -17,7 +17,6 @@ const logger = (0, _logtape_logtape.getLogger)([
17
17
  * a single accessor call from fanning out to many servers.
18
18
  */
19
19
  const MAX_GATEWAY_HINTS = 5;
20
- const LOCATION_HINT_PARAMETER = "@gateway";
21
20
  const BASELINE_CONTEXT_URLS = /* @__PURE__ */ new Set([
22
21
  "https://w3id.org/identity/v1",
23
22
  "https://www.w3.org/ns/activitystreams",
@@ -55,15 +54,15 @@ function getPortableGatewayCandidates(url, gateways) {
55
54
  };
56
55
  if (gateways != null) {
57
56
  for (const gateway of gateways) {
58
- const parsed = parseGatewayOrigin(gateway);
57
+ const parsed = require_url.parseGatewayOrigin(gateway);
59
58
  if (parsed == null) throw new TypeError("FEP-ef61 gateways must be HTTP(S) origins with no credentials, path, query, or fragment: " + String(gateway));
60
59
  add(parsed);
61
60
  }
62
61
  return candidates;
63
62
  }
64
- for (const hint of new URLSearchParams(url.search).getAll(LOCATION_HINT_PARAMETER)) {
63
+ for (const hint of new URLSearchParams(url.search).getAll(require_url.GATEWAY_HINT_PARAMETER)) {
65
64
  if (candidates.length >= MAX_GATEWAY_HINTS) break;
66
- const parsed = parseGatewayOrigin(hint);
65
+ const parsed = require_url.parseGatewayOrigin(hint);
67
66
  if (parsed == null) {
68
67
  logger.debug("Ignoring an invalid FEP-ef61 gateway hint {hint} in {url}.", {
69
68
  hint,
@@ -75,14 +74,6 @@ function getPortableGatewayCandidates(url, gateways) {
75
74
  }
76
75
  return candidates;
77
76
  }
78
- function parseGatewayOrigin(gateway) {
79
- let url;
80
- if (gateway instanceof URL) url = new URL(gateway.href);
81
- else if (typeof gateway === "string" && URL.canParse(gateway)) url = new URL(gateway);
82
- else return null;
83
- if (url.protocol !== "http:" && url.protocol !== "https:" || url.href !== `${url.origin}/`) return null;
84
- return url;
85
- }
86
77
  /**
87
78
  * Creates a context loader that returns the same context documents for the
88
79
  * whole dereference operation, so that the identity check, the proof
@@ -172,7 +163,7 @@ const hrefs = (urls) => urls.map((url) => url.href);
172
163
  ]) (0, node_assert_strict.throws)(() => getPortableGatewayCandidates(url, [gateway]), TypeError);
173
164
  });
174
165
  (0, node_test.test)("getPortableGatewayCandidates() reads @gateway hints", () => {
175
- (0, node_assert_strict.deepStrictEqual)(hrefs(getPortableGatewayCandidates(require_url.parseIri("ap://did:key:z6Mkabc/actor?page=1&@gateway=https%3A%2F%2Fa.example&gateways=https%3A%2F%2Flegacy.example&@gateway=not%20a%20URL&@gateway=https%3A%2F%2Fb.example%2Fpath&%40gateway=https%3A%2F%2Fb.example&@gateway=https%3A%2F%2Fa.example%2F"))), ["https://a.example/", "https://b.example/"]);
166
+ (0, node_assert_strict.deepStrictEqual)(hrefs(getPortableGatewayCandidates(require_url.parseIri("ap://did:key:z6Mkabc/actor?page=1&@gateway=https%3A%2F%2Fa.example&gateways=https%3A%2F%2Flegacy.example&@gateway=not%20a%20URL&@gateway=https%3A%2F%2Fb.example%2Fpath&@gateway=https%3A%2F%2Fquery.example%2F%3F&@gateway=https%3A%2F%2Ffragment.example%2F%23&%40gateway=https%3A%2F%2Fb.example&@gateway=https%3A%2F%2Fa.example%2F"))), ["https://a.example/", "https://b.example/"]);
176
167
  (0, node_assert_strict.deepStrictEqual)(getPortableGatewayCandidates(require_url.parseIri("ap://did:key:z6Mkabc/actor")), []);
177
168
  const hints = Array.from({ length: 7 }, (_, i) => `@gateway=https%3A%2F%2Fg${i}.example`);
178
169
  (0, node_assert_strict.deepStrictEqual)(hrefs(getPortableGatewayCandidates(require_url.parseIri(`ap://did:key:z6Mkabc/actor?${hints.join("&")}`))), [
@@ -183,6 +174,16 @@ const hrefs = (urls) => urls.map((url) => url.href);
183
174
  4
184
175
  ].map((i) => `https://g${i}.example/`));
185
176
  });
177
+ (0, node_test.test)("getPortableGatewayCandidates() reads hints from withGatewayHints()", () => {
178
+ const gateways = Array.from({ length: 7 }, (_, i) => `https://g${i}.example`);
179
+ (0, node_assert_strict.deepStrictEqual)(hrefs(getPortableGatewayCandidates(require_url.withGatewayHints("ap://did:key:z6Mkabc/actor?page=1", gateways))), [
180
+ 0,
181
+ 1,
182
+ 2,
183
+ 3,
184
+ 4
185
+ ].map((i) => `https://g${i}.example/`));
186
+ });
186
187
  (0, node_test.test)("createSnapshotContextLoader() does not nest released snapshots", async () => {
187
188
  const calls = [];
188
189
  const base = (url) => {