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,18 +92,31 @@ 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 uri_list = Array . isArray ( uri ) ? uri : [ uri ] ;
106
+
107
+ if ( uri_list . 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 uri_list . 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
@@ -110,7 +128,7 @@ export class SocksProxyAgent extends Agent {
110
128
req : http . ClientRequest ,
111
129
opts : AgentConnectOpts
112
130
) : Promise < net . Socket > {
113
- const { shouldLookup, proxy , timeout } = this ;
131
+ const { shouldLookup, proxies , timeout } = this ;
114
132
115
133
if ( ! opts . host ) {
116
134
throw new Error ( 'No `host` defined!' ) ;
@@ -133,25 +151,46 @@ export class SocksProxyAgent extends Agent {
133
151
} ) ;
134
152
}
135
153
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
-
154
+ let socket : net . Socket ;
146
155
const cleanup = ( tlsSocket ?: tls . TLSSocket ) => {
147
156
req . destroy ( ) ;
148
157
socket . destroy ( ) ;
149
158
if ( tlsSocket ) tlsSocket . destroy ( ) ;
150
159
} ;
151
160
152
- debug ( 'Creating socks proxy connection: %o' , socksOpts ) ;
153
- const { socket } = await SocksClient . createConnection ( socksOpts ) ;
154
- debug ( 'Successfully created socks proxy connection' ) ;
161
+ if ( proxies . length === 1 ) {
162
+ const socksOpts : SocksClientOptions = {
163
+ proxy : proxies [ 0 ] ,
164
+ destination : {
165
+ host,
166
+ port : typeof port === 'number' ? port : parseInt ( port , 10 ) ,
167
+ } ,
168
+ command : 'connect' ,
169
+ timeout : timeout ?? undefined ,
170
+ } ;
171
+
172
+ debug ( 'Creating socks proxy connection: %o' , socksOpts ) ;
173
+ const connection = await SocksClient . createConnection ( socksOpts ) ;
174
+ socket = connection . socket ;
175
+ debug ( 'Successfully created socks proxy connection' ) ;
176
+ } else {
177
+ const socksOpts : SocksClientChainOptions = {
178
+ proxies : proxies ,
179
+ destination : {
180
+ host,
181
+ port : typeof port === 'number' ? port : parseInt ( port , 10 ) ,
182
+ } ,
183
+ command : 'connect' ,
184
+ timeout : timeout ?? undefined ,
185
+ } ;
186
+
187
+ debug ( 'Creating chained socks proxy connection: %o' , socksOpts ) ;
188
+ const connection = await SocksClient . createConnectionChain (
189
+ socksOpts
190
+ ) ;
191
+ socket = connection . socket ;
192
+ debug ( 'Successfully created chained socks proxy connection' ) ;
193
+ }
155
194
156
195
if ( timeout !== null ) {
157
196
socket . setTimeout ( timeout ) ;
0 commit comments