@fedify/vocab-runtime 2.5.0-dev.2446 → 2.5.0-dev.2492

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 (121) hide show
  1. package/deno.json +5 -2
  2. package/dist/{docloader-BBnBpbsG.d.cts → docloader-DD6binpN.d.cts} +4 -8
  3. package/dist/{docloader-BBnBpbsG.d.ts → docloader-DD6binpN.d.ts} +4 -8
  4. package/dist/internal/jsonld-cache.cjs +1 -1
  5. package/dist/internal/jsonld-cache.d.cts +1 -1
  6. package/dist/internal/jsonld-cache.d.ts +1 -1
  7. package/dist/internal/jsonld-cache.js +1 -1
  8. package/dist/internal/portable-dereference.cjs +72 -7
  9. package/dist/internal/portable-dereference.d.cts +63 -3
  10. package/dist/internal/portable-dereference.d.ts +63 -3
  11. package/dist/internal/portable-dereference.js +72 -8
  12. package/dist/internal.cjs +6 -0
  13. package/dist/internal.d.cts +108 -0
  14. package/dist/internal.d.ts +108 -0
  15. package/dist/internal.js +3 -0
  16. package/dist/mod.cjs +104 -38
  17. package/dist/mod.d.cts +3 -336
  18. package/dist/mod.d.ts +4 -336
  19. package/dist/mod.js +104 -38
  20. package/dist/{portable-BDydjp5H.d.ts → portable-1VSnSJFj.d.ts} +1 -1
  21. package/dist/{portable-Dfvcg_Wy.d.cts → portable-Cr7NwZwb.d.cts} +1 -1
  22. package/dist/public-fetch-Dvfvawei.cjs +527 -0
  23. package/dist/public-fetch-ziZ39L5I.js +510 -0
  24. package/dist/tests/{body-Ddx6WgR5.cjs → body-C8d1PmDa.cjs} +1 -1
  25. package/dist/tests/{body-CQi2bVi5.mjs → body-HSLxhexN.mjs} +1 -1
  26. package/dist/tests/body.test.cjs +1 -1
  27. package/dist/tests/body.test.mjs +1 -1
  28. package/dist/tests/decimal.test.cjs +4 -4
  29. package/dist/tests/decimal.test.mjs +4 -4
  30. package/dist/tests/{docloader-C3vDXwn1.cjs → docloader-D1fNCGam.cjs} +111 -39
  31. package/dist/tests/{docloader-CrVannnU.mjs → docloader-DkEUFJui.mjs} +106 -40
  32. package/dist/tests/docloader.test.cjs +531 -1293
  33. package/dist/tests/docloader.test.mjs +472 -1252
  34. package/dist/tests/esm-Ds-eHTfY.mjs +1229 -0
  35. package/dist/tests/esm-Q2tdh5f3.cjs +1215 -0
  36. package/dist/tests/internal/portable-dereference.test.cjs +3 -3
  37. package/dist/tests/internal/portable-dereference.test.mjs +3 -3
  38. package/dist/tests/{jsonld-cache-BEXiiM-V.cjs → jsonld-cache-DKIWKxEX.cjs} +1 -1
  39. package/dist/tests/{jsonld-cache-DFnWsLuZ.mjs → jsonld-cache-KH40z9_7.mjs} +1 -1
  40. package/dist/tests/jsonld-cache.test.cjs +2 -2
  41. package/dist/tests/jsonld-cache.test.mjs +2 -2
  42. package/dist/tests/node-CbBR-th-.mjs +84 -0
  43. package/dist/tests/node-Ch29PDXR.cjs +89 -0
  44. package/dist/tests/pin-grant-CPrpk1l4.cjs +70 -0
  45. package/dist/tests/pin-grant-H5XhM1Xs.mjs +53 -0
  46. package/dist/tests/pin-grant.test.cjs +87 -0
  47. package/dist/tests/pin-grant.test.d.cts +1 -0
  48. package/dist/tests/pin-grant.test.d.mts +1 -0
  49. package/dist/tests/pin-grant.test.mjs +88 -0
  50. package/dist/tests/pinners/bun.test.cjs +166 -0
  51. package/dist/tests/pinners/bun.test.d.cts +1 -0
  52. package/dist/tests/pinners/bun.test.d.mts +1 -0
  53. package/dist/tests/pinners/bun.test.mjs +167 -0
  54. package/dist/tests/pinners/node.test.cjs +269 -0
  55. package/dist/tests/pinners/node.test.d.cts +1 -0
  56. package/dist/tests/pinners/node.test.d.mts +1 -0
  57. package/dist/tests/pinners/node.test.mjs +268 -0
  58. package/dist/tests/pinners/pinners.test.cjs +196 -0
  59. package/dist/tests/pinners/pinners.test.d.cts +1 -0
  60. package/dist/tests/pinners/pinners.test.d.mts +1 -0
  61. package/dist/tests/pinners/pinners.test.mjs +197 -0
  62. package/dist/tests/pinners/testdata/node-child.cjs +135 -0
  63. package/dist/tests/pinners/testdata/node-child.d.cts +1 -0
  64. package/dist/tests/pinners/testdata/node-child.d.mts +1 -0
  65. package/dist/tests/pinners/testdata/node-child.mjs +134 -0
  66. package/dist/tests/pinners-DLH_npLf.cjs +220 -0
  67. package/dist/tests/pinners-tGcl49uF.mjs +203 -0
  68. package/dist/tests/{portable-dereference-DaTBBvCj.cjs → portable-dereference-BUoDzMxk.cjs} +2 -2
  69. package/dist/tests/{portable-dereference-sGRs_qdT.mjs → portable-dereference-Du2k3ngE.mjs} +2 -2
  70. package/dist/tests/{portable-media-BJf_gGQz.cjs → portable-media-BM9zgj9g.cjs} +3 -3
  71. package/dist/tests/{portable-media-Cc58dUeE.mjs → portable-media-CTxGibcj.mjs} +3 -3
  72. package/dist/tests/portable-media.test.cjs +1 -1
  73. package/dist/tests/portable-media.test.mjs +1 -1
  74. package/dist/tests/portable-workers.test.cjs +2 -2
  75. package/dist/tests/portable-workers.test.mjs +2 -2
  76. package/dist/tests/public-fetch-BGj5pzUD.cjs +186 -0
  77. package/dist/tests/public-fetch-Cxo3Yzdf.mjs +170 -0
  78. package/dist/tests/public-fetch.test.cjs +1073 -0
  79. package/dist/tests/public-fetch.test.d.cts +1 -0
  80. package/dist/tests/public-fetch.test.d.mts +1 -0
  81. package/dist/tests/public-fetch.test.mjs +1074 -0
  82. package/dist/tests/{request-glvxfx2U.cjs → request-7q-Zfe1p.cjs} +2 -2
  83. package/dist/tests/{request-1pPyYz3e.mjs → request-D5uGtmmq.mjs} +2 -2
  84. package/dist/tests/request.test.cjs +2 -2
  85. package/dist/tests/request.test.mjs +2 -2
  86. package/dist/tests/support-Bj44xlQu.mjs +272 -0
  87. package/dist/tests/support-BrdlbLJk.cjs +363 -0
  88. package/dist/tests/{url-BNakuZ8k.cjs → url-Bdj-rTcC.cjs} +88 -12
  89. package/dist/tests/{url-DMxmp7ZG.mjs → url-COj5Q8mb.mjs} +65 -13
  90. package/dist/tests/url.test.cjs +4 -1
  91. package/dist/tests/url.test.mjs +4 -1
  92. package/dist/url-4EE56sNP.d.cts +352 -0
  93. package/dist/url-4EE56sNP.d.ts +352 -0
  94. package/dist/{url-DrGTR8yv.cjs → url-DIKZnaJv.cjs} +88 -12
  95. package/dist/{url-Dzyp-NsC.js → url-mWveR5GA.js} +65 -13
  96. package/package.json +14 -3
  97. package/src/docloader.test.ts +696 -48
  98. package/src/docloader.ts +186 -75
  99. package/src/internal/portable-dereference.ts +116 -6
  100. package/src/internal.ts +13 -0
  101. package/src/pin-grant.test.ts +118 -0
  102. package/src/pin-grant.ts +99 -0
  103. package/src/pinners/bun.test.ts +278 -0
  104. package/src/pinners/bun.ts +223 -0
  105. package/src/pinners/mod.ts +59 -0
  106. package/src/pinners/node.test.ts +415 -0
  107. package/src/pinners/node.ts +144 -0
  108. package/src/pinners/pinners.test.ts +316 -0
  109. package/src/pinners/testdata/node-child.ts +147 -0
  110. package/src/pinners/testdata/proxy-child.ts +82 -0
  111. package/src/pinners/testdata/support.ts +343 -0
  112. package/src/pinners/testdata/tls-cert.pem +12 -0
  113. package/src/pinners/testdata/tls-child.ts +88 -0
  114. package/src/pinners/testdata/tls-key.pem +5 -0
  115. package/src/pinners/types.ts +48 -0
  116. package/src/public-fetch.test.ts +1286 -0
  117. package/src/public-fetch.ts +338 -0
  118. package/src/testdata/public-fetch-deno-worker.ts +74 -0
  119. package/src/url.test.ts +11 -0
  120. package/src/url.ts +113 -22
  121. package/tsdown.config.ts +7 -2
@@ -0,0 +1,135 @@
1
+ const require_rolldown_runtime = require("../../rolldown-runtime-emK7D4bc.cjs");
2
+ const require_pin_grant = require("../../pin-grant-CPrpk1l4.cjs");
3
+ const require_node = require("../../node-Ch29PDXR.cjs");
4
+ let node_process = require("node:process");
5
+ node_process = require_rolldown_runtime.__toESM(node_process, 1);
6
+ //#region src/pinners/testdata/node-child.ts
7
+ /**
8
+ * A child process for the regression tests of the Node.js pinner in
9
+ * *../node.test.ts* that depend on the environment at startup: Node.js reads
10
+ * proxy environment variables (with `NODE_USE_ENV_PROXY=1`) and
11
+ * `NODE_EXTRA_CA_CERTS` only at startup.
12
+ *
13
+ * Node.js cannot run TypeScript sources without flags before 22.6.0, so
14
+ * *../../../tsdown.config.ts* compiles this script into
15
+ * *dist/tests/pinners/testdata/node-child.mjs*, which the tests run. It has
16
+ * no top-level `await`, so that it can be compiled to CommonJS as well.
17
+ *
18
+ * Usage:
19
+ *
20
+ * - `node node-child.mjs proxy <http-port> <https-port>`: requests
21
+ * `http://pinned-host.invalid:<http-port>/pinned` and
22
+ * `https://pinned-host.invalid:<https-port>/pinned` through a pinner
23
+ * that allows loopback addresses, with both pinned to 127.0.0.1, and then
24
+ * `http://127.0.0.1:<http-port>/control` with the global `fetch()` as
25
+ * a control.
26
+ * - `node node-child.mjs tls <https-port>`: requests
27
+ * `https://<hostname>:<https-port>/<case>` through a pinner that allows
28
+ * loopback addresses, with every hostname pinned to 127.0.0.1, in the same
29
+ * order as *tls-child.ts* does for Bun.
30
+ *
31
+ * It prints the results as a JSON line.
32
+ * @module
33
+ */
34
+ const loopback = [{
35
+ address: "127.0.0.1",
36
+ family: 4
37
+ }];
38
+ /**
39
+ * Describes an error by its code, or by the code of its cause, since
40
+ * the `fetch()` of Node.js rejects with a `TypeError` caused by the actual
41
+ * error.
42
+ */
43
+ function describeError(error) {
44
+ for (let e = error; typeof e === "object" && e !== null;) {
45
+ const { code, cause } = e;
46
+ if (typeof code === "string") return code;
47
+ e = cause;
48
+ }
49
+ return String(error);
50
+ }
51
+ async function attempt(fn) {
52
+ try {
53
+ const response = await fn();
54
+ return {
55
+ status: response.status,
56
+ body: await response.text()
57
+ };
58
+ } catch (error) {
59
+ return { error: describeError(error) };
60
+ }
61
+ }
62
+ function fetchPinned(pinner, url, withSignals = false) {
63
+ const grant = require_pin_grant.issuePinGrant(url, loopback);
64
+ return attempt(() => withSignals ? pinner.fetch(grant, new Request(url, { signal: new AbortController().signal }), new AbortController().signal) : pinner.fetch(grant, new Request(url)));
65
+ }
66
+ async function proxy(pinner, httpPort, httpsPort) {
67
+ const http = await fetchPinned(pinner, new URL(`http://pinned-host.invalid:${httpPort}/pinned`));
68
+ const https = await fetchPinned(pinner, new URL(`https://pinned-host.invalid:${httpsPort}/pinned`));
69
+ const control = await attempt(() => fetch(`http://127.0.0.1:${httpPort}/control`, {
70
+ redirect: "manual",
71
+ signal: AbortSignal.timeout(1e4)
72
+ }));
73
+ return {
74
+ version: node_process.default.version,
75
+ http,
76
+ https,
77
+ control
78
+ };
79
+ }
80
+ async function tls(pinner, port) {
81
+ const results = [];
82
+ for (const [name, hostname, withSignals] of [
83
+ [
84
+ "pinned",
85
+ "pinned-host.invalid",
86
+ false
87
+ ],
88
+ [
89
+ "unlisted",
90
+ "unlisted-host.invalid",
91
+ false
92
+ ],
93
+ [
94
+ "unlisted-again",
95
+ "unlisted-host.invalid",
96
+ false
97
+ ],
98
+ [
99
+ "alternate",
100
+ "alternate-host.invalid",
101
+ false
102
+ ],
103
+ [
104
+ "signals",
105
+ "pinned-host.invalid",
106
+ true
107
+ ]
108
+ ]) {
109
+ const url = new URL(`https://${hostname}:${port}/${name}`);
110
+ results.push({
111
+ case: name,
112
+ ...await fetchPinned(pinner, url, withSignals)
113
+ });
114
+ }
115
+ return {
116
+ version: node_process.default.version,
117
+ results
118
+ };
119
+ }
120
+ async function main() {
121
+ const [mode, ...ports] = node_process.default.argv.slice(2);
122
+ const [first, second] = ports.map(Number);
123
+ const pinner = require_node.createNodePinner({
124
+ verifyAddress() {},
125
+ undici: await import("undici")
126
+ });
127
+ if (mode === "proxy" && Number.isInteger(first) && Number.isInteger(second)) return await proxy(pinner, first, second);
128
+ else if (mode === "tls" && Number.isInteger(first)) return await tls(pinner, first);
129
+ throw new TypeError("Usage: node-child.mjs proxy <http-port> <https-port> | node-child.mjs tls <https-port>");
130
+ }
131
+ main().then((output) => node_process.default.stdout.write(`${JSON.stringify(output)}\n`, () => node_process.default.exit(0)), (error) => {
132
+ console.error(error);
133
+ node_process.default.exit(1);
134
+ });
135
+ //#endregion
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,134 @@
1
+ import { t as issuePinGrant } from "../../pin-grant-H5XhM1Xs.mjs";
2
+ import { t as createNodePinner } from "../../node-CbBR-th-.mjs";
3
+ import process from "node:process";
4
+ //#region src/pinners/testdata/node-child.ts
5
+ /**
6
+ * A child process for the regression tests of the Node.js pinner in
7
+ * *../node.test.ts* that depend on the environment at startup: Node.js reads
8
+ * proxy environment variables (with `NODE_USE_ENV_PROXY=1`) and
9
+ * `NODE_EXTRA_CA_CERTS` only at startup.
10
+ *
11
+ * Node.js cannot run TypeScript sources without flags before 22.6.0, so
12
+ * *../../../tsdown.config.ts* compiles this script into
13
+ * *dist/tests/pinners/testdata/node-child.mjs*, which the tests run. It has
14
+ * no top-level `await`, so that it can be compiled to CommonJS as well.
15
+ *
16
+ * Usage:
17
+ *
18
+ * - `node node-child.mjs proxy <http-port> <https-port>`: requests
19
+ * `http://pinned-host.invalid:<http-port>/pinned` and
20
+ * `https://pinned-host.invalid:<https-port>/pinned` through a pinner
21
+ * that allows loopback addresses, with both pinned to 127.0.0.1, and then
22
+ * `http://127.0.0.1:<http-port>/control` with the global `fetch()` as
23
+ * a control.
24
+ * - `node node-child.mjs tls <https-port>`: requests
25
+ * `https://<hostname>:<https-port>/<case>` through a pinner that allows
26
+ * loopback addresses, with every hostname pinned to 127.0.0.1, in the same
27
+ * order as *tls-child.ts* does for Bun.
28
+ *
29
+ * It prints the results as a JSON line.
30
+ * @module
31
+ */
32
+ const loopback = [{
33
+ address: "127.0.0.1",
34
+ family: 4
35
+ }];
36
+ /**
37
+ * Describes an error by its code, or by the code of its cause, since
38
+ * the `fetch()` of Node.js rejects with a `TypeError` caused by the actual
39
+ * error.
40
+ */
41
+ function describeError(error) {
42
+ for (let e = error; typeof e === "object" && e !== null;) {
43
+ const { code, cause } = e;
44
+ if (typeof code === "string") return code;
45
+ e = cause;
46
+ }
47
+ return String(error);
48
+ }
49
+ async function attempt(fn) {
50
+ try {
51
+ const response = await fn();
52
+ return {
53
+ status: response.status,
54
+ body: await response.text()
55
+ };
56
+ } catch (error) {
57
+ return { error: describeError(error) };
58
+ }
59
+ }
60
+ function fetchPinned(pinner, url, withSignals = false) {
61
+ const grant = issuePinGrant(url, loopback);
62
+ return attempt(() => withSignals ? pinner.fetch(grant, new Request(url, { signal: new AbortController().signal }), new AbortController().signal) : pinner.fetch(grant, new Request(url)));
63
+ }
64
+ async function proxy(pinner, httpPort, httpsPort) {
65
+ const http = await fetchPinned(pinner, new URL(`http://pinned-host.invalid:${httpPort}/pinned`));
66
+ const https = await fetchPinned(pinner, new URL(`https://pinned-host.invalid:${httpsPort}/pinned`));
67
+ const control = await attempt(() => fetch(`http://127.0.0.1:${httpPort}/control`, {
68
+ redirect: "manual",
69
+ signal: AbortSignal.timeout(1e4)
70
+ }));
71
+ return {
72
+ version: process.version,
73
+ http,
74
+ https,
75
+ control
76
+ };
77
+ }
78
+ async function tls(pinner, port) {
79
+ const results = [];
80
+ for (const [name, hostname, withSignals] of [
81
+ [
82
+ "pinned",
83
+ "pinned-host.invalid",
84
+ false
85
+ ],
86
+ [
87
+ "unlisted",
88
+ "unlisted-host.invalid",
89
+ false
90
+ ],
91
+ [
92
+ "unlisted-again",
93
+ "unlisted-host.invalid",
94
+ false
95
+ ],
96
+ [
97
+ "alternate",
98
+ "alternate-host.invalid",
99
+ false
100
+ ],
101
+ [
102
+ "signals",
103
+ "pinned-host.invalid",
104
+ true
105
+ ]
106
+ ]) {
107
+ const url = new URL(`https://${hostname}:${port}/${name}`);
108
+ results.push({
109
+ case: name,
110
+ ...await fetchPinned(pinner, url, withSignals)
111
+ });
112
+ }
113
+ return {
114
+ version: process.version,
115
+ results
116
+ };
117
+ }
118
+ async function main() {
119
+ const [mode, ...ports] = process.argv.slice(2);
120
+ const [first, second] = ports.map(Number);
121
+ const pinner = createNodePinner({
122
+ verifyAddress() {},
123
+ undici: await import("undici")
124
+ });
125
+ if (mode === "proxy" && Number.isInteger(first) && Number.isInteger(second)) return await proxy(pinner, first, second);
126
+ else if (mode === "tls" && Number.isInteger(first)) return await tls(pinner, first);
127
+ throw new TypeError("Usage: node-child.mjs proxy <http-port> <https-port> | node-child.mjs tls <https-port>");
128
+ }
129
+ main().then((output) => process.stdout.write(`${JSON.stringify(output)}\n`, () => process.exit(0)), (error) => {
130
+ console.error(error);
131
+ process.exit(1);
132
+ });
133
+ //#endregion
134
+ export {};
@@ -0,0 +1,220 @@
1
+ const require_url = require("./url-Bdj-rTcC.cjs");
2
+ const require_pin_grant = require("./pin-grant-CPrpk1l4.cjs");
3
+ const require_node = require("./node-Ch29PDXR.cjs");
4
+ let _logtape_logtape = require("@logtape/logtape");
5
+ let node_net = require("node:net");
6
+ //#region src/pinners/bun.ts
7
+ const logger = (0, _logtape_logtape.getLogger)([
8
+ "fedify",
9
+ "runtime",
10
+ "fetch"
11
+ ]);
12
+ /**
13
+ * The first Bun version that honors `proxy: false` when proxy environment
14
+ * variables are set at startup. Earlier versions send pinned requests
15
+ * through the proxy, with the validated IP address as the target.
16
+ */
17
+ const PROXY_FALSE_MIN_VERSION = "1.4.3";
18
+ const PROXY_VARIABLES = [
19
+ "HTTP_PROXY",
20
+ "http_proxy",
21
+ "HTTPS_PROXY",
22
+ "https_proxy",
23
+ "ALL_PROXY",
24
+ "all_proxy"
25
+ ];
26
+ const FALLBACK_ERROR_CODES = /* @__PURE__ */ new Set([
27
+ "ConnectionRefused",
28
+ "ECONNREFUSED",
29
+ "EHOSTUNREACH",
30
+ "ENETUNREACH",
31
+ "ETIMEDOUT"
32
+ ]);
33
+ /**
34
+ * Bun versions before 1.3.11 verify the server certificate against the `Host`
35
+ * header, including a non-default port, instead of `tls.serverName`, so
36
+ * pinned requests to HTTPS servers on non-default ports always fail the check
37
+ * there. A custom `checkServerIdentity` cannot stand in for it: these
38
+ * versions send the whole request before a failing custom check aborts it
39
+ * (and 1.3.10 even reuses the connection for the next request), so such
40
+ * requests are refused instead.
41
+ */
42
+ const HOST_HEADER_IDENTITY_RANGE = "<1.3.11";
43
+ /**
44
+ * Creates a pinner for Bun. Every request is sent to an IP literal URL with
45
+ * the original `Host` header, `tls.serverName` set to the original hostname
46
+ * (so that the certificate is still verified against it), and `proxy: false`.
47
+ *
48
+ * GET and HEAD requests fall back to the next address on transport errors
49
+ * raised before a response arrives. Other methods use only the first
50
+ * address, since a transport error does not guarantee that the request did
51
+ * not reach the server, and retrying it could deliver an activity twice.
52
+ * Together with the transport retries of `doubleKnock()`, a GET request is
53
+ * attempted at most twice per address. Note that Bun 1.4 also reports
54
+ * a connection reset after the request was sent as a `TypeError`, so GET and
55
+ * HEAD requests may be repeated against the next address in that case, which
56
+ * is acceptable for these idempotent methods.
57
+ *
58
+ * Bun versions before 1.3.11 verify certificates against the `Host` header,
59
+ * which includes a non-default port, and cannot safely check them against
60
+ * the original hostname in any other way, so requests to HTTPS servers on
61
+ * non-default ports fail there with a `TypeError` before connecting.
62
+ *
63
+ * Bun versions before 1.4.3 ignore `proxy: false` when proxy environment
64
+ * variables are set, so requests go through the proxy with the validated IP
65
+ * address as the target. In that case a warning is logged once.
66
+ * @param options The pinner options.
67
+ * @returns The pinner.
68
+ */
69
+ function createBunPinner({ verifyAddress }) {
70
+ let proxyWarningPending = ignoresProxyFalse() && hasProxyVariables();
71
+ return {
72
+ supports(url) {
73
+ return url.protocol === "http:" || url.protocol === "https:";
74
+ },
75
+ async fetch(grant, request, signal) {
76
+ const url = new URL(request.url);
77
+ const { hostname, addresses } = require_pin_grant.redeemPinGrant(grant, url);
78
+ if (proxyWarningPending) {
79
+ proxyWarningPending = false;
80
+ logger.warn("Bun {version} ignores proxy: false while proxy environment variables are set, so outbound requests go through the proxy instead of connecting directly to the validated addresses. Upgrade to Bun {minVersion} or later, or unset the proxy environment variables; see {url}.", {
81
+ version: getBun()?.version,
82
+ minVersion: PROXY_FALSE_MIN_VERSION,
83
+ url: "https://fedify.dev/manual/deploy#bun-behind-an-http-proxy"
84
+ });
85
+ }
86
+ const fallback = request.method === "GET" || request.method === "HEAD";
87
+ const candidates = fallback ? addresses : addresses.slice(0, 1);
88
+ const body = request.body == null ? null : await request.arrayBuffer();
89
+ let lastError = /* @__PURE__ */ new TypeError(`No address to connect to for ${url.host}.`);
90
+ for (const address of candidates) {
91
+ throwIfAborted(signal, request.signal);
92
+ verifyAddress(address);
93
+ const headers = new Headers(request.headers);
94
+ headers.set("Host", url.host);
95
+ const init = {
96
+ method: request.method,
97
+ headers,
98
+ body,
99
+ redirect: "manual",
100
+ signal: signal == null || signal === request.signal ? request.signal : anySignal(signal, request.signal),
101
+ proxy: false
102
+ };
103
+ if (url.protocol === "https:") init.tls = getTlsOptions(url, hostname);
104
+ try {
105
+ return await fetch(toAddressUrl(url, address), init);
106
+ } catch (error) {
107
+ throwIfAborted(signal, request.signal);
108
+ if (!fallback || !isFallbackError(error)) throw error;
109
+ lastError = error;
110
+ }
111
+ }
112
+ throw lastError;
113
+ },
114
+ release() {}
115
+ };
116
+ }
117
+ function getTlsOptions(url, hostname) {
118
+ const bun = getBun();
119
+ if (url.port !== "" && bun != null && bun.semver.satisfies(bun.version, HOST_HEADER_IDENTITY_RANGE)) throw new TypeError(`Bun ${bun.version} cannot verify the certificate of ${url.host} when connecting to a validated address; upgrade to Bun 1.3.11 or later to fetch from HTTPS servers on non-default ports.`);
120
+ return (0, node_net.isIP)(hostname) === 0 ? { serverName: hostname } : {};
121
+ }
122
+ /**
123
+ * Combines two signals without `AbortSignal.any()`, which Bun 1.1.0 lacks.
124
+ */
125
+ function anySignal(a, b) {
126
+ if (a.aborted) return a;
127
+ if (b.aborted) return b;
128
+ const controller = new AbortController();
129
+ const abort = (event) => {
130
+ controller.abort(event.target.reason);
131
+ a.removeEventListener("abort", abort);
132
+ b.removeEventListener("abort", abort);
133
+ };
134
+ a.addEventListener("abort", abort);
135
+ b.addEventListener("abort", abort);
136
+ return controller.signal;
137
+ }
138
+ function toAddressUrl(url, address) {
139
+ const result = new URL(url);
140
+ result.hostname = address.family === 6 ? `[${address.address}]` : address.address;
141
+ return result;
142
+ }
143
+ function isFallbackError(error) {
144
+ if (error instanceof Error && error.name === "AbortError") return false;
145
+ if (error instanceof TypeError) return true;
146
+ const code = error?.code;
147
+ return typeof code === "string" && FALLBACK_ERROR_CODES.has(code);
148
+ }
149
+ function throwIfAborted(...signals) {
150
+ for (const signal of signals) signal?.throwIfAborted();
151
+ }
152
+ function getBun() {
153
+ return globalThis.Bun;
154
+ }
155
+ function ignoresProxyFalse() {
156
+ const bun = getBun();
157
+ if (bun == null) return false;
158
+ return !bun.semver.satisfies(bun.version, `>=${PROXY_FALSE_MIN_VERSION}`);
159
+ }
160
+ function hasProxyVariables() {
161
+ const env = globalThis.process?.env;
162
+ if (env == null) return false;
163
+ return PROXY_VARIABLES.some((name) => (env[name] ?? "") !== "");
164
+ }
165
+ //#endregion
166
+ //#region src/pinners/mod.ts
167
+ /**
168
+ * Detects the current runtime. Cloudflare Workers is checked first, since
169
+ * `process` can exist there with `nodejs_compat`, and Deno and Bun before
170
+ * Node.js, since both provide `process.versions.node`.
171
+ * @returns The current runtime.
172
+ */
173
+ function detectRuntime() {
174
+ const g = globalThis;
175
+ if (g.navigator?.userAgent === "Cloudflare-Workers") return "cfworkers";
176
+ if ("Deno" in globalThis) return "deno";
177
+ if ("Bun" in globalThis) return "bun";
178
+ if (g.process?.versions?.node != null) return "node";
179
+ return "other";
180
+ }
181
+ let runtimePinner;
182
+ /**
183
+ * Gets the production pinner of the current runtime, which rejects
184
+ * non-public addresses right before connecting. Deno and Cloudflare Workers
185
+ * have no pinner.
186
+ * @returns The pinner, or `null` if the runtime has none.
187
+ */
188
+ function getRuntimePinner() {
189
+ runtimePinner ??= createRuntimePinner(detectRuntime());
190
+ return runtimePinner;
191
+ }
192
+ async function createRuntimePinner(runtime) {
193
+ switch (runtime) {
194
+ case "bun": return createBunPinner({ verifyAddress: require_url.assertPublicAddress });
195
+ case "node": return require_node.createNodePinner({
196
+ verifyAddress: require_url.assertPublicAddress,
197
+ undici: await import("undici")
198
+ });
199
+ default: return null;
200
+ }
201
+ }
202
+ //#endregion
203
+ Object.defineProperty(exports, "createBunPinner", {
204
+ enumerable: true,
205
+ get: function() {
206
+ return createBunPinner;
207
+ }
208
+ });
209
+ Object.defineProperty(exports, "detectRuntime", {
210
+ enumerable: true,
211
+ get: function() {
212
+ return detectRuntime;
213
+ }
214
+ });
215
+ Object.defineProperty(exports, "getRuntimePinner", {
216
+ enumerable: true,
217
+ get: function() {
218
+ return getRuntimePinner;
219
+ }
220
+ });
@@ -0,0 +1,203 @@
1
+ import { i as assertPublicAddress } from "./url-COj5Q8mb.mjs";
2
+ import { n as redeemPinGrant } from "./pin-grant-H5XhM1Xs.mjs";
3
+ import { t as createNodePinner } from "./node-CbBR-th-.mjs";
4
+ import { getLogger } from "@logtape/logtape";
5
+ import { isIP } from "node:net";
6
+ //#region src/pinners/bun.ts
7
+ const logger = getLogger([
8
+ "fedify",
9
+ "runtime",
10
+ "fetch"
11
+ ]);
12
+ /**
13
+ * The first Bun version that honors `proxy: false` when proxy environment
14
+ * variables are set at startup. Earlier versions send pinned requests
15
+ * through the proxy, with the validated IP address as the target.
16
+ */
17
+ const PROXY_FALSE_MIN_VERSION = "1.4.3";
18
+ const PROXY_VARIABLES = [
19
+ "HTTP_PROXY",
20
+ "http_proxy",
21
+ "HTTPS_PROXY",
22
+ "https_proxy",
23
+ "ALL_PROXY",
24
+ "all_proxy"
25
+ ];
26
+ const FALLBACK_ERROR_CODES = /* @__PURE__ */ new Set([
27
+ "ConnectionRefused",
28
+ "ECONNREFUSED",
29
+ "EHOSTUNREACH",
30
+ "ENETUNREACH",
31
+ "ETIMEDOUT"
32
+ ]);
33
+ /**
34
+ * Bun versions before 1.3.11 verify the server certificate against the `Host`
35
+ * header, including a non-default port, instead of `tls.serverName`, so
36
+ * pinned requests to HTTPS servers on non-default ports always fail the check
37
+ * there. A custom `checkServerIdentity` cannot stand in for it: these
38
+ * versions send the whole request before a failing custom check aborts it
39
+ * (and 1.3.10 even reuses the connection for the next request), so such
40
+ * requests are refused instead.
41
+ */
42
+ const HOST_HEADER_IDENTITY_RANGE = "<1.3.11";
43
+ /**
44
+ * Creates a pinner for Bun. Every request is sent to an IP literal URL with
45
+ * the original `Host` header, `tls.serverName` set to the original hostname
46
+ * (so that the certificate is still verified against it), and `proxy: false`.
47
+ *
48
+ * GET and HEAD requests fall back to the next address on transport errors
49
+ * raised before a response arrives. Other methods use only the first
50
+ * address, since a transport error does not guarantee that the request did
51
+ * not reach the server, and retrying it could deliver an activity twice.
52
+ * Together with the transport retries of `doubleKnock()`, a GET request is
53
+ * attempted at most twice per address. Note that Bun 1.4 also reports
54
+ * a connection reset after the request was sent as a `TypeError`, so GET and
55
+ * HEAD requests may be repeated against the next address in that case, which
56
+ * is acceptable for these idempotent methods.
57
+ *
58
+ * Bun versions before 1.3.11 verify certificates against the `Host` header,
59
+ * which includes a non-default port, and cannot safely check them against
60
+ * the original hostname in any other way, so requests to HTTPS servers on
61
+ * non-default ports fail there with a `TypeError` before connecting.
62
+ *
63
+ * Bun versions before 1.4.3 ignore `proxy: false` when proxy environment
64
+ * variables are set, so requests go through the proxy with the validated IP
65
+ * address as the target. In that case a warning is logged once.
66
+ * @param options The pinner options.
67
+ * @returns The pinner.
68
+ */
69
+ function createBunPinner({ verifyAddress }) {
70
+ let proxyWarningPending = ignoresProxyFalse() && hasProxyVariables();
71
+ return {
72
+ supports(url) {
73
+ return url.protocol === "http:" || url.protocol === "https:";
74
+ },
75
+ async fetch(grant, request, signal) {
76
+ const url = new URL(request.url);
77
+ const { hostname, addresses } = redeemPinGrant(grant, url);
78
+ if (proxyWarningPending) {
79
+ proxyWarningPending = false;
80
+ logger.warn("Bun {version} ignores proxy: false while proxy environment variables are set, so outbound requests go through the proxy instead of connecting directly to the validated addresses. Upgrade to Bun {minVersion} or later, or unset the proxy environment variables; see {url}.", {
81
+ version: getBun()?.version,
82
+ minVersion: PROXY_FALSE_MIN_VERSION,
83
+ url: "https://fedify.dev/manual/deploy#bun-behind-an-http-proxy"
84
+ });
85
+ }
86
+ const fallback = request.method === "GET" || request.method === "HEAD";
87
+ const candidates = fallback ? addresses : addresses.slice(0, 1);
88
+ const body = request.body == null ? null : await request.arrayBuffer();
89
+ let lastError = /* @__PURE__ */ new TypeError(`No address to connect to for ${url.host}.`);
90
+ for (const address of candidates) {
91
+ throwIfAborted(signal, request.signal);
92
+ verifyAddress(address);
93
+ const headers = new Headers(request.headers);
94
+ headers.set("Host", url.host);
95
+ const init = {
96
+ method: request.method,
97
+ headers,
98
+ body,
99
+ redirect: "manual",
100
+ signal: signal == null || signal === request.signal ? request.signal : anySignal(signal, request.signal),
101
+ proxy: false
102
+ };
103
+ if (url.protocol === "https:") init.tls = getTlsOptions(url, hostname);
104
+ try {
105
+ return await fetch(toAddressUrl(url, address), init);
106
+ } catch (error) {
107
+ throwIfAborted(signal, request.signal);
108
+ if (!fallback || !isFallbackError(error)) throw error;
109
+ lastError = error;
110
+ }
111
+ }
112
+ throw lastError;
113
+ },
114
+ release() {}
115
+ };
116
+ }
117
+ function getTlsOptions(url, hostname) {
118
+ const bun = getBun();
119
+ if (url.port !== "" && bun != null && bun.semver.satisfies(bun.version, HOST_HEADER_IDENTITY_RANGE)) throw new TypeError(`Bun ${bun.version} cannot verify the certificate of ${url.host} when connecting to a validated address; upgrade to Bun 1.3.11 or later to fetch from HTTPS servers on non-default ports.`);
120
+ return isIP(hostname) === 0 ? { serverName: hostname } : {};
121
+ }
122
+ /**
123
+ * Combines two signals without `AbortSignal.any()`, which Bun 1.1.0 lacks.
124
+ */
125
+ function anySignal(a, b) {
126
+ if (a.aborted) return a;
127
+ if (b.aborted) return b;
128
+ const controller = new AbortController();
129
+ const abort = (event) => {
130
+ controller.abort(event.target.reason);
131
+ a.removeEventListener("abort", abort);
132
+ b.removeEventListener("abort", abort);
133
+ };
134
+ a.addEventListener("abort", abort);
135
+ b.addEventListener("abort", abort);
136
+ return controller.signal;
137
+ }
138
+ function toAddressUrl(url, address) {
139
+ const result = new URL(url);
140
+ result.hostname = address.family === 6 ? `[${address.address}]` : address.address;
141
+ return result;
142
+ }
143
+ function isFallbackError(error) {
144
+ if (error instanceof Error && error.name === "AbortError") return false;
145
+ if (error instanceof TypeError) return true;
146
+ const code = error?.code;
147
+ return typeof code === "string" && FALLBACK_ERROR_CODES.has(code);
148
+ }
149
+ function throwIfAborted(...signals) {
150
+ for (const signal of signals) signal?.throwIfAborted();
151
+ }
152
+ function getBun() {
153
+ return globalThis.Bun;
154
+ }
155
+ function ignoresProxyFalse() {
156
+ const bun = getBun();
157
+ if (bun == null) return false;
158
+ return !bun.semver.satisfies(bun.version, `>=${PROXY_FALSE_MIN_VERSION}`);
159
+ }
160
+ function hasProxyVariables() {
161
+ const env = globalThis.process?.env;
162
+ if (env == null) return false;
163
+ return PROXY_VARIABLES.some((name) => (env[name] ?? "") !== "");
164
+ }
165
+ //#endregion
166
+ //#region src/pinners/mod.ts
167
+ /**
168
+ * Detects the current runtime. Cloudflare Workers is checked first, since
169
+ * `process` can exist there with `nodejs_compat`, and Deno and Bun before
170
+ * Node.js, since both provide `process.versions.node`.
171
+ * @returns The current runtime.
172
+ */
173
+ function detectRuntime() {
174
+ const g = globalThis;
175
+ if (g.navigator?.userAgent === "Cloudflare-Workers") return "cfworkers";
176
+ if ("Deno" in globalThis) return "deno";
177
+ if ("Bun" in globalThis) return "bun";
178
+ if (g.process?.versions?.node != null) return "node";
179
+ return "other";
180
+ }
181
+ let runtimePinner;
182
+ /**
183
+ * Gets the production pinner of the current runtime, which rejects
184
+ * non-public addresses right before connecting. Deno and Cloudflare Workers
185
+ * have no pinner.
186
+ * @returns The pinner, or `null` if the runtime has none.
187
+ */
188
+ function getRuntimePinner() {
189
+ runtimePinner ??= createRuntimePinner(detectRuntime());
190
+ return runtimePinner;
191
+ }
192
+ async function createRuntimePinner(runtime) {
193
+ switch (runtime) {
194
+ case "bun": return createBunPinner({ verifyAddress: assertPublicAddress });
195
+ case "node": return createNodePinner({
196
+ verifyAddress: assertPublicAddress,
197
+ undici: await import("undici")
198
+ });
199
+ default: return null;
200
+ }
201
+ }
202
+ //#endregion
203
+ export { getRuntimePinner as n, createBunPinner as r, detectRuntime as t };