"use strict"; /** * Creates commands instance. * * @class * @name Commands * @classdesc Aggregates commands to manage `GlaceJS` proxy. * @arg {object} config - Commands config. * @arg {object} [opts] - Commands options. * @arg {function} [opts.logger] - Commands logger. Default is system logger. * @arg {boolean} [opts.colored=false] - Flag to use ANSI color in log message. * @arg {object} [opts.GlobalProxy] - Global proxy class. * @arg {object} [opts.HttpProxy] - HTTP proxy class. * @arg {object} [opts.isRunning] - Function to detect is process launched. */ var fs = require("fs"); var url = require("url"); require("colors"); var _ = require("lodash"); var chromeLauncher = require("chrome-launcher"); var fse = require("fs-extra"); var isRunning = require("is-running"); var temp = require("temp").track(); var U = require("glace-utils"); var cache = require("./middleware/cache"); var GlobalProxy = require("./globalProxy"); var HttpProxy = require("./httpProxy"); var LOG = U.logger; var Commands = function (config, opts) { this._config = config; opts = U.defVal(opts, {}); this._log = U.defVal(opts.logger, LOG.info.bind(LOG)); this._coloredLog = U.defVal(opts.colored, false); this._chrome = null; this._httpProxy = null; this._globalProxy = null; this.__GlobalProxy = U.defVal(opts.GlobalProxy, GlobalProxy); this.__HttpProxy = U.defVal(opts.HttpProxy, HttpProxy); this.__isRunning = U.defVal(opts.isRunning, isRunning); this.__chromeLauncher = U.defVal(opts.chromeLauncher, chromeLauncher); this.__fs = U.defVal(opts.fs, fs); this.__fse = U.defVal(opts.fse, fse); this.__cache = U.defVal(opts.cache, cache); }; module.exports = Commands; /** * Command to set proxied URL. * * @async * @method * @return {Promise} */ Commands.prototype.setProxiedUrl = async function (proxiedUrl) { this._config.web.url = proxiedUrl; if (this._isHttpProxyLaunched()) { this._httpProxy.setUrl(proxiedUrl); }; if (this._isChromeLaunched()) return await this.restartChrome(); return true; }; /** * Command to launch HTTP proxy. * * @async * @method * @return {Promise<boolean>} `true` if HTTP proxy was launched. `false` if * command can't be executed. Causes to not launch HTTP proxy: * - HTTP proxy is launched already. * - Proxied URL isn't specified. */ Commands.prototype.launchHttpProxy = async function () { var msg; if (this._isHttpProxyLaunched()) { msg = "HTTP proxy is launched already"; if (this._coloredLog) msg = msg.red; this._log(msg); return false; }; if (!this._config.web.url) { msg = "HTTP proxy isn't launched because proxied URL is missed"; if (this._coloredLog) msg = msg.red; this._log(msg); return false; }; this._httpProxy = this._httpProxy || new this.__HttpProxy({ port: this._config.proxy.port, timeout: this._config.proxy.timeout, reconnect: this._config.proxy.reconnect, useCache: this._config.cache.use, url: this._config.web.url, speed: this._config.proxy.speed, }); return this._httpProxy .start() .then(() => { if (this._coloredLog) { msg = `HTTP proxy is started on host ${U.hostname.cyan} ` + `and port ${this._httpProxy._port.toString().yellow}`; } else { msg = `HTTP proxy is started on host '${U.hostname}' and ` + `port '${this._httpProxy._port}'`; }; this._log(msg); }) .then(() => { if (this._isChromeLaunched()) return this.restartChrome(); }) .then(() => true); }; /** * Command to stop HTTP proxy. * * @async * @method * @arg {object} [opts] - Options. * @arg {boolean} [opts.restartChrome=true] - Restart chrome. * @return {Promise<boolean>} `true` if HTTP proxy was stopped. `false` if * command can't be executed. Causes to not stop HTTP proxy: * - HTTP proxy isn't launched yet. */ Commands.prototype.stopHttpProxy = async function (opts) { if (!this._isHttpProxyLaunched()) { var msg = "HTTP proxy isn't launched yet"; if (this._coloredLog) msg = msg.red; this._log(msg); return false; }; opts = U.defVal(opts, {}); var restartChrome = U.defVal(opts.restartChrome, true); this._httpProxy.stop(); if (restartChrome && this._isChromeLaunched()) await this.restartChrome(); return true; }; /** * Command to restart HTTP proxy. * * @async * @method * @return {Promise<boolean>} `true` if HTTP proxy was restarted. `false` if * command can't be executed. Causes to not restart HTTP proxy are the same as * for command to launch HTTP proxy. */ Commands.prototype.restartHttpProxy = function() { return this .stopHttpProxy({ restartChrome: false }) .then(() => this.launchHttpProxy()); }; /** * Command to launch global transparent proxy. * * @async * @method * @return {Promise<boolean>} `true` if global transparent proxy isn't * launched. `false` if command can't be executed. Causes to not launch * global transparent proxy: * - Global transparent proxy is launched already. */ Commands.prototype.launchGlobalProxy = function () { var msg; if (this._isGlobalProxyLaunched()) { msg = "Global transparent proxy is launched already"; if (this._coloredLog) msg = msg.red; this._log(msg); return false; }; this._globalProxy = this._globalProxy || new this.__GlobalProxy({ port: this._config.proxy.globalPort, timeout: this._config.proxy.timeout, reconnect: this._config.proxy.reconnect, installCertificate: this._config.proxy.installCertificate, sslCaDir: this._config.proxy.sslCaDir, useCache: this._config.cache.use, speed: this._config.proxy.speed, }); return this._globalProxy .start() .then(() => { if (this._coloredLog) { msg = "Global transparent proxy is started on " + `${U.hostname.cyan} and port ` + `${this._globalProxy.getPort().toString().yellow}`; } else { msg = "Global transparent proxy is started on host " + `'${U.hostname}' and port '${this._globalProxy.getPort()}'`; }; this._log(msg); }) .then(() => { if (this._isChromeLaunched()) return this.restartChrome(); }) .then(() => true); }; /** * Command to stop global transparent proxy. * * @async * @method * @arg {object} [opts] - Options. * @arg {boolean} [opts.restartChrome=true] - Restart chrome. * @return {Promise<boolean>} `true` if global transparent proxy was stopped. * `false` if command can't be executed. Causes to not stop global transparent * proxy: * - Global transparent proxy isn't launched yet. */ Commands.prototype.stopGlobalProxy = async function (opts) { if (!this._isGlobalProxyLaunched()) { var msg = "Global transparent proxy isn't launched yet"; if (this._coloredLog) msg = msg.red; this._log(msg); return false; }; opts = U.defVal(opts, {}); var restartChrome = U.defVal(opts.restartChrome, true); this._globalProxy.stop(); if (restartChrome && this._isChromeLaunched()) await this.restartChrome(); return true; }; /** * Command to restart global transparent proxy. * * @async * @method * @return {Promise<boolean>} `true` if global transparent proxy was restarted. * `false` if command can't be executed. Causes to not restart global * transparent proxy are the same as for command to launch global * transparent proxy. */ Commands.prototype.restartGlobalProxy = function() { return this .stopGlobalProxy({ restartChrome: false }) .then(() => this.launchGlobalProxy()); }; /** * Command to launch chrome browser. * * @async * @method * @arg {object} [opts] - Options. * @arg {string[]} [opts.chromeOpts=[]] - Chrome options list. * @return {Promise<boolean>} `true` if chrome browser was launched. `false` * if command can't be executed. Causes to not launch chrome browser: * - HTTP proxy isn't launched yet. * - Chrome browser is launched already. */ Commands.prototype.launchChrome = async function (opts) { opts = U.defVal(opts, {}); var chromeOpts = U.defVal(opts.chromeOpts, []); if (this._isChromeLaunched()) { var msg = "Chrome browser is launched already"; if (this._coloredLog) msg = msg.red; this._log(msg); return false; }; var chromeFlags = [ "start-maximized", "disable-infobars", "enable-precise-memory-info", "disable-translate", ]; var opt; for (opt of chromeOpts) { if (!isOptPresent(opt, chromeFlags)) chromeFlags.push(opt); }; if (this._config.chrome.incognito && !isOptPresent("incognito", chromeFlags)) { chromeFlags.push("incognito"); }; if (this._isGlobalProxyLaunched()) { var proxyOpts = [ "ignore-certificate-errors", `proxy-server=${U.hostname}:${this._globalProxy.getPort()}`, `proxy-bypass-list=localhost,127.0.0.1,${U.hostname}`, ]; for (opt of proxyOpts) { if (!isOptPresent(opt, chromeFlags)) chromeFlags.push(opt); }; }; for (var i = 0; i < chromeFlags.length; i++) { if (!chromeFlags[i].startsWith("-")) { chromeFlags[i] = "--" + chromeFlags[i]; }; }; var profileDir = temp.path(); fse.mkdirsSync(profileDir); return this.__chromeLauncher.launch({ startingUrl: this._chromeUrl(), userDataDir: profileDir, chromeFlags: chromeFlags, handleSIGINT: true, }).then(chrome => { this._chrome = chrome; var msg; if (this._coloredLog) { msg = "Chrome is launched with PID " + `${chrome.pid.toString().yellow}`; } else { msg = `Chrome is launched with PID '${chrome.pid}'`; } this._log(msg); LOG.info(`Chrome ${chrome.pid}`); LOG.info(`Chrome debugging port ${chrome.port}`); LOG.info(`Chrome profile ${profileDir}`); return true; }); }; /** * Command to close chrome browser. * * @async * @method * @return {Promise<boolean>} `true` if chrome browser was closed. `false` if * command can't be executed. Causes to not close chrome browser: * - Chrome browser isn't launched yet. */ Commands.prototype.closeChrome = async function () { if (!this._isChromeLaunched()) { var msg = "Chrome browser isn't launched yet"; if (this._coloredLog) msg = msg.red; this._log(msg); return false; }; await this._chrome.kill(); this._chrome = null; return true; }; /** * Command to restart chrome browser. * * @async * @method * @return {Promise<boolean>} `true` if chrome browser was restarted. `false` * if command can't be executed. Causes to not restart chrome browser are the * the same as for command to launch chrome browser. */ Commands.prototype.restartChrome = function () { return this.closeChrome().then(() => this.launchChrome()); }; /** * Command to set proxy speed. * * @async * @method * @arg {number} speed - Proxy speed, kb/s. * @arg {?number} [speed.req] - Requests speed, kb/s. * @arg {?number} [speed.res] - Responses speed, kb/s. * @return {Promise<boolean>} `true` if proxy speed was set. `false` if * command can't be executed. Causes to not set proxy speed: * - HTTP proxy isn't launched. */ Commands.prototype.setProxySpeed = async function (speed) { if (!this._checkProxy()) return false; if (this._httpProxy) { this._httpProxy.setSpeed(speed); }; if (this._globalProxy) { this._globalProxy.setSpeed(speed); }; return true; }; /** * Command to reset proxy speed. * * @async * @method * @return {Promise<boolean>} `true` if proxy speed was reset. `false` if * command can't be executed. Causes to not reset proxy speed: * - HTTP proxy isn't launched. */ Commands.prototype.resetProxySpeed = async function () { if (!this._checkProxy()) return false; if (this._httpProxy) { this._httpProxy.resetSpeed(); }; if (this._globalProxy) { this._globalProxy.resetSpeed(); }; return true; }; /** * Command to enable proxy cache. * * @async * @method * @return {Promise<boolean>} `true` if proxy cache wasn't enabled. `false` if * command can't be executed. Causes to not enable proxy cache: * - HTTP proxy isn't launched. */ Commands.prototype.enableProxyCache = async function () { if (!this._checkProxy()) return false; if (this._httpProxy) { this._httpProxy.useCache = true; }; if (this._globalProxy) { this._globalProxy.useCache = true; }; return true; }; /** * Command to disable proxy cache. * * @async * @method * @return {Promise<boolean>} `true` if proxy cache wasn't disabled. `false` * if command can't be executed. Causes to not disable proxy cache: * - HTTP proxy isn't launched. */ Commands.prototype.disableProxyCache = async function () { if (!this._checkProxy()) return false; if (this._httpProxy) { this._httpProxy.useCache = false; }; if (this._globalProxy) { this._globalProxy.useCache = false; }; return true; }; /** * Command to clear proxy cache. * * @async * @method * @return {Promise<boolean>} `true` when cache will be cleared. */ Commands.prototype.clearProxyCache = async function () { if (this.__fs.existsSync(this._config.cache.folder)) { this.__fse.removeSync(this._config.cache.folder); }; await this.__cache.init({ force: true }); return true; }; /** * Helper to check whether any proxy is launched. * * @method * @protected * @return {boolean} `true` if proxy exists and launched, `false` otherwise. */ Commands.prototype._checkProxy = function () { if ((this._isHttpProxyLaunched()) || (this._isGlobalProxyLaunched())) return true; var msg = "No one of http or global proxy isn't launched yet"; if (this._coloredLog) msg = msg.red; this._log(msg); return false; }; /** * Helper to define whether chrome is launched. * * @method * @protected * @return {boolean} */ Commands.prototype._isChromeLaunched = function () { return !!(this._chrome && this.__isRunning(this._chrome.pid)); }; /** * Helper to get URL to open in chrome browser. If HTTP proxy is launched, * it will return proxy URL. * * @method * @protected * @return {string} URL to open chrome. */ Commands.prototype._chromeUrl = function () { var result; if (this._isHttpProxyLaunched()) { result = _.trim( this._httpProxy.url + url.parse(this._config.web.url).pathname, "/"); } else { result = this._config.web.url; }; return result; }; /** * Helper to define whether http proxy is running. * * @method * @protected * @return {boolean} */ Commands.prototype._isHttpProxyLaunched = function () { return !!(this._httpProxy && this._httpProxy.isRunning); }; /** * Helper to define whether global proxy is running. * * @method * @protected * @return {boolean} */ Commands.prototype._isGlobalProxyLaunched = function () { return !!(this._globalProxy && this._globalProxy.isRunning); }; /** * Helper to define whether chrome option is present in options list. * * @ignore * @function * @arg {string} opt - Checking option. * @arg {string[]} opts - List of options. * @return {boolean} */ var isOptPresent = (opt, opts) => { var optStart = opt.split("=")[0]; for (var o of opts) { if (o.split("=")[0] === optStart) return true; }; return false; };