@fedify/vocab-runtime 2.2.18 → 2.2.20

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 -28
  8. package/dist/mod.d.ts +6 -28
  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-CbvYxP3V.mjs → body-B9_hb-GO.mjs} +1 -1
  15. package/dist/tests/{body-CaJBg0tr.cjs → body-pOSG2RFb.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-jQMjHIRI.mjs → docloader-Bdlp7FkK.mjs} +103 -37
  21. package/dist/tests/{docloader-k2T5kUlV.cjs → docloader-Ck3VfY29.cjs} +107 -35
  22. package/dist/tests/docloader.test.cjs +569 -1273
  23. package/dist/tests/docloader.test.mjs +515 -1238
  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-BngWcP0t.cjs → request-B3NUKbyk.cjs} +1 -1
  74. package/dist/tests/{request-BE7XIrGw.mjs → request-BSg3IQzf.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 +784 -32
  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-BE7XIrGw.mjs";
1
+ import { t as FetchError } from "./request-BSg3IQzf.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-BngWcP0t.cjs");
2
+ const require_request = require("./request-B3NUKbyk.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-CaJBg0tr.cjs");
2
+ const require_body = require("./body-pOSG2RFb.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-CbvYxP3V.mjs";
1
+ import { r as readBoundedText, t as BodyTooLargeError } from "./body-B9_hb-GO.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-k2T5kUlV.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-Ck3VfY29.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-jQMjHIRI.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-Bdlp7FkK.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-BE7XIrGw.mjs";
2
- import { n as MAX_BODY_SIZE, r as readBoundedText } from "./body-CbvYxP3V.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-BSg3IQzf.mjs";
2
+ import { n as MAX_BODY_SIZE, r as readBoundedText } from "./body-B9_hb-GO.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,13 +4735,59 @@ async function getRemoteDocument(url, response, fetch) {
4699
4735
  * @returns The document loader.
4700
4736
  * @since 1.3.0
4701
4737
  */
4702
- function getDocumentLoader({ allowPrivateAddress, maxRedirection, 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, maxRedirection, skipPreloadedContexts, userAgent } = {}, deps = {}) {
4703
4749
  const tracer = trace.getTracerProvider().getTracer(name, version);
4704
4750
  const maximumRedirection = maxRedirection ?? DEFAULT_MAX_REDIRECTION;
4751
+ async function openHop(url, signal) {
4752
+ try {
4753
+ if (deps.openHop != null) return await deps.openHop(url);
4754
+ return allowPrivateAddress ? await openDirectFetch(url) : await openPublicFetch(url, { signal });
4755
+ } catch (error) {
4756
+ if (error instanceof 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 = createActivityPubRequest(url, { userAgent });
4770
+ 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 UrlError(`Unsupported protocol: ${parsedUrl.protocol}`);
4790
+ if (parsedUrl.username !== "" || parsedUrl.password !== "") throw new 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, maxRedirection, skipPreloadedC
4715
4797
  documentUrl: currentUrl
4716
4798
  };
4717
4799
  }
4718
- if (!allowPrivateAddress) try {
4719
- await validatePublicUrl(currentUrl);
4720
- } catch (error) {
4721
- if (error instanceof 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: SpanKind.CLIENT,
4734
4804
  attributes: { "url.full": currentUrl }
4735
4805
  }, async (span) => {
4736
4806
  try {
4737
- const request = createActivityPubRequest(currentUrl, { userAgent });
4738
- 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 >= maximumRedirection) {
4746
4810
  logger.error("Too many redirections ({redirections}) while fetching document.", {
4747
4811
  redirections: redirected + 1,
@@ -4749,7 +4813,7 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
4749
4813
  });
4750
4814
  throw new 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, maxRedirection, skipPreloadedC
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 FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
4766
4832
  if (visited.has(alternateUrl)) throw new 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;
@@ -4784,4 +4850,4 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
4784
4850
  return (url, options) => load(url, options);
4785
4851
  }
4786
4852
  //#endregion
4787
- export { cid_v1_default as i, getRemoteDocument as n, preloadedContexts as r, getDocumentLoader as t };
4853
+ 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-BngWcP0t.cjs");
3
- const require_body = require("./body-CaJBg0tr.cjs");
2
+ const require_request = require("./request-B3NUKbyk.cjs");
3
+ const require_body = require("./body-pOSG2RFb.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,13 +4736,59 @@ async function getRemoteDocument(url, response, fetch) {
4700
4736
  * @returns The document loader.
4701
4737
  * @since 1.3.0
4702
4738
  */
4703
- function getDocumentLoader({ allowPrivateAddress, maxRedirection, 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, maxRedirection, skipPreloadedContexts, userAgent } = {}, deps = {}) {
4704
4750
  const tracer = _opentelemetry_api.trace.getTracerProvider().getTracer(require_request.name, require_request.version);
4705
4751
  const maximumRedirection = maxRedirection ?? DEFAULT_MAX_REDIRECTION;
4752
+ async function openHop(url, signal) {
4753
+ try {
4754
+ if (deps.openHop != null) return await deps.openHop(url);
4755
+ return allowPrivateAddress ? await require_public_fetch.openDirectFetch(url) : await require_public_fetch.openPublicFetch(url, { signal });
4756
+ } catch (error) {
4757
+ if (error instanceof require_url.UrlError) if (error.reason === "dns") logger.debug("DNS lookup failed for {url}", {
4758
+ url,
4759
+ error
4760
+ });
4761
+ else logger.error("Disallowed private URL: {url}", {
4762
+ url,
4763
+ error
4764
+ });
4765
+ throw error;
4766
+ }
4767
+ }
4768
+ async function fetchHop(session, url, span, options) {
4769
+ try {
4770
+ const request = require_request.createActivityPubRequest(url, { userAgent });
4771
+ require_request.logRequest(logger, request);
4772
+ const response = await session.fetch(request, {
4773
+ redirect: "manual",
4774
+ signal: options?.signal
4775
+ });
4776
+ span.setAttribute("http.response.status_code", response.status);
4777
+ if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
4778
+ const location = response.headers.get("Location");
4779
+ cancelResponseBody(response, url);
4780
+ return { location };
4781
+ }
4782
+ return await parseRemoteDocument(url, response);
4783
+ } finally {
4784
+ await session.close();
4785
+ }
4786
+ }
4706
4787
  async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
4707
4788
  options?.signal?.throwIfAborted();
4708
4789
  const parsedUrl = new URL(url);
4709
4790
  if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") throw new require_url.UrlError(`Unsupported protocol: ${parsedUrl.protocol}`);
4791
+ if (parsedUrl.username !== "" || parsedUrl.password !== "") throw new require_url.UrlError("URLs with credentials are not allowed");
4710
4792
  const currentUrl = parsedUrl.href;
4711
4793
  if (!skipPreloadedContexts && currentUrl in preloadedContexts) {
4712
4794
  logger.debug("Using preloaded context: {url}.", { url: currentUrl });
@@ -4716,33 +4798,15 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
4716
4798
  documentUrl: currentUrl
4717
4799
  };
4718
4800
  }
4719
- if (!allowPrivateAddress) try {
4720
- await require_url.validatePublicUrl(currentUrl);
4721
- } catch (error) {
4722
- if (error instanceof require_url.UrlError) if (error.reason === "dns") logger.debug("DNS lookup failed for {url}", {
4723
- url: currentUrl,
4724
- error
4725
- });
4726
- else logger.error("Disallowed private URL: {url}", {
4727
- url: currentUrl,
4728
- error
4729
- });
4730
- throw error;
4731
- }
4801
+ const session = await openHop(currentUrl, options?.signal);
4732
4802
  visited.add(currentUrl);
4733
4803
  return await tracer.startActiveSpan("activitypub.fetch_document", {
4734
4804
  kind: _opentelemetry_api.SpanKind.CLIENT,
4735
4805
  attributes: { "url.full": currentUrl }
4736
4806
  }, async (span) => {
4737
4807
  try {
4738
- const request = require_request.createActivityPubRequest(currentUrl, { userAgent });
4739
- require_request.logRequest(logger, request);
4740
- const response = await fetch(request, {
4741
- redirect: "manual",
4742
- signal: options?.signal
4743
- });
4744
- span.setAttribute("http.response.status_code", response.status);
4745
- if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
4808
+ const outcome = await fetchHop(session, currentUrl, span, options);
4809
+ if ("location" in outcome) {
4746
4810
  if (redirected >= maximumRedirection) {
4747
4811
  logger.error("Too many redirections ({redirections}) while fetching document.", {
4748
4812
  redirections: redirected + 1,
@@ -4750,7 +4814,7 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
4750
4814
  });
4751
4815
  throw new require_request.FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
4752
4816
  }
4753
- const redirectUrl = new URL(response.headers.get("Location"), response.url === "" ? currentUrl : response.url).href;
4817
+ const redirectUrl = new URL(outcome.location, currentUrl).href;
4754
4818
  span.setAttribute("http.redirect.url", redirectUrl);
4755
4819
  if (visited.has(redirectUrl)) {
4756
4820
  logger.error("Detected a redirect loop while fetching document: {url} -> {redirectUrl}", {
@@ -4761,12 +4825,14 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
4761
4825
  }
4762
4826
  return await load(redirectUrl, options, redirected + 1, visited);
4763
4827
  }
4764
- const result = await getRemoteDocument(currentUrl, response, async (alternateUrl) => {
4828
+ let result;
4829
+ if ("alternate" in outcome) {
4830
+ const alternateUrl = outcome.alternate;
4765
4831
  options?.signal?.throwIfAborted();
4766
4832
  if (redirected >= DEFAULT_MAX_REDIRECTION) throw new require_request.FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
4767
4833
  if (visited.has(alternateUrl)) throw new require_request.FetchError(currentUrl, `Redirect loop detected: ${alternateUrl}`);
4768
- return await load(alternateUrl, options, redirected + 1, visited);
4769
- });
4834
+ result = await load(alternateUrl, options, redirected + 1, visited);
4835
+ } else result = outcome.document;
4770
4836
  span.setAttribute("docloader.document_url", result.documentUrl);
4771
4837
  if (result.contextUrl != null) span.setAttribute("docloader.context_url", result.contextUrl);
4772
4838
  return result;
@@ -4791,6 +4857,12 @@ Object.defineProperty(exports, "cid_v1_default", {
4791
4857
  return cid_v1_default;
4792
4858
  }
4793
4859
  });
4860
+ Object.defineProperty(exports, "createDocumentLoader", {
4861
+ enumerable: true,
4862
+ get: function() {
4863
+ return createDocumentLoader;
4864
+ }
4865
+ });
4794
4866
  Object.defineProperty(exports, "getDocumentLoader", {
4795
4867
  enumerable: true,
4796
4868
  get: function() {