@jameskh/proxywire 1.0.0
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/LICENSE +21 -0
- package/README.md +189 -0
- package/dist/errors.d.ts +25 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +53 -0
- package/dist/errors.js.map +1 -0
- package/dist/fetcher.d.ts +48 -0
- package/dist/fetcher.d.ts.map +1 -0
- package/dist/fetcher.js +147 -0
- package/dist/fetcher.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/list.d.ts +18 -0
- package/dist/list.d.ts.map +1 -0
- package/dist/list.js +44 -0
- package/dist/list.js.map +1 -0
- package/dist/parse.d.ts +38 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +185 -0
- package/dist/parse.js.map +1 -0
- package/dist/types.d.ts +66 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +57 -0
- package/src/errors.ts +61 -0
- package/src/fetcher.ts +185 -0
- package/src/index.ts +28 -0
- package/src/list.ts +58 -0
- package/src/parse.ts +246 -0
- package/src/types.ts +71 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 proxywire contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# proxywire
|
|
2
|
+
|
|
3
|
+
Proxy list parsing and proxy-aware `fetch` for Node and Bun.
|
|
4
|
+
|
|
5
|
+
Two jobs, both of which tend to get reinvented badly:
|
|
6
|
+
|
|
7
|
+
1. **Turn whatever your provider exported into a URL you can dial.** Residential
|
|
8
|
+
proxy vendors ship lists in at least five mutually incompatible notations.
|
|
9
|
+
`proxywire` accepts all of them and hands back one normalized URL.
|
|
10
|
+
2. **Give you a `fetch` that actually goes through the proxy.** On both runtimes.
|
|
11
|
+
This is less obvious than it sounds — see [Bun](#bun) below.
|
|
12
|
+
|
|
13
|
+
No framework, no globals, no logging to the console. Node 18+ or Bun.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
bun add @jameskh/proxywire # or: npm i @jameskh/proxywire
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { loadProxyFile, makeFetcher, maskProxy, shuffleProxies } from "@jameskh/proxywire";
|
|
25
|
+
|
|
26
|
+
const proxies = shuffleProxies(loadProxyFile("./proxies.txt"));
|
|
27
|
+
|
|
28
|
+
for (const proxy of proxies) {
|
|
29
|
+
const { fetch, close } = makeFetcher(proxy, { timeoutMs: 45_000 });
|
|
30
|
+
try {
|
|
31
|
+
const response = await fetch("https://api.ipify.org?format=json");
|
|
32
|
+
console.log(maskProxy(proxy), await response.json());
|
|
33
|
+
break;
|
|
34
|
+
} finally {
|
|
35
|
+
await close();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Proxy notations
|
|
41
|
+
|
|
42
|
+
`normalizeProxy` accepts every shape below and returns a dialable URL. Anything
|
|
43
|
+
it cannot understand throws a `ProxyParseError` naming the source and line.
|
|
44
|
+
|
|
45
|
+
| Input | Result |
|
|
46
|
+
| --- | --- |
|
|
47
|
+
| `socks5://bob:pw@1.2.3.4:1080` | unchanged |
|
|
48
|
+
| `socks5://1.2.3.4:1080:bob:pw` | `socks5://bob:pw@1.2.3.4:1080` |
|
|
49
|
+
| `bob:pw@1.2.3.4:1080` | `socks5://bob:pw@1.2.3.4:1080` |
|
|
50
|
+
| `1.2.3.4:1080@bob:pw` | `socks5://bob:pw@1.2.3.4:1080` |
|
|
51
|
+
| `bob:pw:1.2.3.4:1080` | `socks5://bob:pw@1.2.3.4:1080` |
|
|
52
|
+
| `1.2.3.4:1080:bob:pw` | `socks5://bob:pw@1.2.3.4:1080` |
|
|
53
|
+
|
|
54
|
+
The last four carry no scheme, so one is applied — `socks5` unless you say
|
|
55
|
+
otherwise. Host and credentials are told apart by which field looks like a port,
|
|
56
|
+
and credentials are percent-encoded so a password containing `@` or `:` cannot
|
|
57
|
+
silently rewrite the host.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
normalizeProxy("bob:pw@1.2.3.4:1080", { defaultProtocol: "http" });
|
|
61
|
+
|
|
62
|
+
// Resolve the scheme lazily when it comes from the environment, so a variable
|
|
63
|
+
// set after import time is still honoured.
|
|
64
|
+
normalizeProxy(line, { defaultProtocol: () => process.env.PROXY_PROTOCOL ?? "socks5" });
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Supported schemes: `http`, `https`, `socks4`, `socks5`.
|
|
68
|
+
|
|
69
|
+
## Reading a list
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { loadProxyFile, parseProxyList } from "@jameskh/proxywire";
|
|
73
|
+
|
|
74
|
+
loadProxyFile("./config/proxies.txt"); // [] when the file is absent
|
|
75
|
+
parseProxyList(text, { source: "PROXY_ENV" }); // same parser, any string
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Blank lines and `#` comments are skipped, but line numbers are counted against
|
|
79
|
+
the original text, so errors point at the line you actually have to fix.
|
|
80
|
+
|
|
81
|
+
Lists still holding the placeholder rows that ship in example files
|
|
82
|
+
(`user:pass@host:port` and friends) are rejected outright — running against a
|
|
83
|
+
template is a configuration mistake, and failing on it beats a confusing DNS
|
|
84
|
+
error later. Pass `rejectPlaceholders: false` to skip the check, or
|
|
85
|
+
`commentPrefix` to use something other than `#`.
|
|
86
|
+
|
|
87
|
+
## Making requests
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
const { fetch, dispatcher, close } = makeFetcher(proxy, {
|
|
91
|
+
timeoutMs: 30_000, // applied unless you pass your own signal
|
|
92
|
+
socksConnectTimeoutMs: 15_000,
|
|
93
|
+
minTlsVersion: "TLSv1.2",
|
|
94
|
+
logger: { debug: (m) => log.debug(m) },
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Call `close()` when you are finished. Without it every request cycle leaks an
|
|
99
|
+
undici `Agent` and its sockets, which exhausts file descriptors in any
|
|
100
|
+
long-running process. `makeFetcher()` with no proxy returns a plain `fetch` and
|
|
101
|
+
a `close()` that does nothing, so the calling code needs no branching.
|
|
102
|
+
|
|
103
|
+
SOCKS proxies get an undici `Agent` whose `connect` opens the tunnel by hand and
|
|
104
|
+
starts TLS over it, since undici has no SOCKS support of its own. HTTP and HTTPS
|
|
105
|
+
proxies go to `ProxyAgent`.
|
|
106
|
+
|
|
107
|
+
### Bun
|
|
108
|
+
|
|
109
|
+
Bun ignores undici's `dispatcher` option. A request aimed at a dead proxy still
|
|
110
|
+
succeeds and reports the machine's own address — every "proxied" call silently
|
|
111
|
+
goes out unproxied, which is the worst possible failure mode for anything that
|
|
112
|
+
uses proxies for a reason. Bun's own `fetch` takes a `proxy` option that it does
|
|
113
|
+
honour, so `makeFetcher` uses that path under Bun and the dispatcher path under
|
|
114
|
+
Node.
|
|
115
|
+
|
|
116
|
+
Bun's `proxy` option cannot do SOCKS. Rather than fall through to a dispatcher
|
|
117
|
+
Bun will ignore, `makeFetcher` throws:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
SOCKS proxies are not supported when running under Bun (1.2.3.4:1080).
|
|
121
|
+
Use an http:// proxy, or set allowSocksUnderBun to opt out of this check.
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The proxy is masked to `host:port` in that message — credentials in a proxy URL
|
|
125
|
+
are as sensitive as any other password and must never reach a log.
|
|
126
|
+
|
|
127
|
+
## Diagnosing failures
|
|
128
|
+
|
|
129
|
+
`fetch` throws a bare `TypeError: fetch failed` for every network failure and
|
|
130
|
+
puts the real reason in `cause`, so a dead proxy, a bad credential and a slow
|
|
131
|
+
host are indistinguishable. `describeError` flattens the chain:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
describeError(error); // "fetch failed -> connection refused (ECONNREFUSED)"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`retryOnTransportFailure` reruns an operation once on failure, which is enough
|
|
138
|
+
for the connection drops a rotating pool produces on its own:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
await retryOnTransportFailure("token request", () => fetch(url, init), { logger });
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## API
|
|
145
|
+
|
|
146
|
+
| Export | Purpose |
|
|
147
|
+
| --- | --- |
|
|
148
|
+
| `makeFetcher(proxy?, options?)` | `{ fetch, dispatcher?, close() }` bound to one proxy |
|
|
149
|
+
| `makeProxyDispatcher(proxy, options?)` | The undici dispatcher on its own |
|
|
150
|
+
| `closeDispatcher(dispatcher?)` | Release a dispatcher's socket pool |
|
|
151
|
+
| `withRequestTimeout(init, ms)` | Attach a timeout signal unless one exists |
|
|
152
|
+
| `retryOnTransportFailure(label, run, options?)` | Run once more on failure |
|
|
153
|
+
| `normalizeProxy(input, options?)` | Any notation to a dialable URL |
|
|
154
|
+
| `parseProxyList(text, options?)` | Newline-separated list to URLs |
|
|
155
|
+
| `loadProxyFile(path, options?)` | Same, from a file; `[]` when absent |
|
|
156
|
+
| `parseSchemelessProxy(input)` | The four schemeless shapes, as parts |
|
|
157
|
+
| `isPlaceholderProxy(input)` | True for example-file template rows |
|
|
158
|
+
| `maskProxy(proxy)` | `host:port`, safe to log |
|
|
159
|
+
| `shuffleProxies(list)` | Fisher-Yates copy, to spread load |
|
|
160
|
+
| `describeError(error)` | Flatten an error and its `cause` chain |
|
|
161
|
+
| `IS_BUN` | Runtime check used for the paths above |
|
|
162
|
+
| `SUPPORTED_PROXY_PROTOCOLS` | `["http", "https", "socks4", "socks5"]` |
|
|
163
|
+
| `ProxyParseError` | Carries `source` and `lineNumber` |
|
|
164
|
+
|
|
165
|
+
Types: `Fetcher`, `FetcherOptions`, `ProxyDispatcher`, `ProxyFetch`,
|
|
166
|
+
`ProxyListOptions`, `ProxyLogger`, `ProxyParseOptions`, `ProxyProtocol`.
|
|
167
|
+
|
|
168
|
+
## Runtime support
|
|
169
|
+
|
|
170
|
+
The published package ships both compiled JavaScript and its TypeScript source.
|
|
171
|
+
Node resolves `dist/` with full `.d.ts` declarations; Bun resolves the `bun`
|
|
172
|
+
export condition and reads `src/` directly, so a linked checkout picks up edits
|
|
173
|
+
with no build step.
|
|
174
|
+
|
|
175
|
+
## Development
|
|
176
|
+
|
|
177
|
+
```sh
|
|
178
|
+
bun install
|
|
179
|
+
bun test
|
|
180
|
+
bun run typecheck
|
|
181
|
+
bun run build # emits dist/ for Node consumers
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Relative imports carry explicit `.js` extensions so the emitted ESM resolves
|
|
185
|
+
under Node. TypeScript maps `./errors.js` back to `errors.ts` at compile time.
|
|
186
|
+
|
|
187
|
+
## License
|
|
188
|
+
|
|
189
|
+
MIT
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Thrown when a proxy string cannot be understood, with the offending location. */
|
|
2
|
+
export declare class ProxyParseError extends Error {
|
|
3
|
+
readonly source?: string;
|
|
4
|
+
readonly lineNumber?: number;
|
|
5
|
+
constructor(message: string, location?: {
|
|
6
|
+
source?: string;
|
|
7
|
+
lineNumber?: number;
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
/** Renders `in <source> on line <n>` for error messages, omitting what is unknown. */
|
|
11
|
+
export declare function describeLocation(location: {
|
|
12
|
+
source?: string;
|
|
13
|
+
lineNumber?: number;
|
|
14
|
+
}): string;
|
|
15
|
+
/**
|
|
16
|
+
* Flattens an error and its `cause` chain into one readable line.
|
|
17
|
+
*
|
|
18
|
+
* `fetch` throws a bare `TypeError: fetch failed` for every underlying network
|
|
19
|
+
* failure - DNS, TLS, proxy handshake, timeout, refused connection - and puts
|
|
20
|
+
* the real reason in `cause`. Without walking that chain there is no way to
|
|
21
|
+
* tell a dead proxy from a bad credential from a slow host, which is exactly
|
|
22
|
+
* the distinction anyone debugging a proxy pool needs.
|
|
23
|
+
*/
|
|
24
|
+
export declare function describeError(error: unknown): string;
|
|
25
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;gBAEjB,OAAO,EAAE,MAAM,EAAE,QAAQ,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAO;CAMrF;AAED,sFAAsF;AACtF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAW3F;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAwBpD"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** Thrown when a proxy string cannot be understood, with the offending location. */
|
|
2
|
+
export class ProxyParseError extends Error {
|
|
3
|
+
source;
|
|
4
|
+
lineNumber;
|
|
5
|
+
constructor(message, location = {}) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "ProxyParseError";
|
|
8
|
+
this.source = location.source;
|
|
9
|
+
this.lineNumber = location.lineNumber;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** Renders `in <source> on line <n>` for error messages, omitting what is unknown. */
|
|
13
|
+
export function describeLocation(location) {
|
|
14
|
+
const parts = [];
|
|
15
|
+
if (location.source) {
|
|
16
|
+
parts.push(` in ${location.source}`);
|
|
17
|
+
}
|
|
18
|
+
if (location.lineNumber !== undefined) {
|
|
19
|
+
parts.push(` on line ${location.lineNumber}`);
|
|
20
|
+
}
|
|
21
|
+
return parts.join("");
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Flattens an error and its `cause` chain into one readable line.
|
|
25
|
+
*
|
|
26
|
+
* `fetch` throws a bare `TypeError: fetch failed` for every underlying network
|
|
27
|
+
* failure - DNS, TLS, proxy handshake, timeout, refused connection - and puts
|
|
28
|
+
* the real reason in `cause`. Without walking that chain there is no way to
|
|
29
|
+
* tell a dead proxy from a bad credential from a slow host, which is exactly
|
|
30
|
+
* the distinction anyone debugging a proxy pool needs.
|
|
31
|
+
*/
|
|
32
|
+
export function describeError(error) {
|
|
33
|
+
const parts = [];
|
|
34
|
+
let current = error;
|
|
35
|
+
const seen = new Set();
|
|
36
|
+
while (current && !seen.has(current)) {
|
|
37
|
+
seen.add(current);
|
|
38
|
+
if (current instanceof Error) {
|
|
39
|
+
const code = current.code;
|
|
40
|
+
const detail = code && !current.message.includes(code) ? `${current.message} (${code})` : current.message;
|
|
41
|
+
if (!parts.includes(detail)) {
|
|
42
|
+
parts.push(detail);
|
|
43
|
+
}
|
|
44
|
+
current = current.cause;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
parts.push(String(current));
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return parts.length > 0 ? parts.join(" -> ") : "Unknown error";
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAC/B,MAAM,CAAU;IAChB,UAAU,CAAU;IAE7B,YAAY,OAAe,EAAE,WAAqD,EAAE;QAClF,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAC9B,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC;QAC9B,IAAI,CAAC,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAC;IACxC,CAAC;CACF;AAED,sFAAsF;AACtF,MAAM,UAAU,gBAAgB,CAAC,QAAkD;IACjF,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;QACpB,KAAK,CAAC,IAAI,CAAC,OAAO,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IACvC,CAAC;IAED,IAAI,QAAQ,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACtC,KAAK,CAAC,IAAI,CAAC,YAAY,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACxB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,OAAO,GAAY,KAAK,CAAC;IAC7B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAW,CAAC;IAEhC,OAAO,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QACrC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAElB,IAAI,OAAO,YAAY,KAAK,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAI,OAAiC,CAAC,IAAI,CAAC;YACrD,MAAM,MAAM,GACV,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,OAAO,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC;YAC7F,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC5B,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACrB,CAAC;YAED,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC;QAC1B,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;YAC5B,MAAM;QACR,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC;AACjE,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { FetcherOptions, ProxyDispatcher, ProxyFetch } from "./types.js";
|
|
2
|
+
/** True when running under Bun rather than Node. */
|
|
3
|
+
export declare const IS_BUN: boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Builds an undici dispatcher for a proxy URL.
|
|
6
|
+
*
|
|
7
|
+
* SOCKS gets a plain `Agent` whose `connect` opens the SOCKS tunnel by hand and
|
|
8
|
+
* then starts TLS over it, because undici has no SOCKS support of its own.
|
|
9
|
+
* HTTP/HTTPS proxies go to `ProxyAgent`, which already speaks CONNECT.
|
|
10
|
+
*/
|
|
11
|
+
export declare function makeProxyDispatcher(proxy: string, options?: FetcherOptions): ProxyDispatcher;
|
|
12
|
+
/** Applies the request budget, but never overrides a signal the caller supplied. */
|
|
13
|
+
export declare function withRequestTimeout(options: RequestInit, timeoutMs: number): RequestInit;
|
|
14
|
+
/**
|
|
15
|
+
* Runs `run` again once if it fails.
|
|
16
|
+
*
|
|
17
|
+
* Proxy pools drop the occasional connection for reasons that have nothing to
|
|
18
|
+
* do with the request - a rotating exit going away mid-handshake, most often -
|
|
19
|
+
* and a single immediate retry turns most of those into a success.
|
|
20
|
+
*/
|
|
21
|
+
export declare function retryOnTransportFailure<T>(describe: string, run: () => Promise<T>, options?: FetcherOptions): Promise<T>;
|
|
22
|
+
export interface Fetcher {
|
|
23
|
+
/** A `fetch` that routes through the proxy this fetcher was built for. */
|
|
24
|
+
fetch: ProxyFetch;
|
|
25
|
+
/** The dispatcher backing it, absent when proxyless or on Bun's native path. */
|
|
26
|
+
dispatcher?: ProxyDispatcher;
|
|
27
|
+
/** Releases the dispatcher's socket pool. Safe to call more than once. */
|
|
28
|
+
close(): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A `fetch` bound to one proxy, or an unproxied one when `proxy` is omitted.
|
|
32
|
+
*
|
|
33
|
+
* The two runtimes need different mechanisms and this is the whole reason the
|
|
34
|
+
* function exists. Bun ignores undici's `dispatcher` option outright, so a
|
|
35
|
+
* request aimed at a dead proxy still succeeds and reports the machine's own
|
|
36
|
+
* address - every "proxied" call silently goes out unproxied. Bun's own `fetch`
|
|
37
|
+
* takes a `proxy` option that it does honour, so that path is used there, and
|
|
38
|
+
* the dispatcher path is kept for Node.
|
|
39
|
+
*/
|
|
40
|
+
export declare function makeFetcher(proxy?: string, options?: FetcherOptions): Fetcher;
|
|
41
|
+
/**
|
|
42
|
+
* Releases a dispatcher's connection pool.
|
|
43
|
+
*
|
|
44
|
+
* Without this every request cycle leaks an `Agent`/`ProxyAgent` and its
|
|
45
|
+
* sockets, which exhausts file descriptors in any long-running process.
|
|
46
|
+
*/
|
|
47
|
+
export declare function closeDispatcher(dispatcher?: ProxyDispatcher): Promise<void>;
|
|
48
|
+
//# sourceMappingURL=fetcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetcher.d.ts","sourceRoot":"","sources":["../src/fetcher.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAO9E,oDAAoD;AACpD,eAAO,MAAM,MAAM,SAA+D,CAAC;AAEnF;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,eAAe,CAqDhG;AAED,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,CAEvF;AAED;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAAC,CAAC,EAC7C,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACrB,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,CAAC,CAAC,CAUZ;AAED,MAAM,WAAW,OAAO;IACtB,0EAA0E;IAC1E,KAAK,EAAE,UAAU,CAAC;IAClB,gFAAgF;IAChF,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,0EAA0E;IAC1E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CA0CjF;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,UAAU,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAUjF"}
|
package/dist/fetcher.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import * as tls from "node:tls";
|
|
2
|
+
import { SocksClient } from "socks";
|
|
3
|
+
import { Agent, fetch as undiciFetch, ProxyAgent } from "undici";
|
|
4
|
+
import { describeError } from "./errors.js";
|
|
5
|
+
import { maskProxy } from "./parse.js";
|
|
6
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
7
|
+
const DEFAULT_SOCKS_CONNECT_TIMEOUT_MS = 15_000;
|
|
8
|
+
const DEFAULT_SOCKS_PORT = 1080;
|
|
9
|
+
const DEFAULT_DESTINATION_PORT = 443;
|
|
10
|
+
/** True when running under Bun rather than Node. */
|
|
11
|
+
export const IS_BUN = typeof globalThis.Bun !== "undefined";
|
|
12
|
+
/**
|
|
13
|
+
* Builds an undici dispatcher for a proxy URL.
|
|
14
|
+
*
|
|
15
|
+
* SOCKS gets a plain `Agent` whose `connect` opens the SOCKS tunnel by hand and
|
|
16
|
+
* then starts TLS over it, because undici has no SOCKS support of its own.
|
|
17
|
+
* HTTP/HTTPS proxies go to `ProxyAgent`, which already speaks CONNECT.
|
|
18
|
+
*/
|
|
19
|
+
export function makeProxyDispatcher(proxy, options = {}) {
|
|
20
|
+
if (proxy.startsWith("socks5://") || proxy.startsWith("socks4://")) {
|
|
21
|
+
const url = new URL(proxy);
|
|
22
|
+
const proxyHost = url.hostname;
|
|
23
|
+
const proxyPort = Number(url.port) || DEFAULT_SOCKS_PORT;
|
|
24
|
+
const proxyType = proxy.startsWith("socks5://") ? 5 : 4;
|
|
25
|
+
const auth = url.username
|
|
26
|
+
? {
|
|
27
|
+
username: decodeURIComponent(url.username),
|
|
28
|
+
password: decodeURIComponent(url.password),
|
|
29
|
+
}
|
|
30
|
+
: undefined;
|
|
31
|
+
return new Agent({
|
|
32
|
+
connect: async (connectOptions, callback) => {
|
|
33
|
+
try {
|
|
34
|
+
const destinationPort = Number(connectOptions.port) || DEFAULT_DESTINATION_PORT;
|
|
35
|
+
const { socket } = await SocksClient.createConnection({
|
|
36
|
+
proxy: {
|
|
37
|
+
host: proxyHost,
|
|
38
|
+
port: proxyPort,
|
|
39
|
+
type: proxyType,
|
|
40
|
+
...(auth
|
|
41
|
+
? {
|
|
42
|
+
userId: auth.username,
|
|
43
|
+
password: auth.password,
|
|
44
|
+
}
|
|
45
|
+
: {}),
|
|
46
|
+
},
|
|
47
|
+
command: "connect",
|
|
48
|
+
destination: {
|
|
49
|
+
host: connectOptions.hostname,
|
|
50
|
+
port: destinationPort,
|
|
51
|
+
},
|
|
52
|
+
timeout: options.socksConnectTimeoutMs ?? DEFAULT_SOCKS_CONNECT_TIMEOUT_MS,
|
|
53
|
+
});
|
|
54
|
+
const tlsSocket = tls.connect({
|
|
55
|
+
socket: socket,
|
|
56
|
+
servername: connectOptions.hostname,
|
|
57
|
+
minVersion: options.minTlsVersion ?? "TLSv1.2",
|
|
58
|
+
});
|
|
59
|
+
tlsSocket.on("secureConnect", () => callback(null, tlsSocket));
|
|
60
|
+
tlsSocket.on("error", (error) => callback(error, null));
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
callback(error, null);
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return new ProxyAgent(proxy);
|
|
69
|
+
}
|
|
70
|
+
/** Applies the request budget, but never overrides a signal the caller supplied. */
|
|
71
|
+
export function withRequestTimeout(options, timeoutMs) {
|
|
72
|
+
return options.signal ? options : { ...options, signal: AbortSignal.timeout(timeoutMs) };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Runs `run` again once if it fails.
|
|
76
|
+
*
|
|
77
|
+
* Proxy pools drop the occasional connection for reasons that have nothing to
|
|
78
|
+
* do with the request - a rotating exit going away mid-handshake, most often -
|
|
79
|
+
* and a single immediate retry turns most of those into a success.
|
|
80
|
+
*/
|
|
81
|
+
export async function retryOnTransportFailure(describe, run, options = {}) {
|
|
82
|
+
try {
|
|
83
|
+
return await run();
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
const detail = describeError(error);
|
|
87
|
+
options.logger?.debug?.(`${describe} failed at the transport level, retrying once - ${detail.slice(0, 120)}`);
|
|
88
|
+
return run();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* A `fetch` bound to one proxy, or an unproxied one when `proxy` is omitted.
|
|
93
|
+
*
|
|
94
|
+
* The two runtimes need different mechanisms and this is the whole reason the
|
|
95
|
+
* function exists. Bun ignores undici's `dispatcher` option outright, so a
|
|
96
|
+
* request aimed at a dead proxy still succeeds and reports the machine's own
|
|
97
|
+
* address - every "proxied" call silently goes out unproxied. Bun's own `fetch`
|
|
98
|
+
* takes a `proxy` option that it does honour, so that path is used there, and
|
|
99
|
+
* the dispatcher path is kept for Node.
|
|
100
|
+
*/
|
|
101
|
+
export function makeFetcher(proxy, options = {}) {
|
|
102
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
103
|
+
if (proxy && IS_BUN && !options.allowSocksUnderBun) {
|
|
104
|
+
if (!/^https?:\/\//iu.test(proxy)) {
|
|
105
|
+
// Bun's fetch answers UnsupportedProxyProtocol for socks, and the
|
|
106
|
+
// dispatcher fallback below does nothing there, so refuse rather than
|
|
107
|
+
// run unproxied without saying so.
|
|
108
|
+
throw new Error(`SOCKS proxies are not supported when running under Bun (${maskProxy(proxy)}). ` +
|
|
109
|
+
`Use an http:// proxy, or set allowSocksUnderBun to opt out of this check.`);
|
|
110
|
+
}
|
|
111
|
+
const fetcher = async (url, requestOptions = {}) => fetch(url, { ...withRequestTimeout(requestOptions, timeoutMs), proxy });
|
|
112
|
+
return { fetch: fetcher, close: async () => { } };
|
|
113
|
+
}
|
|
114
|
+
const dispatcher = proxy ? makeProxyDispatcher(proxy, options) : undefined;
|
|
115
|
+
const fetcher = async (url, requestOptions = {}) => {
|
|
116
|
+
const withTimeout = withRequestTimeout(requestOptions, timeoutMs);
|
|
117
|
+
if (dispatcher) {
|
|
118
|
+
// On Node the global fetch is backed by whatever undici ships inside the
|
|
119
|
+
// running binary, which is often a different major from the "undici"
|
|
120
|
+
// package the dispatcher came from, and the handler interfaces are not
|
|
121
|
+
// compatible across majors. Using undici's own fetch keeps the
|
|
122
|
+
// dispatcher and the implementation on the same version.
|
|
123
|
+
const response = await undiciFetch(url, { ...withTimeout, dispatcher });
|
|
124
|
+
return response;
|
|
125
|
+
}
|
|
126
|
+
return fetch(url, withTimeout);
|
|
127
|
+
};
|
|
128
|
+
return { fetch: fetcher, dispatcher, close: () => closeDispatcher(dispatcher) };
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Releases a dispatcher's connection pool.
|
|
132
|
+
*
|
|
133
|
+
* Without this every request cycle leaks an `Agent`/`ProxyAgent` and its
|
|
134
|
+
* sockets, which exhausts file descriptors in any long-running process.
|
|
135
|
+
*/
|
|
136
|
+
export async function closeDispatcher(dispatcher) {
|
|
137
|
+
if (!dispatcher) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
await dispatcher.close();
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// Already closed, or could not close cleanly - nothing actionable either way.
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=fetcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetcher.js","sourceRoot":"","sources":["../src/fetcher.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { describeError, ProxyParseError } from "./errors.js";
|
|
2
|
+
export { closeDispatcher, IS_BUN, makeFetcher, makeProxyDispatcher, retryOnTransportFailure, withRequestTimeout, } from "./fetcher.js";
|
|
3
|
+
export type { Fetcher } from "./fetcher.js";
|
|
4
|
+
export { loadProxyFile, parseProxyList } from "./list.js";
|
|
5
|
+
export { isPlaceholderProxy, maskProxy, normalizeProxy, parseSchemelessProxy, shuffleProxies, SUPPORTED_PROXY_PROTOCOLS, } from "./parse.js";
|
|
6
|
+
export type { ProxyProtocol } from "./parse.js";
|
|
7
|
+
export type { FetcherOptions, ProxyDispatcher, ProxyFetch, ProxyListOptions, ProxyLogger, ProxyParseOptions, } from "./types.js";
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EACL,eAAe,EACf,MAAM,EACN,WAAW,EACX,mBAAmB,EACnB,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EACL,kBAAkB,EAClB,SAAS,EACT,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,yBAAyB,GAC1B,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,YAAY,EACV,cAAc,EACd,eAAe,EACf,UAAU,EACV,gBAAgB,EAChB,WAAW,EACX,iBAAiB,GAClB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { describeError, ProxyParseError } from "./errors.js";
|
|
2
|
+
export { closeDispatcher, IS_BUN, makeFetcher, makeProxyDispatcher, retryOnTransportFailure, withRequestTimeout, } from "./fetcher.js";
|
|
3
|
+
export { loadProxyFile, parseProxyList } from "./list.js";
|
|
4
|
+
export { isPlaceholderProxy, maskProxy, normalizeProxy, parseSchemelessProxy, shuffleProxies, SUPPORTED_PROXY_PROTOCOLS, } from "./parse.js";
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EACL,eAAe,EACf,MAAM,EACN,WAAW,EACX,mBAAmB,EACnB,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EACL,kBAAkB,EAClB,SAAS,EACT,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,yBAAyB,GAC1B,MAAM,YAAY,CAAC"}
|
package/dist/list.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ProxyListOptions } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Parses a newline-separated proxy list into dialable URLs.
|
|
4
|
+
*
|
|
5
|
+
* Line numbers are captured before blanks and comments are filtered out, so an
|
|
6
|
+
* error still points at the right line of the original text. Comments are
|
|
7
|
+
* skipped for a practical reason: example files are usually all comments, and
|
|
8
|
+
* the placeholder text inside them would otherwise read as proxies and trip the
|
|
9
|
+
* placeholder check. Skipping them also lets a real list carry notes.
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseProxyList(text: string, options?: ProxyListOptions): string[];
|
|
12
|
+
/**
|
|
13
|
+
* Reads and parses a proxy list file, returning an empty list when the file is
|
|
14
|
+
* absent. A missing file means "no proxies configured", which is a valid state
|
|
15
|
+
* for anything that treats proxying as optional.
|
|
16
|
+
*/
|
|
17
|
+
export declare function loadProxyFile(path: string, options?: ProxyListOptions): string[];
|
|
18
|
+
//# sourceMappingURL=list.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../src/list.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAInD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,EAAE,CA4BrF;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,EAAE,CAMpF"}
|
package/dist/list.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { ProxyParseError } from "./errors.js";
|
|
3
|
+
import { isPlaceholderProxy, normalizeProxy } from "./parse.js";
|
|
4
|
+
const DEFAULT_COMMENT_PREFIX = "#";
|
|
5
|
+
/**
|
|
6
|
+
* Parses a newline-separated proxy list into dialable URLs.
|
|
7
|
+
*
|
|
8
|
+
* Line numbers are captured before blanks and comments are filtered out, so an
|
|
9
|
+
* error still points at the right line of the original text. Comments are
|
|
10
|
+
* skipped for a practical reason: example files are usually all comments, and
|
|
11
|
+
* the placeholder text inside them would otherwise read as proxies and trip the
|
|
12
|
+
* placeholder check. Skipping them also lets a real list carry notes.
|
|
13
|
+
*/
|
|
14
|
+
export function parseProxyList(text, options = {}) {
|
|
15
|
+
const commentPrefix = options.commentPrefix ?? DEFAULT_COMMENT_PREFIX;
|
|
16
|
+
const entries = text
|
|
17
|
+
.split(/\r?\n/u)
|
|
18
|
+
.map((line, index) => ({ proxy: line.trim(), lineNumber: index + 1 }))
|
|
19
|
+
.filter(({ proxy }) => proxy.length > 0 && (commentPrefix === "" || !proxy.startsWith(commentPrefix)));
|
|
20
|
+
if (entries.length === 0) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
if (options.rejectPlaceholders !== false) {
|
|
24
|
+
const placeholders = entries.filter(({ proxy }) => isPlaceholderProxy(proxy));
|
|
25
|
+
if (placeholders.length > 0) {
|
|
26
|
+
const location = { source: options.source, lineNumber: placeholders[0].lineNumber };
|
|
27
|
+
throw new ProxyParseError(`Placeholder proxies detected${options.source ? ` in ${options.source}` : ""}. ` +
|
|
28
|
+
`Replace the placeholder entries with real proxies.`, location);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return entries.map(({ proxy, lineNumber }) => normalizeProxy(proxy, { ...options, lineNumber }));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Reads and parses a proxy list file, returning an empty list when the file is
|
|
35
|
+
* absent. A missing file means "no proxies configured", which is a valid state
|
|
36
|
+
* for anything that treats proxying as optional.
|
|
37
|
+
*/
|
|
38
|
+
export function loadProxyFile(path, options = {}) {
|
|
39
|
+
if (!existsSync(path)) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
return parseProxyList(readFileSync(path, "utf8"), { source: path, ...options });
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=list.js.map
|
package/dist/list.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.js","sourceRoot":"","sources":["../src/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGhE,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAEnC;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,UAA4B,EAAE;IACzE,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,sBAAsB,CAAC;IAEtE,MAAM,OAAO,GAAG,IAAI;SACjB,KAAK,CAAC,QAAQ,CAAC;SACf,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,UAAU,EAAE,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC;SACrE,MAAM,CACL,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CACZ,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,aAAa,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CACjF,CAAC;IAEJ,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,OAAO,CAAC,kBAAkB,KAAK,KAAK,EAAE,CAAC;QACzC,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC;QAC9E,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,MAAM,QAAQ,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC,CAAE,CAAC,UAAU,EAAE,CAAC;YACrF,MAAM,IAAI,eAAe,CACvB,+BAA+B,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI;gBAC9E,oDAAoD,EACtD,QAAQ,CACT,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,cAAc,CAAC,KAAK,EAAE,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;AACnG,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,UAA4B,EAAE;IACxE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,cAAc,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;AAClF,CAAC"}
|
package/dist/parse.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ProxyParseOptions } from "./types.js";
|
|
2
|
+
/** Schemes that can actually be dialled by `makeProxyDispatcher`. */
|
|
3
|
+
export declare const SUPPORTED_PROXY_PROTOCOLS: readonly ["http", "https", "socks4", "socks5"];
|
|
4
|
+
export type ProxyProtocol = (typeof SUPPORTED_PROXY_PROTOCOLS)[number];
|
|
5
|
+
/** True when a line is one of the template entries rather than a real proxy. */
|
|
6
|
+
export declare function isPlaceholderProxy(proxy: string): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* A proxy reduced to `host:port`, safe to put in a log line or an error
|
|
9
|
+
* message. Credentials in a proxy URL are as sensitive as any other password
|
|
10
|
+
* and must never reach a log file.
|
|
11
|
+
*/
|
|
12
|
+
export declare function maskProxy(proxy: string): string;
|
|
13
|
+
/** Fisher-Yates. Spreads load across a pool instead of hammering the first entry. */
|
|
14
|
+
export declare function shuffleProxies<T>(proxies: readonly T[]): T[];
|
|
15
|
+
/**
|
|
16
|
+
* Parses the four schemeless shapes providers export, told apart by which
|
|
17
|
+
* field looks like a port:
|
|
18
|
+
* login:password@ip:port
|
|
19
|
+
* ip:port@login:password
|
|
20
|
+
* login:password:ip:port
|
|
21
|
+
* ip:port:login:password
|
|
22
|
+
* The scheme is supplied separately - see `defaultProtocol`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function parseSchemelessProxy(proxy: string): {
|
|
25
|
+
host: string;
|
|
26
|
+
port: string;
|
|
27
|
+
username: string;
|
|
28
|
+
password: string;
|
|
29
|
+
} | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Turns any supported proxy notation into a dialable URL, leaving an already
|
|
32
|
+
* valid URL untouched. Throws a `ProxyParseError` naming the source and line
|
|
33
|
+
* when nothing matches.
|
|
34
|
+
*/
|
|
35
|
+
export declare function normalizeProxy(proxy: string, options?: ProxyParseOptions & {
|
|
36
|
+
lineNumber?: number;
|
|
37
|
+
}): string;
|
|
38
|
+
//# sourceMappingURL=parse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,qEAAqE;AACrE,eAAO,MAAM,yBAAyB,gDAAiD,CAAC;AAExF,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAC;AAuCvE,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAMzD;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAM/C;AAED,qFAAqF;AACrF,wBAAgB,cAAc,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,EAAE,CAO5D;AA+CD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,GACZ;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAgDhF;AAqCD;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,iBAAiB,GAAG;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,GACxD,MAAM,CAmBR"}
|