@fedify/vocab-runtime 2.4.0-dev.2228 → 2.4.0-dev.2240

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 (57) 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 +6 -18
  5. package/dist/internal/portable-dereference.js +4 -16
  6. package/dist/mod.cjs +5 -2
  7. package/dist/mod.d.cts +107 -4
  8. package/dist/mod.d.ts +107 -4
  9. package/dist/mod.js +3 -3
  10. package/dist/tests/{body-BmNV5g5S.mjs → body-C00dWiYW.mjs} +1 -1
  11. package/dist/tests/{body-BQZL1aJ1.cjs → body-DHk-vT0I.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-ButlYQxr.mjs → docloader-Bi982uOa.mjs} +3 -3
  17. package/dist/tests/{docloader-BnuGCXLg.cjs → docloader-gX-8knd3.cjs} +3 -3
  18. package/dist/tests/docloader.test.cjs +59 -65
  19. package/dist/tests/docloader.test.mjs +38 -44
  20. package/dist/tests/internal/portable-dereference.test.cjs +30 -157
  21. package/dist/tests/internal/portable-dereference.test.mjs +13 -140
  22. package/dist/tests/{jsonld-cache-C7mkqjiE.mjs → jsonld-cache-CKfgtxPB.mjs} +1 -1
  23. package/dist/tests/{jsonld-cache-CXA76Xi6.cjs → jsonld-cache-DzxT9IXH.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/multibase/multibase.test.cjs +7 -7
  27. package/dist/tests/multibase/multibase.test.mjs +5 -5
  28. package/dist/tests/portable-dereference-C_cwGjz6.mjs +132 -0
  29. package/dist/tests/portable-dereference-CyFYTBnr.cjs +149 -0
  30. package/dist/tests/{portable-media-CFrGpz-3.cjs → portable-media-B39cvJGK.cjs} +3 -3
  31. package/dist/tests/{portable-media-xFDDMte7.mjs → portable-media-l0ET9Pq8.mjs} +3 -3
  32. package/dist/tests/portable-media.test.cjs +1 -1
  33. package/dist/tests/portable-media.test.mjs +1 -1
  34. package/dist/tests/portable-workers.test.cjs +36 -0
  35. package/dist/tests/portable-workers.test.d.cts +2 -0
  36. package/dist/tests/portable-workers.test.d.mts +2 -0
  37. package/dist/tests/portable-workers.test.mjs +35 -0
  38. package/dist/tests/{request-2GDEEJng.mjs → request-BRPiDcmG.mjs} +1 -1
  39. package/dist/tests/{request-Cme6ShRu.cjs → request-fo2l2yJS.cjs} +1 -1
  40. package/dist/tests/request.test.cjs +1 -1
  41. package/dist/tests/request.test.mjs +1 -1
  42. package/dist/tests/{url-R9TTZ67B.mjs → url-CWC-NI3a.mjs} +224 -8
  43. package/dist/tests/{url-B6WlNhra.cjs → url-DsMBGqCc.cjs} +253 -7
  44. package/dist/tests/url.test.cjs +192 -1
  45. package/dist/tests/url.test.mjs +192 -1
  46. package/dist/{url-CHAbe3hE.cjs → url-BJMXv2kE.cjs} +253 -7
  47. package/dist/{url-BfguNa6K.js → url-CZm2-7ZO.js} +224 -8
  48. package/package.json +6 -3
  49. package/scripts/test-bun.mjs +17 -0
  50. package/src/docloader.test.ts +82 -74
  51. package/src/internal/portable-dereference.test.ts +14 -1
  52. package/src/internal/portable-dereference.ts +5 -19
  53. package/src/mod.ts +3 -0
  54. package/src/multibase/multibase.test.ts +5 -5
  55. package/src/portable-workers.test.ts +81 -0
  56. package/src/url.test.ts +385 -0
  57. package/src/url.ts +347 -13
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.2240+79613fa6",
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 --allow-env=LOG"
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-BJMXv2kE.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-CZm2-7ZO.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-BJMXv2kE.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",
@@ -138,13 +137,10 @@ function copyPortableProvenance(from, to) {
138
137
  * Semantic Versioning decisions.
139
138
  */
140
139
  function parseCompatibleEf61Reference(url) {
141
- if (typeof url === "string") {
142
- if (!URL.canParse(url)) return void 0;
143
- url = new URL(url);
144
- }
145
140
  try {
146
141
  const id = require_url.fromCompatibleEf61Id(url);
147
142
  if (id == null) return void 0;
143
+ if (typeof url === "string") url = new URL(url);
148
144
  return {
149
145
  id,
150
146
  gateway: new URL(url.origin)
@@ -152,7 +148,7 @@ function parseCompatibleEf61Reference(url) {
152
148
  } catch (error) {
153
149
  if (error instanceof TypeError) {
154
150
  logger.debug("Invalid FEP-ef61 compatible identifier {url}: {error}", {
155
- url: url.href,
151
+ url: typeof url === "string" ? url : url.href,
156
152
  error
157
153
  });
158
154
  return null;
@@ -359,15 +355,15 @@ function getPortableGatewayCandidates(url, gateways) {
359
355
  };
360
356
  if (gateways != null) {
361
357
  for (const gateway of gateways) {
362
- const parsed = parseGatewayOrigin(gateway);
358
+ const parsed = require_url.parseGatewayOrigin(gateway);
363
359
  if (parsed == null) throw new TypeError("FEP-ef61 gateways must be HTTP(S) origins with no credentials, path, query, or fragment: " + String(gateway));
364
360
  add(parsed);
365
361
  }
366
362
  return candidates;
367
363
  }
368
- for (const hint of new URLSearchParams(url.search).getAll(LOCATION_HINT_PARAMETER)) {
364
+ for (const hint of new URLSearchParams(url.search).getAll(require_url.GATEWAY_HINT_PARAMETER)) {
369
365
  if (candidates.length >= MAX_GATEWAY_HINTS) break;
370
- const parsed = parseGatewayOrigin(hint);
366
+ const parsed = require_url.parseGatewayOrigin(hint);
371
367
  if (parsed == null) {
372
368
  logger.debug("Ignoring an invalid FEP-ef61 gateway hint {hint} in {url}.", {
373
369
  hint,
@@ -379,14 +375,6 @@ function getPortableGatewayCandidates(url, gateways) {
379
375
  }
380
376
  return candidates;
381
377
  }
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
378
  /**
391
379
  * Creates a context loader that returns the same context documents for the
392
380
  * 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-CZm2-7ZO.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",
@@ -137,13 +136,10 @@ function copyPortableProvenance(from, to) {
137
136
  * Semantic Versioning decisions.
138
137
  */
139
138
  function parseCompatibleEf61Reference(url) {
140
- if (typeof url === "string") {
141
- if (!URL.canParse(url)) return void 0;
142
- url = new URL(url);
143
- }
144
139
  try {
145
140
  const id = fromCompatibleEf61Id(url);
146
141
  if (id == null) return void 0;
142
+ if (typeof url === "string") url = new URL(url);
147
143
  return {
148
144
  id,
149
145
  gateway: new URL(url.origin)
@@ -151,7 +147,7 @@ function parseCompatibleEf61Reference(url) {
151
147
  } catch (error) {
152
148
  if (error instanceof TypeError) {
153
149
  logger.debug("Invalid FEP-ef61 compatible identifier {url}: {error}", {
154
- url: url.href,
150
+ url: typeof url === "string" ? url : url.href,
155
151
  error
156
152
  });
157
153
  return null;
@@ -364,7 +360,7 @@ function getPortableGatewayCandidates(url, gateways) {
364
360
  }
365
361
  return candidates;
366
362
  }
367
- for (const hint of new URLSearchParams(url.search).getAll(LOCATION_HINT_PARAMETER)) {
363
+ for (const hint of new URLSearchParams(url.search).getAll(GATEWAY_HINT_PARAMETER)) {
368
364
  if (candidates.length >= MAX_GATEWAY_HINTS) break;
369
365
  const parsed = parseGatewayOrigin(hint);
370
366
  if (parsed == null) {
@@ -378,14 +374,6 @@ function getPortableGatewayCandidates(url, gateways) {
378
374
  }
379
375
  return candidates;
380
376
  }
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
377
  /**
390
378
  * Creates a context loader that returns the same context documents for the
391
379
  * 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-BJMXv2kE.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.2240+79613fa6";
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
@@ -467,10 +467,19 @@ declare class UrlError extends Error {
467
467
  declare function parseJsonLdId(id: string | undefined, base?: string | URL): URL | undefined;
468
468
  /**
469
469
  * Parses an IRI as a URL, including FEP-ef61 portable ActivityPub IRIs.
470
+ * Portable URI and FEP-ef61 compatible identifier strings whose path contains
471
+ * a `.` or `..` segment, including percent-encoded spellings, throw a
472
+ * `TypeError`: JavaScript `URL` would otherwise identify a different object.
473
+ * This also applies to compatible identifier strings used as relative bases.
474
+ * A `URL` argument may already have lost such segments before this function
475
+ * receives it.
470
476
  */
471
477
  declare function parseIri(iri: string | URL, base?: string | URL): URL;
472
478
  /**
473
479
  * Formats a URL as an IRI, including FEP-ef61 portable ActivityPub IRIs.
480
+ * Portable URI and FEP-ef61 compatible identifier strings with dot segments
481
+ * throw a `TypeError` because their paths cannot be represented by JavaScript
482
+ * `URL` without normalization.
474
483
  */
475
484
  declare function formatIri(iri: string | URL): string;
476
485
  /**
@@ -570,8 +579,9 @@ declare function parseGatewayUrl(url: string): URL;
570
579
  * malformed, e.g., it has an invalid DID, no object path,
571
580
  * invalid percent-encoding, credentials, or location
572
581
  * hints (`@gateway` query parameters, or the legacy
573
- * `gateways` parameter), which FEP-ef61 forbids in
574
- * compatible identifiers.
582
+ * `gateways` parameter), or its raw string path would
583
+ * change during URL parsing. Already-parsed `URL`
584
+ * arguments cannot reveal segments lost by their parser.
575
585
  * @since 2.4.0
576
586
  */
577
587
  declare function fromCompatibleEf61Id(input: string | URL): URL | null;
@@ -603,13 +613,106 @@ declare function fromCompatibleEf61Id(input: string | URL): URL | null;
603
613
  * @returns The compatible identifier.
604
614
  * @throws {TypeError} If the portable ID is not a valid `ap:` or `ap+ef61:`
605
615
  * URI, if its path has `.` or `..` segments (which
606
- * HTTP(S) URLs cannot represent), or if the gateway is
616
+ * HTTP(S) URLs cannot represent without changing the
617
+ * identified object), or if the gateway is
607
618
  * not an HTTP(S) origin with no credentials, path, query,
608
619
  * or fragment.
609
620
  * @since 2.4.0
610
621
  */
611
622
  declare function toCompatibleEf61Id(portableId: string | URL, gateway: string | URL): URL;
612
623
  /**
624
+ * Returns a copy of an [FEP-ef61] portable ActivityPub URI with `@gateway`
625
+ * location hints for the given gateways, which tell consumers where they can
626
+ * retrieve the object. Put hints on *references* to portable actors, e.g.,
627
+ * in `actor`, `attributedTo`, `to`, or `cc`, when constructing an object, as
628
+ * FEP-ef61 recommends:
629
+ *
630
+ * ~~~~ typescript
631
+ * withGatewayHints("ap://did:key:z6Mk.../actor", [
632
+ * "https://server1.example",
633
+ * "https://server2.example",
634
+ * ]);
635
+ * // ap+ef61://did:key:z6Mk.../actor?@gateway=https%3A%2F%2Fserver1.example&@gateway=https%3A%2F%2Fserver2.example
636
+ * ~~~~
637
+ *
638
+ * Do not put hints on an object's own `id`. Hints do not change the
639
+ * identity of a portable URI, since FEP-ef61 drops the query when comparing
640
+ * portable URIs, but implementations that do not canonicalize portable URIs
641
+ * would take a hinted ID for another object. Add hints before signing the
642
+ * object, since its Object Integrity Proof covers its references too.
643
+ *
644
+ * The hints that the URI already has, including the legacy `gateways`
645
+ * parameter, are replaced. Each gateway becomes a `@gateway` query
646
+ * parameter whose value is its URI-encoded origin, e.g.,
647
+ * `@gateway=https%3A%2F%2Fserver1.example`, in the given order after the
648
+ * other query parameters. Duplicate gateways are dropped, and an empty list
649
+ * removes the hints as {@link withoutGatewayHints} does. The other query
650
+ * parameters, their order, and the fragment are kept, but percent-encoding
651
+ * is normalized the same way as {@link canonicalizePortableUri} does it.
652
+ *
653
+ * Fedify follows at most five hints when dereferencing a portable URI
654
+ * (three for the key ID of an HTTP Signature), so list the preferred
655
+ * gateways first; there is no limit on the number of hints added here.
656
+ *
657
+ * [FEP-ef61]: https://w3id.org/fep/ef61
658
+ *
659
+ * @param portableId The `ap:` or `ap+ef61:` URI. Pass the raw string rather
660
+ * than a `URL` if its path may have `.` or `..` segments,
661
+ * because the `URL` class resolves them.
662
+ * @param gateways The gateways, e.g., the `gateways` of the actor that the
663
+ * URI refers to. Each has to be an HTTP(S) origin with no
664
+ * credentials, path, query, or fragment.
665
+ * @returns The portable URI with the hints, in the same internal `URL` form
666
+ * as {@link parseIri} returns. Use {@link formatIri} to get its
667
+ * canonical string.
668
+ * @throws {TypeError} If the portable ID is not a valid `ap:` or `ap+ef61:`
669
+ * URI, e.g., it is a compatible identifier, which must
670
+ * not have location hints; if its path has `.` or `..`
671
+ * segments, which the `URL` class cannot represent; or
672
+ * if a gateway is invalid.
673
+ * @since 2.4.0
674
+ */
675
+ declare function withGatewayHints(portableId: string | URL, gateways: Iterable<string | URL>): URL;
676
+ /**
677
+ * Returns a copy of an [FEP-ef61] portable ActivityPub URI without its
678
+ * location hints, i.e., `@gateway` query parameters and the legacy
679
+ * `gateways` parameter. The other query parameters, their order, and the
680
+ * fragment are kept, but percent-encoding is normalized the same way as
681
+ * {@link canonicalizePortableUri} does it.
682
+ *
683
+ * [FEP-ef61]: https://w3id.org/fep/ef61
684
+ *
685
+ * @param portableId The `ap:` or `ap+ef61:` URI. Pass the raw string rather
686
+ * than a `URL` if its path may have `.` or `..` segments,
687
+ * because the `URL` class resolves them.
688
+ * @returns The portable URI without the hints, in the same internal `URL`
689
+ * form as {@link parseIri} returns.
690
+ * @throws {TypeError} If the portable ID is not a valid `ap:` or `ap+ef61:`
691
+ * URI, or if its path has `.` or `..` segments, which
692
+ * the `URL` class cannot represent.
693
+ * @since 2.4.0
694
+ */
695
+ declare function withoutGatewayHints(portableId: string | URL): URL;
696
+ /**
697
+ * Gets the gateways in the `@gateway` location hints of an [FEP-ef61]
698
+ * portable ActivityPub URI, in order. Hints that are not valid gateways,
699
+ * i.e., HTTP(S) origins with no credentials, path, query, or fragment, are
700
+ * skipped, and so are duplicates. The legacy `gateways` parameter is not
701
+ * read.
702
+ *
703
+ * Unlike Fedify's dereferencing, which follows at most five hints, this
704
+ * returns all of them.
705
+ *
706
+ * [FEP-ef61]: https://w3id.org/fep/ef61
707
+ *
708
+ * @param portableId The `ap:` or `ap+ef61:` URI.
709
+ * @returns The gateways, e.g., `https://server1.example/`.
710
+ * @throws {TypeError} If the portable ID is not a valid `ap:` or `ap+ef61:`
711
+ * URI.
712
+ * @since 2.4.0
713
+ */
714
+ declare function getGatewayHints(portableId: string | URL): URL[];
715
+ /**
613
716
  * Validates a URL to prevent SSRF attacks.
614
717
  */
615
718
  declare function validatePublicUrl(url: string): Promise<void>;
@@ -617,4 +720,4 @@ declare function isValidPublicIPv4Address(address: string): boolean;
617
720
  declare function isValidPublicIPv6Address(address: string): boolean;
618
721
  declare function expandIPv6Address(address: string): string;
619
722
  //#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 };
723
+ 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
@@ -466,10 +466,19 @@ declare class UrlError extends Error {
466
466
  declare function parseJsonLdId(id: string | undefined, base?: string | URL): URL | undefined;
467
467
  /**
468
468
  * Parses an IRI as a URL, including FEP-ef61 portable ActivityPub IRIs.
469
+ * Portable URI and FEP-ef61 compatible identifier strings whose path contains
470
+ * a `.` or `..` segment, including percent-encoded spellings, throw a
471
+ * `TypeError`: JavaScript `URL` would otherwise identify a different object.
472
+ * This also applies to compatible identifier strings used as relative bases.
473
+ * A `URL` argument may already have lost such segments before this function
474
+ * receives it.
469
475
  */
470
476
  declare function parseIri(iri: string | URL, base?: string | URL): URL;
471
477
  /**
472
478
  * Formats a URL as an IRI, including FEP-ef61 portable ActivityPub IRIs.
479
+ * Portable URI and FEP-ef61 compatible identifier strings with dot segments
480
+ * throw a `TypeError` because their paths cannot be represented by JavaScript
481
+ * `URL` without normalization.
473
482
  */
474
483
  declare function formatIri(iri: string | URL): string;
475
484
  /**
@@ -569,8 +578,9 @@ declare function parseGatewayUrl(url: string): URL;
569
578
  * malformed, e.g., it has an invalid DID, no object path,
570
579
  * invalid percent-encoding, credentials, or location
571
580
  * hints (`@gateway` query parameters, or the legacy
572
- * `gateways` parameter), which FEP-ef61 forbids in
573
- * compatible identifiers.
581
+ * `gateways` parameter), or its raw string path would
582
+ * change during URL parsing. Already-parsed `URL`
583
+ * arguments cannot reveal segments lost by their parser.
574
584
  * @since 2.4.0
575
585
  */
576
586
  declare function fromCompatibleEf61Id(input: string | URL): URL | null;
@@ -602,13 +612,106 @@ declare function fromCompatibleEf61Id(input: string | URL): URL | null;
602
612
  * @returns The compatible identifier.
603
613
  * @throws {TypeError} If the portable ID is not a valid `ap:` or `ap+ef61:`
604
614
  * URI, if its path has `.` or `..` segments (which
605
- * HTTP(S) URLs cannot represent), or if the gateway is
615
+ * HTTP(S) URLs cannot represent without changing the
616
+ * identified object), or if the gateway is
606
617
  * not an HTTP(S) origin with no credentials, path, query,
607
618
  * or fragment.
608
619
  * @since 2.4.0
609
620
  */
610
621
  declare function toCompatibleEf61Id(portableId: string | URL, gateway: string | URL): URL;
611
622
  /**
623
+ * Returns a copy of an [FEP-ef61] portable ActivityPub URI with `@gateway`
624
+ * location hints for the given gateways, which tell consumers where they can
625
+ * retrieve the object. Put hints on *references* to portable actors, e.g.,
626
+ * in `actor`, `attributedTo`, `to`, or `cc`, when constructing an object, as
627
+ * FEP-ef61 recommends:
628
+ *
629
+ * ~~~~ typescript
630
+ * withGatewayHints("ap://did:key:z6Mk.../actor", [
631
+ * "https://server1.example",
632
+ * "https://server2.example",
633
+ * ]);
634
+ * // ap+ef61://did:key:z6Mk.../actor?@gateway=https%3A%2F%2Fserver1.example&@gateway=https%3A%2F%2Fserver2.example
635
+ * ~~~~
636
+ *
637
+ * Do not put hints on an object's own `id`. Hints do not change the
638
+ * identity of a portable URI, since FEP-ef61 drops the query when comparing
639
+ * portable URIs, but implementations that do not canonicalize portable URIs
640
+ * would take a hinted ID for another object. Add hints before signing the
641
+ * object, since its Object Integrity Proof covers its references too.
642
+ *
643
+ * The hints that the URI already has, including the legacy `gateways`
644
+ * parameter, are replaced. Each gateway becomes a `@gateway` query
645
+ * parameter whose value is its URI-encoded origin, e.g.,
646
+ * `@gateway=https%3A%2F%2Fserver1.example`, in the given order after the
647
+ * other query parameters. Duplicate gateways are dropped, and an empty list
648
+ * removes the hints as {@link withoutGatewayHints} does. The other query
649
+ * parameters, their order, and the fragment are kept, but percent-encoding
650
+ * is normalized the same way as {@link canonicalizePortableUri} does it.
651
+ *
652
+ * Fedify follows at most five hints when dereferencing a portable URI
653
+ * (three for the key ID of an HTTP Signature), so list the preferred
654
+ * gateways first; there is no limit on the number of hints added here.
655
+ *
656
+ * [FEP-ef61]: https://w3id.org/fep/ef61
657
+ *
658
+ * @param portableId The `ap:` or `ap+ef61:` URI. Pass the raw string rather
659
+ * than a `URL` if its path may have `.` or `..` segments,
660
+ * because the `URL` class resolves them.
661
+ * @param gateways The gateways, e.g., the `gateways` of the actor that the
662
+ * URI refers to. Each has to be an HTTP(S) origin with no
663
+ * credentials, path, query, or fragment.
664
+ * @returns The portable URI with the hints, in the same internal `URL` form
665
+ * as {@link parseIri} returns. Use {@link formatIri} to get its
666
+ * canonical string.
667
+ * @throws {TypeError} If the portable ID is not a valid `ap:` or `ap+ef61:`
668
+ * URI, e.g., it is a compatible identifier, which must
669
+ * not have location hints; if its path has `.` or `..`
670
+ * segments, which the `URL` class cannot represent; or
671
+ * if a gateway is invalid.
672
+ * @since 2.4.0
673
+ */
674
+ declare function withGatewayHints(portableId: string | URL, gateways: Iterable<string | URL>): URL;
675
+ /**
676
+ * Returns a copy of an [FEP-ef61] portable ActivityPub URI without its
677
+ * location hints, i.e., `@gateway` query parameters and the legacy
678
+ * `gateways` parameter. The other query parameters, their order, and the
679
+ * fragment are kept, but percent-encoding is normalized the same way as
680
+ * {@link canonicalizePortableUri} does it.
681
+ *
682
+ * [FEP-ef61]: https://w3id.org/fep/ef61
683
+ *
684
+ * @param portableId The `ap:` or `ap+ef61:` URI. Pass the raw string rather
685
+ * than a `URL` if its path may have `.` or `..` segments,
686
+ * because the `URL` class resolves them.
687
+ * @returns The portable URI without the hints, in the same internal `URL`
688
+ * form as {@link parseIri} returns.
689
+ * @throws {TypeError} If the portable ID is not a valid `ap:` or `ap+ef61:`
690
+ * URI, or if its path has `.` or `..` segments, which
691
+ * the `URL` class cannot represent.
692
+ * @since 2.4.0
693
+ */
694
+ declare function withoutGatewayHints(portableId: string | URL): URL;
695
+ /**
696
+ * Gets the gateways in the `@gateway` location hints of an [FEP-ef61]
697
+ * portable ActivityPub URI, in order. Hints that are not valid gateways,
698
+ * i.e., HTTP(S) origins with no credentials, path, query, or fragment, are
699
+ * skipped, and so are duplicates. The legacy `gateways` parameter is not
700
+ * read.
701
+ *
702
+ * Unlike Fedify's dereferencing, which follows at most five hints, this
703
+ * returns all of them.
704
+ *
705
+ * [FEP-ef61]: https://w3id.org/fep/ef61
706
+ *
707
+ * @param portableId The `ap:` or `ap+ef61:` URI.
708
+ * @returns The gateways, e.g., `https://server1.example/`.
709
+ * @throws {TypeError} If the portable ID is not a valid `ap:` or `ap+ef61:`
710
+ * URI.
711
+ * @since 2.4.0
712
+ */
713
+ declare function getGatewayHints(portableId: string | URL): URL[];
714
+ /**
612
715
  * Validates a URL to prevent SSRF attacks.
613
716
  */
614
717
  declare function validatePublicUrl(url: string): Promise<void>;
@@ -616,4 +719,4 @@ declare function isValidPublicIPv4Address(address: string): boolean;
616
719
  declare function isValidPublicIPv6Address(address: string): boolean;
617
720
  declare function expandIPv6Address(address: string): string;
618
721
  //#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 };
722
+ 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-CZm2-7ZO.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.2240+79613fa6";
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-BRPiDcmG.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-fo2l2yJS.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-DHk-vT0I.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-C00dWiYW.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-DsMBGqCc.cjs");
2
+ require("./docloader-gX-8knd3.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-B39cvJGK.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-CWC-NI3a.mjs";
2
+ import "./docloader-Bi982uOa.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-l0ET9Pq8.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
- 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-BRPiDcmG.mjs";
2
+ import { i as readBoundedText, n as MAX_BODY_SIZE, r as readBoundedBytes, t as BodyTooLargeError } from "./body-C00dWiYW.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-CWC-NI3a.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
- const require_request = require("./request-Cme6ShRu.cjs");
2
- const require_body = require("./body-BQZL1aJ1.cjs");
1
+ const require_request = require("./request-fo2l2yJS.cjs");
2
+ const require_body = require("./body-DHk-vT0I.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-DsMBGqCc.cjs");
6
6
  let _logtape_logtape = require("@logtape/logtape");
7
7
  let _opentelemetry_api = require("@opentelemetry/api");
8
8
  //#region src/docloader.ts