@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
@@ -1,4 +1,4 @@
1
- import { a as formatIri, c as haveSameFe34Origin, l as haveSameIriOrigin } from "./url-R9TTZ67B.mjs";
1
+ import { d as haveSameIriOrigin, o as formatIri, u as haveSameFe34Origin } from "./url-Cdu4ASgJ.mjs";
2
2
  import jsonld from "jsonld/dist/jsonld.esm.js";
3
3
  //#region src/jsonld.ts
4
4
  var jsonld_default = jsonld;
@@ -1,5 +1,5 @@
1
1
  const require_rolldown_runtime = require("./rolldown-runtime-emK7D4bc.cjs");
2
- const require_url = require("./url-B6WlNhra.cjs");
2
+ const require_url = require("./url-CtHyzMR4.cjs");
3
3
  let jsonld_dist_jsonld_esm_js = require("jsonld/dist/jsonld.esm.js");
4
4
  jsonld_dist_jsonld_esm_js = require_rolldown_runtime.__toESM(jsonld_dist_jsonld_esm_js, 1);
5
5
  //#region src/jsonld.ts
@@ -1,5 +1,5 @@
1
- const require_url = require("./url-B6WlNhra.cjs");
2
- const require_jsonld_cache = require("./jsonld-cache-CXA76Xi6.cjs");
1
+ const require_url = require("./url-CtHyzMR4.cjs");
2
+ const require_jsonld_cache = require("./jsonld-cache-BTgizq9A.cjs");
3
3
  let node_test = require("node:test");
4
4
  let node_assert = require("node:assert");
5
5
  //#region src/jsonld-cache.test.ts
@@ -1,5 +1,5 @@
1
- import { m as parseIri } from "./url-R9TTZ67B.mjs";
2
- import { a as normalizeJsonLdIris, c as jsonld_default, i as isTrustedIriOrigin, r as getJsonLdContext, t as compactJsonLdCache } from "./jsonld-cache-C7mkqjiE.mjs";
1
+ import { _ as parseIri } from "./url-Cdu4ASgJ.mjs";
2
+ import { a as normalizeJsonLdIris, c as jsonld_default, i as isTrustedIriOrigin, r as getJsonLdContext, t as compactJsonLdCache } from "./jsonld-cache-BKrsWPCQ.mjs";
3
3
  import { test } from "node:test";
4
4
  import { deepStrictEqual, ok, strictEqual } from "node:assert";
5
5
  //#region src/jsonld-cache.test.ts
@@ -1,6 +1,6 @@
1
- import { r as getUserAgent } from "./request-2GDEEJng.mjs";
2
- import { r as readBoundedBytes } from "./body-BmNV5g5S.mjs";
3
- import { p as parseGatewayUrl, v as validatePublicUrl } from "./url-R9TTZ67B.mjs";
1
+ import { r as getUserAgent } from "./request-D3elVRyB.mjs";
2
+ import { r as readBoundedBytes } from "./body-BEYUpKvX.mjs";
3
+ import { g as parseGatewayUrl, x as validatePublicUrl } from "./url-Cdu4ASgJ.mjs";
4
4
  import { a as verifyDigestMultibase, i as parseHashlink, r as parseDigestMultibase } from "./digest-COC7xDiQ.mjs";
5
5
  //#region src/portable-media.ts
6
6
  function resolveTimeout(value, fallback) {
@@ -1,6 +1,6 @@
1
- const require_request = require("./request-Cme6ShRu.cjs");
2
- const require_body = require("./body-BQZL1aJ1.cjs");
3
- const require_url = require("./url-B6WlNhra.cjs");
1
+ const require_request = require("./request-BQlkB8du.cjs");
2
+ const require_body = require("./body-DQKTFMsH.cjs");
3
+ const require_url = require("./url-CtHyzMR4.cjs");
4
4
  const require_digest = require("./digest-3FeH2Y-Q.cjs");
5
5
  //#region src/portable-media.ts
6
6
  function resolveTimeout(value, fallback) {
@@ -1,5 +1,5 @@
1
1
  const require_digest = require("./digest-3FeH2Y-Q.cjs");
2
- const require_portable_media = require("./portable-media-CFrGpz-3.cjs");
2
+ const require_portable_media = require("./portable-media-DHaAEpaQ.cjs");
3
3
  let node_assert_strict = require("node:assert/strict");
4
4
  let node_test = require("node:test");
5
5
  let node_http = require("node:http");
@@ -1,5 +1,5 @@
1
1
  import { n as createHashlink, t as computeDigestMultibase } from "./digest-COC7xDiQ.mjs";
2
- import { t as fetchPortableMedia } from "./portable-media-xFDDMte7.mjs";
2
+ import { t as fetchPortableMedia } from "./portable-media-1NjUQari.mjs";
3
3
  import { deepStrictEqual, equal, rejects } from "node:assert/strict";
4
4
  import { test } from "node:test";
5
5
  import { createServer } from "node:http";
@@ -3,7 +3,7 @@ let node_process = require("node:process");
3
3
  node_process = require_rolldown_runtime.__toESM(node_process, 1);
4
4
  //#region deno.json
5
5
  var name = "@fedify/vocab-runtime";
6
- var version = "2.4.0-dev.2228+6d017d55";
6
+ var version = "2.4.0-dev.2233+abb4a6b2";
7
7
  //#endregion
8
8
  //#region src/request.ts
9
9
  /**
@@ -1,7 +1,7 @@
1
1
  import process from "node:process";
2
2
  //#region deno.json
3
3
  var name = "@fedify/vocab-runtime";
4
- var version = "2.4.0-dev.2228+6d017d55";
4
+ var version = "2.4.0-dev.2233+abb4a6b2";
5
5
  //#endregion
6
6
  //#region src/request.ts
7
7
  /**
@@ -1,5 +1,5 @@
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
  let node_test = require("node:test");
4
4
  let node_process = require("node:process");
5
5
  node_process = require_rolldown_runtime.__toESM(node_process, 1);
@@ -1,4 +1,4 @@
1
- import { n as createActivityPubRequest, o as version, r as getUserAgent } from "./request-2GDEEJng.mjs";
1
+ import { n as createActivityPubRequest, o as version, r as getUserAgent } from "./request-D3elVRyB.mjs";
2
2
  import { test } from "node:test";
3
3
  import process from "node:process";
4
4
  import { deepStrictEqual } from "node:assert";
@@ -383,6 +383,166 @@ function isLocationHint(pair) {
383
383
  }
384
384
  }
385
385
  /**
386
+ * The name of the FEP-ef61 location hint query parameter.
387
+ * @internal
388
+ */
389
+ const GATEWAY_HINT_PARAMETER = "@gateway";
390
+ /**
391
+ * Parses an FEP-ef61 gateway, which has to be an HTTP(S) origin with no
392
+ * credentials, path, query, or fragment.
393
+ * @returns The gateway, or `null` if it is not a valid gateway.
394
+ * @internal
395
+ */
396
+ function parseGatewayOrigin(gateway) {
397
+ let url;
398
+ if (gateway instanceof URL) url = new URL(gateway.href);
399
+ else if (typeof gateway === "string" && URL.canParse(gateway)) url = new URL(gateway);
400
+ else return null;
401
+ return isGatewayUrl(url) ? url : null;
402
+ }
403
+ /**
404
+ * Returns a copy of an [FEP-ef61] portable ActivityPub URI with `@gateway`
405
+ * location hints for the given gateways, which tell consumers where they can
406
+ * retrieve the object. Put hints on *references* to portable actors, e.g.,
407
+ * in `actor`, `attributedTo`, `to`, or `cc`, when constructing an object, as
408
+ * FEP-ef61 recommends:
409
+ *
410
+ * ~~~~ typescript
411
+ * withGatewayHints("ap://did:key:z6Mk.../actor", [
412
+ * "https://server1.example",
413
+ * "https://server2.example",
414
+ * ]);
415
+ * // ap+ef61://did:key:z6Mk.../actor?@gateway=https%3A%2F%2Fserver1.example&@gateway=https%3A%2F%2Fserver2.example
416
+ * ~~~~
417
+ *
418
+ * Do not put hints on an object's own `id`. Hints do not change the
419
+ * identity of a portable URI, since FEP-ef61 drops the query when comparing
420
+ * portable URIs, but implementations that do not canonicalize portable URIs
421
+ * would take a hinted ID for another object. Add hints before signing the
422
+ * object, since its Object Integrity Proof covers its references too.
423
+ *
424
+ * The hints that the URI already has, including the legacy `gateways`
425
+ * parameter, are replaced. Each gateway becomes a `@gateway` query
426
+ * parameter whose value is its URI-encoded origin, e.g.,
427
+ * `@gateway=https%3A%2F%2Fserver1.example`, in the given order after the
428
+ * other query parameters. Duplicate gateways are dropped, and an empty list
429
+ * removes the hints as {@link withoutGatewayHints} does. The other query
430
+ * parameters, their order, and the fragment are kept, but percent-encoding
431
+ * is normalized the same way as {@link canonicalizePortableUri} does it.
432
+ *
433
+ * Fedify follows at most five hints when dereferencing a portable URI
434
+ * (three for the key ID of an HTTP Signature), so list the preferred
435
+ * gateways first; there is no limit on the number of hints added here.
436
+ *
437
+ * [FEP-ef61]: https://w3id.org/fep/ef61
438
+ *
439
+ * @param portableId The `ap:` or `ap+ef61:` URI. Pass the raw string rather
440
+ * than a `URL` if its path may have `.` or `..` segments,
441
+ * because the `URL` class resolves them.
442
+ * @param gateways The gateways, e.g., the `gateways` of the actor that the
443
+ * URI refers to. Each has to be an HTTP(S) origin with no
444
+ * credentials, path, query, or fragment.
445
+ * @returns The portable URI with the hints, in the same internal `URL` form
446
+ * as {@link parseIri} returns. Use {@link formatIri} to get its
447
+ * canonical string.
448
+ * @throws {TypeError} If the portable ID is not a valid `ap:` or `ap+ef61:`
449
+ * URI, e.g., it is a compatible identifier, which must
450
+ * not have location hints; if its path has `.` or `..`
451
+ * segments, which the `URL` class cannot represent; or
452
+ * if a gateway is invalid.
453
+ * @since 2.4.0
454
+ */
455
+ function withGatewayHints(portableId, gateways) {
456
+ const parts = splitPortableIri(portableId);
457
+ if (typeof gateways === "string") throw new TypeError("The gateways must be an iterable of gateways, not a string.");
458
+ const hints = [];
459
+ const seen = /* @__PURE__ */ new Set();
460
+ for (const gateway of gateways) {
461
+ const url = parseGatewayOrigin(gateway);
462
+ if (url == null) throw new TypeError("FEP-ef61 gateways must be HTTP(S) origins with no credentials, path, query, or fragment: " + String(gateway));
463
+ if (seen.has(url.href)) continue;
464
+ seen.add(url.href);
465
+ hints.push(url);
466
+ }
467
+ return replaceGatewayHints(parts, hints);
468
+ }
469
+ /**
470
+ * Returns a copy of an [FEP-ef61] portable ActivityPub URI without its
471
+ * location hints, i.e., `@gateway` query parameters and the legacy
472
+ * `gateways` parameter. The other query parameters, their order, and the
473
+ * fragment are kept, but percent-encoding is normalized the same way as
474
+ * {@link canonicalizePortableUri} does it.
475
+ *
476
+ * [FEP-ef61]: https://w3id.org/fep/ef61
477
+ *
478
+ * @param portableId The `ap:` or `ap+ef61:` URI. Pass the raw string rather
479
+ * than a `URL` if its path may have `.` or `..` segments,
480
+ * because the `URL` class resolves them.
481
+ * @returns The portable URI without the hints, in the same internal `URL`
482
+ * form as {@link parseIri} returns.
483
+ * @throws {TypeError} If the portable ID is not a valid `ap:` or `ap+ef61:`
484
+ * URI, or if its path has `.` or `..` segments, which
485
+ * the `URL` class cannot represent.
486
+ * @since 2.4.0
487
+ */
488
+ function withoutGatewayHints(portableId) {
489
+ return replaceGatewayHints(splitPortableIri(portableId), []);
490
+ }
491
+ /**
492
+ * Gets the gateways in the `@gateway` location hints of an [FEP-ef61]
493
+ * portable ActivityPub URI, in order. Hints that are not valid gateways,
494
+ * i.e., HTTP(S) origins with no credentials, path, query, or fragment, are
495
+ * skipped, and so are duplicates. The legacy `gateways` parameter is not
496
+ * read.
497
+ *
498
+ * Unlike Fedify's dereferencing, which follows at most five hints, this
499
+ * returns all of them.
500
+ *
501
+ * [FEP-ef61]: https://w3id.org/fep/ef61
502
+ *
503
+ * @param portableId The `ap:` or `ap+ef61:` URI.
504
+ * @returns The gateways, e.g., `https://server1.example/`.
505
+ * @throws {TypeError} If the portable ID is not a valid `ap:` or `ap+ef61:`
506
+ * URI.
507
+ * @since 2.4.0
508
+ */
509
+ function getGatewayHints(portableId) {
510
+ const { query } = splitPortableIri(portableId);
511
+ return query == null ? [] : parseGatewayHints(query);
512
+ }
513
+ function splitPortableIri(portableId) {
514
+ const raw = getRawPortableIri(portableId);
515
+ const match = raw.match(PORTABLE_IRI_PATTERN);
516
+ const parsed = parsePortableIri(raw);
517
+ if (match == null || parsed == null) throw new TypeError("Invalid portable ActivityPub IRI.");
518
+ return {
519
+ raw,
520
+ parsed,
521
+ path: normalizePortableComponent(match[3]),
522
+ query: match[4] == null ? null : normalizePortableComponent(match[4].slice(1)),
523
+ fragment: match[5] == null ? "" : normalizePortableComponent(match[5])
524
+ };
525
+ }
526
+ function parseGatewayHints(query) {
527
+ const hints = [];
528
+ const seen = /* @__PURE__ */ new Set();
529
+ for (const hint of new URLSearchParams(`?${query}`).getAll(GATEWAY_HINT_PARAMETER)) {
530
+ const url = parseGatewayOrigin(hint);
531
+ if (url == null || seen.has(url.href)) continue;
532
+ seen.add(url.href);
533
+ hints.push(url);
534
+ }
535
+ return hints;
536
+ }
537
+ function replaceGatewayHints(parts, hints) {
538
+ const pairs = parts.query == null ? [] : parts.query.split("&").filter((pair) => pair !== "" && !isLocationHint(pair));
539
+ for (const hint of hints) pairs.push(`${GATEWAY_HINT_PARAMETER}=${encodeURIComponent(hint.origin)}`);
540
+ const query = pairs.length < 1 ? "" : `?${pairs.join("&")}`;
541
+ const result = parsePortableIri(`ap+ef61://${parts.parsed.host}${parts.path}${query}${parts.fragment}`);
542
+ if (result == null || canonicalizePortableUri(result.href) !== canonicalizePortableUri(parts.raw) || parseGatewayHints(result.search.slice(1)).map((url) => url.href).join(" ") !== hints.map((url) => url.href).join(" ")) throw new TypeError("The portable ActivityPub IRI cannot be represented as a URL without changing the object it refers to.");
543
+ return result;
544
+ }
545
+ /**
386
546
  * Validates a URL to prevent SSRF attacks.
387
547
  */
388
548
  async function validatePublicUrl(url) {
@@ -578,4 +738,4 @@ function matchesIPv6Prefix(address, prefixWords, prefixLength) {
578
738
  return true;
579
739
  }
580
740
  //#endregion
581
- export { validateLookupAddresses as _, formatIri as a, haveSameFe34Origin as c, isValidPublicIPv4Address as d, isValidPublicIPv6Address as f, toCompatibleEf61Id as g, parseJsonLdId as h, expandIPv6Address as i, haveSameIriOrigin as l, parseIri as m, arePortableUrisEqual as n, fromCompatibleEf61Id as o, parseGatewayUrl as p, canonicalizePortableUri as r, getFe34Origin as s, UrlError as t, isGatewayUrl as u, validatePublicUrl as v };
741
+ export { withoutGatewayHints as C, withGatewayHints as S, parseIri as _, expandIPv6Address as a, validateLookupAddresses as b, getFe34Origin as c, haveSameIriOrigin as d, isGatewayUrl as f, parseGatewayUrl as g, parseGatewayOrigin as h, canonicalizePortableUri as i, getGatewayHints as l, isValidPublicIPv6Address as m, UrlError as n, formatIri as o, isValidPublicIPv4Address as p, arePortableUrisEqual as r, fromCompatibleEf61Id as s, GATEWAY_HINT_PARAMETER as t, haveSameFe34Origin as u, parseJsonLdId as v, validatePublicUrl as x, toCompatibleEf61Id as y };
@@ -385,6 +385,166 @@ function isLocationHint(pair) {
385
385
  }
386
386
  }
387
387
  /**
388
+ * The name of the FEP-ef61 location hint query parameter.
389
+ * @internal
390
+ */
391
+ const GATEWAY_HINT_PARAMETER = "@gateway";
392
+ /**
393
+ * Parses an FEP-ef61 gateway, which has to be an HTTP(S) origin with no
394
+ * credentials, path, query, or fragment.
395
+ * @returns The gateway, or `null` if it is not a valid gateway.
396
+ * @internal
397
+ */
398
+ function parseGatewayOrigin(gateway) {
399
+ let url;
400
+ if (gateway instanceof URL) url = new URL(gateway.href);
401
+ else if (typeof gateway === "string" && URL.canParse(gateway)) url = new URL(gateway);
402
+ else return null;
403
+ return isGatewayUrl(url) ? url : null;
404
+ }
405
+ /**
406
+ * Returns a copy of an [FEP-ef61] portable ActivityPub URI with `@gateway`
407
+ * location hints for the given gateways, which tell consumers where they can
408
+ * retrieve the object. Put hints on *references* to portable actors, e.g.,
409
+ * in `actor`, `attributedTo`, `to`, or `cc`, when constructing an object, as
410
+ * FEP-ef61 recommends:
411
+ *
412
+ * ~~~~ typescript
413
+ * withGatewayHints("ap://did:key:z6Mk.../actor", [
414
+ * "https://server1.example",
415
+ * "https://server2.example",
416
+ * ]);
417
+ * // ap+ef61://did:key:z6Mk.../actor?@gateway=https%3A%2F%2Fserver1.example&@gateway=https%3A%2F%2Fserver2.example
418
+ * ~~~~
419
+ *
420
+ * Do not put hints on an object's own `id`. Hints do not change the
421
+ * identity of a portable URI, since FEP-ef61 drops the query when comparing
422
+ * portable URIs, but implementations that do not canonicalize portable URIs
423
+ * would take a hinted ID for another object. Add hints before signing the
424
+ * object, since its Object Integrity Proof covers its references too.
425
+ *
426
+ * The hints that the URI already has, including the legacy `gateways`
427
+ * parameter, are replaced. Each gateway becomes a `@gateway` query
428
+ * parameter whose value is its URI-encoded origin, e.g.,
429
+ * `@gateway=https%3A%2F%2Fserver1.example`, in the given order after the
430
+ * other query parameters. Duplicate gateways are dropped, and an empty list
431
+ * removes the hints as {@link withoutGatewayHints} does. The other query
432
+ * parameters, their order, and the fragment are kept, but percent-encoding
433
+ * is normalized the same way as {@link canonicalizePortableUri} does it.
434
+ *
435
+ * Fedify follows at most five hints when dereferencing a portable URI
436
+ * (three for the key ID of an HTTP Signature), so list the preferred
437
+ * gateways first; there is no limit on the number of hints added here.
438
+ *
439
+ * [FEP-ef61]: https://w3id.org/fep/ef61
440
+ *
441
+ * @param portableId The `ap:` or `ap+ef61:` URI. Pass the raw string rather
442
+ * than a `URL` if its path may have `.` or `..` segments,
443
+ * because the `URL` class resolves them.
444
+ * @param gateways The gateways, e.g., the `gateways` of the actor that the
445
+ * URI refers to. Each has to be an HTTP(S) origin with no
446
+ * credentials, path, query, or fragment.
447
+ * @returns The portable URI with the hints, in the same internal `URL` form
448
+ * as {@link parseIri} returns. Use {@link formatIri} to get its
449
+ * canonical string.
450
+ * @throws {TypeError} If the portable ID is not a valid `ap:` or `ap+ef61:`
451
+ * URI, e.g., it is a compatible identifier, which must
452
+ * not have location hints; if its path has `.` or `..`
453
+ * segments, which the `URL` class cannot represent; or
454
+ * if a gateway is invalid.
455
+ * @since 2.4.0
456
+ */
457
+ function withGatewayHints(portableId, gateways) {
458
+ const parts = splitPortableIri(portableId);
459
+ if (typeof gateways === "string") throw new TypeError("The gateways must be an iterable of gateways, not a string.");
460
+ const hints = [];
461
+ const seen = /* @__PURE__ */ new Set();
462
+ for (const gateway of gateways) {
463
+ const url = parseGatewayOrigin(gateway);
464
+ if (url == null) throw new TypeError("FEP-ef61 gateways must be HTTP(S) origins with no credentials, path, query, or fragment: " + String(gateway));
465
+ if (seen.has(url.href)) continue;
466
+ seen.add(url.href);
467
+ hints.push(url);
468
+ }
469
+ return replaceGatewayHints(parts, hints);
470
+ }
471
+ /**
472
+ * Returns a copy of an [FEP-ef61] portable ActivityPub URI without its
473
+ * location hints, i.e., `@gateway` query parameters and the legacy
474
+ * `gateways` parameter. The other query parameters, their order, and the
475
+ * fragment are kept, but percent-encoding is normalized the same way as
476
+ * {@link canonicalizePortableUri} does it.
477
+ *
478
+ * [FEP-ef61]: https://w3id.org/fep/ef61
479
+ *
480
+ * @param portableId The `ap:` or `ap+ef61:` URI. Pass the raw string rather
481
+ * than a `URL` if its path may have `.` or `..` segments,
482
+ * because the `URL` class resolves them.
483
+ * @returns The portable URI without the hints, in the same internal `URL`
484
+ * form as {@link parseIri} returns.
485
+ * @throws {TypeError} If the portable ID is not a valid `ap:` or `ap+ef61:`
486
+ * URI, or if its path has `.` or `..` segments, which
487
+ * the `URL` class cannot represent.
488
+ * @since 2.4.0
489
+ */
490
+ function withoutGatewayHints(portableId) {
491
+ return replaceGatewayHints(splitPortableIri(portableId), []);
492
+ }
493
+ /**
494
+ * Gets the gateways in the `@gateway` location hints of an [FEP-ef61]
495
+ * portable ActivityPub URI, in order. Hints that are not valid gateways,
496
+ * i.e., HTTP(S) origins with no credentials, path, query, or fragment, are
497
+ * skipped, and so are duplicates. The legacy `gateways` parameter is not
498
+ * read.
499
+ *
500
+ * Unlike Fedify's dereferencing, which follows at most five hints, this
501
+ * returns all of them.
502
+ *
503
+ * [FEP-ef61]: https://w3id.org/fep/ef61
504
+ *
505
+ * @param portableId The `ap:` or `ap+ef61:` URI.
506
+ * @returns The gateways, e.g., `https://server1.example/`.
507
+ * @throws {TypeError} If the portable ID is not a valid `ap:` or `ap+ef61:`
508
+ * URI.
509
+ * @since 2.4.0
510
+ */
511
+ function getGatewayHints(portableId) {
512
+ const { query } = splitPortableIri(portableId);
513
+ return query == null ? [] : parseGatewayHints(query);
514
+ }
515
+ function splitPortableIri(portableId) {
516
+ const raw = getRawPortableIri(portableId);
517
+ const match = raw.match(PORTABLE_IRI_PATTERN);
518
+ const parsed = parsePortableIri(raw);
519
+ if (match == null || parsed == null) throw new TypeError("Invalid portable ActivityPub IRI.");
520
+ return {
521
+ raw,
522
+ parsed,
523
+ path: normalizePortableComponent(match[3]),
524
+ query: match[4] == null ? null : normalizePortableComponent(match[4].slice(1)),
525
+ fragment: match[5] == null ? "" : normalizePortableComponent(match[5])
526
+ };
527
+ }
528
+ function parseGatewayHints(query) {
529
+ const hints = [];
530
+ const seen = /* @__PURE__ */ new Set();
531
+ for (const hint of new URLSearchParams(`?${query}`).getAll(GATEWAY_HINT_PARAMETER)) {
532
+ const url = parseGatewayOrigin(hint);
533
+ if (url == null || seen.has(url.href)) continue;
534
+ seen.add(url.href);
535
+ hints.push(url);
536
+ }
537
+ return hints;
538
+ }
539
+ function replaceGatewayHints(parts, hints) {
540
+ const pairs = parts.query == null ? [] : parts.query.split("&").filter((pair) => pair !== "" && !isLocationHint(pair));
541
+ for (const hint of hints) pairs.push(`${GATEWAY_HINT_PARAMETER}=${encodeURIComponent(hint.origin)}`);
542
+ const query = pairs.length < 1 ? "" : `?${pairs.join("&")}`;
543
+ const result = parsePortableIri(`ap+ef61://${parts.parsed.host}${parts.path}${query}${parts.fragment}`);
544
+ if (result == null || canonicalizePortableUri(result.href) !== canonicalizePortableUri(parts.raw) || parseGatewayHints(result.search.slice(1)).map((url) => url.href).join(" ") !== hints.map((url) => url.href).join(" ")) throw new TypeError("The portable ActivityPub IRI cannot be represented as a URL without changing the object it refers to.");
545
+ return result;
546
+ }
547
+ /**
388
548
  * Validates a URL to prevent SSRF attacks.
389
549
  */
390
550
  async function validatePublicUrl(url) {
@@ -580,6 +740,12 @@ function matchesIPv6Prefix(address, prefixWords, prefixLength) {
580
740
  return true;
581
741
  }
582
742
  //#endregion
743
+ Object.defineProperty(exports, "GATEWAY_HINT_PARAMETER", {
744
+ enumerable: true,
745
+ get: function() {
746
+ return GATEWAY_HINT_PARAMETER;
747
+ }
748
+ });
583
749
  Object.defineProperty(exports, "UrlError", {
584
750
  enumerable: true,
585
751
  get: function() {
@@ -622,6 +788,12 @@ Object.defineProperty(exports, "getFe34Origin", {
622
788
  return getFe34Origin;
623
789
  }
624
790
  });
791
+ Object.defineProperty(exports, "getGatewayHints", {
792
+ enumerable: true,
793
+ get: function() {
794
+ return getGatewayHints;
795
+ }
796
+ });
625
797
  Object.defineProperty(exports, "haveSameFe34Origin", {
626
798
  enumerable: true,
627
799
  get: function() {
@@ -652,6 +824,12 @@ Object.defineProperty(exports, "isValidPublicIPv6Address", {
652
824
  return isValidPublicIPv6Address;
653
825
  }
654
826
  });
827
+ Object.defineProperty(exports, "parseGatewayOrigin", {
828
+ enumerable: true,
829
+ get: function() {
830
+ return parseGatewayOrigin;
831
+ }
832
+ });
655
833
  Object.defineProperty(exports, "parseGatewayUrl", {
656
834
  enumerable: true,
657
835
  get: function() {
@@ -688,3 +866,15 @@ Object.defineProperty(exports, "validatePublicUrl", {
688
866
  return validatePublicUrl;
689
867
  }
690
868
  });
869
+ Object.defineProperty(exports, "withGatewayHints", {
870
+ enumerable: true,
871
+ get: function() {
872
+ return withGatewayHints;
873
+ }
874
+ });
875
+ Object.defineProperty(exports, "withoutGatewayHints", {
876
+ enumerable: true,
877
+ get: function() {
878
+ return withoutGatewayHints;
879
+ }
880
+ });
@@ -1,4 +1,4 @@
1
- const require_url = require("./url-B6WlNhra.cjs");
1
+ const require_url = require("./url-CtHyzMR4.cjs");
2
2
  let node_test = require("node:test");
3
3
  let node_assert = require("node:assert");
4
4
  //#region src/url.test.ts
@@ -620,4 +620,122 @@ let node_assert = require("node:assert");
620
620
  (0, node_assert.deepStrictEqual)(require_url.expandIPv6Address("2001:db8::1"), "2001:0db8:0000:0000:0000:0000:0000:0001");
621
621
  (0, node_assert.deepStrictEqual)(require_url.expandIPv6Address("64:ff9b::8.8.8.8"), "0064:ff9b:0000:0000:0000:0000:0808:0808");
622
622
  });
623
+ (0, node_test.test)("withGatewayHints() adds @gateway location hints", () => {
624
+ const expected = "ap+ef61://did:key:z6Mkabc/actor?@gateway=https%3A%2F%2Fserver1.example&@gateway=https%3A%2F%2Fserver2.example";
625
+ for (const id of [
626
+ "ap://did:key:z6Mkabc/actor",
627
+ "ap+ef61://did:key:z6Mkabc/actor",
628
+ "ap://did%3Akey%3Az6Mkabc/actor",
629
+ require_url.parseIri("ap://did:key:z6Mkabc/actor"),
630
+ new URL("ap://did%3Akey%3Az6Mkabc/actor")
631
+ ]) {
632
+ const hinted = require_url.withGatewayHints(id, ["https://server1.example", new URL("https://server2.example/")]);
633
+ (0, node_assert.ok)(hinted instanceof URL);
634
+ (0, node_assert.strictEqual)(hinted.href, "ap+ef61://did%3Akey%3Az6Mkabc/actor?@gateway=https%3A%2F%2Fserver1.example&@gateway=https%3A%2F%2Fserver2.example");
635
+ (0, node_assert.strictEqual)(require_url.formatIri(hinted), expected);
636
+ (0, node_assert.deepStrictEqual)(require_url.parseIri(require_url.formatIri(hinted)), hinted);
637
+ }
638
+ });
639
+ (0, node_test.test)("withGatewayHints() encodes and deduplicates gateway origins", () => {
640
+ function* gateways() {
641
+ yield "https://A.example:443";
642
+ yield "https://a.example/";
643
+ yield new URL("https://a.example");
644
+ yield "https://b.example:8443";
645
+ yield "http://c.example:80/";
646
+ yield "https://例え.jp";
647
+ }
648
+ (0, node_assert.strictEqual)(require_url.formatIri(require_url.withGatewayHints("ap://did:key:z6Mkabc/actor", gateways())), "ap+ef61://did:key:z6Mkabc/actor?@gateway=https%3A%2F%2Fa.example&@gateway=https%3A%2F%2Fb.example%3A8443&@gateway=http%3A%2F%2Fc.example&@gateway=https%3A%2F%2Fxn--r8jz45g.jp");
649
+ (0, node_assert.strictEqual)(require_url.formatIri(require_url.withGatewayHints("ap://did:key:z6Mkabc/actor", /* @__PURE__ */ new Set(["https://b.example", "https://a.example"]))), "ap+ef61://did:key:z6Mkabc/actor?@gateway=https%3A%2F%2Fb.example&@gateway=https%3A%2F%2Fa.example");
650
+ const many = Array.from({ length: 7 }, (_, i) => `https://server${i}.example`);
651
+ (0, node_assert.deepStrictEqual)(require_url.getGatewayHints(require_url.withGatewayHints("ap://did:key:z6Mkabc/actor", many)).map((url) => url.origin), many);
652
+ });
653
+ (0, node_test.test)("withGatewayHints() keeps other query parameters and fragments", () => {
654
+ (0, node_assert.strictEqual)(require_url.formatIri(require_url.withGatewayHints("ap://did:key:z6Mkabc/collection?page=3&maxItems=20&q=a+b%20c%2b", ["https://server.example"])), "ap+ef61://did:key:z6Mkabc/collection?page=3&maxItems=20&q=a+b%20c%2B&@gateway=https%3A%2F%2Fserver.example");
655
+ (0, node_assert.strictEqual)(require_url.formatIri(require_url.withGatewayHints("ap://did:key:z6Mkabc/actor?x=%26%3D#main-key", ["https://server.example"])), "ap+ef61://did:key:z6Mkabc/actor?x=%26%3D&@gateway=https%3A%2F%2Fserver.example#main-key");
656
+ (0, node_assert.strictEqual)(require_url.withGatewayHints("ap://did:key:z6Mkabc/actor#", ["https://s.example"]).href, "ap+ef61://did%3Akey%3Az6Mkabc/actor?@gateway=https%3A%2F%2Fs.example#");
657
+ });
658
+ (0, node_test.test)("withGatewayHints() replaces existing location hints", () => {
659
+ (0, node_assert.strictEqual)(require_url.formatIri(require_url.withGatewayHints("ap://did:key:z6Mkabc/collection?@gateway=https%3A%2F%2Fold.example&page=2&%40gateway=https%3A%2F%2Fold2.example&@gateway&gateways=https%3A%2F%2Flegacy.example&%2540gateway=kept", ["https://new.example"])), "ap+ef61://did:key:z6Mkabc/collection?page=2&%2540gateway=kept&@gateway=https%3A%2F%2Fnew.example");
660
+ for (const char of [
661
+ " ",
662
+ "\n",
663
+ "\r"
664
+ ]) (0, node_assert.deepStrictEqual)(require_url.getGatewayHints(require_url.withGatewayHints(`ap://did:key:z6Mkabc/actor?@gate${char}way=https%3A%2F%2Fevil.example`, ["https://new.example"])).map((url) => url.href), ["https://new.example/"]);
665
+ });
666
+ (0, node_test.test)("withGatewayHints() with no gateways removes location hints", () => {
667
+ for (const [input, expected] of [
668
+ ["ap://did:key:z6Mkabc/actor?@gateway=https%3A%2F%2Fa.example", "ap+ef61://did%3Akey%3Az6Mkabc/actor"],
669
+ ["ap://did:key:z6Mkabc/actor?&@gateway=https%3A%2F%2Fa.example&&p=1&", "ap+ef61://did%3Akey%3Az6Mkabc/actor?p=1"],
670
+ ["ap://did:key:z6Mkabc/actor?", "ap+ef61://did%3Akey%3Az6Mkabc/actor"],
671
+ ["ap://did:key:z6Mkabc/actor?gateways=https%3A%2F%2Fa.example#k", "ap+ef61://did%3Akey%3Az6Mkabc/actor#k"]
672
+ ]) {
673
+ (0, node_assert.strictEqual)(require_url.withGatewayHints(input, []).href, expected, input);
674
+ (0, node_assert.strictEqual)(require_url.withoutGatewayHints(input).href, expected, input);
675
+ }
676
+ const input = require_url.parseIri("ap://did:key:z6Mkabc/actor?@gateway=https%3A%2F%2Fa.example");
677
+ const href = input.href;
678
+ require_url.withoutGatewayHints(input);
679
+ (0, node_assert.strictEqual)(input.href, href);
680
+ });
681
+ (0, node_test.test)("withGatewayHints() rejects non-portable IDs", () => {
682
+ for (const id of [
683
+ "https://server.example/.well-known/apgateway/did:key:z6Mkabc/actor",
684
+ "https://example.com/actor",
685
+ "did:key:z6Mkabc#z6Mkabc",
686
+ "ap://did:key:z6Mkabc",
687
+ "ap://example.com/actor",
688
+ "ap://did:key:z6Mkabc/actor%zz",
689
+ new URL("https://example.com/actor"),
690
+ new URL("ap://did%3Akey%3Az6Mkabc:8080/actor"),
691
+ new URL("ap://user@did%3Akey%3Az6Mkabc/actor")
692
+ ]) {
693
+ (0, node_assert.throws)(() => require_url.withGatewayHints(id, ["https://server.example"]), TypeError, String(id));
694
+ (0, node_assert.throws)(() => require_url.withoutGatewayHints(id), TypeError, String(id));
695
+ (0, node_assert.throws)(() => require_url.getGatewayHints(id), TypeError, String(id));
696
+ }
697
+ });
698
+ (0, node_test.test)("withGatewayHints() rejects paths that URLs cannot represent", () => {
699
+ for (const id of [
700
+ "ap://did:key:z6Mkabc/a/../actor",
701
+ "ap://did:key:z6Mkabc/a/./actor",
702
+ "ap://did:key:z6Mkabc/a/%2e%2e/actor",
703
+ "ap://did:key:z6Mkabc/a/%2E/actor"
704
+ ]) {
705
+ (0, node_assert.throws)(() => require_url.withGatewayHints(id, ["https://server.example"]), TypeError, id);
706
+ (0, node_assert.throws)(() => require_url.withoutGatewayHints(id), TypeError, id);
707
+ }
708
+ (0, node_assert.strictEqual)(require_url.withoutGatewayHints("ap://did:key:z6Mkabc/a b").href, "ap+ef61://did%3Akey%3Az6Mkabc/a%09b");
709
+ });
710
+ (0, node_test.test)("withGatewayHints() rejects invalid gateways", () => {
711
+ for (const gateway of [
712
+ "https://server.example/path",
713
+ "https://server.example/?",
714
+ "https://server.example/#",
715
+ "https://server.example/?q=1",
716
+ "https://user:pass@server.example",
717
+ "ftp://server.example",
718
+ "ap://did:key:z6Mkabc/actor",
719
+ "server.example",
720
+ new URL("https://server.example/path")
721
+ ]) (0, node_assert.throws)(() => require_url.withGatewayHints("ap://did:key:z6Mkabc/actor", [gateway]), TypeError, String(gateway));
722
+ (0, node_assert.throws)(() => require_url.withGatewayHints("ap://did:key:z6Mkabc/actor", "https://server.example"), TypeError);
723
+ });
724
+ (0, node_test.test)("getGatewayHints() reads @gateway location hints", () => {
725
+ (0, node_assert.deepStrictEqual)(require_url.getGatewayHints("ap://did:key:z6Mkabc/actor?@gateway=https%3A%2F%2Fa.example&@gateway=invalid&%40gateway=https%3A%2F%2Fb.example%2F&@gateway=https%3A%2F%2Fa.example%2F&@gateway=https%3A%2F%2Fc.example%2Fpath&gateways=https%3A%2F%2Flegacy.example&page=1#k").map((url) => url.href), ["https://a.example/", "https://b.example/"]);
726
+ (0, node_assert.deepStrictEqual)(require_url.getGatewayHints("ap://did:key:z6Mkabc/actor"), []);
727
+ const questioned = "ap://did:key:z6Mkabc/actor??@gateway=https%3A%2F%2Fa.example";
728
+ (0, node_assert.deepStrictEqual)(require_url.getGatewayHints(questioned), []);
729
+ (0, node_assert.strictEqual)(require_url.withoutGatewayHints(questioned).href, "ap+ef61://did%3Akey%3Az6Mkabc/actor??@gateway=https%3A%2F%2Fa.example");
730
+ (0, node_assert.deepStrictEqual)(require_url.getGatewayHints(require_url.withGatewayHints(questioned, ["https://b.example"])).map((url) => url.href), ["https://b.example/"]);
731
+ (0, node_assert.deepStrictEqual)(require_url.getGatewayHints("ap://did:key:z6Mkabc/actor?@gate%09way=https%3A%2F%2Fa.example"), []);
732
+ (0, node_assert.deepStrictEqual)(require_url.getGatewayHints("ap://did:key:z6Mkabc/actor?@gate way=https%3A%2F%2Fa.example"), []);
733
+ });
734
+ (0, node_test.test)("withGatewayHints() round-trips with other portable ID helpers", () => {
735
+ const hinted = require_url.withGatewayHints("ap://did:key:z6Mkabc/objects/1#frag", ["https://server1.example", "https://server2.example"]);
736
+ (0, node_assert.strictEqual)(require_url.canonicalizePortableUri(require_url.formatIri(hinted)), "ap+ef61://did:key:z6Mkabc/objects/1#frag");
737
+ (0, node_assert.ok)(require_url.arePortableUrisEqual(require_url.formatIri(hinted), "ap://did:key:z6Mkabc/objects/1#frag"));
738
+ (0, node_assert.strictEqual)(require_url.toCompatibleEf61Id(hinted, "https://server1.example").href, "https://server1.example/.well-known/apgateway/did:key:z6Mkabc/objects/1#frag");
739
+ (0, node_assert.strictEqual)(require_url.formatIri(require_url.withoutGatewayHints(hinted)), "ap+ef61://did:key:z6Mkabc/objects/1#frag");
740
+ });
623
741
  //#endregion