@percy/client 1.32.12-beta.1 → 1.32.12-beta.3
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/dist/proxy.js +47 -12
- package/dist/utils.js +6 -2
- package/package.json +5 -5
package/dist/proxy.js
CHANGED
|
@@ -86,13 +86,17 @@ export function href(options) {
|
|
|
86
86
|
}
|
|
87
87
|
;
|
|
88
88
|
|
|
89
|
-
// Returns the proxy URL for a set of request options
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
let
|
|
93
|
-
|
|
94
|
-
|
|
89
|
+
// Returns the proxy URL for a set of request options. An explicit `override` proxy URL is
|
|
90
|
+
// used as-is, without consulting the proxy env vars or NO_PROXY; the caller owns bypassing.
|
|
91
|
+
export function getProxy(options, override) {
|
|
92
|
+
let envProxyUrl = options.protocol === 'https:' && (process.env.https_proxy || process.env.HTTPS_PROXY) || process.env.http_proxy || process.env.HTTP_PROXY;
|
|
93
|
+
let shouldProxy = !!override || !!envProxyUrl && !hostnameMatches(stripQuotesAndSpaces(process.env.no_proxy || process.env.NO_PROXY), href(options));
|
|
94
|
+
|
|
95
|
+
// only env values may carry stray quotes/spaces; an explicit override is used verbatim
|
|
96
|
+
if (envProxyUrl && typeof envProxyUrl === 'string') {
|
|
97
|
+
envProxyUrl = stripQuotesAndSpaces(envProxyUrl);
|
|
95
98
|
}
|
|
99
|
+
let proxyUrl = override || envProxyUrl;
|
|
96
100
|
if (shouldProxy) {
|
|
97
101
|
proxyUrl = new URL(proxyUrl);
|
|
98
102
|
let isHttps = proxyUrl.protocol === 'https:';
|
|
@@ -120,11 +124,20 @@ export class ProxyHttpAgent extends http.Agent {
|
|
|
120
124
|
httpsAgent = new https.Agent({
|
|
121
125
|
keepAlive: true
|
|
122
126
|
});
|
|
127
|
+
|
|
128
|
+
// an optional `proxy` URL takes precedence over the proxy env vars
|
|
129
|
+
constructor({
|
|
130
|
+
proxy,
|
|
131
|
+
...options
|
|
132
|
+
} = {}) {
|
|
133
|
+
super(options);
|
|
134
|
+
this.proxy = proxy;
|
|
135
|
+
}
|
|
123
136
|
addRequest(request, options) {
|
|
124
137
|
var _request$outputData;
|
|
125
|
-
let proxy = getProxy(options);
|
|
138
|
+
let proxy = getProxy(options, this.proxy);
|
|
126
139
|
if (!proxy) return super.addRequest(request, options);
|
|
127
|
-
logger('client:proxy').debug(`Proxying request: ${options
|
|
140
|
+
logger('client:proxy').debug(`Proxying request: ${href(options)}`);
|
|
128
141
|
|
|
129
142
|
// modify the request for proxying
|
|
130
143
|
request.path = href(options);
|
|
@@ -157,15 +170,20 @@ export class ProxyHttpAgent extends http.Agent {
|
|
|
157
170
|
|
|
158
171
|
// Proxified https agent
|
|
159
172
|
export class ProxyHttpsAgent extends https.Agent {
|
|
160
|
-
|
|
173
|
+
// an optional `proxy` URL takes precedence over the proxy env vars
|
|
174
|
+
constructor({
|
|
175
|
+
proxy,
|
|
176
|
+
...options
|
|
177
|
+
} = {}) {
|
|
161
178
|
// default keep-alive
|
|
162
179
|
super({
|
|
163
180
|
keepAlive: true,
|
|
164
181
|
...options
|
|
165
182
|
});
|
|
183
|
+
this.proxy = proxy;
|
|
166
184
|
}
|
|
167
185
|
createConnection(options, callback) {
|
|
168
|
-
let proxy = getProxy(options);
|
|
186
|
+
let proxy = getProxy(options, this.proxy);
|
|
169
187
|
if (!proxy) return super.createConnection(options, callback);
|
|
170
188
|
logger('client:proxy').debug(`Proxying request: ${href(options)}`);
|
|
171
189
|
|
|
@@ -180,8 +198,13 @@ export class ProxyHttpsAgent extends https.Agent {
|
|
|
180
198
|
|
|
181
199
|
// start the proxy connection and setup listeners
|
|
182
200
|
let socket = proxy.connect();
|
|
201
|
+
|
|
202
|
+
// destroying the socket re-emits 'error' and 'close'; only the first failure counts
|
|
203
|
+
let failed = false;
|
|
183
204
|
let handleError = err => {
|
|
184
205
|
var _err$message, _err$message2;
|
|
206
|
+
if (failed) return;
|
|
207
|
+
failed = true;
|
|
185
208
|
socket.destroy(err);
|
|
186
209
|
logger('client:proxy').error(`Proxying request ${href(options)} failed: ${err}`);
|
|
187
210
|
|
|
@@ -194,6 +217,12 @@ export class ProxyHttpsAgent extends https.Agent {
|
|
|
194
217
|
callback(err);
|
|
195
218
|
};
|
|
196
219
|
let handleClose = () => handleError(new Error('Connection closed while sending request to upstream proxy'));
|
|
220
|
+
|
|
221
|
+
// a request's `timeout` only starts once it has a socket, which is handed over after the
|
|
222
|
+
// CONNECT reply; bound the handshake itself so a silent proxy cannot hang the request
|
|
223
|
+
let handleTimeout = () => handleError(Object.assign(new Error(`Request to ${href(options)} timed out after ${options.timeout}ms waiting for the proxy`), {
|
|
224
|
+
code: 'ETIMEDOUT'
|
|
225
|
+
}));
|
|
197
226
|
let buffer = '';
|
|
198
227
|
let handleData = data => {
|
|
199
228
|
var _buffer$match;
|
|
@@ -205,11 +234,16 @@ export class ProxyHttpsAgent extends https.Agent {
|
|
|
205
234
|
if (((_buffer$match = buffer.match(STATUS_REG)) === null || _buffer$match === void 0 ? void 0 : _buffer$match[1]) !== '200') {
|
|
206
235
|
return handleError(new Error('Error establishing proxy connection. ' + `Response from server was: ${buffer}`));
|
|
207
236
|
}
|
|
237
|
+
|
|
238
|
+
// the tunnel is established; its later close (e.g. keep-alive teardown) is not a failure
|
|
239
|
+
socket.off('error', handleError).off('close', handleClose).off('timeout', handleTimeout);
|
|
240
|
+
socket.setTimeout(0);
|
|
208
241
|
options.socket = socket;
|
|
209
242
|
options.servername = options.hostname;
|
|
210
243
|
// callback not passed in so not to be added as a listener
|
|
211
244
|
callback(null, super.createConnection(options));
|
|
212
245
|
};
|
|
246
|
+
if (options.timeout) socket.setTimeout(options.timeout, handleTimeout);
|
|
213
247
|
|
|
214
248
|
// send and handle the connect message
|
|
215
249
|
socket.on('error', handleError).on('close', handleClose).on('data', handleData).write(connectMessage);
|
|
@@ -222,6 +256,7 @@ export function proxyAgentFor(url, options) {
|
|
|
222
256
|
hostname
|
|
223
257
|
} = new URL(url);
|
|
224
258
|
let cachekey = `${protocol}//${hostname}`;
|
|
259
|
+
if (options !== null && options !== void 0 && options.proxy) cachekey += ` via ${options.proxy}`;
|
|
225
260
|
|
|
226
261
|
// If we already have a cached agent, return it
|
|
227
262
|
if (cache.has(cachekey)) {
|
|
@@ -231,8 +266,8 @@ export function proxyAgentFor(url, options) {
|
|
|
231
266
|
let agent;
|
|
232
267
|
const pacUrl = process.env.PERCY_PAC_FILE_URL;
|
|
233
268
|
|
|
234
|
-
// If PAC URL is provided, use PAC proxy
|
|
235
|
-
if (pacUrl) {
|
|
269
|
+
// If PAC URL is provided, use PAC proxy (an explicit proxy takes precedence)
|
|
270
|
+
if (pacUrl && !(options !== null && options !== void 0 && options.proxy)) {
|
|
236
271
|
logger('client:proxy').info(`Using PAC file from: ${pacUrl}`);
|
|
237
272
|
agent = createPacAgent(pacUrl, options);
|
|
238
273
|
} else {
|
package/dist/utils.js
CHANGED
|
@@ -116,7 +116,8 @@ const RETRY_ERROR_CODES = ['ECONNREFUSED', 'ECONNRESET', 'EPIPE', 'EHOSTUNREACH'
|
|
|
116
116
|
// and any received error details. Server 500 errors are retried up to 5 times at 50ms intervals by
|
|
117
117
|
// default, and 404 errors may also be optionally retried. If a callback is provided, it is called
|
|
118
118
|
// with the parsed response body and response details. If the callback returns a value, that value
|
|
119
|
-
// will be returned in the final resolved promise instead of the response body.
|
|
119
|
+
// will be returned in the final resolved promise instead of the response body. A `proxy` URL
|
|
120
|
+
// routes the request through that proxy regardless of the proxy env vars.
|
|
120
121
|
export async function request(url, options = {}, callback) {
|
|
121
122
|
// accept `request(url, callback)`
|
|
122
123
|
if (typeof options === 'function') [options, callback] = [{}, options];
|
|
@@ -129,6 +130,7 @@ export async function request(url, options = {}, callback) {
|
|
|
129
130
|
retryNotFound,
|
|
130
131
|
interval,
|
|
131
132
|
noProxy,
|
|
133
|
+
proxy,
|
|
132
134
|
buffer,
|
|
133
135
|
meta = {},
|
|
134
136
|
...requestOptions
|
|
@@ -163,7 +165,9 @@ export async function request(url, options = {}, callback) {
|
|
|
163
165
|
|
|
164
166
|
// combine request options
|
|
165
167
|
Object.assign(requestOptions, {
|
|
166
|
-
agent: requestOptions.agent ||
|
|
168
|
+
agent: requestOptions.agent || proxy && proxyAgentFor(url, {
|
|
169
|
+
proxy
|
|
170
|
+
}) || !noProxy && proxyAgentFor(url) || null,
|
|
167
171
|
path: pathname + search + hash,
|
|
168
172
|
protocol,
|
|
169
173
|
hostname,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@percy/client",
|
|
3
|
-
"version": "1.32.12-beta.
|
|
3
|
+
"version": "1.32.12-beta.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
"test:coverage": "yarn test --coverage"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@percy/config": "1.32.12-beta.
|
|
37
|
-
"@percy/env": "1.32.12-beta.
|
|
38
|
-
"@percy/logger": "1.32.12-beta.
|
|
36
|
+
"@percy/config": "1.32.12-beta.3",
|
|
37
|
+
"@percy/env": "1.32.12-beta.3",
|
|
38
|
+
"@percy/logger": "1.32.12-beta.3",
|
|
39
39
|
"pac-proxy-agent": "^7.0.2",
|
|
40
40
|
"pako": "^2.1.0"
|
|
41
41
|
},
|
|
42
|
-
"gitHead": "
|
|
42
|
+
"gitHead": "abb8d63fc4b9d7dff41c0720e675819716c94ca0"
|
|
43
43
|
}
|