@fedify/vocab-runtime 2.1.29 → 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 (114) hide show
  1. package/deno.json +5 -2
  2. package/dist/internal.cjs +6 -0
  3. package/dist/internal.d.cts +3 -0
  4. package/dist/internal.d.ts +3 -0
  5. package/dist/internal.js +3 -0
  6. package/dist/mod.cjs +105 -252
  7. package/dist/mod.d.cts +5 -27
  8. package/dist/mod.d.ts +6 -27
  9. package/dist/mod.js +99 -245
  10. package/dist/public-fetch-BC-nWJNM.d.ts +143 -0
  11. package/dist/public-fetch-CFnW7XMn.cjs +821 -0
  12. package/dist/public-fetch-DBpt3xxf.d.cts +143 -0
  13. package/dist/public-fetch-Dfmc_Kfy.js +772 -0
  14. package/dist/tests/{body-C-p-RXaQ.mjs → body-B0Z2Vegb.mjs} +1 -1
  15. package/dist/tests/{body-BI7IK9DK.cjs → body-i_FdQE3f.cjs} +1 -1
  16. package/dist/tests/body.test.cjs +1 -1
  17. package/dist/tests/body.test.mjs +1 -1
  18. package/dist/tests/decimal.test.cjs +6 -6
  19. package/dist/tests/decimal.test.mjs +6 -6
  20. package/dist/tests/{docloader-DVamy9FW.mjs → docloader-dwostFF6.mjs} +103 -37
  21. package/dist/tests/{docloader-Okt4FgM8.cjs → docloader-oZrKg7F3.cjs} +107 -35
  22. package/dist/tests/docloader.test.cjs +568 -1272
  23. package/dist/tests/docloader.test.mjs +514 -1237
  24. package/dist/tests/esm-CnLt0wAF.mjs +1225 -0
  25. package/dist/tests/esm-CxbqfkLP.cjs +1208 -0
  26. package/dist/tests/internal/multicodec.test.cjs +1 -1
  27. package/dist/tests/internal/multicodec.test.mjs +1 -1
  28. package/dist/tests/{key-BDIsC6hV.mjs → key-GEdpxA4M.mjs} +2 -2
  29. package/dist/tests/{key-BarKGLQz.cjs → key-L6ReMuUb.cjs} +2 -2
  30. package/dist/tests/key.test.cjs +2 -2
  31. package/dist/tests/key.test.mjs +2 -2
  32. package/dist/tests/{langstr-BsqFJupv.cjs → langstr-CZpWmpo5.cjs} +1 -1
  33. package/dist/tests/{langstr-ABdAOKlF.mjs → langstr-DTB4sl0a.mjs} +1 -1
  34. package/dist/tests/langstr.test.cjs +1 -1
  35. package/dist/tests/langstr.test.mjs +1 -1
  36. package/dist/tests/langtag.test.cjs +1 -1
  37. package/dist/tests/langtag.test.mjs +1 -1
  38. package/dist/tests/link.test.mjs +1 -1
  39. package/dist/tests/multibase/multibase.test.cjs +1 -1
  40. package/dist/tests/multibase/multibase.test.mjs +1 -1
  41. package/dist/tests/node-C3bjFjL1.mjs +84 -0
  42. package/dist/tests/node-DWD76vPo.cjs +90 -0
  43. package/dist/tests/pin-grant-CU-o367k.cjs +70 -0
  44. package/dist/tests/pin-grant-HW3B6SKv.mjs +53 -0
  45. package/dist/tests/pin-grant.test.cjs +88 -0
  46. package/dist/tests/pin-grant.test.d.cts +1 -0
  47. package/dist/tests/pin-grant.test.d.mts +1 -0
  48. package/dist/tests/pin-grant.test.mjs +88 -0
  49. package/dist/tests/pinners/bun.test.cjs +167 -0
  50. package/dist/tests/pinners/bun.test.d.cts +1 -0
  51. package/dist/tests/pinners/bun.test.d.mts +1 -0
  52. package/dist/tests/pinners/bun.test.mjs +167 -0
  53. package/dist/tests/pinners/node.test.cjs +269 -0
  54. package/dist/tests/pinners/node.test.d.cts +1 -0
  55. package/dist/tests/pinners/node.test.d.mts +1 -0
  56. package/dist/tests/pinners/node.test.mjs +268 -0
  57. package/dist/tests/pinners/pinners.test.cjs +197 -0
  58. package/dist/tests/pinners/pinners.test.d.cts +1 -0
  59. package/dist/tests/pinners/pinners.test.d.mts +1 -0
  60. package/dist/tests/pinners/pinners.test.mjs +197 -0
  61. package/dist/tests/pinners/testdata/node-child.cjs +135 -0
  62. package/dist/tests/pinners/testdata/node-child.d.cts +1 -0
  63. package/dist/tests/pinners/testdata/node-child.d.mts +1 -0
  64. package/dist/tests/pinners/testdata/node-child.mjs +134 -0
  65. package/dist/tests/pinners-BjE-YJLL.cjs +221 -0
  66. package/dist/tests/pinners-ZgZDx9y6.mjs +203 -0
  67. package/dist/tests/public-fetch-Ddjfxbwc.cjs +187 -0
  68. package/dist/tests/public-fetch-Yi7Vks9s.mjs +170 -0
  69. package/dist/tests/public-fetch.test.cjs +1074 -0
  70. package/dist/tests/public-fetch.test.d.cts +1 -0
  71. package/dist/tests/public-fetch.test.d.mts +1 -0
  72. package/dist/tests/public-fetch.test.mjs +1074 -0
  73. package/dist/tests/{request-C_PrlGe_.cjs → request-CzYw8aEp.cjs} +1 -1
  74. package/dist/tests/{request-B9R4Y9qS.mjs → request-DY2AaRpH.mjs} +1 -1
  75. package/dist/tests/request.test.cjs +1 -1
  76. package/dist/tests/request.test.mjs +1 -1
  77. package/dist/tests/support-D5-KVjWZ.cjs +363 -0
  78. package/dist/tests/support-M5ClN-no.mjs +272 -0
  79. package/dist/tests/{url-vZTgkIZg.cjs → url-DLuSj57U.cjs} +88 -12
  80. package/dist/tests/{url-3fyJuqC0.mjs → url-DQWrMGhu.mjs} +65 -13
  81. package/dist/tests/url.test.cjs +4 -1
  82. package/dist/tests/url.test.mjs +4 -1
  83. package/package.json +14 -3
  84. package/src/docloader.test.ts +780 -31
  85. package/src/docloader.ts +179 -67
  86. package/src/internal.ts +13 -0
  87. package/src/pin-grant.test.ts +118 -0
  88. package/src/pin-grant.ts +99 -0
  89. package/src/pinners/bun.test.ts +278 -0
  90. package/src/pinners/bun.ts +223 -0
  91. package/src/pinners/mod.ts +59 -0
  92. package/src/pinners/node.test.ts +415 -0
  93. package/src/pinners/node.ts +144 -0
  94. package/src/pinners/pinners.test.ts +316 -0
  95. package/src/pinners/testdata/node-child.ts +147 -0
  96. package/src/pinners/testdata/proxy-child.ts +82 -0
  97. package/src/pinners/testdata/support.ts +343 -0
  98. package/src/pinners/testdata/tls-cert.pem +12 -0
  99. package/src/pinners/testdata/tls-child.ts +88 -0
  100. package/src/pinners/testdata/tls-key.pem +5 -0
  101. package/src/pinners/types.ts +48 -0
  102. package/src/public-fetch.test.ts +1286 -0
  103. package/src/public-fetch.ts +338 -0
  104. package/src/testdata/public-fetch-deno-worker.ts +74 -0
  105. package/src/url.test.ts +11 -0
  106. package/src/url.ts +113 -22
  107. package/tsdown.config.ts +12 -3
  108. /package/dist/tests/{langtag-LDBqlxHv.mjs → langtag-D8FWxjv8.mjs} +0 -0
  109. /package/dist/tests/{langtag-DgySESmk.cjs → langtag-a2-s9uyi.cjs} +0 -0
  110. /package/dist/tests/{link-CYszIegt.mjs → link-CHMtH2-k.mjs} +0 -0
  111. /package/dist/tests/{multibase-8zhYPa7o.cjs → multibase-BmWaK56H.cjs} +0 -0
  112. /package/dist/tests/{multibase-Ds75BvXs.mjs → multibase-D642Uv3S.mjs} +0 -0
  113. /package/dist/tests/{multicodec-ByS9XLsw.cjs → multicodec-B-_CSt1M.cjs} +0 -0
  114. /package/dist/tests/{multicodec-BBPZjRfg.mjs → multicodec-caWdG98t.mjs} +0 -0
@@ -1,4 +1,4 @@
1
- import { t as FetchError } from "./request-B9R4Y9qS.mjs";
1
+ import { t as FetchError } from "./request-DY2AaRpH.mjs";
2
2
  import { getLogger } from "@logtape/logtape";
3
3
  //#region src/body.ts
4
4
  /** Decoded JSON body limit: 16 MiB. @internal */
@@ -1,5 +1,5 @@
1
1
  require("./chunk-Do9eywBl.cjs");
2
- const require_request = require("./request-C_PrlGe_.cjs");
2
+ const require_request = require("./request-CzYw8aEp.cjs");
3
3
  let _logtape_logtape = require("@logtape/logtape");
4
4
  //#region src/body.ts
5
5
  /** Decoded JSON body limit: 16 MiB. @internal */
@@ -1,5 +1,5 @@
1
1
  require("./chunk-Do9eywBl.cjs");
2
- const require_body = require("./body-BI7IK9DK.cjs");
2
+ const require_body = require("./body-i_FdQE3f.cjs");
3
3
  let node_assert_strict = require("node:assert/strict");
4
4
  let node_test = require("node:test");
5
5
  let node_zlib = require("node:zlib");
@@ -1,4 +1,4 @@
1
- import { r as readBoundedText, t as BodyTooLargeError } from "./body-C-p-RXaQ.mjs";
1
+ import { r as readBoundedText, t as BodyTooLargeError } from "./body-B0Z2Vegb.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
1
  require("./chunk-Do9eywBl.cjs");
2
- require("./docloader-Okt4FgM8.cjs");
3
- require("./url-vZTgkIZg.cjs");
4
- require("./key-BarKGLQz.cjs");
5
- require("./multibase-8zhYPa7o.cjs");
6
- require("./langtag-DgySESmk.cjs");
7
- require("./langstr-BsqFJupv.cjs");
2
+ require("./docloader-oZrKg7F3.cjs");
3
+ require("./url-DLuSj57U.cjs");
4
+ require("./key-L6ReMuUb.cjs");
5
+ require("./multibase-BmWaK56H.cjs");
6
+ require("./langtag-a2-s9uyi.cjs");
7
+ require("./langstr-CZpWmpo5.cjs");
8
8
  let node_test = require("node:test");
9
9
  let node_assert = require("node:assert");
10
10
  //#region src/decimal.ts
@@ -1,9 +1,9 @@
1
- import "./docloader-DVamy9FW.mjs";
2
- import "./url-3fyJuqC0.mjs";
3
- import "./key-BDIsC6hV.mjs";
4
- import "./multibase-Ds75BvXs.mjs";
5
- import "./langtag-LDBqlxHv.mjs";
6
- import "./langstr-ABdAOKlF.mjs";
1
+ import "./docloader-dwostFF6.mjs";
2
+ import "./url-DQWrMGhu.mjs";
3
+ import "./key-GEdpxA4M.mjs";
4
+ import "./multibase-D642Uv3S.mjs";
5
+ import "./langtag-D8FWxjv8.mjs";
6
+ import "./langstr-DTB4sl0a.mjs";
7
7
  import { test } from "node:test";
8
8
  import { deepStrictEqual, throws } from "node:assert";
9
9
  //#region src/decimal.ts
@@ -1,7 +1,8 @@
1
- import { a as name, i as logRequest, n as createActivityPubRequest, o as version, t as FetchError } from "./request-B9R4Y9qS.mjs";
2
- import { n as MAX_BODY_SIZE, r as readBoundedText } from "./body-C-p-RXaQ.mjs";
3
- import { t as HttpHeaderLink } from "./link-CYszIegt.mjs";
4
- import { o as validatePublicUrl, t as UrlError } from "./url-3fyJuqC0.mjs";
1
+ import { a as name, i as logRequest, n as createActivityPubRequest, o as version, t as FetchError } from "./request-DY2AaRpH.mjs";
2
+ import { n as MAX_BODY_SIZE, r as readBoundedText } from "./body-B0Z2Vegb.mjs";
3
+ import { t as HttpHeaderLink } from "./link-CHMtH2-k.mjs";
4
+ import { t as UrlError } from "./url-DQWrMGhu.mjs";
5
+ import { n as openDirectFetch, r as openPublicFetch } from "./public-fetch-Yi7Vks9s.mjs";
5
6
  import { getLogger } from "@logtape/logtape";
6
7
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
7
8
  //#region src/contexts/activitystreams.json
@@ -4591,13 +4592,47 @@ const DEFAULT_MAX_REDIRECTION = 20;
4591
4592
  * Gets a {@link RemoteDocument} from the given response.
4592
4593
  * @param url The URL of the document to load.
4593
4594
  * @param response The response to get the document from.
4594
- * @param fetch The function to fetch the document.
4595
+ * @param fetch The function to fetch the document. It is called with the URL
4596
+ * of an alternate document, if any, after the response body has
4597
+ * been either read or its cancellation has been started.
4595
4598
  * @returns The loaded remote document.
4596
4599
  * @throws {FetchError} If the response is not OK.
4597
4600
  * @internal
4598
4601
  */
4599
4602
  async function getRemoteDocument(url, response, fetch) {
4600
- const documentUrl = response.url === "" ? url : response.url;
4603
+ const result = await parseRemoteDocument(url, response);
4604
+ if ("alternate" in result) return await fetch(result.alternate);
4605
+ return result.document;
4606
+ }
4607
+ /**
4608
+ * Starts cancelling the body of a response without waiting for it to settle.
4609
+ * The cancellation of a branch of a tee (e.g., a body cloned by a fetch mock)
4610
+ * settles only after the other branch is cancelled too, per the Streams
4611
+ * standard, so awaiting it could block forever. Failures are only logged.
4612
+ * @param response The response whose body is cancelled.
4613
+ * @param url The URL of the response, for logging.
4614
+ */
4615
+ function cancelResponseBody(response, url) {
4616
+ response.body?.cancel().catch((error) => {
4617
+ logger.debug("Failed to cancel the response body from {url}: {error}", {
4618
+ url,
4619
+ error
4620
+ });
4621
+ });
4622
+ }
4623
+ /**
4624
+ * Gets a {@link RemoteDocument} from the given response, or the URL of an
4625
+ * alternate document to load instead. When an alternate document is found,
4626
+ * the response body has been either read or its cancellation has been started
4627
+ * (without waiting for it), so that the alternate document can be loaded after
4628
+ * the response's session is closed.
4629
+ * @param url The URL of the document to load.
4630
+ * @param response The response to get the document from.
4631
+ * @returns The loaded remote document, or the alternate document URL.
4632
+ * @throws {FetchError} If the response is not OK.
4633
+ */
4634
+ async function parseRemoteDocument(url, response) {
4635
+ const documentUrl = url;
4601
4636
  const docUrl = new URL(documentUrl);
4602
4637
  if (!response.ok) {
4603
4638
  logger.error("Failed to fetch document: {status} {url} {headers}", {
@@ -4634,7 +4669,8 @@ async function getRemoteDocument(url, response, fetch) {
4634
4669
  alternateUrl: altUri.href,
4635
4670
  url: documentUrl
4636
4671
  });
4637
- return await fetch(altUri.href);
4672
+ cancelResponseBody(response, documentUrl);
4673
+ return { alternate: altUri.href };
4638
4674
  }
4639
4675
  }
4640
4676
  }
@@ -4659,7 +4695,7 @@ async function getRemoteDocument(url, response, fetch) {
4659
4695
  alternateUrl: attribs.href,
4660
4696
  url: documentUrl
4661
4697
  });
4662
- return await fetch(new URL(attribs.href, docUrl).href);
4698
+ return { alternate: new URL(attribs.href, docUrl).href };
4663
4699
  }
4664
4700
  }
4665
4701
  document = JSON.parse(html);
@@ -4669,11 +4705,11 @@ async function getRemoteDocument(url, response, fetch) {
4669
4705
  url: documentUrl,
4670
4706
  headers: Object.fromEntries(response.headers.entries())
4671
4707
  });
4672
- return {
4708
+ return { document: {
4673
4709
  contextUrl,
4674
4710
  document,
4675
4711
  documentUrl
4676
- };
4712
+ } };
4677
4713
  }
4678
4714
  /**
4679
4715
  * Creates a JSON-LD document loader that utilizes the browser's `fetch` API.
@@ -4699,12 +4735,58 @@ async function getRemoteDocument(url, response, fetch) {
4699
4735
  * @returns The document loader.
4700
4736
  * @since 1.3.0
4701
4737
  */
4702
- function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAgent } = {}) {
4738
+ function getDocumentLoader(options = {}) {
4739
+ return createDocumentLoader(options);
4740
+ }
4741
+ /**
4742
+ * The implementation of {@link getDocumentLoader}, with injectable
4743
+ * dependencies. This is not exported from any entry point.
4744
+ * @param options Options for the document loader.
4745
+ * @param deps The dependencies.
4746
+ * @returns The document loader.
4747
+ */
4748
+ function createDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAgent } = {}, deps = {}) {
4703
4749
  const tracer = trace.getTracerProvider().getTracer(name, version);
4750
+ async function openHop(url, signal) {
4751
+ try {
4752
+ if (deps.openHop != null) return await deps.openHop(url);
4753
+ return allowPrivateAddress ? await openDirectFetch(url) : await openPublicFetch(url, { signal });
4754
+ } catch (error) {
4755
+ if (error instanceof UrlError) if (error.reason === "dns") logger.debug("DNS lookup failed for {url}", {
4756
+ url,
4757
+ error
4758
+ });
4759
+ else logger.error("Disallowed private URL: {url}", {
4760
+ url,
4761
+ error
4762
+ });
4763
+ throw error;
4764
+ }
4765
+ }
4766
+ async function fetchHop(session, url, span, options) {
4767
+ try {
4768
+ const request = createActivityPubRequest(url, { userAgent });
4769
+ logRequest(logger, request);
4770
+ const response = await session.fetch(request, {
4771
+ redirect: "manual",
4772
+ signal: options?.signal
4773
+ });
4774
+ span.setAttribute("http.response.status_code", response.status);
4775
+ if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
4776
+ const location = response.headers.get("Location");
4777
+ cancelResponseBody(response, url);
4778
+ return { location };
4779
+ }
4780
+ return await parseRemoteDocument(url, response);
4781
+ } finally {
4782
+ await session.close();
4783
+ }
4784
+ }
4704
4785
  async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
4705
4786
  options?.signal?.throwIfAborted();
4706
4787
  const parsedUrl = new URL(url);
4707
4788
  if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") throw new UrlError(`Unsupported protocol: ${parsedUrl.protocol}`);
4789
+ if (parsedUrl.username !== "" || parsedUrl.password !== "") throw new UrlError("URLs with credentials are not allowed");
4708
4790
  const currentUrl = parsedUrl.href;
4709
4791
  if (!skipPreloadedContexts && currentUrl in preloadedContexts) {
4710
4792
  logger.debug("Using preloaded context: {url}.", { url: currentUrl });
@@ -4714,33 +4796,15 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
4714
4796
  documentUrl: currentUrl
4715
4797
  };
4716
4798
  }
4717
- if (!allowPrivateAddress) try {
4718
- await validatePublicUrl(currentUrl);
4719
- } catch (error) {
4720
- if (error instanceof UrlError) if (error.reason === "dns") logger.debug("DNS lookup failed for {url}", {
4721
- url: currentUrl,
4722
- error
4723
- });
4724
- else logger.error("Disallowed private URL: {url}", {
4725
- url: currentUrl,
4726
- error
4727
- });
4728
- throw error;
4729
- }
4799
+ const session = await openHop(currentUrl, options?.signal);
4730
4800
  visited.add(currentUrl);
4731
4801
  return await tracer.startActiveSpan("activitypub.fetch_document", {
4732
4802
  kind: SpanKind.CLIENT,
4733
4803
  attributes: { "url.full": currentUrl }
4734
4804
  }, async (span) => {
4735
4805
  try {
4736
- const request = createActivityPubRequest(currentUrl, { userAgent });
4737
- logRequest(logger, request);
4738
- const response = await fetch(request, {
4739
- redirect: "manual",
4740
- signal: options?.signal
4741
- });
4742
- span.setAttribute("http.response.status_code", response.status);
4743
- if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
4806
+ const outcome = await fetchHop(session, currentUrl, span, options);
4807
+ if ("location" in outcome) {
4744
4808
  if (redirected >= DEFAULT_MAX_REDIRECTION) {
4745
4809
  logger.error("Too many redirections ({redirections}) while fetching document.", {
4746
4810
  redirections: redirected + 1,
@@ -4748,7 +4812,7 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
4748
4812
  });
4749
4813
  throw new FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
4750
4814
  }
4751
- const redirectUrl = new URL(response.headers.get("Location"), response.url === "" ? currentUrl : response.url).href;
4815
+ const redirectUrl = new URL(outcome.location, currentUrl).href;
4752
4816
  span.setAttribute("http.redirect.url", redirectUrl);
4753
4817
  if (visited.has(redirectUrl)) {
4754
4818
  logger.error("Detected a redirect loop while fetching document: {url} -> {redirectUrl}", {
@@ -4759,12 +4823,14 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
4759
4823
  }
4760
4824
  return await load(redirectUrl, options, redirected + 1, visited);
4761
4825
  }
4762
- const result = await getRemoteDocument(currentUrl, response, async (alternateUrl) => {
4826
+ let result;
4827
+ if ("alternate" in outcome) {
4828
+ const alternateUrl = outcome.alternate;
4763
4829
  options?.signal?.throwIfAborted();
4764
4830
  if (redirected >= DEFAULT_MAX_REDIRECTION) throw new FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
4765
4831
  if (visited.has(alternateUrl)) throw new FetchError(currentUrl, `Redirect loop detected: ${alternateUrl}`);
4766
- return await load(alternateUrl, options, redirected + 1, visited);
4767
- });
4832
+ result = await load(alternateUrl, options, redirected + 1, visited);
4833
+ } else result = outcome.document;
4768
4834
  span.setAttribute("docloader.document_url", result.documentUrl);
4769
4835
  if (result.contextUrl != null) span.setAttribute("docloader.context_url", result.contextUrl);
4770
4836
  return result;
@@ -4783,4 +4849,4 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
4783
4849
  return (url, options) => load(url, options);
4784
4850
  }
4785
4851
  //#endregion
4786
- export { cid_v1_default as i, getRemoteDocument as n, preloadedContexts as r, getDocumentLoader as t };
4852
+ export { cid_v1_default as a, preloadedContexts as i, getDocumentLoader as n, getRemoteDocument as r, createDocumentLoader as t };
@@ -1,8 +1,9 @@
1
1
  require("./chunk-Do9eywBl.cjs");
2
- const require_request = require("./request-C_PrlGe_.cjs");
3
- const require_body = require("./body-BI7IK9DK.cjs");
2
+ const require_request = require("./request-CzYw8aEp.cjs");
3
+ const require_body = require("./body-i_FdQE3f.cjs");
4
4
  const require_link = require("./link-qDTmjPw_.cjs");
5
- const require_url = require("./url-vZTgkIZg.cjs");
5
+ const require_url = require("./url-DLuSj57U.cjs");
6
+ const require_public_fetch = require("./public-fetch-Ddjfxbwc.cjs");
6
7
  let _logtape_logtape = require("@logtape/logtape");
7
8
  let _opentelemetry_api = require("@opentelemetry/api");
8
9
  //#region src/contexts/activitystreams.json
@@ -4592,13 +4593,47 @@ const DEFAULT_MAX_REDIRECTION = 20;
4592
4593
  * Gets a {@link RemoteDocument} from the given response.
4593
4594
  * @param url The URL of the document to load.
4594
4595
  * @param response The response to get the document from.
4595
- * @param fetch The function to fetch the document.
4596
+ * @param fetch The function to fetch the document. It is called with the URL
4597
+ * of an alternate document, if any, after the response body has
4598
+ * been either read or its cancellation has been started.
4596
4599
  * @returns The loaded remote document.
4597
4600
  * @throws {FetchError} If the response is not OK.
4598
4601
  * @internal
4599
4602
  */
4600
4603
  async function getRemoteDocument(url, response, fetch) {
4601
- const documentUrl = response.url === "" ? url : response.url;
4604
+ const result = await parseRemoteDocument(url, response);
4605
+ if ("alternate" in result) return await fetch(result.alternate);
4606
+ return result.document;
4607
+ }
4608
+ /**
4609
+ * Starts cancelling the body of a response without waiting for it to settle.
4610
+ * The cancellation of a branch of a tee (e.g., a body cloned by a fetch mock)
4611
+ * settles only after the other branch is cancelled too, per the Streams
4612
+ * standard, so awaiting it could block forever. Failures are only logged.
4613
+ * @param response The response whose body is cancelled.
4614
+ * @param url The URL of the response, for logging.
4615
+ */
4616
+ function cancelResponseBody(response, url) {
4617
+ response.body?.cancel().catch((error) => {
4618
+ logger.debug("Failed to cancel the response body from {url}: {error}", {
4619
+ url,
4620
+ error
4621
+ });
4622
+ });
4623
+ }
4624
+ /**
4625
+ * Gets a {@link RemoteDocument} from the given response, or the URL of an
4626
+ * alternate document to load instead. When an alternate document is found,
4627
+ * the response body has been either read or its cancellation has been started
4628
+ * (without waiting for it), so that the alternate document can be loaded after
4629
+ * the response's session is closed.
4630
+ * @param url The URL of the document to load.
4631
+ * @param response The response to get the document from.
4632
+ * @returns The loaded remote document, or the alternate document URL.
4633
+ * @throws {FetchError} If the response is not OK.
4634
+ */
4635
+ async function parseRemoteDocument(url, response) {
4636
+ const documentUrl = url;
4602
4637
  const docUrl = new URL(documentUrl);
4603
4638
  if (!response.ok) {
4604
4639
  logger.error("Failed to fetch document: {status} {url} {headers}", {
@@ -4635,7 +4670,8 @@ async function getRemoteDocument(url, response, fetch) {
4635
4670
  alternateUrl: altUri.href,
4636
4671
  url: documentUrl
4637
4672
  });
4638
- return await fetch(altUri.href);
4673
+ cancelResponseBody(response, documentUrl);
4674
+ return { alternate: altUri.href };
4639
4675
  }
4640
4676
  }
4641
4677
  }
@@ -4660,7 +4696,7 @@ async function getRemoteDocument(url, response, fetch) {
4660
4696
  alternateUrl: attribs.href,
4661
4697
  url: documentUrl
4662
4698
  });
4663
- return await fetch(new URL(attribs.href, docUrl).href);
4699
+ return { alternate: new URL(attribs.href, docUrl).href };
4664
4700
  }
4665
4701
  }
4666
4702
  document = JSON.parse(html);
@@ -4670,11 +4706,11 @@ async function getRemoteDocument(url, response, fetch) {
4670
4706
  url: documentUrl,
4671
4707
  headers: Object.fromEntries(response.headers.entries())
4672
4708
  });
4673
- return {
4709
+ return { document: {
4674
4710
  contextUrl,
4675
4711
  document,
4676
4712
  documentUrl
4677
- };
4713
+ } };
4678
4714
  }
4679
4715
  /**
4680
4716
  * Creates a JSON-LD document loader that utilizes the browser's `fetch` API.
@@ -4700,12 +4736,58 @@ async function getRemoteDocument(url, response, fetch) {
4700
4736
  * @returns The document loader.
4701
4737
  * @since 1.3.0
4702
4738
  */
4703
- function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAgent } = {}) {
4739
+ function getDocumentLoader(options = {}) {
4740
+ return createDocumentLoader(options);
4741
+ }
4742
+ /**
4743
+ * The implementation of {@link getDocumentLoader}, with injectable
4744
+ * dependencies. This is not exported from any entry point.
4745
+ * @param options Options for the document loader.
4746
+ * @param deps The dependencies.
4747
+ * @returns The document loader.
4748
+ */
4749
+ function createDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAgent } = {}, deps = {}) {
4704
4750
  const tracer = _opentelemetry_api.trace.getTracerProvider().getTracer(require_request.name, require_request.version);
4751
+ async function openHop(url, signal) {
4752
+ try {
4753
+ if (deps.openHop != null) return await deps.openHop(url);
4754
+ return allowPrivateAddress ? await require_public_fetch.openDirectFetch(url) : await require_public_fetch.openPublicFetch(url, { signal });
4755
+ } catch (error) {
4756
+ if (error instanceof require_url.UrlError) if (error.reason === "dns") logger.debug("DNS lookup failed for {url}", {
4757
+ url,
4758
+ error
4759
+ });
4760
+ else logger.error("Disallowed private URL: {url}", {
4761
+ url,
4762
+ error
4763
+ });
4764
+ throw error;
4765
+ }
4766
+ }
4767
+ async function fetchHop(session, url, span, options) {
4768
+ try {
4769
+ const request = require_request.createActivityPubRequest(url, { userAgent });
4770
+ require_request.logRequest(logger, request);
4771
+ const response = await session.fetch(request, {
4772
+ redirect: "manual",
4773
+ signal: options?.signal
4774
+ });
4775
+ span.setAttribute("http.response.status_code", response.status);
4776
+ if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
4777
+ const location = response.headers.get("Location");
4778
+ cancelResponseBody(response, url);
4779
+ return { location };
4780
+ }
4781
+ return await parseRemoteDocument(url, response);
4782
+ } finally {
4783
+ await session.close();
4784
+ }
4785
+ }
4705
4786
  async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
4706
4787
  options?.signal?.throwIfAborted();
4707
4788
  const parsedUrl = new URL(url);
4708
4789
  if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") throw new require_url.UrlError(`Unsupported protocol: ${parsedUrl.protocol}`);
4790
+ if (parsedUrl.username !== "" || parsedUrl.password !== "") throw new require_url.UrlError("URLs with credentials are not allowed");
4709
4791
  const currentUrl = parsedUrl.href;
4710
4792
  if (!skipPreloadedContexts && currentUrl in preloadedContexts) {
4711
4793
  logger.debug("Using preloaded context: {url}.", { url: currentUrl });
@@ -4715,33 +4797,15 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
4715
4797
  documentUrl: currentUrl
4716
4798
  };
4717
4799
  }
4718
- if (!allowPrivateAddress) try {
4719
- await require_url.validatePublicUrl(currentUrl);
4720
- } catch (error) {
4721
- if (error instanceof require_url.UrlError) if (error.reason === "dns") logger.debug("DNS lookup failed for {url}", {
4722
- url: currentUrl,
4723
- error
4724
- });
4725
- else logger.error("Disallowed private URL: {url}", {
4726
- url: currentUrl,
4727
- error
4728
- });
4729
- throw error;
4730
- }
4800
+ const session = await openHop(currentUrl, options?.signal);
4731
4801
  visited.add(currentUrl);
4732
4802
  return await tracer.startActiveSpan("activitypub.fetch_document", {
4733
4803
  kind: _opentelemetry_api.SpanKind.CLIENT,
4734
4804
  attributes: { "url.full": currentUrl }
4735
4805
  }, async (span) => {
4736
4806
  try {
4737
- const request = require_request.createActivityPubRequest(currentUrl, { userAgent });
4738
- require_request.logRequest(logger, request);
4739
- const response = await fetch(request, {
4740
- redirect: "manual",
4741
- signal: options?.signal
4742
- });
4743
- span.setAttribute("http.response.status_code", response.status);
4744
- if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
4807
+ const outcome = await fetchHop(session, currentUrl, span, options);
4808
+ if ("location" in outcome) {
4745
4809
  if (redirected >= DEFAULT_MAX_REDIRECTION) {
4746
4810
  logger.error("Too many redirections ({redirections}) while fetching document.", {
4747
4811
  redirections: redirected + 1,
@@ -4749,7 +4813,7 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
4749
4813
  });
4750
4814
  throw new require_request.FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
4751
4815
  }
4752
- const redirectUrl = new URL(response.headers.get("Location"), response.url === "" ? currentUrl : response.url).href;
4816
+ const redirectUrl = new URL(outcome.location, currentUrl).href;
4753
4817
  span.setAttribute("http.redirect.url", redirectUrl);
4754
4818
  if (visited.has(redirectUrl)) {
4755
4819
  logger.error("Detected a redirect loop while fetching document: {url} -> {redirectUrl}", {
@@ -4760,12 +4824,14 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
4760
4824
  }
4761
4825
  return await load(redirectUrl, options, redirected + 1, visited);
4762
4826
  }
4763
- const result = await getRemoteDocument(currentUrl, response, async (alternateUrl) => {
4827
+ let result;
4828
+ if ("alternate" in outcome) {
4829
+ const alternateUrl = outcome.alternate;
4764
4830
  options?.signal?.throwIfAborted();
4765
4831
  if (redirected >= DEFAULT_MAX_REDIRECTION) throw new require_request.FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
4766
4832
  if (visited.has(alternateUrl)) throw new require_request.FetchError(currentUrl, `Redirect loop detected: ${alternateUrl}`);
4767
- return await load(alternateUrl, options, redirected + 1, visited);
4768
- });
4833
+ result = await load(alternateUrl, options, redirected + 1, visited);
4834
+ } else result = outcome.document;
4769
4835
  span.setAttribute("docloader.document_url", result.documentUrl);
4770
4836
  if (result.contextUrl != null) span.setAttribute("docloader.context_url", result.contextUrl);
4771
4837
  return result;
@@ -4790,6 +4856,12 @@ Object.defineProperty(exports, "cid_v1_default", {
4790
4856
  return cid_v1_default;
4791
4857
  }
4792
4858
  });
4859
+ Object.defineProperty(exports, "createDocumentLoader", {
4860
+ enumerable: true,
4861
+ get: function() {
4862
+ return createDocumentLoader;
4863
+ }
4864
+ });
4793
4865
  Object.defineProperty(exports, "getDocumentLoader", {
4794
4866
  enumerable: true,
4795
4867
  get: function() {