@openclaw/proxyline 0.3.3 → 0.3.4
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.
- package/CHANGELOG.md +6 -0
- package/README.md +5 -1
- package/dist/connect.d.ts +1 -0
- package/dist/connect.d.ts.map +1 -1
- package/dist/connect.js +9 -0
- package/dist/node-http.d.ts.map +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +25 -1
- package/dist/shared.d.ts.map +1 -1
- package/docs/api-reference.md +2 -0
- package/docs/surfaces.md +2 -0
- package/package.json +15 -9
- package/scripts/prepack-build.mjs +6 -1
- package/src/connect.ts +11 -0
- package/src/runtime.ts +26 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.4 - 2026-07-20
|
|
4
|
+
|
|
5
|
+
- Added `AbortSignal` cancellation to explicit CONNECT tunnels, including active socket cleanup.
|
|
6
|
+
- Fixed managed HTTPS proxy connections to IPv6 literal endpoints so equivalent compressed and expanded certificate addresses validate without sending IP-literal SNI.
|
|
7
|
+
- Updated Undici to 8.5.0 and raised the compatible peer floor to pick up current security fixes; refreshed pnpm, Node types, and transitive esbuild tooling.
|
|
8
|
+
|
|
3
9
|
## 0.3.3 - 2026-05-18
|
|
4
10
|
|
|
5
11
|
- Fixed managed `globalThis.fetch` so later Undici global dispatcher replacement cannot bypass the active Proxyline dispatcher. Thanks @jesse-merhi.
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# 🌐 Proxyline
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
|
|
3
5
|
[](https://www.npmjs.com/package/@openclaw/proxyline)
|
|
4
6
|
[](https://nodejs.org/)
|
|
5
7
|
[](./LICENSE)
|
|
@@ -31,7 +33,7 @@ pnpm add @openclaw/proxyline
|
|
|
31
33
|
npm install @openclaw/proxyline
|
|
32
34
|
```
|
|
33
35
|
|
|
34
|
-
Requires Node 22.19.0+ and a host `undici` dependency compatible with `>=8.
|
|
36
|
+
Requires Node 22.19.0+ and a host `undici` dependency compatible with `>=8.5.0 <9`.
|
|
35
37
|
|
|
36
38
|
## Quick start
|
|
37
39
|
|
|
@@ -76,12 +78,14 @@ const socket = new WebSocket("wss://events.example.com/", {
|
|
|
76
78
|
```ts
|
|
77
79
|
import { openProxyConnectTunnel } from "@openclaw/proxyline";
|
|
78
80
|
|
|
81
|
+
const controller = new AbortController();
|
|
79
82
|
const socket = await openProxyConnectTunnel({
|
|
80
83
|
proxyUrl: "https://proxy.corp.example:8443",
|
|
81
84
|
proxyTls: { caFile: "/etc/proxy-ca.pem" },
|
|
82
85
|
targetHost: "api.example.com",
|
|
83
86
|
targetPort: 443,
|
|
84
87
|
timeoutMs: 2_000,
|
|
88
|
+
signal: controller.signal,
|
|
85
89
|
});
|
|
86
90
|
```
|
|
87
91
|
|
package/dist/connect.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export type OpenProxyConnectTunnelOptions = Readonly<{
|
|
|
7
7
|
targetHost: string;
|
|
8
8
|
targetPort: number;
|
|
9
9
|
timeoutMs?: number;
|
|
10
|
+
signal?: AbortSignal;
|
|
10
11
|
}>;
|
|
11
12
|
type ProxySocket = net.Socket | tls.TLSSocket;
|
|
12
13
|
export declare function formatConnectAuthority(targetHost: string, targetPort: number): string;
|
package/dist/connect.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../src/connect.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,EAAkB,KAAK,mBAAmB,EAAqC,MAAM,aAAa,CAAC;AAE1G,MAAM,MAAM,6BAA6B,GAAG,QAAQ,CAAC;IACnD,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC;IACvB,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../src/connect.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,EAAkB,KAAK,mBAAmB,EAAqC,MAAM,aAAa,CAAC;AAE1G,MAAM,MAAM,6BAA6B,GAAG,QAAQ,CAAC;IACnD,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC;IACvB,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC,CAAC;AAMH,KAAK,WAAW,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,SAAS,CAAC;AAsB9C,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAqBrF;AAkDD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,WAAW,CAAC,CAwHtB"}
|
package/dist/connect.js
CHANGED
|
@@ -100,6 +100,7 @@ export async function openProxyConnectTunnel(options) {
|
|
|
100
100
|
socket?.off("close", onClosed);
|
|
101
101
|
socket?.off("connect", onConnected);
|
|
102
102
|
socket?.off("secureConnect", onConnected);
|
|
103
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
103
104
|
};
|
|
104
105
|
const fail = (error) => {
|
|
105
106
|
if (settled) {
|
|
@@ -162,7 +163,15 @@ export async function openProxyConnectTunnel(options) {
|
|
|
162
163
|
const onClosed = () => {
|
|
163
164
|
fail(new Error("proxy socket closed before CONNECT response"));
|
|
164
165
|
};
|
|
166
|
+
const onAbort = () => {
|
|
167
|
+
fail(options.signal?.reason ?? new Error("proxy CONNECT aborted"));
|
|
168
|
+
};
|
|
165
169
|
try {
|
|
170
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
171
|
+
if (options.signal?.aborted) {
|
|
172
|
+
onAbort();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
166
175
|
if (options.timeoutMs !== undefined && options.timeoutMs > 0) {
|
|
167
176
|
timeout = setTimeout(() => {
|
|
168
177
|
fail(new Error(`proxy CONNECT timed out after ${Math.trunc(options.timeoutMs ?? 0)}ms`));
|
package/dist/node-http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-http.d.ts","sourceRoot":"","sources":["../src/node-http.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,KAAK,MAAM,YAAY,CAAC;AAI/B,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,UAAU,CAAC;AAElB,OAAO,EAAqC,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC1F,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAElE,MAAM,MAAM,sBAAsB,GAAG,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC,cAAc,GAAG;IAChF,KAAK,CAAC,EAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;CAC5B,CAAC;AAEF,KAAK,cAAc,GAAG,OAAO,IAAI,CAAC,OAAO,CAAC;AAC1C,KAAK,gBAAgB,GAAG,CAAC,OAAO,EAAE,sBAAsB,KAAK,IAAI,CAAC,KAAK,CAAC;AACxE,KAAK,gBAAgB,GAAG,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC,YAAY,CAAC;AAC/D,KAAK,uBAAuB,GAAG,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC,cAAc,GAAG;IAC1E,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAYF,KAAK,qBAAqB,GAAG,gBAAgB,GAAG;IAC9C,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACnC,cAAc,EAAE,CACd,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,gBAAgB,EAC1B,OAAO,CAAC,EAAE,IAAI,CAAC,aAAa,KACzB,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,mBAAmB,CAAC;CAChC,CAAC;AAOF,eAAO,MAAM,4BAA4B,
|
|
1
|
+
{"version":3,"file":"node-http.d.ts","sourceRoot":"","sources":["../src/node-http.ts"],"names":[],"mappings":"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"}
|
package/dist/runtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAwCA,OAAO,KAAK,EAIV,eAAe,EACf,gBAAgB,EAIjB,MAAM,YAAY,CAAC;AAqzBpB,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,GAAG,eAAe,CAgI3E;AAED,eAAO,MAAM,kBAAkB,yBAAmB,CAAC"}
|
package/dist/runtime.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
2
|
import https from "node:https";
|
|
3
3
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
|
+
import { X509Certificate } from "node:crypto";
|
|
4
5
|
import net from "node:net";
|
|
6
|
+
import tls from "node:tls";
|
|
5
7
|
import { Agent as UndiciAgent, Dispatcher, FormData as UndiciFormData, Headers as UndiciHeaders, Pool as UndiciPool, Request as UndiciRequest, Response as UndiciResponse, errors as undiciErrors, fetch as undiciFetch, getGlobalDispatcher, ProxyAgent as UndiciProxyAgent, setGlobalDispatcher, } from "undici";
|
|
6
8
|
import { createAmbientProxyResolver, EMPTY_PROXY_ENV, resolveAmbientProxyForUrl, readProxyEnv, } from "./env.js";
|
|
7
9
|
import { bindNodeHttpMethod, createDirectNodeAgent, createNodeProxyAgent, } from "./node-http.js";
|
|
@@ -301,6 +303,21 @@ function stripIpServernameFromConnect(connect) {
|
|
|
301
303
|
}
|
|
302
304
|
return (options, callback) => connect(stripIpServernameFromConnectOptions(options), callback);
|
|
303
305
|
}
|
|
306
|
+
const checkProxyServerIdentity = (hostname, certificate) => {
|
|
307
|
+
const normalizedHostname = hostname.replace(/^\[|\]$/g, "");
|
|
308
|
+
if (net.isIP(normalizedHostname) === 6) {
|
|
309
|
+
try {
|
|
310
|
+
// Compare binary addresses so compressed and expanded IPv6 SAN forms remain equivalent.
|
|
311
|
+
if (new X509Certificate(certificate.raw).checkIP(normalizedHostname) !== undefined) {
|
|
312
|
+
return undefined;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
// Let Node return its canonical certificate validation error below.
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return tls.checkServerIdentity(normalizedHostname, certificate);
|
|
320
|
+
};
|
|
304
321
|
function createProxyClientFactory() {
|
|
305
322
|
return (origin, options) => {
|
|
306
323
|
const clientOptions = isObjectRecord(options)
|
|
@@ -310,11 +327,18 @@ function createProxyClientFactory() {
|
|
|
310
327
|
};
|
|
311
328
|
}
|
|
312
329
|
function createUndiciProxyAgent(proxyUrl, options) {
|
|
330
|
+
const proxyHostname = new URL(proxyUrl).hostname.replace(/^\[|\]$/g, "");
|
|
331
|
+
const proxyTls = options.proxyCa !== undefined || net.isIP(proxyHostname) === 6
|
|
332
|
+
? {
|
|
333
|
+
...(options.proxyCa !== undefined ? { ca: options.proxyCa } : {}),
|
|
334
|
+
...(net.isIP(proxyHostname) === 6 ? { checkServerIdentity: checkProxyServerIdentity } : {}),
|
|
335
|
+
}
|
|
336
|
+
: undefined;
|
|
313
337
|
return new UndiciProxyAgent({
|
|
314
338
|
...resolveUndiciBaseOptions(options.undici),
|
|
315
339
|
uri: proxyUrl,
|
|
316
340
|
clientFactory: createProxyClientFactory(),
|
|
317
|
-
...(
|
|
341
|
+
...(proxyTls !== undefined ? { proxyTls } : {}),
|
|
318
342
|
});
|
|
319
343
|
}
|
|
320
344
|
function createUndiciProxyDispatcher(options, dispatcherOptions) {
|
package/dist/shared.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC,CAAC;AAEH,qBAAa,cAAe,SAAQ,KAAK;IACvC,SAAgB,IAAI,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC,CAAC;AAEH,qBAAa,cAAe,SAAQ,KAAK;IACvC,SAAgB,IAAI,EAAE,MAAM,CAAC;IAE7B,YAAmB,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAI/C;CACF;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,mBAAmB,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAW9F;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,MAAM,CAErD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,MAAM,CAO1D"}
|
package/docs/api-reference.md
CHANGED
|
@@ -257,6 +257,7 @@ type OpenProxyConnectTunnelOptions = Readonly<{
|
|
|
257
257
|
targetHost: string;
|
|
258
258
|
targetPort: number;
|
|
259
259
|
timeoutMs?: number;
|
|
260
|
+
signal?: AbortSignal;
|
|
260
261
|
}>;
|
|
261
262
|
```
|
|
262
263
|
|
|
@@ -264,6 +265,7 @@ type OpenProxyConnectTunnelOptions = Readonly<{
|
|
|
264
265
|
- `proxyTls` — CA trust for HTTPS proxies. See [Proxy TLS](./proxy-tls.md).
|
|
265
266
|
- `targetHost` / `targetPort` — what to ask the proxy to connect to.
|
|
266
267
|
- `timeoutMs` — overall budget for the CONNECT handshake.
|
|
268
|
+
- `signal` — optional caller cancellation; aborting rejects the handshake and destroys its active proxy socket.
|
|
267
269
|
|
|
268
270
|
### `AmbientNodeProxyAgentOptions`
|
|
269
271
|
|
package/docs/surfaces.md
CHANGED
|
@@ -83,6 +83,7 @@ const socket = await openProxyConnectTunnel({
|
|
|
83
83
|
targetHost: "api.example.com",
|
|
84
84
|
targetPort: 443,
|
|
85
85
|
timeoutMs: 2_000,
|
|
86
|
+
signal: AbortSignal.timeout(2_000),
|
|
86
87
|
});
|
|
87
88
|
```
|
|
88
89
|
|
|
@@ -93,6 +94,7 @@ Properties:
|
|
|
93
94
|
- Userinfo in the `proxyUrl` becomes a `Proxy-Authorization: Basic ...` header.
|
|
94
95
|
- A bounded `16 KiB` header buffer protects against malicious or runaway proxy responses.
|
|
95
96
|
- `timeoutMs` is enforced and emits a `CONNECT_FAILED` error on expiry.
|
|
97
|
+
- `signal` aborts an in-progress handshake and destroys its active proxy socket.
|
|
96
98
|
- Bytes the proxy sends after the response headers are re-injected with `socket.unshift()` so the caller sees the full target stream.
|
|
97
99
|
- Non-2xx status lines, header overrun, premature close, and socket errors are all surfaced as `ProxylineError` with code `CONNECT_FAILED`.
|
|
98
100
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/proxyline",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Process-global proxy routing for Node.js.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"url": "https://github.com/openclaw/proxyline/issues"
|
|
18
18
|
},
|
|
19
19
|
"homepage": "https://proxyline.dev",
|
|
20
|
-
"packageManager": "pnpm@11.
|
|
20
|
+
"packageManager": "pnpm@11.9.0",
|
|
21
21
|
"exports": {
|
|
22
22
|
".": {
|
|
23
23
|
"types": "./dist/index.d.ts",
|
|
@@ -53,20 +53,26 @@
|
|
|
53
53
|
"prepare": "node scripts/prepack-build.mjs",
|
|
54
54
|
"publish:dry-run": "pnpm publish --dry-run --access public",
|
|
55
55
|
"test": "pnpm build && tsx --test test/index.test.ts test/e2e.test.ts test/package.test.ts test/scripts.test.ts && tsx --test test/package-artifact.test.ts",
|
|
56
|
-
"typecheck": "pnpm build && tsc --noEmit"
|
|
56
|
+
"typecheck": "pnpm build && tsc --noEmit",
|
|
57
|
+
"check:changed": "pnpm run check",
|
|
58
|
+
"test:changed": "pnpm run test",
|
|
59
|
+
"crabbox:hydrate": "crabbox actions hydrate",
|
|
60
|
+
"crabbox:run": "crabbox run",
|
|
61
|
+
"crabbox:stop": "crabbox stop",
|
|
62
|
+
"crabbox:warmup": "crabbox warmup"
|
|
57
63
|
},
|
|
58
64
|
"devDependencies": {
|
|
59
|
-
"@types/node": "
|
|
65
|
+
"@types/node": "26.1.1",
|
|
60
66
|
"@types/ws": "8.18.1",
|
|
61
|
-
"tsx": "4.
|
|
62
|
-
"typescript": "
|
|
63
|
-
"undici": "8.
|
|
64
|
-
"ws": "8.
|
|
67
|
+
"tsx": "4.23.0",
|
|
68
|
+
"typescript": "^7.0.2",
|
|
69
|
+
"undici": "8.6.0",
|
|
70
|
+
"ws": "8.21.0"
|
|
65
71
|
},
|
|
66
72
|
"engines": {
|
|
67
73
|
"node": ">=22.19.0"
|
|
68
74
|
},
|
|
69
75
|
"peerDependencies": {
|
|
70
|
-
"undici": ">=8.
|
|
76
|
+
"undici": ">=8.5.0 <9"
|
|
71
77
|
}
|
|
72
78
|
}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
3
4
|
import { createRequire } from "node:module";
|
|
5
|
+
import { dirname, resolve } from "node:path";
|
|
4
6
|
|
|
5
7
|
const require = createRequire(import.meta.url);
|
|
6
8
|
|
|
7
9
|
function resolveTypeScriptCompiler() {
|
|
8
10
|
try {
|
|
9
|
-
|
|
11
|
+
const packageJsonPath = require.resolve("typescript/package.json");
|
|
12
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
13
|
+
const compiler = typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.tsc;
|
|
14
|
+
return typeof compiler === "string" ? resolve(dirname(packageJsonPath), compiler) : undefined;
|
|
10
15
|
} catch {
|
|
11
16
|
return undefined;
|
|
12
17
|
}
|
package/src/connect.ts
CHANGED
|
@@ -8,6 +8,7 @@ export type OpenProxyConnectTunnelOptions = Readonly<{
|
|
|
8
8
|
targetHost: string;
|
|
9
9
|
targetPort: number;
|
|
10
10
|
timeoutMs?: number;
|
|
11
|
+
signal?: AbortSignal;
|
|
11
12
|
}>;
|
|
12
13
|
|
|
13
14
|
const MAX_CONNECT_RESPONSE_HEADER_BYTES = 16 * 1024;
|
|
@@ -131,6 +132,7 @@ export async function openProxyConnectTunnel(
|
|
|
131
132
|
socket?.off("close", onClosed);
|
|
132
133
|
socket?.off("connect", onConnected);
|
|
133
134
|
socket?.off("secureConnect", onConnected);
|
|
135
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
134
136
|
};
|
|
135
137
|
|
|
136
138
|
const fail = (error: unknown): void => {
|
|
@@ -203,7 +205,16 @@ export async function openProxyConnectTunnel(
|
|
|
203
205
|
fail(new Error("proxy socket closed before CONNECT response"));
|
|
204
206
|
};
|
|
205
207
|
|
|
208
|
+
const onAbort = (): void => {
|
|
209
|
+
fail(options.signal?.reason ?? new Error("proxy CONNECT aborted"));
|
|
210
|
+
};
|
|
211
|
+
|
|
206
212
|
try {
|
|
213
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
214
|
+
if (options.signal?.aborted) {
|
|
215
|
+
onAbort();
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
207
218
|
if (options.timeoutMs !== undefined && options.timeoutMs > 0) {
|
|
208
219
|
timeout = setTimeout(() => {
|
|
209
220
|
fail(new Error(`proxy CONNECT timed out after ${Math.trunc(options.timeoutMs ?? 0)}ms`));
|
package/src/runtime.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
2
|
import https from "node:https";
|
|
3
3
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
|
+
import { X509Certificate } from "node:crypto";
|
|
4
5
|
import net from "node:net";
|
|
6
|
+
import tls from "node:tls";
|
|
5
7
|
import {
|
|
6
8
|
Agent as UndiciAgent,
|
|
7
9
|
Dispatcher,
|
|
@@ -483,6 +485,21 @@ function stripIpServernameFromConnect(connect: unknown): unknown {
|
|
|
483
485
|
(connect as UnknownFunction)(stripIpServernameFromConnectOptions(options), callback);
|
|
484
486
|
}
|
|
485
487
|
|
|
488
|
+
const checkProxyServerIdentity: typeof tls.checkServerIdentity = (hostname, certificate) => {
|
|
489
|
+
const normalizedHostname = hostname.replace(/^\[|\]$/g, "");
|
|
490
|
+
if (net.isIP(normalizedHostname) === 6) {
|
|
491
|
+
try {
|
|
492
|
+
// Compare binary addresses so compressed and expanded IPv6 SAN forms remain equivalent.
|
|
493
|
+
if (new X509Certificate(certificate.raw).checkIP(normalizedHostname) !== undefined) {
|
|
494
|
+
return undefined;
|
|
495
|
+
}
|
|
496
|
+
} catch {
|
|
497
|
+
// Let Node return its canonical certificate validation error below.
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return tls.checkServerIdentity(normalizedHostname, certificate);
|
|
501
|
+
};
|
|
502
|
+
|
|
486
503
|
function createProxyClientFactory(): UndiciProxyClientFactory {
|
|
487
504
|
return (origin: URL, options: object): Dispatcher => {
|
|
488
505
|
const clientOptions = isObjectRecord(options)
|
|
@@ -499,11 +516,19 @@ function createUndiciProxyAgent(
|
|
|
499
516
|
proxyUrl: string,
|
|
500
517
|
options: UndiciDispatcherOptions,
|
|
501
518
|
): UndiciProxyAgent {
|
|
519
|
+
const proxyHostname = new URL(proxyUrl).hostname.replace(/^\[|\]$/g, "");
|
|
520
|
+
const proxyTls =
|
|
521
|
+
options.proxyCa !== undefined || net.isIP(proxyHostname) === 6
|
|
522
|
+
? {
|
|
523
|
+
...(options.proxyCa !== undefined ? { ca: options.proxyCa } : {}),
|
|
524
|
+
...(net.isIP(proxyHostname) === 6 ? { checkServerIdentity: checkProxyServerIdentity } : {}),
|
|
525
|
+
}
|
|
526
|
+
: undefined;
|
|
502
527
|
return new UndiciProxyAgent({
|
|
503
528
|
...resolveUndiciBaseOptions(options.undici),
|
|
504
529
|
uri: proxyUrl,
|
|
505
530
|
clientFactory: createProxyClientFactory(),
|
|
506
|
-
...(
|
|
531
|
+
...(proxyTls !== undefined ? { proxyTls } : {}),
|
|
507
532
|
} as ConstructorParameters<typeof UndiciProxyAgent>[0]);
|
|
508
533
|
}
|
|
509
534
|
|