UNPKG

@extra/proxy-router

Version:
191 lines 9.17 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProxyRouterStandalone = exports.ProxyRouter = void 0; const proxy_chain_1 = require("proxy-chain"); const port_1 = __importDefault(require("./utils/port")); const stats_1 = require("./stats"); const debug_1 = __importDefault(require("debug")); const debug = (0, debug_1.default)('puppeteer-extra:proxy-router'); const debugVerbose = debug.extend('verbose'); const warn = console.warn.bind(console, `\n[proxy-router] %s`); // Preserves line numbers class ProxyRouter { constructor(opts = {}) { var _a, _b; this.isListening = false; /** Internal list of failed connections to only print the same connection issue once */ this.failedConnections = []; const proxyServerOpts = Object.assign(Object.assign({}, opts.proxyServerOpts), { prepareRequestFunction: this.handleProxyServerRequest.bind(this) }); proxyServerOpts.port = proxyServerOpts.port || 2800; this.proxies = opts.proxies || {}; this.routeByHost = opts.routeByHost || null; this.proxyServer = new proxy_chain_1.Server(proxyServerOpts); this.collectStats = (_a = opts.collectStats) !== null && _a !== void 0 ? _a : true; this.stats = new stats_1.ProxyRouterStats(this.proxyServer); this.muteProxyErrors = (_b = opts.muteProxyErrors) !== null && _b !== void 0 ? _b : false; this.muteProxyErrorsForHost = opts.muteProxyErrorsForHost || []; debug('initialized', opts); // Emitted when HTTP connection is closed this.proxyServer.on('connectionClosed', ({ connectionId, stats }) => { if (stats && this.collectStats) { this.stats.addStats(connectionId, stats); } debugVerbose(`Connection ${connectionId} closed`); }); // Emitted when a HTTP request fails this.proxyServer.on('requestFailed', ({ request, error }) => { if (!this.muteProxyErrors) { warn('Request failed:', request.url, error); } }); // Emitted in case of a upstream proxy error (which can mean various things) this.proxyServer.on('proxyAuthenticationFailed', ({ connectionId, str: errorStr, }) => { // resolve the affected host and proxy const { host, proxy } = this.stats.connectionLog.find(({ id }) => id === connectionId) || {}; const proxyUrl = !!proxy ? this.getProxyForName(proxy) : null; const info = [errorStr]; info.push("This error can be thrown if a resource on a site simply can't be accessed (often temporarily), in this case this can be ignored.", ` - To not have errors like this printed to the console you can set 'muteProxyErrors: true' ${!!host ? `or 'muteProxyErrorsForHost: ["${host}"]'` : ''}`, 'It can also indicate incorrect proxy credentials or that the target host is blocked by the proxy.', ' - Make sure the provided proxy string and credentials are correct and the site is not blocked by the proxy (or vice versa).', " - In case the site is blocked by the proxy: Use 'routeByHost' to route the host through a different proxy or as 'DIRECT' or 'ABORT'."); if (host && proxy) { info.push('', `Affected target host: "${host}"`, `Affected proxy name: "${proxy}"`); } if (proxyUrl) { info.push(`Affected proxy URL: "${proxyUrl}"`); info.push('', `To test the proxy with curl: curl -v --proxy '${proxyUrl}' 'https://${host}'`, ''); if (!`${proxyUrl}`.includes('http://')) { info.push('PS: Did you forget to prefix the proxy with "http://"?'); } } const probablyNoise = errorStr.includes('authenticate') && errorStr.includes('522'); const isMuted = this.muteProxyErrors || this.muteProxyErrorsForHost.includes(host); const alreadySeen = !!this.failedConnections.find((entry) => entry.host === host && entry.proxy === proxy); const logger = probablyNoise || isMuted || alreadySeen ? debug : warn; logger(info.join('\n')); if (host && proxy) { this.failedConnections.push({ host, proxy }); } }); // Resurface some errors that proxy-chain seems to swallow this.proxyServer.log = (function (originalMethod, context) { return function (connectionId, str) { if (`${str}`.includes('Failed to authenticate upstream proxy')) { context.emit('proxyAuthenticationFailed', { connectionId, str, }); } if (`${str}`.includes('Error: Invalid "upstreamProxyUrl" provided')) { context.emit('proxyAuthenticationFailed', { connectionId, str, }); } if (`${str}`.includes('Failed to connect to upstream proxy')) { context.emit('proxyAuthenticationFailed', { connectionId, str, }); } originalMethod.apply(context, [connectionId, str]); }; })(this.proxyServer.log, this.proxyServer); } /** Proxy server URL of the local proxy server used for routing */ get proxyServerUrl() { var _a; const port = (_a = this.proxyServer) === null || _a === void 0 ? void 0 : _a.port; if (!port || !this.isListening) { return; } return `http://localhost:${port}`; } get effectiveProxies() { return Object.assign({ DIRECT: null }, (this.proxies || {})); } /** Start the local proxy server and accept connections */ async listen() { debug('starting server..'); if (this.serverStartPromise) { debug('server start promise exists already'); return this.serverStartPromise; } this.serverStartPromise = new Promise(async (resolve) => { if (this.isListening) { debug('server listening already'); return resolve(this.proxyServer.port); } const desiredPort = this.proxyServer.port; debug('finding available port', { desiredPort }); const availablePort = await (0, port_1.default)({ port: desiredPort }); debug('availablePort:', availablePort); this.proxyServer.port = availablePort; this.proxyServer.listen((err) => { if (err === null) { debug(`server listening on port ${this.proxyServer.port}`); this.isListening = true; return resolve(this.proxyServer.port); } warn('Unable to start local server:', err); }); }); return this.serverStartPromise; } /** Stop the local proxy server */ async close() { debug('closing..'); return new Promise((resolve) => { this.proxyServer.close(true, (err) => { if (err === null) { debug('closed without error'); return resolve(null); } debug('closed with error', err); return resolve(err); }); }); } getProxyForName(name) { return this.effectiveProxies[name]; } /** Handle requests to the proxy server */ async handleProxyServerRequest({ request, hostname: host, port, connectionId, isHttp, }) { let proxyName = 'DEFAULT'; if (!!this.routeByHost) { const fnResult = await this.routeByHost({ host, isHttp, port }); if (typeof fnResult === 'string' && !!fnResult) { proxyName = fnResult; } } if (this.collectStats) { this.stats.addConnection(connectionId, proxyName, host); } let proxyUrl = this.getProxyForName(proxyName); debugVerbose('handleProxyServerRequest', host, proxyName, redactProxyUrl(proxyUrl)); if (proxyName === 'ABORT') { throw new proxy_chain_1.RequestError('Request aborted', 400); } if (!proxyUrl && proxyUrl !== null) { warn(`No proxy configured for proxy name "${proxyName}" - configuration error?`); proxyUrl = null; } return { upstreamProxyUrl: proxyUrl, }; } } exports.ProxyRouter = ProxyRouter; function redactProxyUrl(input) { if (!input || typeof input !== 'string') { return `${input}`; } try { return (0, proxy_chain_1.redactUrl)(input); } catch (err) { return `${input}`; } } /** Standalone proxy router not requiring plugin events */ exports.ProxyRouterStandalone = ProxyRouter; //# sourceMappingURL=router.js.map