-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.ts
98 lines (85 loc) · 2.91 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
import { SocksClient, SocksProxy } from "socks";
import { Agent, buildConnector } from "undici";
import Connector = buildConnector.connector;
import TLSOptions = buildConnector.BuildOptions;
export type SocksProxies = SocksProxy | SocksProxy[];
/**
* Since socks does not guess HTTP ports, we need to do that.
*
* @param protocol Upper layer protocol, "http:" or "https:"
* @param port A string containing the port number of the URL, maybe empty.
*/
function resolvePort(protocol: string, port: string) {
return port ? Number.parseInt(port) : protocol === "http:" ? 80 : 443;
}
/**
* Create an Undici connector which establish the connection through socks proxies.
*
* If the proxies is an empty array, it will connect directly.
*
* @param proxies The proxy server to use or the list of proxy servers to chain.
* @param tlsOpts TLS upgrade options.
*/
export function socksConnector(proxies: SocksProxies, tlsOpts: TLSOptions = {}): Connector {
const chain = Array.isArray(proxies) ? proxies : [proxies];
const { timeout = 1e4 } = tlsOpts;
const undiciConnect = buildConnector(tlsOpts);
return async (options, callback) => {
let { protocol, hostname, port, httpSocket } = options;
for (let i = 0; i < chain.length; i++) {
const next = chain[i + 1];
const destination = i === chain.length - 1 ? {
host: hostname,
port: resolvePort(protocol, port),
} : {
port: next.port,
host: next.host ?? next.ipaddress!,
};
const socksOpts = {
command: "connect" as const,
proxy: chain[i],
timeout,
destination,
existing_socket: httpSocket,
};
try {
const r = await SocksClient.createConnection(socksOpts);
httpSocket = r.socket;
} catch (error) {
return callback(error, null);
}
}
// httpSocket may not exist when the chain is empty.
if (httpSocket && protocol !== "https:") {
return callback(null, httpSocket.setNoDelay());
}
/*
* There are 2 cases here:
* If httpSocket doesn't exist, let Undici make a connection.
* If httpSocket exists & protocol is HTTPS, do TLS upgrade.
*/
return undiciConnect({ ...options, httpSocket }, callback);
};
}
export interface SocksDispatcherOptions extends Agent.Options {
/**
* TLS upgrade options, see:
* https://undici.nodejs.org/#/docs/api/Client?id=parameter-connectoptions
*
* The connect function is not supported.
* If you want to create a custom connector, you can use `socksConnector`.
*/
connect?: TLSOptions;
}
/**
* Create a Undici Agent with socks connector.
*
* If the proxies is an empty array, it will connect directly.
*
* @param proxies The proxy server to use or the list of proxy servers to chain.
* @param options Additional options passed to the Agent constructor.
*/
export function socksDispatcher(proxies: SocksProxies, options: SocksDispatcherOptions = {}) {
const { connect, ...rest } = options;
return new Agent({ ...rest, connect: socksConnector(proxies, connect) });
}