@fedify/vocab-runtime 2.4.0-dev.2228 → 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 +93 -1
  8. package/dist/mod.d.ts +93 -1
  9. package/dist/mod.js +3 -3
  10. package/dist/tests/{body-BmNV5g5S.mjs → body-BEYUpKvX.mjs} +1 -1
  11. package/dist/tests/{body-BQZL1aJ1.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-BnuGCXLg.cjs → docloader-DHCYdBXf.cjs} +3 -3
  17. package/dist/tests/{docloader-ButlYQxr.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 +15 -14
  21. package/dist/tests/internal/portable-dereference.test.mjs +13 -12
  22. package/dist/tests/{jsonld-cache-C7mkqjiE.mjs → jsonld-cache-BKrsWPCQ.mjs} +1 -1
  23. package/dist/tests/{jsonld-cache-CXA76Xi6.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-xFDDMte7.mjs → portable-media-1NjUQari.mjs} +3 -3
  27. package/dist/tests/{portable-media-CFrGpz-3.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-Cme6ShRu.cjs → request-BQlkB8du.cjs} +1 -1
  31. package/dist/tests/{request-2GDEEJng.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-R9TTZ67B.mjs → url-Cdu4ASgJ.mjs} +161 -1
  35. package/dist/tests/{url-B6WlNhra.cjs → url-CtHyzMR4.cjs} +190 -0
  36. package/dist/tests/url.test.cjs +119 -1
  37. package/dist/tests/url.test.mjs +119 -1
  38. package/dist/{url-CHAbe3hE.cjs → url-BHfQuRdy.cjs} +190 -0
  39. package/dist/{url-BfguNa6K.js → url-DqqDZY9i.js} +161 -1
  40. package/package.json +1 -1
  41. package/src/internal/portable-dereference.test.ts +14 -1
  42. package/src/internal/portable-dereference.ts +3 -14
  43. package/src/mod.ts +3 -0
  44. package/src/url.test.ts +295 -0
  45. package/src/url.ts +232 -0
package/deno.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab-runtime",
3
- "version": "2.4.0-dev.2228+6d017d55",
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-CHAbe3hE.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-BfguNa6K.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-CHAbe3hE.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 (!require_url.isGatewayUrl(url)) 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, u as isGatewayUrl } from "../url-BfguNa6K.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 (!isGatewayUrl(url)) 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-CHAbe3hE.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.2228+6d017d55";
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
@@ -610,6 +610,98 @@ declare function fromCompatibleEf61Id(input: string | URL): URL | null;
610
610
  */
611
611
  declare function toCompatibleEf61Id(portableId: string | URL, gateway: string | URL): URL;
612
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
+ /**
613
705
  * Validates a URL to prevent SSRF attacks.
614
706
  */
615
707
  declare function validatePublicUrl(url: string): Promise<void>;
@@ -617,4 +709,4 @@ declare function isValidPublicIPv4Address(address: string): boolean;
617
709
  declare function isValidPublicIPv6Address(address: string): boolean;
618
710
  declare function expandIPv6Address(address: string): string;
619
711
  //#endregion
620
- 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
@@ -609,6 +609,98 @@ declare function fromCompatibleEf61Id(input: string | URL): URL | null;
609
609
  */
610
610
  declare function toCompatibleEf61Id(portableId: string | URL, gateway: string | URL): URL;
611
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
+ /**
612
704
  * Validates a URL to prevent SSRF attacks.
613
705
  */
614
706
  declare function validatePublicUrl(url: string): Promise<void>;
@@ -616,4 +708,4 @@ declare function isValidPublicIPv4Address(address: string): boolean;
616
708
  declare function isValidPublicIPv6Address(address: string): boolean;
617
709
  declare function expandIPv6Address(address: string): string;
618
710
  //#endregion
619
- 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-BfguNa6K.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.2228+6d017d55";
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-2GDEEJng.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-Cme6ShRu.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-BQZL1aJ1.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-BmNV5g5S.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-B6WlNhra.cjs");
2
- require("./docloader-BnuGCXLg.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-CFrGpz-3.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-R9TTZ67B.mjs";
2
- import "./docloader-ButlYQxr.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-xFDDMte7.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-Cme6ShRu.cjs");
2
- const require_body = require("./body-BQZL1aJ1.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-B6WlNhra.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-2GDEEJng.mjs";
2
- import { i as readBoundedText, n as MAX_BODY_SIZE, r as readBoundedBytes, t as BodyTooLargeError } from "./body-BmNV5g5S.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-R9TTZ67B.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-Cme6ShRu.cjs");
2
+ const require_request = require("./request-BQlkB8du.cjs");
3
3
  const require_contexts = require("./contexts-DizzBjz4.cjs");
4
- const require_url = require("./url-B6WlNhra.cjs");
5
- const require_docloader = require("./docloader-BnuGCXLg.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-2GDEEJng.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-R9TTZ67B.mjs";
4
- import { i as withDocumentLoaderTimeout, n as getRemoteDocument, r as resolveDocumentLoaderTimeout, t as getDocumentLoader } from "./docloader-ButlYQxr.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-B6WlNhra.cjs");
3
- const require_jsonld_cache = require("../jsonld-cache-CXA76Xi6.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 (!require_url.isGatewayUrl(url)) 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
@@ -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) => {
@@ -1,6 +1,6 @@
1
1
  import { t as preloadedContexts } from "../contexts-CIKsin4e.mjs";
2
- import { a as formatIri, m as parseIri, u as isGatewayUrl } from "../url-R9TTZ67B.mjs";
3
- import { n as createScopedContextLoader, o as registerDocumentLoaderWrapper, s as unwrapReleasedDocumentLoader } from "../jsonld-cache-C7mkqjiE.mjs";
2
+ import { S as withGatewayHints, _ as parseIri, h as parseGatewayOrigin, o as formatIri, t as GATEWAY_HINT_PARAMETER } from "../url-Cdu4ASgJ.mjs";
3
+ import { n as createScopedContextLoader, o as registerDocumentLoaderWrapper, s as unwrapReleasedDocumentLoader } from "../jsonld-cache-BKrsWPCQ.mjs";
4
4
  import { deepStrictEqual, ok, throws } from "node:assert/strict";
5
5
  import { test } from "node:test";
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",
@@ -61,7 +60,7 @@ function getPortableGatewayCandidates(url, gateways) {
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(GATEWAY_HINT_PARAMETER)) {
65
64
  if (candidates.length >= MAX_GATEWAY_HINTS) break;
66
65
  const parsed = parseGatewayOrigin(hint);
67
66
  if (parsed == null) {
@@ -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 (!isGatewayUrl(url)) 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
@@ -183,6 +174,16 @@ test("getPortableGatewayCandidates() reads @gateway hints", () => {
183
174
  4
184
175
  ].map((i) => `https://g${i}.example/`));
185
176
  });
177
+ test("getPortableGatewayCandidates() reads hints from withGatewayHints()", () => {
178
+ const gateways = Array.from({ length: 7 }, (_, i) => `https://g${i}.example`);
179
+ deepStrictEqual(hrefs(getPortableGatewayCandidates(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
  test("createSnapshotContextLoader() does not nest released snapshots", async () => {
187
188
  const calls = [];
188
189
  const base = (url) => {