@openclaw/proxyline 0.3.4 → 0.3.5

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 CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.5 - 2026-08-01
4
+
5
+ - Fixed malformed percent-encoding in proxy credentials to fail through CONNECT promises and Node request errors with `INVALID_PROXY_USERINFO`. Thanks @SebTardif.
6
+ - Updated Undici, WebSocket and TypeScript tooling, pnpm, and pinned GitHub Actions to their latest stable releases.
7
+
3
8
  ## 0.3.4 - 2026-07-20
4
9
 
5
10
  - Added `AbortSignal` cancellation to explicit CONNECT tunnels, including active socket cleanup.
@@ -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;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"}
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,EAAgD,KAAK,mBAAmB,EAAqC,MAAM,aAAa,CAAC;AAExI,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,CA4HtB"}
package/dist/connect.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import net from "node:net";
2
2
  import tls from "node:tls";
3
- import { ProxylineError, redactProxyUrl, resolveProxyTlsCa } from "./shared.js";
3
+ import { ProxylineError, decodeProxyUserinfoComponent, redactProxyUrl, resolveProxyTlsCa } from "./shared.js";
4
4
  const MAX_CONNECT_RESPONSE_HEADER_BYTES = 16 * 1024;
5
5
  const INVALID_CONNECT_AUTHORITY_PATTERN = /[\u0000-\u0020\u007f]/;
6
6
  const INVALID_CONNECT_HOST_DELIMITER_PATTERN = /[/:?#@\\]/;
@@ -17,8 +17,8 @@ function resolveProxyAuthorization(proxy) {
17
17
  if (!proxy.username && !proxy.password) {
18
18
  return undefined;
19
19
  }
20
- const username = decodeURIComponent(proxy.username);
21
- const password = decodeURIComponent(proxy.password);
20
+ const username = decodeProxyUserinfoComponent(proxy.username);
21
+ const password = decodeProxyUserinfoComponent(proxy.password);
22
22
  return `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
23
23
  }
24
24
  export function formatConnectAuthority(targetHost, targetPort) {
@@ -109,7 +109,7 @@ export async function openProxyConnectTunnel(options) {
109
109
  settled = true;
110
110
  cleanup();
111
111
  socket?.destroy();
112
- reject(failConnect(proxy, error));
112
+ reject(error instanceof ProxylineError ? error : failConnect(proxy, error));
113
113
  };
114
114
  const succeed = (connectedSocket, tunneledBytes) => {
115
115
  if (settled) {
@@ -128,7 +128,12 @@ export async function openProxyConnectTunnel(options) {
128
128
  fail(new Error("proxy socket missing after connect"));
129
129
  return;
130
130
  }
131
- writeConnectRequest(socket, proxy, target);
131
+ try {
132
+ writeConnectRequest(socket, proxy, target);
133
+ }
134
+ catch (error) {
135
+ fail(error);
136
+ }
132
137
  };
133
138
  const onData = (chunk) => {
134
139
  responseBuffer = Buffer.concat([responseBuffer, chunk]);
@@ -1 +1 @@
1
- {"version":3,"file":"node-http.d.ts","sourceRoot":"","sources":["../src/node-http.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"node-http.d.ts","sourceRoot":"","sources":["../src/node-http.ts"],"names":[],"mappings":"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"}
package/dist/node-http.js CHANGED
@@ -5,7 +5,7 @@ import tls from "node:tls";
5
5
  import { domainToASCII } from "node:url";
6
6
  import { readProxyEnv, resolveAmbientProxyForUrl, } from "./env.js";
7
7
  import { formatConnectAuthority } from "./connect.js";
8
- import { ProxylineError, resolveProxyTlsCa } from "./shared.js";
8
+ import { ProxylineError, decodeProxyUserinfoComponent, resolveProxyTlsCa } from "./shared.js";
9
9
  const MAX_CONNECT_RESPONSE_HEADER_BYTES = 16 * 1024;
10
10
  const INVALID_PROXY_TARGET_HOST_DELIMITER_PATTERN = /[/:?#@\\]/;
11
11
  const INVALID_PROXY_TARGET_HOST_CONTROL_PATTERN = /[\u0000-\u0020\u007f]/;
@@ -133,8 +133,8 @@ function proxyAuthorization(proxy) {
133
133
  if (!proxy.username && !proxy.password) {
134
134
  return undefined;
135
135
  }
136
- const username = decodeURIComponent(proxy.username);
137
- const password = decodeURIComponent(proxy.password);
136
+ const username = decodeProxyUserinfoComponent(proxy.username);
137
+ const password = decodeProxyUserinfoComponent(proxy.password);
138
138
  return `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
139
139
  }
140
140
  function proxyConnectOptions(proxy, proxyTls) {
@@ -445,26 +445,23 @@ class ProxylineConnectAgent extends http.Agent {
445
445
  finish(error, proxySocket);
446
446
  };
447
447
  const onConnected = () => {
448
- let target;
449
448
  try {
450
- target = connectTarget(options);
449
+ const { host, port } = connectTarget(options);
450
+ const authority = formatConnectAuthority(host, port);
451
+ const headers = [
452
+ `CONNECT ${authority} HTTP/1.1`,
453
+ `Host: ${authority}`,
454
+ `Proxy-Connection: ${this.#keepAlive ? "Keep-Alive" : "close"}`,
455
+ ];
456
+ const authorization = proxyAuthorization(this.#proxy);
457
+ if (authorization !== undefined) {
458
+ headers.push(`Proxy-Authorization: ${authorization}`);
459
+ }
460
+ proxySocket.write([...headers, "", ""].join("\r\n"));
451
461
  }
452
462
  catch (error) {
453
463
  fail(error instanceof Error ? error : new Error(String(error)));
454
- return;
455
- }
456
- const { host, port } = target;
457
- const authority = formatConnectAuthority(host, port);
458
- const headers = [
459
- `CONNECT ${authority} HTTP/1.1`,
460
- `Host: ${authority}`,
461
- `Proxy-Connection: ${this.#keepAlive ? "Keep-Alive" : "close"}`,
462
- ];
463
- const authorization = proxyAuthorization(this.#proxy);
464
- if (authorization !== undefined) {
465
- headers.push(`Proxy-Authorization: ${authorization}`);
466
464
  }
467
- proxySocket.write([...headers, "", ""].join("\r\n"));
468
465
  };
469
466
  const onData = (chunk) => {
470
467
  responseBuffer = Buffer.concat([responseBuffer, chunk]);
@@ -1 +1 @@
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"}
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;AAs0BpB,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,GAAG,eAAe,CAgI3E;AAED,eAAO,MAAM,kBAAkB,yBAAmB,CAAC"}
package/dist/runtime.js CHANGED
@@ -334,13 +334,25 @@ function createUndiciProxyAgent(proxyUrl, options) {
334
334
  ...(net.isIP(proxyHostname) === 6 ? { checkServerIdentity: checkProxyServerIdentity } : {}),
335
335
  }
336
336
  : undefined;
337
+ const dispatcherFactory = createProxyClientFactory();
337
338
  return new UndiciProxyAgent({
338
339
  ...resolveUndiciBaseOptions(options.undici),
339
340
  uri: proxyUrl,
340
- clientFactory: createProxyClientFactory(),
341
+ clientFactory: dispatcherFactory,
342
+ factory: dispatcherFactory,
341
343
  ...(proxyTls !== undefined ? { proxyTls } : {}),
342
344
  });
343
345
  }
346
+ function normalizeUndiciDispatchOptions(options) {
347
+ if (options.origin === undefined || !/^https?:\/\//i.test(options.path)) {
348
+ return options;
349
+ }
350
+ const pathUrl = new URL(options.path);
351
+ return {
352
+ ...options,
353
+ path: `${pathUrl.pathname}${pathUrl.search}`,
354
+ };
355
+ }
344
356
  function createUndiciProxyDispatcher(options, dispatcherOptions) {
345
357
  if (options.mode === "ambient") {
346
358
  if (!options.active) {
@@ -379,10 +391,11 @@ class ManagedUndiciDispatcher extends Dispatcher {
379
391
  if (this.#closedError !== undefined) {
380
392
  return reportClosedDispatchError(handler, this.#closedError);
381
393
  }
382
- const url = resolveUndiciDispatchUrl(options);
394
+ const normalizedOptions = normalizeUndiciDispatchOptions(options);
395
+ const url = resolveUndiciDispatchUrl(normalizedOptions);
383
396
  const proxyUrl = url === undefined ? "" : this.#resolver.getProxyForUrl(url, "undici");
384
397
  const dispatcher = proxyUrl === "" ? this.#directDispatcher : this.#proxyDispatcher(proxyUrl);
385
- return dispatcher.dispatch(options, handler);
398
+ return dispatcher.dispatch(normalizedOptions, handler);
386
399
  }
387
400
  close(callback) {
388
401
  const closing = this.#closeAll();
@@ -445,10 +458,11 @@ class AmbientUndiciDispatcher extends Dispatcher {
445
458
  if (this.#closedError !== undefined) {
446
459
  return reportClosedDispatchError(handler, this.#closedError);
447
460
  }
448
- const url = resolveUndiciDispatchUrl(options);
461
+ const normalizedOptions = normalizeUndiciDispatchOptions(options);
462
+ const url = resolveUndiciDispatchUrl(normalizedOptions);
449
463
  const proxyUrl = url === undefined ? undefined : resolveAmbientProxyForUrl(url, this.#env);
450
464
  const dispatcher = proxyUrl === undefined ? this.#directDispatcher : this.#proxyDispatcher(proxyUrl);
451
- return dispatcher.dispatch(options, handler);
465
+ return dispatcher.dispatch(normalizedOptions, handler);
452
466
  }
453
467
  close(callback) {
454
468
  const closing = this.#closeAll();
package/dist/shared.d.ts CHANGED
@@ -6,6 +6,8 @@ export declare class ProxylineError extends Error {
6
6
  readonly code: string;
7
7
  constructor(code: string, message: string);
8
8
  }
9
+ /** Decode proxy userinfo; invalid percent-encoding must not throw URIError mid-CONNECT. */
10
+ export declare function decodeProxyUserinfoComponent(value: string): string;
9
11
  export declare function resolveProxyTlsCa(options: ProxylineTlsOptions | undefined): string | undefined;
10
12
  export declare function formatUrl(value: string | URL): string;
11
13
  export declare function redactProxyUrl(value: string | URL): string;
@@ -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;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"}
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,2FAA2F;AAC3F,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CASlE;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/dist/shared.js CHANGED
@@ -7,6 +7,15 @@ export class ProxylineError extends Error {
7
7
  this.code = code;
8
8
  }
9
9
  }
10
+ /** Decode proxy userinfo; invalid percent-encoding must not throw URIError mid-CONNECT. */
11
+ export function decodeProxyUserinfoComponent(value) {
12
+ try {
13
+ return decodeURIComponent(value);
14
+ }
15
+ catch {
16
+ throw new ProxylineError("INVALID_PROXY_USERINFO", "Proxy username or password contains invalid percent-encoding.");
17
+ }
18
+ }
10
19
  export function resolveProxyTlsCa(options) {
11
20
  if (!options) {
12
21
  return undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/proxyline",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
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.9.0",
20
+ "packageManager": "pnpm@11.18.0",
21
21
  "exports": {
22
22
  ".": {
23
23
  "types": "./dist/index.d.ts",
@@ -62,12 +62,12 @@
62
62
  "crabbox:warmup": "crabbox warmup"
63
63
  },
64
64
  "devDependencies": {
65
- "@types/node": "26.1.1",
65
+ "@types/node": "26.1.2",
66
66
  "@types/ws": "8.18.1",
67
- "tsx": "4.23.0",
67
+ "tsx": "4.23.1",
68
68
  "typescript": "^7.0.2",
69
- "undici": "8.6.0",
70
- "ws": "8.21.0"
69
+ "undici": "8.9.0",
70
+ "ws": "8.21.1"
71
71
  },
72
72
  "engines": {
73
73
  "node": ">=22.19.0"
package/src/connect.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import net from "node:net";
2
2
  import tls from "node:tls";
3
- import { ProxylineError, type ProxylineTlsOptions, redactProxyUrl, resolveProxyTlsCa } from "./shared.js";
3
+ import { ProxylineError, decodeProxyUserinfoComponent, type ProxylineTlsOptions, redactProxyUrl, resolveProxyTlsCa } from "./shared.js";
4
4
 
5
5
  export type OpenProxyConnectTunnelOptions = Readonly<{
6
6
  proxyUrl: string | URL;
@@ -32,8 +32,8 @@ function resolveProxyAuthorization(proxy: URL): string | undefined {
32
32
  if (!proxy.username && !proxy.password) {
33
33
  return undefined;
34
34
  }
35
- const username = decodeURIComponent(proxy.username);
36
- const password = decodeURIComponent(proxy.password);
35
+ const username = decodeProxyUserinfoComponent(proxy.username);
36
+ const password = decodeProxyUserinfoComponent(proxy.password);
37
37
  return `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
38
38
  }
39
39
 
@@ -142,7 +142,7 @@ export async function openProxyConnectTunnel(
142
142
  settled = true;
143
143
  cleanup();
144
144
  socket?.destroy();
145
- reject(failConnect(proxy, error));
145
+ reject(error instanceof ProxylineError ? error : failConnect(proxy, error));
146
146
  };
147
147
 
148
148
  const succeed = (connectedSocket: ProxySocket, tunneledBytes: Buffer | undefined): void => {
@@ -163,7 +163,11 @@ export async function openProxyConnectTunnel(
163
163
  fail(new Error("proxy socket missing after connect"));
164
164
  return;
165
165
  }
166
- writeConnectRequest(socket, proxy, target);
166
+ try {
167
+ writeConnectRequest(socket, proxy, target);
168
+ } catch (error) {
169
+ fail(error);
170
+ }
167
171
  };
168
172
 
169
173
  const onData = (chunk: Buffer): void => {
package/src/node-http.ts CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  type ProxyEnvSnapshot,
10
10
  } from "./env.js";
11
11
  import { formatConnectAuthority } from "./connect.js";
12
- import { ProxylineError, resolveProxyTlsCa, type ProxylineTlsOptions } from "./shared.js";
12
+ import { ProxylineError, decodeProxyUserinfoComponent, resolveProxyTlsCa, type ProxylineTlsOptions } from "./shared.js";
13
13
  import type { ProxylineSurface, ProxyResolver } from "./types.js";
14
14
 
15
15
  export type NodeHttpRequestOptions = http.RequestOptions & https.RequestOptions & {
@@ -198,8 +198,8 @@ function proxyAuthorization(proxy: URL): string | undefined {
198
198
  if (!proxy.username && !proxy.password) {
199
199
  return undefined;
200
200
  }
201
- const username = decodeURIComponent(proxy.username);
202
- const password = decodeURIComponent(proxy.password);
201
+ const username = decodeProxyUserinfoComponent(proxy.username);
202
+ const password = decodeProxyUserinfoComponent(proxy.password);
203
203
  return `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
204
204
  }
205
205
 
@@ -591,25 +591,22 @@ class ProxylineConnectAgent extends http.Agent {
591
591
  };
592
592
 
593
593
  const onConnected = (): void => {
594
- let target: { host: string; port: number };
595
594
  try {
596
- target = connectTarget(options);
595
+ const { host, port } = connectTarget(options);
596
+ const authority = formatConnectAuthority(host, port);
597
+ const headers = [
598
+ `CONNECT ${authority} HTTP/1.1`,
599
+ `Host: ${authority}`,
600
+ `Proxy-Connection: ${this.#keepAlive ? "Keep-Alive" : "close"}`,
601
+ ];
602
+ const authorization = proxyAuthorization(this.#proxy);
603
+ if (authorization !== undefined) {
604
+ headers.push(`Proxy-Authorization: ${authorization}`);
605
+ }
606
+ proxySocket.write([...headers, "", ""].join("\r\n"));
597
607
  } catch (error) {
598
608
  fail(error instanceof Error ? error : new Error(String(error)));
599
- return;
600
- }
601
- const { host, port } = target;
602
- const authority = formatConnectAuthority(host, port);
603
- const headers = [
604
- `CONNECT ${authority} HTTP/1.1`,
605
- `Host: ${authority}`,
606
- `Proxy-Connection: ${this.#keepAlive ? "Keep-Alive" : "close"}`,
607
- ];
608
- const authorization = proxyAuthorization(this.#proxy);
609
- if (authorization !== undefined) {
610
- headers.push(`Proxy-Authorization: ${authorization}`);
611
609
  }
612
- proxySocket.write([...headers, "", ""].join("\r\n"));
613
610
  };
614
611
 
615
612
  const onData = (chunk: Buffer): void => {
package/src/runtime.ts CHANGED
@@ -524,14 +524,29 @@ function createUndiciProxyAgent(
524
524
  ...(net.isIP(proxyHostname) === 6 ? { checkServerIdentity: checkProxyServerIdentity } : {}),
525
525
  }
526
526
  : undefined;
527
+ const dispatcherFactory = createProxyClientFactory();
527
528
  return new UndiciProxyAgent({
528
529
  ...resolveUndiciBaseOptions(options.undici),
529
530
  uri: proxyUrl,
530
- clientFactory: createProxyClientFactory(),
531
+ clientFactory: dispatcherFactory,
532
+ factory: dispatcherFactory,
531
533
  ...(proxyTls !== undefined ? { proxyTls } : {}),
532
534
  } as ConstructorParameters<typeof UndiciProxyAgent>[0]);
533
535
  }
534
536
 
537
+ function normalizeUndiciDispatchOptions(
538
+ options: Dispatcher.DispatchOptions,
539
+ ): Dispatcher.DispatchOptions {
540
+ if (options.origin === undefined || !/^https?:\/\//i.test(options.path)) {
541
+ return options;
542
+ }
543
+ const pathUrl = new URL(options.path);
544
+ return {
545
+ ...options,
546
+ path: `${pathUrl.pathname}${pathUrl.search}`,
547
+ };
548
+ }
549
+
535
550
  function createUndiciProxyDispatcher(
536
551
  options:
537
552
  | { mode: "managed"; resolver: ProxyResolver }
@@ -585,10 +600,11 @@ class ManagedUndiciDispatcher extends Dispatcher {
585
600
  if (this.#closedError !== undefined) {
586
601
  return reportClosedDispatchError(handler, this.#closedError);
587
602
  }
588
- const url = resolveUndiciDispatchUrl(options);
603
+ const normalizedOptions = normalizeUndiciDispatchOptions(options);
604
+ const url = resolveUndiciDispatchUrl(normalizedOptions);
589
605
  const proxyUrl = url === undefined ? "" : this.#resolver.getProxyForUrl(url, "undici");
590
606
  const dispatcher = proxyUrl === "" ? this.#directDispatcher : this.#proxyDispatcher(proxyUrl);
591
- return dispatcher.dispatch(options, handler);
607
+ return dispatcher.dispatch(normalizedOptions, handler);
592
608
  }
593
609
 
594
610
  public override close(callback: () => void): void;
@@ -671,10 +687,11 @@ class AmbientUndiciDispatcher extends Dispatcher {
671
687
  if (this.#closedError !== undefined) {
672
688
  return reportClosedDispatchError(handler, this.#closedError);
673
689
  }
674
- const url = resolveUndiciDispatchUrl(options);
690
+ const normalizedOptions = normalizeUndiciDispatchOptions(options);
691
+ const url = resolveUndiciDispatchUrl(normalizedOptions);
675
692
  const proxyUrl = url === undefined ? undefined : resolveAmbientProxyForUrl(url, this.#env);
676
693
  const dispatcher = proxyUrl === undefined ? this.#directDispatcher : this.#proxyDispatcher(proxyUrl);
677
- return dispatcher.dispatch(options, handler);
694
+ return dispatcher.dispatch(normalizedOptions, handler);
678
695
  }
679
696
 
680
697
  public override close(callback: () => void): void;
package/src/shared.ts CHANGED
@@ -15,6 +15,18 @@ export class ProxylineError extends Error {
15
15
  }
16
16
  }
17
17
 
18
+ /** Decode proxy userinfo; invalid percent-encoding must not throw URIError mid-CONNECT. */
19
+ export function decodeProxyUserinfoComponent(value: string): string {
20
+ try {
21
+ return decodeURIComponent(value);
22
+ } catch {
23
+ throw new ProxylineError(
24
+ "INVALID_PROXY_USERINFO",
25
+ "Proxy username or password contains invalid percent-encoding.",
26
+ );
27
+ }
28
+ }
29
+
18
30
  export function resolveProxyTlsCa(options: ProxylineTlsOptions | undefined): string | undefined {
19
31
  if (!options) {
20
32
  return undefined;