1
- import { SocksClient , SocksProxy , SocksClientOptions } from 'socks' ;
1
+ import {
2
+ SocksClient ,
3
+ SocksProxy ,
4
+ SocksClientOptions ,
5
+ SocksClientChainOptions ,
6
+ } from 'socks' ;
2
7
import { Agent , AgentConnectOpts } from 'agent-base' ;
3
8
import createDebug from 'debug' ;
4
9
import * as dns from 'dns' ;
@@ -87,21 +92,38 @@ export class SocksProxyAgent extends Agent {
87
92
'socks5h' ,
88
93
] as const ;
89
94
90
- readonly shouldLookup : boolean ;
91
- readonly proxy : SocksProxy ;
95
+ readonly shouldLookup ! : boolean ;
96
+ readonly proxies : SocksProxy [ ] ;
92
97
timeout : number | null ;
93
98
94
- constructor ( uri : string | URL , opts ?: SocksProxyAgentOptions ) {
99
+ constructor (
100
+ uri : string | URL | string [ ] | URL [ ] ,
101
+ opts ?: SocksProxyAgentOptions
102
+ ) {
95
103
super ( opts ) ;
96
104
97
- const url = typeof uri === 'string' ? new URL ( uri ) : uri ;
98
- const { proxy, lookup } = parseSocksURL ( url ) ;
105
+ const uriList = Array . isArray ( uri ) ? uri : [ uri ] ;
106
+
107
+ if ( uriList . length === 0 ) {
108
+ throw new Error ( 'At least one proxy server URI must be specified.' ) ;
109
+ }
110
+
111
+ this . proxies = [ ] ;
112
+ for ( const [ i , uri ] of uriList . entries ( ) ) {
113
+ const { proxy, lookup } = parseSocksURL ( new URL ( uri . toString ( ) ) ) ;
114
+ this . proxies . push ( proxy ) ;
115
+ if ( i === 0 ) {
116
+ this . shouldLookup = lookup ;
117
+ }
118
+ }
99
119
100
- this . shouldLookup = lookup ;
101
- this . proxy = proxy ;
102
120
this . timeout = opts ?. timeout ?? null ;
103
121
}
104
122
123
+ get proxy ( ) : SocksProxy {
124
+ return this . proxies [ 0 ] ;
125
+ }
126
+
105
127
/**
106
128
* Initiates a SOCKS connection to the specified SOCKS proxy server,
107
129
* which in turn connects to the specified remote host and port.
@@ -110,7 +132,7 @@ export class SocksProxyAgent extends Agent {
110
132
req : http . ClientRequest ,
111
133
opts : AgentConnectOpts
112
134
) : Promise < net . Socket > {
113
- const { shouldLookup, proxy , timeout } = this ;
135
+ const { shouldLookup, proxies , timeout } = this ;
114
136
115
137
if ( ! opts . host ) {
116
138
throw new Error ( 'No `host` defined!' ) ;
@@ -133,25 +155,46 @@ export class SocksProxyAgent extends Agent {
133
155
} ) ;
134
156
}
135
157
136
- const socksOpts : SocksClientOptions = {
137
- proxy,
138
- destination : {
139
- host,
140
- port : typeof port === 'number' ? port : parseInt ( port , 10 ) ,
141
- } ,
142
- command : 'connect' ,
143
- timeout : timeout ?? undefined ,
144
- } ;
145
-
158
+ let socket : net . Socket ;
146
159
const cleanup = ( tlsSocket ?: tls . TLSSocket ) => {
147
160
req . destroy ( ) ;
148
161
socket . destroy ( ) ;
149
162
if ( tlsSocket ) tlsSocket . destroy ( ) ;
150
163
} ;
151
164
152
- debug ( 'Creating socks proxy connection: %o' , socksOpts ) ;
153
- const { socket } = await SocksClient . createConnection ( socksOpts ) ;
154
- debug ( 'Successfully created socks proxy connection' ) ;
165
+ if ( proxies . length === 1 ) {
166
+ const socksOpts : SocksClientOptions = {
167
+ proxy : proxies [ 0 ] ,
168
+ destination : {
169
+ host,
170
+ port : typeof port === 'number' ? port : parseInt ( port , 10 ) ,
171
+ } ,
172
+ command : 'connect' ,
173
+ timeout : timeout ?? undefined ,
174
+ } ;
175
+
176
+ debug ( 'Creating socks proxy connection: %o' , socksOpts ) ;
177
+ const connection = await SocksClient . createConnection ( socksOpts ) ;
178
+ socket = connection . socket ;
179
+ debug ( 'Successfully created socks proxy connection' ) ;
180
+ } else {
181
+ const socksOpts : SocksClientChainOptions = {
182
+ proxies : proxies ,
183
+ destination : {
184
+ host,
185
+ port : typeof port === 'number' ? port : parseInt ( port , 10 ) ,
186
+ } ,
187
+ command : 'connect' ,
188
+ timeout : timeout ?? undefined ,
189
+ } ;
190
+
191
+ debug ( 'Creating chained socks proxy connection: %o' , socksOpts ) ;
192
+ const connection = await SocksClient . createConnectionChain (
193
+ socksOpts
194
+ ) ;
195
+ socket = connection . socket ;
196
+ debug ( 'Successfully created chained socks proxy connection' ) ;
197
+ }
155
198
156
199
if ( timeout !== null ) {
157
200
socket . setTimeout ( timeout ) ;
0 commit comments