UNPKG

@dependabot/yarn-lib

Version:

📦🐈 Fast, reliable, and secure dependency management.

554 lines (442 loc) 15 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _fs; function _load_fs() { return _fs = _interopRequireDefault(require('fs')); } var _http; function _load_http() { return _http = _interopRequireDefault(require('http')); } var _url; function _load_url() { return _url = _interopRequireDefault(require('url')); } var _dnscache; function _load_dnscache() { return _dnscache = _interopRequireDefault(require('dnscache')); } var _invariant; function _load_invariant() { return _invariant = _interopRequireDefault(require('invariant')); } var _requestCaptureHar; function _load_requestCaptureHar() { return _requestCaptureHar = _interopRequireDefault(require('request-capture-har')); } var _errors; function _load_errors() { return _errors = require('../errors.js'); } var _blockingQueue; function _load_blockingQueue() { return _blockingQueue = _interopRequireDefault(require('./blocking-queue.js')); } var _constants; function _load_constants() { return _constants = _interopRequireWildcard(require('../constants.js')); } var _network; function _load_network() { return _network = _interopRequireWildcard(require('./network.js')); } var _map; function _load_map() { return _map = _interopRequireDefault(require('../util/map.js')); } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } // Initialize DNS cache so we don't look up the same // domains like registry.yarnpkg.com over and over again // for each request. (0, (_dnscache || _load_dnscache()).default)({ enable: true, ttl: 300, cachesize: 10 }); const successHosts = (0, (_map || _load_map()).default)(); const controlOffline = (_network || _load_network()).isOffline(); class RequestManager { constructor(reporter) { this.offlineNoRequests = false; this._requestCaptureHar = null; this._requestModule = null; this.offlineQueue = []; this.captureHar = false; this.httpsProxy = ''; this.ca = null; this.httpProxy = ''; this.strictSSL = true; this.userAgent = ''; this.reporter = reporter; this.running = 0; this.queue = []; this.cache = {}; this.max = (_constants || _load_constants()).NETWORK_CONCURRENCY; this.maxRetryAttempts = 5; } setOptions(opts) { if (opts.userAgent != null) { this.userAgent = opts.userAgent; } if (opts.offline != null) { this.offlineNoRequests = opts.offline; } if (opts.captureHar != null) { this.captureHar = opts.captureHar; } if (opts.httpProxy != null) { this.httpProxy = opts.httpProxy || ''; } if (opts.httpsProxy === '') { this.httpsProxy = opts.httpProxy || ''; } else if (opts.httpsProxy === false) { this.httpsProxy = false; } else { this.httpsProxy = opts.httpsProxy || ''; } if (opts.strictSSL !== null && typeof opts.strictSSL !== 'undefined') { this.strictSSL = opts.strictSSL; } if (opts.ca != null && opts.ca.length > 0) { this.ca = opts.ca; } if (opts.networkConcurrency != null) { this.max = opts.networkConcurrency; } if (opts.networkTimeout != null) { this.timeout = opts.networkTimeout; } if (opts.maxRetryAttempts != null) { this.maxRetryAttempts = opts.maxRetryAttempts; } if (opts.cafile != null && opts.cafile != '') { // The CA bundle file can contain one or more certificates with comments/text between each PEM block. // tls.connect wants an array of certificates without any comments/text, so we need to split the string // and strip out any text in between the certificates try { const bundle = (_fs || _load_fs()).default.readFileSync(opts.cafile).toString(); const hasPemPrefix = block => block.startsWith('-----BEGIN '); // opts.cafile overrides opts.ca, this matches with npm behavior this.ca = bundle.split(/(-----BEGIN .*\r?\n[^-]+\r?\n--.*)/).filter(hasPemPrefix); } catch (err) { this.reporter.error(`Could not open cafile: ${err.message}`); } } if (opts.cert != null) { this.cert = opts.cert; } if (opts.key != null) { this.key = opts.key; } } /** * Lazy load `request` since it is exceptionally expensive to load and is * often not needed at all. */ _getRequestModule() { if (!this._requestModule) { const request = require('request'); if (this.captureHar) { this._requestCaptureHar = new (_requestCaptureHar || _load_requestCaptureHar()).default(request); this._requestModule = this._requestCaptureHar.request.bind(this._requestCaptureHar); } else { this._requestModule = request; } } return this._requestModule; } /** * Queue up a request. */ request(params) { if (this.offlineNoRequests) { return Promise.reject(new (_errors || _load_errors()).MessageError(this.reporter.lang('cantRequestOffline', params.url))); } const cached = this.cache[params.url]; if (cached) { return cached; } params.method = params.method || 'GET'; params.forever = true; params.retryAttempts = 0; params.strictSSL = this.strictSSL; params.headers = Object.assign({ 'User-Agent': this.userAgent }, params.headers); const promise = new Promise((resolve, reject) => { this.queue.push({ params, reject, resolve }); this.shiftQueue(); }); // we can't cache a request with a processor if (!params.process) { this.cache[params.url] = promise; } return promise; } /** * Clear the request cache. This is important as we cache all HTTP requests so you'll * want to do this as soon as you can. */ clearCache() { this.cache = {}; if (this._requestCaptureHar != null) { this._requestCaptureHar.clear(); } } /** * Check if an error is possibly due to lost or poor network connectivity. */ isPossibleOfflineError(err) { const code = err.code, hostname = err.hostname; if (!code) { return false; } // network was previously online but now we're offline const possibleOfflineChange = !controlOffline && !(_network || _load_network()).isOffline(); if (code === 'ENOTFOUND' && possibleOfflineChange) { // can't resolve a domain return true; } // used to be able to resolve this domain! something is wrong if (code === 'ENOTFOUND' && hostname && successHosts[hostname]) { // can't resolve this domain but we've successfully resolved it before return true; } // network was previously offline and we can't resolve the domain if (code === 'ENOTFOUND' && controlOffline) { return true; } // connection was reset or dropped if (code === 'ECONNRESET') { return true; } // TCP timeout if (code === 'ESOCKETTIMEDOUT' || code === 'ETIMEDOUT') { return true; } return false; } /** * Queue up request arguments to be retried. Start a network connectivity timer if there * isn't already one. */ queueForRetry(opts) { if (opts.retryReason) { let containsReason = false; for (var _iterator = this.offlineQueue, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { var _ref; if (_isArray) { if (_i >= _iterator.length) break; _ref = _iterator[_i++]; } else { _i = _iterator.next(); if (_i.done) break; _ref = _i.value; } const queuedOpts = _ref; if (queuedOpts.retryReason === opts.retryReason) { containsReason = true; break; } } if (!containsReason) { this.reporter.info(opts.retryReason); } } if (!this.offlineQueue.length) { this.initOfflineRetry(); } this.offlineQueue.push(opts); } /** * Begin timers to retry failed requests when we possibly establish network connectivity * again. */ initOfflineRetry() { setTimeout(() => { const queue = this.offlineQueue; this.offlineQueue = []; for (var _iterator2 = queue, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) { var _ref2; if (_isArray2) { if (_i2 >= _iterator2.length) break; _ref2 = _iterator2[_i2++]; } else { _i2 = _iterator2.next(); if (_i2.done) break; _ref2 = _i2.value; } const opts = _ref2; this.execute(opts); } }, 3000); } /** * Execute a request. */ execute(opts) { const params = opts.params; const reporter = this.reporter; const buildNext = fn => data => { fn(data); this.running--; this.shiftQueue(); }; const resolve = buildNext(opts.resolve); const rejectNext = buildNext(opts.reject); const reject = function reject(err) { err.message = `${params.url}: ${err.message}`; rejectNext(err); }; const rejectWithoutUrl = function rejectWithoutUrl(err) { err.message = err.message; rejectNext(err); }; const queueForRetry = reason => { const attempts = params.retryAttempts || 0; if (attempts >= this.maxRetryAttempts - 1) { return false; } if (opts.params.method && opts.params.method.toUpperCase() !== 'GET') { return false; } params.retryAttempts = attempts + 1; if (typeof params.cleanup === 'function') { params.cleanup(); } opts.retryReason = reason; this.queueForRetry(opts); return true; }; let calledOnError = false; const onError = err => { if (calledOnError) { return; } calledOnError = true; if (this.isPossibleOfflineError(err)) { if (!queueForRetry(this.reporter.lang('offlineRetrying'))) { reject(err); } } else { reject(err); } }; if (!params.process) { const parts = (_url || _load_url()).default.parse(params.url); params.callback = (err, res, body) => { if (err) { onError(err); return; } successHosts[parts.hostname] = true; this.reporter.verbose(this.reporter.lang('verboseRequestFinish', params.url, res.statusCode)); if (res.statusCode === 408 || res.statusCode >= 500) { const description = `${res.statusCode} ${(_http || _load_http()).default.STATUS_CODES[res.statusCode]}`; if (!queueForRetry(this.reporter.lang('internalServerErrorRetrying', description))) { throw new (_errors || _load_errors()).ResponseError(this.reporter.lang('requestFailed', description), res.statusCode); } else { return; } } if (res.statusCode === 401 && res.caseless && res.caseless.get('server') === 'GitHub.com') { const message = `${res.body.message}. If using GITHUB_TOKEN in your env, check that it is valid.`; rejectWithoutUrl(new Error(this.reporter.lang('unauthorizedResponse', res.caseless.get('server'), message))); } if (res.statusCode === 401 && res.headers['www-authenticate']) { const authMethods = res.headers['www-authenticate'].split(/,\s*/).map(s => s.toLowerCase()); if (authMethods.indexOf('otp') !== -1) { reject(new (_errors || _load_errors()).OneTimePasswordError(res.headers['npm-notice'])); return; } } if (body && typeof body.error === 'string') { reject(new Error(body.error)); return; } if ([400, 401, 404].concat(params.rejectStatusCode || []).indexOf(res.statusCode) !== -1) { // So this is actually a rejection ... the hosted git resolver uses this to know whether http is supported resolve(false); } else if (res.statusCode >= 400) { const errMsg = body && body.message || reporter.lang('requestError', params.url, res.statusCode); reject(new Error(errMsg)); } else { resolve(body); } }; } if (params.buffer) { params.encoding = null; } let proxy = this.httpProxy; if (params.url.startsWith('https:')) { proxy = this.httpsProxy; } if (proxy) { // if no proxy is set, do not pass a proxy down to request. // the request library will internally check the HTTP_PROXY and HTTPS_PROXY env vars. params.proxy = String(proxy); } else if (proxy === false) { // passing empty string prevents the underlying library from falling back to the env vars. // an explicit false in the yarn config should override the env var. See #4546. params.proxy = ''; } if (this.ca != null) { params.ca = this.ca; } if (this.cert != null) { params.cert = this.cert; } if (this.key != null) { params.key = this.key; } if (this.timeout != null) { params.timeout = this.timeout; } const request = this._getRequestModule(); const req = request(params); this.reporter.verbose(this.reporter.lang('verboseRequestStart', params.method, params.url)); req.on('error', onError); const queue = params.queue; if (queue) { req.on('data', queue.stillActive.bind(queue)); } const process = params.process; if (process) { req.on('response', res => { if (res.statusCode >= 200 && res.statusCode < 300) { return; } const description = `${res.statusCode} ${(_http || _load_http()).default.STATUS_CODES[res.statusCode]}`; reject(new (_errors || _load_errors()).ResponseError(this.reporter.lang('requestFailed', description), res.statusCode)); req.abort(); }); process(req, resolve, reject); } } /** * Remove an item from the queue. Create it's request options and execute it. */ shiftQueue() { if (this.running >= this.max || !this.queue.length) { return; } const opts = this.queue.shift(); this.running++; this.execute(opts); } saveHar(filename) { if (!this.captureHar) { throw new Error(this.reporter.lang('requestManagerNotSetupHAR')); } // No request may have occurred at all. this._getRequestModule(); (0, (_invariant || _load_invariant()).default)(this._requestCaptureHar != null, 'request-capture-har not setup'); this._requestCaptureHar.saveHar(filename); } } exports.default = RequestManager;