@percy/client
Advanced tools
Comparing version 1.0.0-beta.65 to 1.0.0-beta.66
@@ -18,8 +18,4 @@ "use strict"; | ||
var _request = _interopRequireWildcard(require("./request")); | ||
var _request = _interopRequireDefault(require("./request")); | ||
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } | ||
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -73,10 +69,3 @@ | ||
this.clientInfo = new Set([].concat(clientInfo)); | ||
this.environmentInfo = new Set([].concat(environmentInfo)); // only use a proxy agent for production https requests | ||
if (apiUrl.startsWith('https:')) { | ||
this.httpsAgent = new _request.ProxyHttpsAgent({ | ||
keepAlive: true, | ||
maxSockets: 5 | ||
}); | ||
} | ||
this.environmentInfo = new Set([].concat(environmentInfo)); | ||
} // Adds additional unique client info. | ||
@@ -126,3 +115,2 @@ | ||
method: 'GET', | ||
agent: this.httpsAgent, | ||
headers: this.headers() | ||
@@ -136,3 +124,2 @@ }); | ||
method: 'POST', | ||
agent: this.httpsAgent, | ||
body: JSON.stringify(body), | ||
@@ -139,0 +126,0 @@ headers: this.headers({ |
@@ -6,7 +6,9 @@ "use strict"; | ||
}); | ||
exports.port = port; | ||
exports.href = href; | ||
exports.getProxy = getProxy; | ||
exports.proxyAgentFor = proxyAgentFor; | ||
exports.default = request; | ||
exports.ProxyHttpsAgent = void 0; | ||
exports.ProxyHttpsAgent = exports.ProxyHttpAgent = void 0; | ||
var _url = _interopRequireDefault(require("url")); | ||
var _net = _interopRequireDefault(require("net")); | ||
@@ -26,2 +28,4 @@ | ||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } | ||
const CRLF = '\r\n'; | ||
@@ -32,48 +36,129 @@ const STATUS_REG = /^HTTP\/1.[01] (\d*)/; | ||
const port = ({ | ||
port, | ||
protocol | ||
}) => port || protocol === 'https:' && 443 || 80; // Proxified https agent | ||
function port(options) { | ||
if (options.port) return options.port; | ||
return options.protocol === 'https:' ? 443 : 80; | ||
} | ||
function href(options) { | ||
let { | ||
protocol, | ||
hostname, | ||
path, | ||
pathname, | ||
search, | ||
hash | ||
} = options; | ||
return `${protocol}//${hostname}:${port(options)}` + (path || `${pathname || ''}${search || ''}${hash || ''}`); | ||
} | ||
class ProxyHttpsAgent extends _https.default.Agent { | ||
// enforce request options | ||
; | ||
function getProxy(options) { | ||
let proxyUrl = options.protocol === 'https:' && (process.env.https_proxy || process.env.HTTPS_PROXY) || process.env.http_proxy || process.env.HTTP_PROXY; | ||
let shouldProxy = !!proxyUrl && !(0, _utils.hostnameMatches)(process.env.no_proxy || process.env.NO_PROXY, href(options)); | ||
if (shouldProxy) { | ||
proxyUrl = new URL(proxyUrl); | ||
let isHttps = proxyUrl.protocol === 'https:'; | ||
if (!isHttps && proxyUrl.protocol !== 'http:') { | ||
throw new Error(`Unsupported proxy protocol: ${proxyUrl.protocol}`); | ||
} | ||
let proxy = { | ||
isHttps | ||
}; | ||
proxy.auth = !!proxyUrl.username && 'Basic ' + (proxyUrl.password ? Buffer.from(`${proxyUrl.username}:${proxyUrl.password}`) : Buffer.from(proxyUrl.username)).toString('base64'); | ||
proxy.host = proxyUrl.hostname; | ||
proxy.port = port(proxyUrl); | ||
proxy.connect = () => (isHttps ? _tls.default : _net.default).connect({ | ||
rejectUnauthorized: options.rejectUnauthorized, | ||
host: proxy.host, | ||
port: proxy.port | ||
}); | ||
return proxy; | ||
} | ||
} // Proxified http agent | ||
class ProxyHttpAgent extends _http.default.Agent { | ||
constructor(...args) { | ||
super(...args); | ||
_defineProperty(this, "httpsAgent", new _https.default.Agent({ | ||
keepAlive: true | ||
})); | ||
} | ||
addRequest(request, options) { | ||
var _options$rejectUnauth; | ||
var _request$outputData; | ||
options.href || (options.href = _url.default.format({ | ||
protocol: options.protocol, | ||
hostname: options.hostname, | ||
port: options.port, | ||
slashes: true | ||
}) + options.path); | ||
options.uri || (options.uri = new URL(options.href)); | ||
let proxyUrl = options.uri.protocol === 'https:' && (process.env.https_proxy || process.env.HTTPS_PROXY) || process.env.http_proxy || process.env.HTTP_PROXY; | ||
let shouldProxy = !!proxyUrl && !(0, _utils.hostnameMatches)(process.env.no_proxy || process.env.NO_PROXY, options.href); | ||
if (shouldProxy) options.proxy = new URL(proxyUrl); // useful when testing | ||
let proxy = getProxy(options); | ||
if (!proxy) return super.addRequest(request, options); | ||
(0, _logger.default)('client:proxy').debug(`Proxying request: ${options.href}`); // modify the request for proxying | ||
(_options$rejectUnauth = options.rejectUnauthorized) !== null && _options$rejectUnauth !== void 0 ? _options$rejectUnauth : options.rejectUnauthorized = this.rejectUnauthorized; | ||
return super.addRequest(request, options); | ||
} // proxy https requests using a TLS connection | ||
request.path = href(options); | ||
if (proxy.auth) { | ||
request.setHeader('Proxy-Authorization', proxy.auth); | ||
} // regenerate headers since we just changed things | ||
createConnection(options, callback) { | ||
let { | ||
uri, | ||
proxy | ||
} = options; | ||
let isProxyHttps = (proxy === null || proxy === void 0 ? void 0 : proxy.protocol) === 'https:'; | ||
if (!proxy) { | ||
return super.createConnection(options, callback); | ||
} else if (proxy.protocol !== 'http:' && !isProxyHttps) { | ||
throw new Error(`Unsupported proxy protocol: ${proxy.protocol}`); | ||
} // setup socket and listeners | ||
delete request._header; | ||
request._implicitHeader(); | ||
let socket = (isProxyHttps ? _tls.default : _net.default).connect({ ...options, | ||
host: proxy.hostname, | ||
port: port(proxy) | ||
if (((_request$outputData = request.outputData) === null || _request$outputData === void 0 ? void 0 : _request$outputData.length) > 0) { | ||
let first = request.outputData[0].data; | ||
let endOfHeaders = first.indexOf(CRLF.repeat(2)) + 4; | ||
request.outputData[0].data = request._header + first.substring(endOfHeaders); | ||
} // coerce the connection to the proxy | ||
options.port = proxy.port; | ||
options.host = proxy.host; | ||
delete options.path; | ||
if (proxy.isHttps) { | ||
// use the underlying https agent to complete the connection | ||
request.agent = this.httpsAgent; | ||
return this.httpsAgent.addRequest(request, options); | ||
} else { | ||
return super.addRequest(request, options); | ||
} | ||
} | ||
} // Proxified https agent | ||
exports.ProxyHttpAgent = ProxyHttpAgent; | ||
class ProxyHttpsAgent extends _https.default.Agent { | ||
constructor(options) { | ||
// default keep-alive | ||
super({ | ||
keepAlive: true, | ||
...options | ||
}); | ||
} | ||
createConnection(options, callback) { | ||
let proxy = getProxy(options); | ||
if (!proxy) return super.createConnection(options, callback); | ||
(0, _logger.default)('client:proxy').debug(`Proxying request: ${href(options)}`); // generate proxy connect message | ||
let host = `${options.hostname}:${port(options)}`; | ||
let connectMessage = [`CONNECT ${host} HTTP/1.1`, `Host: ${host}`]; | ||
if (proxy.auth) { | ||
connectMessage.push(`Proxy-Authorization: ${proxy.auth}`); | ||
} | ||
connectMessage = connectMessage.join(CRLF); | ||
connectMessage += CRLF.repeat(2); // start the proxy connection and setup listeners | ||
let socket = proxy.connect(); | ||
let handleError = err => { | ||
@@ -102,55 +187,50 @@ socket.destroy(err); | ||
options.socket = socket; | ||
options.servername = uri.hostname; // callback not passed in so not to be added as a listener | ||
options.servername = options.hostname; // callback not passed in so not to be added as a listener | ||
callback(null, super.createConnection(options)); | ||
}; // write proxy connect message to the socket | ||
}; // send and handle the connect message | ||
let host = `${uri.hostname}:${port(uri)}`; | ||
let connectMessage = [`CONNECT ${host} HTTP/1.1`, `Host: ${host}`]; | ||
if (proxy.username) { | ||
let auth = proxy.username; | ||
if (proxy.password) auth += `:${proxy.password}`; | ||
connectMessage.push(`Proxy-Authorization: basic ${Buffer.from(auth).toString('base64')}`); | ||
} | ||
connectMessage = connectMessage.join(CRLF); | ||
connectMessage += CRLF.repeat(2); | ||
(0, _logger.default)('client:proxy').debug(`Proxying request: ${options.href}`); | ||
socket.on('error', handleError).on('close', handleClose).on('data', handleData).write(connectMessage); | ||
} | ||
} // Returns true or false if an error should cause the request to be retried | ||
} | ||
exports.ProxyHttpsAgent = ProxyHttpsAgent; | ||
function shouldRetryRequest(error, retryNotFound) { | ||
if (error.response) { | ||
/* istanbul ignore next: client does not retry 404s, but other internal libs may want to */ | ||
return !!retryNotFound && error.response.status === 404 || error.response.status >= 500 && error.response.status < 600; | ||
} else if (error.code) { | ||
return RETRY_ERROR_CODES.includes(error.code); | ||
} else { | ||
return false; | ||
function proxyAgentFor(url, options) { | ||
let cache = proxyAgentFor.cache || (proxyAgentFor.cache = new Map()); | ||
let { | ||
protocol, | ||
hostname | ||
} = new URL(url); | ||
let cachekey = `${protocol}//${hostname}`; | ||
if (!cache.has(cachekey)) { | ||
cache.set(cachekey, protocol === 'https:' ? new ProxyHttpsAgent(options) : new ProxyHttpAgent(options)); | ||
} | ||
} // Returns a promise that resolves when the request is successful and rejects | ||
// when a non-successful response is received. The rejected error contains | ||
// response data and any received error details. Server 500 errors are retried | ||
// up to 5 times at 50ms intervals. | ||
return cache.get(cachekey); | ||
} // Proxified request function that resolves with the response body when the request is successful | ||
// and rejects when a non-successful response is received. The rejected error contains response data | ||
// and any received error details. Server 500 errors are retried up to 5 times at 50ms intervals by | ||
// default, and 404 errors may also be optionally retried. If a callback is provided, it is called | ||
// with the parsed response body and response details. If the callback returns a value, that value | ||
// will be returned in the final resolved promise instead of the response body. | ||
function request(url, { | ||
body, | ||
retries, | ||
retryNotFound, | ||
interval, | ||
...options | ||
}) { | ||
/* istanbul ignore next: the client api is https only, but this helper is borrowed in some | ||
* cli-exec commands for its retryability with the internal api */ | ||
function request(url, options = {}, callback) { | ||
// accept `request(url, callback)` | ||
if (typeof options === 'function') [options, callback] = [{}, options]; | ||
let { | ||
request | ||
} = url.startsWith('https:') ? _https.default : _http.default; | ||
body, | ||
retries, | ||
retryNotFound, | ||
interval, | ||
noProxy, | ||
...requestOptions | ||
} = options; // allow bypassing proxied requests entirely | ||
if (!noProxy) requestOptions.agent || (requestOptions.agent = proxyAgentFor(url)); // parse the requested URL into request options | ||
let { | ||
@@ -161,41 +241,60 @@ protocol, | ||
pathname, | ||
search | ||
search, | ||
hash | ||
} = new URL(url); | ||
options = { ...options, | ||
protocol, | ||
hostname, | ||
port, | ||
path: pathname + search | ||
}; | ||
return (0, _utils.retry)((resolve, reject, retry) => { | ||
let handleError = error => { | ||
return shouldRetryRequest(error, retryNotFound) ? retry(error) : reject(error); | ||
if (handleError.handled) return; | ||
handleError.handled = true; | ||
let shouldRetry = error.response // maybe retry 404s and always retry 500s | ||
? retryNotFound && error.response.status === 404 || error.response.status >= 500 && error.response.status < 600 : !!error.code && RETRY_ERROR_CODES.includes(error.code); | ||
return shouldRetry ? retry(error) : reject(error); | ||
}; | ||
request(options).on('response', res => { | ||
let status = res.statusCode; | ||
let raw = ''; | ||
res.setEncoding('utf8').on('data', chunk => raw += chunk).on('error', handleError).on('end', () => { | ||
let body = raw; | ||
let handleFinished = async (body, res) => { | ||
let raw = body; // attempt to parse the body as json | ||
try { | ||
body = JSON.parse(raw); | ||
} catch (e) {} | ||
try { | ||
body = JSON.parse(body); | ||
} catch (e) {} | ||
if (status >= 200 && status < 300) { | ||
resolve(body); | ||
try { | ||
if (res.statusCode >= 200 && res.statusCode < 300) { | ||
var _await$callback, _callback; | ||
// resolve successful statuses after the callback | ||
resolve((_await$callback = await ((_callback = callback) === null || _callback === void 0 ? void 0 : _callback(body, res))) !== null && _await$callback !== void 0 ? _await$callback : body); | ||
} else { | ||
var _body, _body$errors, _body$errors$find; | ||
handleError(Object.assign(new Error(), { | ||
response: { | ||
status, | ||
body | ||
}, | ||
// use first error detail or the status message | ||
message: ((_body = body) === null || _body === void 0 ? void 0 : (_body$errors = _body.errors) === null || _body$errors === void 0 ? void 0 : (_body$errors$find = _body$errors.find(e => e.detail)) === null || _body$errors$find === void 0 ? void 0 : _body$errors$find.detail) || `${status} ${res.statusMessage || raw}` | ||
})); | ||
// use the first error detail or the status message | ||
throw new Error(((_body = body) === null || _body === void 0 ? void 0 : (_body$errors = _body.errors) === null || _body$errors === void 0 ? void 0 : (_body$errors$find = _body$errors.find(e => e.detail)) === null || _body$errors$find === void 0 ? void 0 : _body$errors$find.detail) || `${res.statusCode} ${res.statusMessage || raw}`); | ||
} | ||
}); | ||
}).on('error', handleError).end(body); | ||
} catch (error) { | ||
handleError(Object.assign(error, { | ||
response: { | ||
status: res.statusCode, | ||
body | ||
} | ||
})); | ||
} | ||
}; | ||
let handleResponse = res => { | ||
let body = ''; | ||
res.setEncoding('utf8'); | ||
res.on('data', chunk => body += chunk); | ||
res.on('end', () => handleFinished(body, res)); | ||
res.on('error', handleError); | ||
}; | ||
let req = (protocol === 'https:' ? _https.default : _http.default).request({ ...requestOptions, | ||
path: pathname + search + hash, | ||
protocol, | ||
hostname, | ||
port | ||
}); | ||
req.on('response', handleResponse); | ||
req.on('error', handleError); | ||
req.end(body); | ||
}, { | ||
@@ -202,0 +301,0 @@ retries, |
{ | ||
"name": "@percy/client", | ||
"version": "1.0.0-beta.65", | ||
"version": "1.0.0-beta.66", | ||
"license": "MIT", | ||
@@ -26,4 +26,4 @@ "main": "dist/index.js", | ||
"dependencies": { | ||
"@percy/env": "1.0.0-beta.65", | ||
"@percy/logger": "1.0.0-beta.65" | ||
"@percy/env": "1.0.0-beta.66", | ||
"@percy/logger": "1.0.0-beta.66" | ||
}, | ||
@@ -35,3 +35,3 @@ "repository": { | ||
}, | ||
"gitHead": "19a727cb92b9b6c06dec9ccec7cc198b0d8f1e12" | ||
"gitHead": "740a335bab4966f7dda4e4693505e17a5fdcfd4c" | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
33623
695
+ Added@percy/env@1.0.0-beta.66(transitive)
+ Added@percy/logger@1.0.0-beta.66(transitive)
- Removed@percy/env@1.0.0-beta.65(transitive)
- Removed@percy/logger@1.0.0-beta.65(transitive)
Updated@percy/env@1.0.0-beta.66
Updated@percy/logger@1.0.0-beta.66