@fedify/fedify 2.1.30 → 2.1.31

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 (84) hide show
  1. package/dist/{builder-CTpfuuVp.mjs → builder-isMRh_I6.mjs} +3 -3
  2. package/dist/{client-CAM_bQXx.d.cts → client-BIBzqEIp.d.cts} +0 -1
  3. package/dist/{client-CjDMsn4a.mjs → client-BZr7TGyu.mjs} +113 -64
  4. package/dist/{client-CSddvgWN.d.ts → client-jE7oGvRh.d.ts} +0 -1
  5. package/dist/compat/mod.d.cts +1 -1
  6. package/dist/compat/mod.d.ts +1 -1
  7. package/dist/compat/transformers.test.mjs +1 -1
  8. package/dist/{context-DLLaDau8.d.ts → context-C0uGVzgk.d.ts} +28 -2
  9. package/dist/{context-BNwv9Q1v.d.cts → context-CLaLxi49.d.cts} +28 -2
  10. package/dist/{deno-BATJ1hq6.mjs → deno-CiaJqTcu.mjs} +1 -1
  11. package/dist/docloader-DOrwedT7.mjs +124 -0
  12. package/dist/federation/builder.test.mjs +1 -1
  13. package/dist/federation/handler.test.mjs +6 -6
  14. package/dist/federation/idempotency.test.mjs +2 -2
  15. package/dist/federation/inbox.test.mjs +1 -1
  16. package/dist/federation/middleware.test.mjs +380 -24
  17. package/dist/federation/mod.cjs +1 -1
  18. package/dist/federation/mod.d.cts +2 -2
  19. package/dist/federation/mod.d.ts +2 -2
  20. package/dist/federation/mod.js +1 -1
  21. package/dist/federation/send.test.mjs +445 -70
  22. package/dist/federation/temporal.test.mjs +1 -1
  23. package/dist/federation/webfinger.test.mjs +1 -1
  24. package/dist/{http-Dl00HXr4.d.ts → http-9IFUxdkp.d.cts} +1 -2
  25. package/dist/{http-Bjd6I92u.mjs → http-A9mP2uM7.mjs} +194 -96
  26. package/dist/{http-BoR1M3W7.cjs → http-CRiVlZd5.cjs} +172 -103
  27. package/dist/{http-CK3efjgM.js → http-Crml3gei.js} +171 -102
  28. package/dist/{http-DqZhyEso.d.cts → http-SbfezERn.d.ts} +1 -2
  29. package/dist/{inbox-9MlBGgs9.mjs → inbox-KESLudq4.mjs} +1 -1
  30. package/dist/{key-ClQh9Qqh.mjs → key-Dg6-DV_O.mjs} +1 -1
  31. package/dist/{kv-cache-BxSx_Iyp.cjs → kv-cache-Cqmm-yQa.cjs} +59 -17
  32. package/dist/{kv-cache-ImApO425.js → kv-cache-DQcyZL5T.js} +60 -18
  33. package/dist/{ld-Crg0Xmwr.mjs → ld-COjDN6Nb.mjs} +2 -2
  34. package/dist/{middleware-CmD3lpy4.cjs → middleware-BbgR7xOI.cjs} +184 -59
  35. package/dist/{middleware-jL9p9V9G.cjs → middleware-BtDQUCcz.cjs} +1 -1
  36. package/dist/{middleware-CE0b69gj.mjs → middleware-COFlKeYn.mjs} +78 -38
  37. package/dist/{middleware-Bi5D6DBT.mjs → middleware-CuQvczZd.mjs} +1 -1
  38. package/dist/{middleware-C2AJFk8W.js → middleware-DIW5S_5L.js} +184 -59
  39. package/dist/{mod-DNEgTI8M.d.ts → mod-BRnwh2oy.d.ts} +2 -7
  40. package/dist/{mod-BLDHhuW0.d.ts → mod-C7w-QVUv.d.ts} +1 -1
  41. package/dist/{mod-CWJNbyju.d.ts → mod-CFRocFeQ.d.ts} +1 -1
  42. package/dist/{mod-BmlfRBmU.d.cts → mod-CGUH8Ujp.d.cts} +1 -1
  43. package/dist/{mod-Cn9GJ5ex.d.cts → mod-Co7LEK9X.d.cts} +2 -7
  44. package/dist/{mod-DiuFTYUt.d.cts → mod-LOEOKbYd.d.cts} +1 -1
  45. package/dist/mod.cjs +5 -5
  46. package/dist/mod.d.cts +6 -6
  47. package/dist/mod.d.ts +6 -6
  48. package/dist/mod.js +5 -5
  49. package/dist/nodeinfo/client.test.mjs +337 -19
  50. package/dist/nodeinfo/handler.test.mjs +1 -1
  51. package/dist/nodeinfo/mod.cjs +1 -1
  52. package/dist/nodeinfo/mod.d.cts +1 -1
  53. package/dist/nodeinfo/mod.d.ts +1 -1
  54. package/dist/nodeinfo/mod.js +1 -1
  55. package/dist/otel/exporter.test.mjs +292 -30
  56. package/dist/otel/mod.cjs +36 -30
  57. package/dist/otel/mod.d.cts +13 -0
  58. package/dist/otel/mod.d.ts +13 -0
  59. package/dist/otel/mod.js +36 -30
  60. package/dist/{owner-BRoKojK1.mjs → owner-CFHq61cq.mjs} +2 -2
  61. package/dist/{proof-CHXVlsH_.cjs → proof-0JkSC_pT.cjs} +1 -1
  62. package/dist/{proof-7R4s10TJ.js → proof-DzksJFK7.js} +1 -1
  63. package/dist/{proof-DMIAezxz.mjs → proof-KSHlbPCG.mjs} +2 -2
  64. package/dist/{send-BHIt_8ia.mjs → send-DaDe2YCb.mjs} +117 -32
  65. package/dist/sig/http.test.mjs +506 -2
  66. package/dist/sig/key.test.mjs +2 -2
  67. package/dist/sig/ld.test.mjs +2 -2
  68. package/dist/sig/mod.cjs +2 -2
  69. package/dist/sig/mod.d.cts +2 -2
  70. package/dist/sig/mod.d.ts +2 -2
  71. package/dist/sig/mod.js +2 -2
  72. package/dist/sig/owner.test.mjs +1 -1
  73. package/dist/sig/proof.test.mjs +1 -1
  74. package/dist/{temporal-DR0JahMu.mjs → temporal-DNGH8o2E.mjs} +1 -1
  75. package/dist/testing/mod.d.mts +26 -1
  76. package/dist/{types-miAYfiGO.js → types-B1_pRFE_.js} +112 -63
  77. package/dist/{types-DRPjmHVV.cjs → types-DOvyymM4.cjs} +111 -62
  78. package/dist/utils/docloader.test.mjs +316 -32
  79. package/dist/utils/mod.cjs +1 -1
  80. package/dist/utils/mod.d.cts +1 -1
  81. package/dist/utils/mod.d.ts +1 -1
  82. package/dist/utils/mod.js +1 -1
  83. package/package.json +6 -6
  84. package/dist/docloader-OLWc7P2D.mjs +0 -82
@@ -10,7 +10,7 @@ import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } fro
10
10
  import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
11
11
  //#region deno.json
12
12
  var name = "@fedify/fedify";
13
- var version = "2.1.30";
13
+ var version = "2.1.31";
14
14
  //#endregion
15
15
  //#region src/sig/accept.ts
16
16
  /**
@@ -1597,10 +1597,10 @@ async function createRedirectRequest(request, location, body, validateRedirect)
1597
1597
  cache: request.cache
1598
1598
  });
1599
1599
  }
1600
- async function fetchDoubleKnockRequest(request, signedRequest, signal) {
1600
+ async function fetchDoubleKnockRequest(session, request, signedRequest, signal) {
1601
1601
  const maxAttempts = request.method === "GET" || request.method === "HEAD" ? 2 : 1;
1602
1602
  for (let attempt = 1;; attempt++) try {
1603
- return await fetch(signedRequest, {
1603
+ return await session.fetch(signedRequest, {
1604
1604
  redirect: "manual",
1605
1605
  signal
1606
1606
  });
@@ -1651,117 +1651,186 @@ function getAbortReason(signal) {
1651
1651
  return signal.reason ?? new DOMException("The operation was aborted.", "AbortError");
1652
1652
  }
1653
1653
  /**
1654
- * Performs a double-knock request to the given URL. For the details of
1655
- * double-knocking, see
1656
- * <https://swicg.github.io/activitypub-http-signature/#how-to-upgrade-supported-versions>.
1654
+ * Performs a double-knock request like {@link doubleKnock}, opening a session
1655
+ * for every hop with the given function, and passes the final response to
1656
+ * the given function. The final session is closed after `consume` settles,
1657
+ * so `consume` must read or cancel the response body before it returns.
1658
+ * This is not exported from any entry point.
1657
1659
  * @param request The request to send.
1658
1660
  * @param identity The identity to use for signing the request.
1659
1661
  * @param options The options for double-knock requests.
1660
- * @returns The response to the request.
1661
- * @since 1.6.0
1662
+ * @param openHop The function to open a session for each hop. The session of
1663
+ * a hop is closed before the next hop is opened.
1664
+ * @param consume The function to consume the final response.
1665
+ * @returns The value returned by `consume`.
1662
1666
  */
1663
- async function doubleKnock(request, identity, options = {}) {
1664
- return await doubleKnockInternal(request, identity, options);
1667
+ async function doubleKnockScoped(request, identity, options, openHop, consume) {
1668
+ const { response, session } = await doubleKnockCore(request, identity, options, openHop);
1669
+ try {
1670
+ return await consume(response);
1671
+ } catch (error) {
1672
+ getLogger([
1673
+ "fedify",
1674
+ "sig",
1675
+ "http"
1676
+ ]).debug("Failed to consume the response from {url}; closing the session: {error}", {
1677
+ url: request.url,
1678
+ error
1679
+ });
1680
+ throw error;
1681
+ } finally {
1682
+ await session.close();
1683
+ }
1665
1684
  }
1666
- async function doubleKnockInternal(request, identity, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
1667
- const { specDeterminer, log, tracerProvider, signal } = options;
1668
- visited.add(request.url);
1669
- const origin = new URL(request.url).origin;
1670
- const firstTrySpec = specDeterminer == null ? "rfc9421" : await specDeterminer.determineSpec(origin);
1671
- const body = options.body !== void 0 ? options.body : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
1672
- let signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
1673
- spec: firstTrySpec,
1674
- tracerProvider,
1675
- body
1676
- });
1677
- log?.(signedRequest);
1678
- let response = await fetchDoubleKnockRequest(request, signedRequest, signal);
1679
- if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
1680
- if (redirected >= DEFAULT_MAX_REDIRECTION) throw new FetchError(request.url, `Too many redirections (${redirected + 1})`);
1681
- const redirectRequest = await createRedirectRequest(request, response.headers.get("Location"), body, options.validateRedirect);
1682
- if (visited.has(redirectRequest.url)) throw new FetchError(request.url, `Redirect loop detected: ${redirectRequest.url}`);
1683
- return doubleKnockInternal(redirectRequest, identity, {
1684
- ...options,
1685
- body
1686
- }, redirected + 1, visited);
1687
- } else if (response.status === 400 || response.status === 401 || response.status > 401) {
1688
- const logger = getLogger([
1685
+ function isRedirectResponse(response) {
1686
+ return response.status >= 300 && response.status < 400 && response.headers.has("Location");
1687
+ }
1688
+ /**
1689
+ * Starts cancelling the body of a response that is not passed to the caller,
1690
+ * without waiting for it to settle. The cancellation of a branch of a tee
1691
+ * (e.g., a body cloned by a fetch mock) settles only after the other branch
1692
+ * is cancelled too, per the Streams standard, so awaiting it could block
1693
+ * forever. Failures are only logged.
1694
+ */
1695
+ function cancelResponseBody(response, url) {
1696
+ response.body?.cancel().catch((error) => {
1697
+ getLogger([
1689
1698
  "fedify",
1690
1699
  "sig",
1691
1700
  "http"
1692
- ]);
1693
- const acceptSigHeader = response.headers.get("Accept-Signature");
1694
- if (acceptSigHeader != null) {
1695
- const entries = validateAcceptSignature(parseAcceptSignature(acceptSigHeader));
1696
- const localKeyId = identity.keyId.href;
1697
- const localAlg = "rsa-v1_5-sha256";
1698
- let fulfilled = false;
1699
- let challengeRequest;
1700
- for (const entry of entries) {
1701
- const rfc9421 = fulfillAcceptSignature(entry, localKeyId, localAlg);
1702
- if (rfc9421 == null) continue;
1703
- logger.debug("Received Accept-Signature challenge; accumulating label {label} and components {components}.", {
1704
- label: rfc9421.label,
1705
- components: rfc9421.components
1706
- });
1707
- try {
1708
- challengeRequest = await signRequest(challengeRequest ?? request, identity.privateKey, identity.keyId, {
1709
- spec: "rfc9421",
1710
- tracerProvider,
1711
- body,
1712
- rfc9421
1701
+ ]).debug("Failed to cancel the response body from {url}: {error}", {
1702
+ url,
1703
+ error
1704
+ });
1705
+ });
1706
+ }
1707
+ async function doubleKnockCore(request, identity, options, openHop) {
1708
+ const { specDeterminer, log, tracerProvider, signal } = options;
1709
+ const visited = /* @__PURE__ */ new Set();
1710
+ const body = options.body !== void 0 ? options.body : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
1711
+ let session = await openHop(request.url);
1712
+ try {
1713
+ for (let redirected = 0;; redirected++) {
1714
+ visited.add(request.url);
1715
+ const origin = new URL(request.url).origin;
1716
+ const firstTrySpec = specDeterminer == null ? "rfc9421" : await specDeterminer.determineSpec(origin);
1717
+ let signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
1718
+ spec: firstTrySpec,
1719
+ tracerProvider,
1720
+ body
1721
+ });
1722
+ log?.(signedRequest);
1723
+ let response = await fetchDoubleKnockRequest(session, request, signedRequest, signal);
1724
+ const firstRedirected = isRedirectResponse(response);
1725
+ if (!firstRedirected && (response.status === 400 || response.status === 401 || response.status > 401)) {
1726
+ const logger = getLogger([
1727
+ "fedify",
1728
+ "sig",
1729
+ "http"
1730
+ ]);
1731
+ const acceptSigHeader = response.headers.get("Accept-Signature");
1732
+ if (acceptSigHeader != null) {
1733
+ const entries = validateAcceptSignature(parseAcceptSignature(acceptSigHeader));
1734
+ const localKeyId = identity.keyId.href;
1735
+ const localAlg = "rsa-v1_5-sha256";
1736
+ let fulfilled = false;
1737
+ let challengeRequest;
1738
+ for (const entry of entries) {
1739
+ const rfc9421 = fulfillAcceptSignature(entry, localKeyId, localAlg);
1740
+ if (rfc9421 == null) continue;
1741
+ logger.debug("Received Accept-Signature challenge; accumulating label {label} and components {components}.", {
1742
+ label: rfc9421.label,
1743
+ components: rfc9421.components
1744
+ });
1745
+ try {
1746
+ challengeRequest = await signRequest(challengeRequest ?? request, identity.privateKey, identity.keyId, {
1747
+ spec: "rfc9421",
1748
+ tracerProvider,
1749
+ body,
1750
+ rfc9421
1751
+ });
1752
+ fulfilled = true;
1753
+ } catch (error) {
1754
+ logger.debug("Failed to fulfill Accept-Signature challenge entry {label}: {error}", {
1755
+ label: entry.label,
1756
+ error
1757
+ });
1758
+ }
1759
+ }
1760
+ if (fulfilled && challengeRequest != null) {
1761
+ cancelResponseBody(response, request.url);
1762
+ signedRequest = challengeRequest;
1763
+ log?.(signedRequest);
1764
+ response = await session.fetch(signedRequest, {
1765
+ redirect: "manual",
1766
+ signal
1767
+ });
1768
+ }
1769
+ if (fulfilled && !isRedirectResponse(response)) {
1770
+ if (response.status < 300) {
1771
+ await specDeterminer?.rememberSpec(origin, "rfc9421");
1772
+ return {
1773
+ response,
1774
+ session
1775
+ };
1776
+ }
1777
+ if (response.status !== 400 && response.status !== 401) return {
1778
+ response,
1779
+ session
1780
+ };
1781
+ }
1782
+ }
1783
+ if (!isRedirectResponse(response)) {
1784
+ const spec = firstTrySpec === "draft-cavage-http-signatures-12" ? "rfc9421" : "draft-cavage-http-signatures-12";
1785
+ getLogger([
1786
+ "fedify",
1787
+ "sig",
1788
+ "http"
1789
+ ]).debug("Failed to verify with the spec {spec} ({status} {statusText}); retrying with spec {secondSpec}... (double-knocking)", {
1790
+ spec: firstTrySpec,
1791
+ secondSpec: spec,
1792
+ status: response.status,
1793
+ statusText: response.statusText
1713
1794
  });
1714
- fulfilled = true;
1715
- } catch (error) {
1716
- logger.debug("Failed to fulfill Accept-Signature challenge entry {label}: {error}", {
1717
- label: entry.label,
1718
- error
1795
+ cancelResponseBody(response, request.url);
1796
+ signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
1797
+ spec,
1798
+ tracerProvider,
1799
+ body
1719
1800
  });
1801
+ log?.(signedRequest);
1802
+ response = await fetchDoubleKnockRequest(session, request, signedRequest, signal);
1803
+ if (!isRedirectResponse(response) && response.status !== 400 && response.status !== 401) await specDeterminer?.rememberSpec(origin, spec);
1720
1804
  }
1721
- }
1722
- if (fulfilled && challengeRequest != null) {
1723
- signedRequest = challengeRequest;
1724
- log?.(signedRequest);
1725
- response = await fetch(signedRequest, {
1726
- redirect: "manual",
1727
- signal
1728
- });
1729
- if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) return doubleKnock(await createRedirectRequest(request, response.headers.get("Location"), body, options.validateRedirect), identity, {
1730
- ...options,
1731
- body
1732
- });
1733
- }
1734
- if (fulfilled && response.status < 300) {
1735
- await specDeterminer?.rememberSpec(origin, "rfc9421");
1736
- return response;
1737
- }
1738
- if (fulfilled && response.status !== 400 && response.status !== 401) return response;
1739
- }
1740
- const spec = firstTrySpec === "draft-cavage-http-signatures-12" ? "rfc9421" : "draft-cavage-http-signatures-12";
1741
- logger.debug("Failed to verify with the spec {spec} ({status} {statusText}); retrying with spec {secondSpec}... (double-knocking)", {
1742
- spec: firstTrySpec,
1743
- secondSpec: spec,
1744
- status: response.status,
1745
- statusText: response.statusText
1746
- });
1747
- signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
1748
- spec,
1749
- tracerProvider,
1750
- body
1751
- });
1752
- log?.(signedRequest);
1753
- response = await fetchDoubleKnockRequest(request, signedRequest, signal);
1754
- if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
1805
+ } else if (!firstRedirected) await specDeterminer?.rememberSpec(origin, firstTrySpec);
1806
+ if (!isRedirectResponse(response)) return {
1807
+ response,
1808
+ session
1809
+ };
1810
+ cancelResponseBody(response, request.url);
1811
+ await session.close();
1755
1812
  if (redirected >= DEFAULT_MAX_REDIRECTION) throw new FetchError(request.url, `Too many redirections (${redirected + 1})`);
1756
- const redirectRequest = await createRedirectRequest(request, response.headers.get("Location"), body, options.validateRedirect);
1813
+ const location = response.headers.get("Location");
1814
+ const redirectRequest = await createRedirectRequest(request, location, body, options.validateRedirect);
1757
1815
  if (visited.has(redirectRequest.url)) throw new FetchError(request.url, `Redirect loop detected: ${redirectRequest.url}`);
1758
- return doubleKnockInternal(redirectRequest, identity, {
1759
- ...options,
1760
- body
1761
- }, redirected + 1, visited);
1762
- } else if (response.status !== 400 && response.status !== 401) await specDeterminer?.rememberSpec(origin, spec);
1763
- } else await specDeterminer?.rememberSpec(origin, firstTrySpec);
1764
- return response;
1816
+ request = redirectRequest;
1817
+ session = await openHop(request.url);
1818
+ }
1819
+ } catch (error) {
1820
+ try {
1821
+ await session.close();
1822
+ } catch (closeError) {
1823
+ getLogger([
1824
+ "fedify",
1825
+ "sig",
1826
+ "http"
1827
+ ]).debug("Failed to close the session for {url}: {error}", {
1828
+ url: request.url,
1829
+ error: closeError
1830
+ });
1831
+ }
1832
+ throw error;
1833
+ }
1765
1834
  }
1766
1835
  /**
1767
1836
  * Performs a timing-safe equality comparison between two `Uint8Array` values.
@@ -1791,4 +1860,4 @@ function timingSafeEqual(a, b) {
1791
1860
  return result === 0;
1792
1861
  }
1793
1862
  //#endregion
1794
- export { validateAcceptSignature as _, verifyRequestDetailed as a, fetchKey as c, importJwk as d, validateCryptoKey as f, parseAcceptSignature as g, fulfillAcceptSignature as h, verifyRequest as i, fetchKeyDetailed as l, formatAcceptSignature as m, parseRfc9421SignatureInput as n, exportJwk as o, verifyKeyOwnership as p, signRequest as r, fetchActorDocument as s, doubleKnock as t, generateCryptoKeyPair as u, name as v, version as y };
1863
+ export { validateAcceptSignature as _, verifyRequestDetailed as a, fetchKey as c, importJwk as d, validateCryptoKey as f, parseAcceptSignature as g, fulfillAcceptSignature as h, verifyRequest as i, fetchKeyDetailed as l, formatAcceptSignature as m, parseRfc9421SignatureInput as n, exportJwk as o, verifyKeyOwnership as p, signRequest as r, fetchActorDocument as s, doubleKnockScoped as t, generateCryptoKeyPair as u, name as v, version as y };
@@ -1,8 +1,7 @@
1
1
  /// <reference lib="esnext.temporal" />
2
2
  import { Actor, CryptographicKey, Multikey } from "@fedify/vocab";
3
- import { DocumentLoader } from "@fedify/vocab-runtime";
4
3
  import { TracerProvider } from "@opentelemetry/api";
5
-
4
+ import { DocumentLoader } from "@fedify/vocab-runtime";
6
5
  //#region src/sig/key.d.ts
7
6
  /**
8
7
  * Generates a key pair which is appropriate for Fedify.
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-BATJ1hq6.mjs";
4
+ import { n as version, t as name } from "./deno-CiaJqTcu.mjs";
5
5
  import { Activity, getTypeId } from "@fedify/vocab";
6
6
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
7
7
  import { getLogger } from "@logtape/logtape";
@@ -1,7 +1,7 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-BATJ1hq6.mjs";
4
+ import { n as version, t as name } from "./deno-CiaJqTcu.mjs";
5
5
  import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
6
6
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
7
7
  import { FetchError, UrlError, getDocumentLoader } from "@fedify/vocab-runtime";
@@ -1,10 +1,11 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  require("./chunk-DDcVe30Y.cjs");
4
- const require_http = require("./http-BoR1M3W7.cjs");
4
+ const require_http = require("./http-CRiVlZd5.cjs");
5
5
  let _logtape_logtape = require("@logtape/logtape");
6
6
  let es_toolkit = require("es-toolkit");
7
7
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
8
+ let _fedify_vocab_runtime_internal = require("@fedify/vocab-runtime/internal");
8
9
  //#region src/utils/docloader.ts
9
10
  const logger$1 = (0, _logtape_logtape.getLogger)([
10
11
  "fedify",
@@ -25,12 +26,34 @@ const DEFAULT_MAX_REDIRECTION = 20;
25
26
  * @throws {TypeError} If the key is invalid or unsupported.
26
27
  * @since 0.4.0
27
28
  */
28
- function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, userAgent, specDeterminer, tracerProvider } = {}) {
29
+ function getAuthenticatedDocumentLoader(identity, options = {}) {
30
+ return createAuthenticatedDocumentLoader(identity, options);
31
+ }
32
+ /**
33
+ * The placeholder that the alternate document callback of
34
+ * `getRemoteDocument()` returns, so that the alternate document is loaded
35
+ * after the current hop is closed.
36
+ */
37
+ const ALTERNATE_PLACEHOLDER = Object.freeze({
38
+ contextUrl: null,
39
+ document: null,
40
+ documentUrl: "about:blank"
41
+ });
42
+ /**
43
+ * The implementation of {@link getAuthenticatedDocumentLoader}. This is not
44
+ * exported from any entry point.
45
+ * @param identity The identity to get the document loader for.
46
+ * @param options The options for the document loader.
47
+ * @param deps The dependencies, for tests.
48
+ * @returns The authenticated document loader.
49
+ * @throws {TypeError} If the key is invalid or unsupported.
50
+ */
51
+ function createAuthenticatedDocumentLoader(identity, { allowPrivateAddress, userAgent, specDeterminer, tracerProvider } = {}, deps = {}) {
29
52
  require_http.validateCryptoKey(identity.privateKey);
30
53
  async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
31
54
  options?.signal?.throwIfAborted();
32
55
  let currentUrl = new URL(url).href;
33
- await validateUrl(currentUrl);
56
+ validateUrl(currentUrl);
34
57
  visited.add(currentUrl);
35
58
  const originalRequest = (0, _fedify_vocab_runtime.createActivityPubRequest)(currentUrl, { userAgent });
36
59
  function follow(nextUrl) {
@@ -39,30 +62,43 @@ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, userAge
39
62
  if (visited.has(nextUrl)) throw new _fedify_vocab_runtime.FetchError(currentUrl, `Redirect loop detected: ${nextUrl}`);
40
63
  redirected++;
41
64
  }
42
- const response = await require_http.doubleKnock(originalRequest, identity, {
65
+ const result = await require_http.doubleKnockScoped(originalRequest, identity, {
43
66
  specDeterminer,
44
67
  log: (0, es_toolkit.curry)(_fedify_vocab_runtime.logRequest)(logger$1),
45
68
  tracerProvider,
46
69
  signal: options?.signal,
47
- validateRedirect: async (nextUrl) => {
70
+ validateRedirect: (nextUrl) => {
48
71
  follow(nextUrl);
49
- await validateUrl(nextUrl);
72
+ validateUrl(nextUrl);
50
73
  visited.add(nextUrl);
51
74
  currentUrl = nextUrl;
52
75
  }
76
+ }, (hopUrl) => openHop(hopUrl, options?.signal), async (response) => {
77
+ let alternateUrl;
78
+ const document = await (0, _fedify_vocab_runtime.getRemoteDocument)(currentUrl, response, (url) => {
79
+ alternateUrl = url;
80
+ return Promise.resolve(ALTERNATE_PLACEHOLDER);
81
+ });
82
+ if (alternateUrl == null) return { document };
83
+ if (!response.bodyUsed && response.body?.locked !== true) response.body?.cancel().catch((error) => {
84
+ logger$1.debug("Failed to cancel the response body from {url}: {error}", {
85
+ url: currentUrl,
86
+ error
87
+ });
88
+ });
89
+ return { alternateUrl };
53
90
  });
54
- return (0, _fedify_vocab_runtime.getRemoteDocument)(currentUrl, response, (alternateUrl) => {
55
- follow(alternateUrl);
56
- return load(alternateUrl, options, redirected, visited);
57
- });
91
+ if ("document" in result) return result.document;
92
+ follow(result.alternateUrl);
93
+ return load(result.alternateUrl, options, redirected, visited);
58
94
  }
59
- async function validateUrl(url) {
60
- const parsedUrl = new URL(url);
61
- const protocol = parsedUrl.protocol;
62
- if (protocol !== "http:" && protocol !== "https:") throw new _fedify_vocab_runtime.UrlError(`Unsupported protocol: ${protocol}`);
63
- if (parsedUrl.username !== "" || parsedUrl.password !== "") throw new _fedify_vocab_runtime.UrlError("URLs with credentials are not allowed");
64
- if (!allowPrivateAddress) try {
65
- await (0, _fedify_vocab_runtime.validatePublicUrl)(url);
95
+ function openUnvalidatedHop(url, signal) {
96
+ if (deps.openHop != null) return deps.openHop(url);
97
+ return allowPrivateAddress ? (0, _fedify_vocab_runtime_internal.openDirectFetch)(url) : (0, _fedify_vocab_runtime_internal.openPublicFetch)(url, { signal });
98
+ }
99
+ async function openHop(url, signal) {
100
+ try {
101
+ return await openUnvalidatedHop(url, signal);
66
102
  } catch (error) {
67
103
  if (error instanceof _fedify_vocab_runtime.UrlError) if (error.reason === "dns") logger$1.debug("DNS lookup failed for {url}", {
68
104
  url,
@@ -75,6 +111,12 @@ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, userAge
75
111
  throw error;
76
112
  }
77
113
  }
114
+ function validateUrl(url) {
115
+ const parsedUrl = new URL(url);
116
+ const protocol = parsedUrl.protocol;
117
+ if (protocol !== "http:" && protocol !== "https:") throw new _fedify_vocab_runtime.UrlError(`Unsupported protocol: ${protocol}`);
118
+ if (parsedUrl.username !== "" || parsedUrl.password !== "") throw new _fedify_vocab_runtime.UrlError("URLs with credentials are not allowed");
119
+ }
78
120
  return (url, options) => load(url, options);
79
121
  }
80
122
  //#endregion
@@ -1,9 +1,10 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { f as validateCryptoKey, t as doubleKnock } from "./http-CK3efjgM.js";
3
+ import { f as validateCryptoKey, t as doubleKnockScoped } from "./http-Crml3gei.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { curry } from "es-toolkit";
6
- import { FetchError, UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
6
+ import { FetchError, UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts } from "@fedify/vocab-runtime";
7
+ import { openDirectFetch, openPublicFetch } from "@fedify/vocab-runtime/internal";
7
8
  //#region src/utils/docloader.ts
8
9
  const logger$1 = getLogger([
9
10
  "fedify",
@@ -24,12 +25,34 @@ const DEFAULT_MAX_REDIRECTION = 20;
24
25
  * @throws {TypeError} If the key is invalid or unsupported.
25
26
  * @since 0.4.0
26
27
  */
27
- function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, userAgent, specDeterminer, tracerProvider } = {}) {
28
+ function getAuthenticatedDocumentLoader(identity, options = {}) {
29
+ return createAuthenticatedDocumentLoader(identity, options);
30
+ }
31
+ /**
32
+ * The placeholder that the alternate document callback of
33
+ * `getRemoteDocument()` returns, so that the alternate document is loaded
34
+ * after the current hop is closed.
35
+ */
36
+ const ALTERNATE_PLACEHOLDER = Object.freeze({
37
+ contextUrl: null,
38
+ document: null,
39
+ documentUrl: "about:blank"
40
+ });
41
+ /**
42
+ * The implementation of {@link getAuthenticatedDocumentLoader}. This is not
43
+ * exported from any entry point.
44
+ * @param identity The identity to get the document loader for.
45
+ * @param options The options for the document loader.
46
+ * @param deps The dependencies, for tests.
47
+ * @returns The authenticated document loader.
48
+ * @throws {TypeError} If the key is invalid or unsupported.
49
+ */
50
+ function createAuthenticatedDocumentLoader(identity, { allowPrivateAddress, userAgent, specDeterminer, tracerProvider } = {}, deps = {}) {
28
51
  validateCryptoKey(identity.privateKey);
29
52
  async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
30
53
  options?.signal?.throwIfAborted();
31
54
  let currentUrl = new URL(url).href;
32
- await validateUrl(currentUrl);
55
+ validateUrl(currentUrl);
33
56
  visited.add(currentUrl);
34
57
  const originalRequest = createActivityPubRequest(currentUrl, { userAgent });
35
58
  function follow(nextUrl) {
@@ -38,30 +61,43 @@ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, userAge
38
61
  if (visited.has(nextUrl)) throw new FetchError(currentUrl, `Redirect loop detected: ${nextUrl}`);
39
62
  redirected++;
40
63
  }
41
- const response = await doubleKnock(originalRequest, identity, {
64
+ const result = await doubleKnockScoped(originalRequest, identity, {
42
65
  specDeterminer,
43
66
  log: curry(logRequest)(logger$1),
44
67
  tracerProvider,
45
68
  signal: options?.signal,
46
- validateRedirect: async (nextUrl) => {
69
+ validateRedirect: (nextUrl) => {
47
70
  follow(nextUrl);
48
- await validateUrl(nextUrl);
71
+ validateUrl(nextUrl);
49
72
  visited.add(nextUrl);
50
73
  currentUrl = nextUrl;
51
74
  }
75
+ }, (hopUrl) => openHop(hopUrl, options?.signal), async (response) => {
76
+ let alternateUrl;
77
+ const document = await getRemoteDocument(currentUrl, response, (url) => {
78
+ alternateUrl = url;
79
+ return Promise.resolve(ALTERNATE_PLACEHOLDER);
80
+ });
81
+ if (alternateUrl == null) return { document };
82
+ if (!response.bodyUsed && response.body?.locked !== true) response.body?.cancel().catch((error) => {
83
+ logger$1.debug("Failed to cancel the response body from {url}: {error}", {
84
+ url: currentUrl,
85
+ error
86
+ });
87
+ });
88
+ return { alternateUrl };
52
89
  });
53
- return getRemoteDocument(currentUrl, response, (alternateUrl) => {
54
- follow(alternateUrl);
55
- return load(alternateUrl, options, redirected, visited);
56
- });
90
+ if ("document" in result) return result.document;
91
+ follow(result.alternateUrl);
92
+ return load(result.alternateUrl, options, redirected, visited);
57
93
  }
58
- async function validateUrl(url) {
59
- const parsedUrl = new URL(url);
60
- const protocol = parsedUrl.protocol;
61
- if (protocol !== "http:" && protocol !== "https:") throw new UrlError(`Unsupported protocol: ${protocol}`);
62
- if (parsedUrl.username !== "" || parsedUrl.password !== "") throw new UrlError("URLs with credentials are not allowed");
63
- if (!allowPrivateAddress) try {
64
- await validatePublicUrl(url);
94
+ function openUnvalidatedHop(url, signal) {
95
+ if (deps.openHop != null) return deps.openHop(url);
96
+ return allowPrivateAddress ? openDirectFetch(url) : openPublicFetch(url, { signal });
97
+ }
98
+ async function openHop(url, signal) {
99
+ try {
100
+ return await openUnvalidatedHop(url, signal);
65
101
  } catch (error) {
66
102
  if (error instanceof UrlError) if (error.reason === "dns") logger$1.debug("DNS lookup failed for {url}", {
67
103
  url,
@@ -74,6 +110,12 @@ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, userAge
74
110
  throw error;
75
111
  }
76
112
  }
113
+ function validateUrl(url) {
114
+ const parsedUrl = new URL(url);
115
+ const protocol = parsedUrl.protocol;
116
+ if (protocol !== "http:" && protocol !== "https:") throw new UrlError(`Unsupported protocol: ${protocol}`);
117
+ if (parsedUrl.username !== "" || parsedUrl.password !== "") throw new UrlError("URLs with credentials are not allowed");
118
+ }
77
119
  return (url, options) => load(url, options);
78
120
  }
79
121
  //#endregion
@@ -1,8 +1,8 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-BATJ1hq6.mjs";
5
- import { r as fetchKey, s as validateCryptoKey } from "./key-ClQh9Qqh.mjs";
4
+ import { n as version, t as name } from "./deno-CiaJqTcu.mjs";
5
+ import { r as fetchKey, s as validateCryptoKey } from "./key-Dg6-DV_O.mjs";
6
6
  import { Activity, CryptographicKey, Object as Object$1, getTypeId } from "@fedify/vocab";
7
7
  import { SpanStatusCode, trace } from "@opentelemetry/api";
8
8
  import { getDocumentLoader } from "@fedify/vocab-runtime";