@openclaw/proxyline 0.3.1 → 0.3.3
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 +9 -0
- package/README.md +3 -2
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +62 -20
- package/docs/README.md +2 -1
- package/docs/getting-started.md +1 -1
- package/docs/security.md +2 -2
- package/docs/surfaces.md +1 -1
- package/docs/testing.md +1 -1
- package/package.json +10 -10
- package/src/runtime.ts +88 -18
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.3 - 2026-05-18
|
|
4
|
+
|
|
5
|
+
- Fixed managed `globalThis.fetch` so later Undici global dispatcher replacement cannot bypass the active Proxyline dispatcher. Thanks @jesse-merhi.
|
|
6
|
+
- Updated package tooling and test dependencies to exact latest versions, including pnpm 11.1.2, TypeScript 6.0.3, and Undici 8.3.0; raised the Node.js floor to 22.19.0 to match Undici 8.
|
|
7
|
+
|
|
8
|
+
## 0.3.2 - 2026-05-17
|
|
9
|
+
|
|
10
|
+
- Fixed managed Undici proxy dispatchers so HTTPS proxy endpoints addressed by IP do not send invalid IP-literal SNI.
|
|
11
|
+
|
|
3
12
|
## 0.3.1 - 2026-05-16
|
|
4
13
|
|
|
5
14
|
- Fixed `withBypass()` to scope temporary bypasses to the calling async context instead of process-wide state.
|
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ pnpm add @openclaw/proxyline
|
|
|
31
31
|
npm install @openclaw/proxyline
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
Requires Node
|
|
34
|
+
Requires Node 22.19.0+ and a host `undici` dependency compatible with `>=8.3.0 <9`.
|
|
35
35
|
|
|
36
36
|
## Quick start
|
|
37
37
|
|
|
@@ -103,7 +103,8 @@ It uses Proxyline's built-in HTTP/HTTPS Node agent, and `proxyTls` applies only
|
|
|
103
103
|
|
|
104
104
|
- `http.request` / `http.get`: covered by global method patching and global agent replacement.
|
|
105
105
|
- `https.request` / `https.get`: covered by global method patching and global agent replacement.
|
|
106
|
-
- `fetch
|
|
106
|
+
- `globalThis.fetch`: covered by the fetch patch, including explicit dispatcher options and later Undici global dispatcher replacement in managed mode.
|
|
107
|
+
- Undici global dispatcher: installed for Undici APIs that read the current process dispatcher.
|
|
107
108
|
- WebSocket clients accepting a Node `agent`: covered with `proxy.createWebSocketAgent()`.
|
|
108
109
|
- Caller-built `http.Agent` / `https.Agent`: overridden in managed and active ambient mode, with TLS options preserved.
|
|
109
110
|
- Explicit HTTP CONNECT sockets: covered with `openProxyConnectTunnel()`.
|
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":"AAsCA,OAAO,KAAK,EAIV,eAAe,EACf,gBAAgB,EAIjB,MAAM,YAAY,CAAC;AA8xBpB,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,GAAG,eAAe,CAgI3E;AAED,eAAO,MAAM,kBAAkB,yBAAmB,CAAC"}
|
package/dist/runtime.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
2
|
import https from "node:https";
|
|
3
3
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
|
-
import
|
|
4
|
+
import net from "node:net";
|
|
5
|
+
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";
|
|
5
6
|
import { createAmbientProxyResolver, EMPTY_PROXY_ENV, resolveAmbientProxyForUrl, readProxyEnv, } from "./env.js";
|
|
6
7
|
import { bindNodeHttpMethod, createDirectNodeAgent, createNodeProxyAgent, } from "./node-http.js";
|
|
7
8
|
import { formatUrl, ProxylineError, redactProxyUrl, resolveProxyTlsCa, } from "./shared.js";
|
|
@@ -99,25 +100,32 @@ async function normalizeFetchInput(input, init, options) {
|
|
|
99
100
|
preserveDispatcher: options.preserveDispatcher,
|
|
100
101
|
});
|
|
101
102
|
}
|
|
102
|
-
function
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
function withManagedFetchDispatcher(init, dispatcher) {
|
|
104
|
+
if (init !== undefined &&
|
|
105
|
+
init !== null &&
|
|
106
|
+
typeof init !== "object" &&
|
|
107
|
+
typeof init !== "function") {
|
|
108
|
+
throw new TypeError(`Request constructor: Expected ${String(init)} to be one of: Null, Undefined, Object.`);
|
|
109
|
+
}
|
|
110
|
+
const sanitized = init === undefined || init === null ? {} : Object.create(init);
|
|
107
111
|
Reflect.defineProperty(sanitized, "dispatcher", {
|
|
108
112
|
configurable: true,
|
|
109
113
|
enumerable: true,
|
|
110
|
-
value:
|
|
114
|
+
value: dispatcher,
|
|
111
115
|
writable: true,
|
|
112
116
|
});
|
|
113
117
|
return sanitized;
|
|
114
118
|
}
|
|
115
119
|
const proxylineFetch = async (input, init) => {
|
|
116
|
-
const
|
|
120
|
+
const managedDispatcher = activeRuntime?.mode === "managed"
|
|
121
|
+
? activeRuntime.installedDispatcher
|
|
122
|
+
: undefined;
|
|
117
123
|
const normalizedInput = await normalizeFetchInput(input, init, {
|
|
118
|
-
preserveDispatcher:
|
|
124
|
+
preserveDispatcher: managedDispatcher === undefined,
|
|
119
125
|
});
|
|
120
|
-
const normalizedInit =
|
|
126
|
+
const normalizedInit = managedDispatcher === undefined
|
|
127
|
+
? init
|
|
128
|
+
: withManagedFetchDispatcher(init, managedDispatcher);
|
|
121
129
|
const response = await Reflect.apply(undiciFetch, undefined, normalizedInit === undefined ? [normalizedInput] : [normalizedInput, normalizedInit]);
|
|
122
130
|
if (!(response instanceof proxylineResponse)) {
|
|
123
131
|
throw new TypeError("Proxyline fetch returned a non-Response value.");
|
|
@@ -272,10 +280,40 @@ function resolveUndiciBaseOptions(options) {
|
|
|
272
280
|
function createUndiciAgent(options) {
|
|
273
281
|
return new UndiciAgent(resolveUndiciBaseOptions(options));
|
|
274
282
|
}
|
|
283
|
+
function isObjectRecord(value) {
|
|
284
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
285
|
+
}
|
|
286
|
+
function stripIpServernameFromConnectOptions(options) {
|
|
287
|
+
if (!isObjectRecord(options) || typeof options.servername !== "string") {
|
|
288
|
+
return options;
|
|
289
|
+
}
|
|
290
|
+
const servername = options.servername.replace(/^\[|\]$/g, "");
|
|
291
|
+
if (net.isIP(servername) === 0) {
|
|
292
|
+
return options;
|
|
293
|
+
}
|
|
294
|
+
const next = { ...options };
|
|
295
|
+
delete next.servername;
|
|
296
|
+
return next;
|
|
297
|
+
}
|
|
298
|
+
function stripIpServernameFromConnect(connect) {
|
|
299
|
+
if (typeof connect !== "function") {
|
|
300
|
+
return connect;
|
|
301
|
+
}
|
|
302
|
+
return (options, callback) => connect(stripIpServernameFromConnectOptions(options), callback);
|
|
303
|
+
}
|
|
304
|
+
function createProxyClientFactory() {
|
|
305
|
+
return (origin, options) => {
|
|
306
|
+
const clientOptions = isObjectRecord(options)
|
|
307
|
+
? { ...options, connect: stripIpServernameFromConnect(options.connect) }
|
|
308
|
+
: options;
|
|
309
|
+
return new UndiciPool(origin, clientOptions);
|
|
310
|
+
};
|
|
311
|
+
}
|
|
275
312
|
function createUndiciProxyAgent(proxyUrl, options) {
|
|
276
313
|
return new UndiciProxyAgent({
|
|
277
314
|
...resolveUndiciBaseOptions(options.undici),
|
|
278
315
|
uri: proxyUrl,
|
|
316
|
+
clientFactory: createProxyClientFactory(),
|
|
279
317
|
...(options.proxyCa !== undefined ? { proxyTls: { ca: options.proxyCa } } : {}),
|
|
280
318
|
});
|
|
281
319
|
}
|
|
@@ -288,6 +326,18 @@ function createUndiciProxyDispatcher(options, dispatcherOptions) {
|
|
|
288
326
|
}
|
|
289
327
|
return new ManagedUndiciDispatcher(options.resolver, dispatcherOptions);
|
|
290
328
|
}
|
|
329
|
+
function reportClosedDispatchError(handler, error) {
|
|
330
|
+
const compatibleHandler = handler;
|
|
331
|
+
if (compatibleHandler.onResponseError !== undefined) {
|
|
332
|
+
compatibleHandler.onResponseError(null, error);
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
if (compatibleHandler.onError !== undefined) {
|
|
336
|
+
compatibleHandler.onError(error);
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
throw error;
|
|
340
|
+
}
|
|
291
341
|
class ManagedUndiciDispatcher extends Dispatcher {
|
|
292
342
|
[PROXYLINE_DISPATCHER_BRAND] = true;
|
|
293
343
|
#directDispatcher;
|
|
@@ -303,11 +353,7 @@ class ManagedUndiciDispatcher extends Dispatcher {
|
|
|
303
353
|
}
|
|
304
354
|
dispatch(options, handler) {
|
|
305
355
|
if (this.#closedError !== undefined) {
|
|
306
|
-
|
|
307
|
-
throw this.#closedError;
|
|
308
|
-
}
|
|
309
|
-
handler.onError(this.#closedError);
|
|
310
|
-
return false;
|
|
356
|
+
return reportClosedDispatchError(handler, this.#closedError);
|
|
311
357
|
}
|
|
312
358
|
const url = resolveUndiciDispatchUrl(options);
|
|
313
359
|
const proxyUrl = url === undefined ? "" : this.#resolver.getProxyForUrl(url, "undici");
|
|
@@ -373,11 +419,7 @@ class AmbientUndiciDispatcher extends Dispatcher {
|
|
|
373
419
|
}
|
|
374
420
|
dispatch(options, handler) {
|
|
375
421
|
if (this.#closedError !== undefined) {
|
|
376
|
-
|
|
377
|
-
throw this.#closedError;
|
|
378
|
-
}
|
|
379
|
-
handler.onError(this.#closedError);
|
|
380
|
-
return false;
|
|
422
|
+
return reportClosedDispatchError(handler, this.#closedError);
|
|
381
423
|
}
|
|
382
424
|
const url = resolveUndiciDispatchUrl(options);
|
|
383
425
|
const proxyUrl = url === undefined ? undefined : resolveAmbientProxyForUrl(url, this.#env);
|
package/docs/README.md
CHANGED
|
@@ -25,7 +25,8 @@ Process-global proxy routing for Node.js. Proxyline patches the network surfaces
|
|
|
25
25
|
|
|
26
26
|
- `http.request` / `http.get`: covered by global method patching and global agent replacement.
|
|
27
27
|
- `https.request` / `https.get`: covered by global method patching and global agent replacement.
|
|
28
|
-
- `fetch
|
|
28
|
+
- `globalThis.fetch`: covered by the fetch patch, including explicit dispatcher options and later Undici global dispatcher replacement in managed mode.
|
|
29
|
+
- Undici global dispatcher: installed for Undici APIs that read the current process dispatcher.
|
|
29
30
|
- WebSocket clients accepting a Node `agent`: covered with `proxy.createWebSocketAgent()`.
|
|
30
31
|
- WebSocket clients without an `agent` option: partially covered when the upgrade path reuses patched `http.request`.
|
|
31
32
|
- Explicit HTTP CONNECT sockets: covered with `openProxyConnectTunnel()`.
|
package/docs/getting-started.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Getting Started
|
|
2
2
|
|
|
3
|
-
Proxyline targets Node.js
|
|
3
|
+
Proxyline targets Node.js 22.19.0+. It is published as `@openclaw/proxyline` and ships ESM with TypeScript types.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
package/docs/security.md
CHANGED
|
@@ -30,7 +30,7 @@ Anything that does not flow through the patched APIs:
|
|
|
30
30
|
|
|
31
31
|
- Direct `net.connect` or `tls.connect` calls. Code that opens raw sockets is not seen by Proxyline.
|
|
32
32
|
- Native modules with private transport stacks (e.g. some database drivers, gRPC C bindings).
|
|
33
|
-
- Libraries that import and call `undici.fetch` directly with their own explicit `Dispatcher`. Proxyline's managed `globalThis.fetch`
|
|
33
|
+
- Libraries that import and call `undici.fetch` directly with their own explicit `Dispatcher`. Proxyline's managed `globalThis.fetch` pins its own dispatcher even when callers pass an explicit dispatcher or later replace the Undici global dispatcher, but it cannot rewrite every imported undici function reference.
|
|
34
34
|
- DNS resolution itself. Proxyline tells the proxy a hostname; DNS-based exfiltration via the local resolver is out of scope.
|
|
35
35
|
- Sockets opened **before** `installProxyline` ran. Existing keepalive connections continue to use whatever transport they were created with.
|
|
36
36
|
- Module references captured before `installProxyline` ran. Anything that stored `http.request` in a local variable at import time keeps the un-patched reference.
|
|
@@ -69,7 +69,7 @@ This is deliberate: two competing proxy patches would race on `http.request` and
|
|
|
69
69
|
| Threat | Mitigated | Notes |
|
|
70
70
|
| --- | --- | --- |
|
|
71
71
|
| Library passes a direct `http.Agent` per request | yes (managed) | Replaced before the request runs |
|
|
72
|
-
| Library passes a direct `Dispatcher` to managed `globalThis.fetch` | yes |
|
|
72
|
+
| Library passes a direct `Dispatcher` to managed `globalThis.fetch` | yes | Proxyline pins its dispatcher |
|
|
73
73
|
| Trusted local endpoint needs direct routing | yes, with `bypassPolicy` | Keep the callback narrow and auditable |
|
|
74
74
|
| Library calls imported `undici.fetch` with a direct `Dispatcher` | no | Imported function references are outside the global fetch patch |
|
|
75
75
|
| Library uses `net.connect` directly | no | Out of scope |
|
package/docs/surfaces.md
CHANGED
|
@@ -50,7 +50,7 @@ import { fetch } from "undici";
|
|
|
50
50
|
await fetch("https://api.example.com/health"); // routed through Proxyline
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
In managed mode, Proxyline's patched `globalThis.fetch`
|
|
53
|
+
In managed mode, Proxyline's patched `globalThis.fetch` passes Proxyline's own dispatcher explicitly, so a per-call undici `Agent` or a later `setGlobalDispatcher()` call cannot bypass the managed proxy through that global fetch path. In ambient mode, and for callers using imported `undici.fetch` directly, an explicit undici `Agent` or `Dispatcher` still wins. Use `proxy.createUndiciDispatcher()` to get a dispatcher pre-wired to the same policy.
|
|
54
54
|
|
|
55
55
|
Proxyline also replaces `globalThis.Request`, `Response`, `Headers`, and `FormData` with versions from its undici dependency so `globalThis.fetch` receives compatible objects on Node versions where the built-in fetch no longer shares the package dispatcher. Requests created before Proxyline installs are normalized through the standard public `Request` fields. Install Proxyline first if you need non-standard Request internals, such as a dispatcher embedded in a pre-install native `Request`, to be preserved.
|
|
56
56
|
|
package/docs/testing.md
CHANGED
|
@@ -15,7 +15,7 @@ pnpm typecheck # type-only
|
|
|
15
15
|
|
|
16
16
|
`pnpm test` builds `dist/`, then runs `node --test` via `tsx` against `test/index.test.ts` (unit), `test/e2e.test.ts` (integration), and `test/package.test.ts` (package entrypoint).
|
|
17
17
|
`pnpm check` enforces native Node coverage for `src/**/*.ts` with thresholds of 85% lines, 80% branches, and 80% functions on Node versions that expose native threshold flags.
|
|
18
|
-
CI runs that check on Ubuntu, macOS, and Windows across Node
|
|
18
|
+
CI runs that check on Ubuntu, macOS, and Windows across Node 22.19.0, 24, and 26; Node 22.19.0 keeps the declared minimum covered with native coverage enabled.
|
|
19
19
|
|
|
20
20
|
## What the lab covers
|
|
21
21
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/proxyline",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
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@
|
|
20
|
+
"packageManager": "pnpm@11.1.2",
|
|
21
21
|
"exports": {
|
|
22
22
|
".": {
|
|
23
23
|
"types": "./dist/index.d.ts",
|
|
@@ -56,17 +56,17 @@
|
|
|
56
56
|
"typecheck": "pnpm build && tsc --noEmit"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@types/node": "
|
|
60
|
-
"@types/ws": "
|
|
61
|
-
"tsx": "
|
|
62
|
-
"typescript": "
|
|
63
|
-
"undici": "
|
|
64
|
-
"ws": "
|
|
59
|
+
"@types/node": "25.8.0",
|
|
60
|
+
"@types/ws": "8.18.1",
|
|
61
|
+
"tsx": "4.22.1",
|
|
62
|
+
"typescript": "6.0.3",
|
|
63
|
+
"undici": "8.3.0",
|
|
64
|
+
"ws": "8.20.1"
|
|
65
65
|
},
|
|
66
66
|
"engines": {
|
|
67
|
-
"node": ">=
|
|
67
|
+
"node": ">=22.19.0"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
|
-
"undici": ">=
|
|
70
|
+
"undici": ">=8.3.0 <9"
|
|
71
71
|
}
|
|
72
72
|
}
|
package/src/runtime.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
2
|
import https from "node:https";
|
|
3
3
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
|
+
import net from "node:net";
|
|
4
5
|
import {
|
|
5
6
|
Agent as UndiciAgent,
|
|
6
7
|
Dispatcher,
|
|
7
8
|
FormData as UndiciFormData,
|
|
8
9
|
Headers as UndiciHeaders,
|
|
10
|
+
Pool as UndiciPool,
|
|
9
11
|
Request as UndiciRequest,
|
|
10
12
|
Response as UndiciResponse,
|
|
11
13
|
errors as undiciErrors,
|
|
@@ -72,6 +74,11 @@ type ProxylineDispatcher = Dispatcher & {
|
|
|
72
74
|
[PROXYLINE_DISPATCHER_BRAND]?: true;
|
|
73
75
|
};
|
|
74
76
|
|
|
77
|
+
type CompatibleDispatchHandler = Dispatcher.DispatchHandler & {
|
|
78
|
+
onError?: (error: Error) => void;
|
|
79
|
+
onResponseError?: (controller: Dispatcher.DispatchController | null, error: Error) => void;
|
|
80
|
+
};
|
|
81
|
+
|
|
75
82
|
// Node's global fetch types come from bundled undici-types, while the runtime
|
|
76
83
|
// implementation intentionally delegates to this package's undici dependency.
|
|
77
84
|
const proxylineHeaders = UndiciHeaders as unknown as typeof globalThis.Headers;
|
|
@@ -214,28 +221,40 @@ async function normalizeFetchInput(
|
|
|
214
221
|
});
|
|
215
222
|
}
|
|
216
223
|
|
|
217
|
-
function
|
|
224
|
+
function withManagedFetchDispatcher(
|
|
218
225
|
init: Parameters<typeof globalThis.fetch>[1],
|
|
226
|
+
dispatcher: Dispatcher,
|
|
219
227
|
): Parameters<typeof globalThis.fetch>[1] {
|
|
220
|
-
if (
|
|
221
|
-
|
|
228
|
+
if (
|
|
229
|
+
init !== undefined &&
|
|
230
|
+
init !== null &&
|
|
231
|
+
typeof init !== "object" &&
|
|
232
|
+
typeof init !== "function"
|
|
233
|
+
) {
|
|
234
|
+
throw new TypeError(
|
|
235
|
+
`Request constructor: Expected ${String(init)} to be one of: Null, Undefined, Object.`,
|
|
236
|
+
);
|
|
222
237
|
}
|
|
223
|
-
const sanitized = Object.create(init);
|
|
238
|
+
const sanitized = init === undefined || init === null ? {} : Object.create(init);
|
|
224
239
|
Reflect.defineProperty(sanitized, "dispatcher", {
|
|
225
240
|
configurable: true,
|
|
226
241
|
enumerable: true,
|
|
227
|
-
value:
|
|
242
|
+
value: dispatcher,
|
|
228
243
|
writable: true,
|
|
229
244
|
});
|
|
230
245
|
return sanitized;
|
|
231
246
|
}
|
|
232
247
|
|
|
233
248
|
const proxylineFetch: typeof globalThis.fetch = async (input, init) => {
|
|
234
|
-
const
|
|
249
|
+
const managedDispatcher = activeRuntime?.mode === "managed"
|
|
250
|
+
? activeRuntime.installedDispatcher
|
|
251
|
+
: undefined;
|
|
235
252
|
const normalizedInput = await normalizeFetchInput(input, init, {
|
|
236
|
-
preserveDispatcher:
|
|
253
|
+
preserveDispatcher: managedDispatcher === undefined,
|
|
237
254
|
});
|
|
238
|
-
const normalizedInit =
|
|
255
|
+
const normalizedInit = managedDispatcher === undefined
|
|
256
|
+
? init
|
|
257
|
+
: withManagedFetchDispatcher(init, managedDispatcher);
|
|
239
258
|
const response: unknown = await Reflect.apply(
|
|
240
259
|
undiciFetch,
|
|
241
260
|
undefined,
|
|
@@ -389,6 +408,11 @@ type UndiciDispatcherOptions = Readonly<{
|
|
|
389
408
|
proxyCa: string | undefined;
|
|
390
409
|
undici: ProxylineUndiciOptions | undefined;
|
|
391
410
|
}>;
|
|
411
|
+
type UndiciProxyAgentOptions = Exclude<ConstructorParameters<typeof UndiciProxyAgent>[0], string | URL>;
|
|
412
|
+
type UndiciProxyClientFactory = NonNullable<
|
|
413
|
+
UndiciProxyAgentOptions["clientFactory"]
|
|
414
|
+
>;
|
|
415
|
+
type UnknownFunction = (...args: unknown[]) => unknown;
|
|
392
416
|
|
|
393
417
|
function finiteNonNegativeInteger(value: number | undefined): number | undefined {
|
|
394
418
|
return typeof value === "number" && Number.isFinite(value) && value >= 0
|
|
@@ -434,6 +458,43 @@ function createUndiciAgent(options: ProxylineUndiciOptions | undefined): UndiciA
|
|
|
434
458
|
return new UndiciAgent(resolveUndiciBaseOptions(options));
|
|
435
459
|
}
|
|
436
460
|
|
|
461
|
+
function isObjectRecord(value: unknown): value is Record<string, unknown> {
|
|
462
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function stripIpServernameFromConnectOptions(options: unknown): unknown {
|
|
466
|
+
if (!isObjectRecord(options) || typeof options.servername !== "string") {
|
|
467
|
+
return options;
|
|
468
|
+
}
|
|
469
|
+
const servername = options.servername.replace(/^\[|\]$/g, "");
|
|
470
|
+
if (net.isIP(servername) === 0) {
|
|
471
|
+
return options;
|
|
472
|
+
}
|
|
473
|
+
const next = { ...options };
|
|
474
|
+
delete next.servername;
|
|
475
|
+
return next;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function stripIpServernameFromConnect(connect: unknown): unknown {
|
|
479
|
+
if (typeof connect !== "function") {
|
|
480
|
+
return connect;
|
|
481
|
+
}
|
|
482
|
+
return (options: unknown, callback: unknown): unknown =>
|
|
483
|
+
(connect as UnknownFunction)(stripIpServernameFromConnectOptions(options), callback);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function createProxyClientFactory(): UndiciProxyClientFactory {
|
|
487
|
+
return (origin: URL, options: object): Dispatcher => {
|
|
488
|
+
const clientOptions = isObjectRecord(options)
|
|
489
|
+
? { ...options, connect: stripIpServernameFromConnect(options.connect) }
|
|
490
|
+
: options;
|
|
491
|
+
return new UndiciPool(
|
|
492
|
+
origin,
|
|
493
|
+
clientOptions as ConstructorParameters<typeof UndiciPool>[1],
|
|
494
|
+
);
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
|
|
437
498
|
function createUndiciProxyAgent(
|
|
438
499
|
proxyUrl: string,
|
|
439
500
|
options: UndiciDispatcherOptions,
|
|
@@ -441,6 +502,7 @@ function createUndiciProxyAgent(
|
|
|
441
502
|
return new UndiciProxyAgent({
|
|
442
503
|
...resolveUndiciBaseOptions(options.undici),
|
|
443
504
|
uri: proxyUrl,
|
|
505
|
+
clientFactory: createProxyClientFactory(),
|
|
444
506
|
...(options.proxyCa !== undefined ? { proxyTls: { ca: options.proxyCa } } : {}),
|
|
445
507
|
} as ConstructorParameters<typeof UndiciProxyAgent>[0]);
|
|
446
508
|
}
|
|
@@ -460,6 +522,22 @@ function createUndiciProxyDispatcher(
|
|
|
460
522
|
return new ManagedUndiciDispatcher(options.resolver, dispatcherOptions);
|
|
461
523
|
}
|
|
462
524
|
|
|
525
|
+
function reportClosedDispatchError(
|
|
526
|
+
handler: Dispatcher.DispatchHandler,
|
|
527
|
+
error: Error,
|
|
528
|
+
): boolean {
|
|
529
|
+
const compatibleHandler = handler as CompatibleDispatchHandler;
|
|
530
|
+
if (compatibleHandler.onResponseError !== undefined) {
|
|
531
|
+
compatibleHandler.onResponseError(null, error);
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
if (compatibleHandler.onError !== undefined) {
|
|
535
|
+
compatibleHandler.onError(error);
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
throw error;
|
|
539
|
+
}
|
|
540
|
+
|
|
463
541
|
class ManagedUndiciDispatcher extends Dispatcher {
|
|
464
542
|
public readonly [PROXYLINE_DISPATCHER_BRAND] = true;
|
|
465
543
|
readonly #directDispatcher: UndiciAgent;
|
|
@@ -480,11 +558,7 @@ class ManagedUndiciDispatcher extends Dispatcher {
|
|
|
480
558
|
handler: Dispatcher.DispatchHandler,
|
|
481
559
|
): boolean {
|
|
482
560
|
if (this.#closedError !== undefined) {
|
|
483
|
-
|
|
484
|
-
throw this.#closedError;
|
|
485
|
-
}
|
|
486
|
-
handler.onError(this.#closedError);
|
|
487
|
-
return false;
|
|
561
|
+
return reportClosedDispatchError(handler, this.#closedError);
|
|
488
562
|
}
|
|
489
563
|
const url = resolveUndiciDispatchUrl(options);
|
|
490
564
|
const proxyUrl = url === undefined ? "" : this.#resolver.getProxyForUrl(url, "undici");
|
|
@@ -570,11 +644,7 @@ class AmbientUndiciDispatcher extends Dispatcher {
|
|
|
570
644
|
handler: Dispatcher.DispatchHandler,
|
|
571
645
|
): boolean {
|
|
572
646
|
if (this.#closedError !== undefined) {
|
|
573
|
-
|
|
574
|
-
throw this.#closedError;
|
|
575
|
-
}
|
|
576
|
-
handler.onError(this.#closedError);
|
|
577
|
-
return false;
|
|
647
|
+
return reportClosedDispatchError(handler, this.#closedError);
|
|
578
648
|
}
|
|
579
649
|
const url = resolveUndiciDispatchUrl(options);
|
|
580
650
|
const proxyUrl = url === undefined ? undefined : resolveAmbientProxyForUrl(url, this.#env);
|