-
Notifications
You must be signed in to change notification settings - Fork 246
/
index.ts
132 lines (117 loc) · 3.68 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import * as http from 'http';
import * as https from 'https';
import LRUCache from 'lru-cache';
import { Agent, AgentConnectOpts } from 'agent-base';
import createDebug from 'debug';
import { getProxyForUrl } from 'proxy-from-env';
import { PacProxyAgent, PacProxyAgentOptions } from 'pac-proxy-agent';
import { HttpProxyAgent, HttpProxyAgentOptions } from 'http-proxy-agent';
import { HttpsProxyAgent, HttpsProxyAgentOptions } from 'https-proxy-agent';
import { SocksProxyAgent, SocksProxyAgentOptions } from 'socks-proxy-agent';
const debug = createDebug('proxy-agent');
const PROTOCOLS = [
...HttpProxyAgent.protocols,
...SocksProxyAgent.protocols,
...PacProxyAgent.protocols,
] as const;
type ValidProtocol = (typeof PROTOCOLS)[number];
/**
* Supported proxy types.
*/
export const proxies: {
[P in ValidProtocol]: new (...args: never[]) => Agent;
} = {
http: HttpProxyAgent,
https: HttpsProxyAgent,
socks: SocksProxyAgent,
socks4: SocksProxyAgent,
socks4a: SocksProxyAgent,
socks5: SocksProxyAgent,
socks5h: SocksProxyAgent,
'pac-data': PacProxyAgent,
'pac-file': PacProxyAgent,
'pac-ftp': PacProxyAgent,
'pac-http': PacProxyAgent,
'pac-https': PacProxyAgent,
};
function isValidProtocol(v: string): v is ValidProtocol {
return (PROTOCOLS as readonly string[]).includes(v);
}
export type ProxyAgentOptions = HttpProxyAgentOptions<''> &
HttpsProxyAgentOptions<''> &
SocksProxyAgentOptions &
PacProxyAgentOptions<''> & {
/**
* Default `http.Agent` instance to use when no proxy is
* configured for a request. Defaults to a new `http.Agent()`
* instance with the proxy agent options passed in.
*/
httpAgent?: http.Agent;
/**
* Default `http.Agent` instance to use when no proxy is
* configured for a request. Defaults to a new `https.Agent()`
* instance with the proxy agent options passed in.
*/
httpsAgent?: http.Agent;
};
/**
* Uses the appropriate `Agent` subclass based off of the "proxy"
* environment variables that are currently set.
*
* An LRU cache is used, to prevent unnecessary creation of proxy
* `http.Agent` instances.
*/
export class ProxyAgent extends Agent {
/**
* Cache for `Agent` instances.
*/
cache = new LRUCache<string, Agent>({ max: 20 });
connectOpts?: ProxyAgentOptions;
httpAgent: http.Agent;
httpsAgent: http.Agent;
constructor(opts?: ProxyAgentOptions) {
super(opts);
debug('Creating new ProxyAgent instance: %o', opts);
this.connectOpts = opts;
this.httpAgent = opts?.httpAgent || new http.Agent(opts);
this.httpsAgent =
opts?.httpsAgent || new https.Agent(opts as https.AgentOptions);
}
async connect(
req: http.ClientRequest,
opts: AgentConnectOpts
): Promise<http.Agent> {
const protocol = opts.secureEndpoint ? 'https:' : 'http:';
const host = req.getHeader('host');
const url = new URL(req.path, `${protocol}//${host}`).href;
const proxy = getProxyForUrl(url);
if (!proxy) {
debug('Proxy not enabled for URL: %o', url);
return opts.secureEndpoint ? this.httpsAgent : this.httpAgent;
}
debug('Request URL: %o', url);
debug('Proxy URL: %o', proxy);
// attempt to get a cached `http.Agent` instance first
let agent = this.cache.get(proxy);
if (!agent) {
const proxyUrl = new URL(proxy);
const proxyProto = proxyUrl.protocol.replace(':', '');
if (!isValidProtocol(proxyProto)) {
throw new Error(`Unsupported protocol for proxy URL: ${proxy}`);
}
const ctor = proxies[proxyProto];
// @ts-expect-error meh…
agent = new ctor(proxy, this.connectOpts);
this.cache.set(proxy, agent);
} else {
debug('Cache hit for proxy URL: %o', proxy);
}
return agent;
}
destroy(): void {
for (const agent of this.cache.values()) {
agent.destroy();
}
super.destroy();
}
}