UNPKG

@lambdatest/node-tunnel

Version:

Nodejs bindings for LambdaTest Tunnel

1,187 lines (1,115 loc) 36.5 kB
const axios = require('axios'); var childProcess = require('child_process'), TunnelBinary = require('./tunnel_binary'), split = require('split'), https = require('https'), http = require('http'), urlParse = require('url'), HttpsProxyAgent = require('https-proxy-agent'), util = require('./util'), getPort = require('get-port'), os = require('os'), logger, httpTunnelConfig, Config_ = require('./config'), package_ = require('../package.json'), packageVersion = package_.version, packageName = package_.name, usingPorts = [], globalForceRetries = 0; const maxBinaryExecutionRetries = 5 const maxCheckTunnelStatusRetries = 10 /** * Tunnel is a function based Class. */ function Tunnel() { this.isProcessRunning = false; this.options = null; /** * start is used to run tunnel binary File * @param {!Setting} options is command line argumants passed during * Initialize the Tunnel * @param {Function} fnCallback is a Callable function. * @return {Object|Error} Return Object with success or Error if any */ this.start = function(options, fnCallback) { if (typeof options !== 'object') { throw new Error("It's look like you have passed invalid Arguments"); } this.options = options; var self = this; if (typeof fnCallback !== 'function') { return new Promise(function(resolve, reject) { if (typeof options['onlyCommand'] !== 'undefined') { return resolve(true); } // Required check if (!options['user'] || !options['key']) { return reject({ message: 'user and key is required' }); } // Configure logger Config_(options, function(error, response) { console.log("Current version of Node Tunnel: ", packageVersion) if (error) { throw new Error(error); } if (response.jsonResponse.supportedVersions.indexOf(packageVersion) === -1) { throw new Error( "\nIt's seems you have unsupported version of " + packageName + '.' + 'Please remove and install newer version \n ' + 'npm uninstall ' + packageName + ' \nnpm i ' + packageName ); } else if (packageVersion !== response.jsonResponse.latest) { console.warn( "\nIt's seems you have older version of " + packageName + '.' + 'For better experience , please update using: ' + '\nnpm update ' + packageName ); } logger = response.logger; httpTunnelConfig = response; // Verifying User Credentials console.log(`Verifying credentials`); verifyToken_(options, function(e, res) { if (e) { console.log(`Auth failed! `); logger.log( options['user'], options['key'], { filename: __filename }, options, 'Getting error while verifying user and key . Error Response is : ' + (res || e) ); console.log(res.message || res || e); return reject(res || e); } console.log(`Auth succeeded`); // On successful verifying of Credential Get binary path to run tunnel getBinaryPath_(self, options, function(binaryPath) { self.binaryPath = binaryPath; // Run binary after getting this and attempting atmost 5 times console.log(`Starting tunnel`); if (options.detachedMode === 'true') { console.log("Running Tunnel in Detached Mode") let tunnelStatus = runBinaryV2_(self, maxBinaryExecutionRetries) if (tunnelStatus){ return Promise.resolve(true) } return Promise.reject() } else{ runBinary_(self, maxBinaryExecutionRetries, function(e, response) { if (e) { return reject(e); } else { return resolve(response); } }); } }); }); }); }); } if (typeof options['onlyCommand'] !== 'undefined') { return fnCallback(null, true); } // Required check if (!options['user'] || !options['key']) { return fnCallback({ message: 'user and key is required' }, false); } // Configure looger Config_(options, function(error, response) { console.log("Current version of Node Tunnel: ", packageVersion) if (error) { throw new Error(error); } if (response.jsonResponse.supportedVersions.indexOf(packageVersion) === -1) { throw new Error( "\nIt's seems you have unsupported version of " + packageName + '.' + 'Please remove and install newer version \n ' + 'npm uninstall ' + packageName + ' \nnpm i ' + packageName ); } else if (packageVersion !== response.jsonResponse.latest) { console.warn( "\nIt's seems you have older version of " + packageName + '.' + 'For better experience , please update using: ' + '\nnpm update ' + packageName ); } logger = response.logger; httpTunnelConfig = response; // Verifying User Credentials console.log(`Verifying credentials`); verifyToken_(options, function(e, res) { if (e) { console.log(`Auth failed! `); console.log(res.message || res || e); return fnCallback(res || e, false); } console.log(`Auth succeeded`); // On successful verifying of Credential Get binary path to run tunnel getBinaryPath_(self, options, function(binaryPath) { self.binaryPath = binaryPath; // Run binary after getting this and attempting atmost 5 times console.log(`Starting tunnel`); if (options.detachedMode === 'true') { console.log("Running Tunnel in Detached Mode") let tunnelStatus = runBinaryV2_(self, maxBinaryExecutionRetries) if (tunnelStatus){ return Promise.resolve(true) } return Promise.reject() } else{ runBinary_(self, maxBinaryExecutionRetries, fnCallback); } }); }); }); }; /** * isRunning method is used to determine the running status * @return {boolean} Return true/false. */ this.isRunning = function() { return this.isProcessRunning && this.proc && true; }; /** * getTunnelName is used to get running tunnel name * @param {Function} fnCallback is a Callable function. * @return {string|null} Return name or null if any */ this.getTunnelName = function(fnCallback) { if (typeof fnCallback !== 'function') { var that = this; return new Promise(function(resolve, reject) { if (that.isRunning()) { if (that.infoAPIPort) { retryTunnelName_(that, that.infoAPIPort, 100, function(tunnelName) { if (tunnelName === null) { return reject(null); } else { return resolve(tunnelName); } }); } else { return reject(null); } } else { console.log('Tunnel is not Running Currently'); return reject(null); } }); } // check tunnel status and Retry atmost 100 time to get tunnel name.We are // doing intentionlly because tunnel may take few times to start server if (this.isRunning()) { if (this.infoAPIPort) { retryTunnelName_(this, this.infoAPIPort, 100, fnCallback); } else { return fnCallback(null); } } else { console.log('Tunnel is not Running Currently'); return fnCallback(null); } }; /** * stop is used to stop the Running Tunnel Process * @param {Function} fnCallback is a Callable function. * @return {null|Error} Return null if stoped successfully else Error if any */ this.stop = function(fnCallback) { if (typeof fnCallback !== 'function') { var that = this; return new Promise(function(resolve, reject) { // Using try-catch , possibility of Error during killing process try { // check tunnel status if not running Return Stop to true if (!that.isRunning()) { return resolve(true); } // Kill the Specified Running process and chield process of this process killRunningProcess_(that, function(e) { if (e) { logger.log( that.options['user'], that.options['key'], { filename: __filename }, that.options, 'Getting error while we are trying to kill the running process. Throws Error : ' + e ); return reject(e); } return resolve(true); }); } catch (e) { logger.log( this.options['user'], this.options['key'], { filename: __filename }, this.options, 'Something unexpected while trying to kill process. Error : ' + e ); return reject(e); } }); } // Using try-catch , possibility of Error during killing process try { // check tunnel status if not running Return Stop to true if (!this.isRunning()) { return fnCallback(false, true); } var that = this; // Kill the Specified Running process and chield process of this process killRunningProcess_(that, function(e) { if (e) { logger.log( that.options['user'], that.options['key'], { filename: __filename }, that.options, 'Getting error while we are trying to kill the running process. Throws Error : ' + e ); return fnCallback(e, false); } return fnCallback(false, true); }); } catch (e) { logger.log( this.options['user'], this.options['key'], { filename: __filename }, this.options, 'Something unexpected while trying to kill process. Error : ' + e ); return fnCallback(e, false); } }; } /** * runBinary_ is used to Run Tunnel Binary * @param {Tunnel} self is Current Tunnel Instance. * @param {number} retries max attempt try to Run tunnel. * @param {Function} fnCallback is a Callable function. * @return {boolean} Return true/false whether tunnel is Running Successfully or * not. */ function runBinary_(self, retries, fnCallback) { if (retries >= 0) { // addArguments_ method return formatted argumants or error if any. addArguments_(self, function(e, binaryArguments) { if (e) { logger.log( self.options['user'], self.options['key'], { filename: __filename }, self.options, 'Getting this error while adding argument . Error : ' + e ); return fnCallback( new Error('Getting this error while adding argument . Error : ' + e), false ); } // Run Binary with argumants in spawn process. self.proc = childProcess.spawn(self.binaryPath, binaryArguments); var isCallback = false; self.proc.stdout.pipe(split()).on('data', function(data) { if (!isCallback) { self.isProcessRunning = true; isCallback = true; self.getTunnelName(function(tunnelName) { if (tunnelName === null) { return fnCallback(true, false); } console.log(`Tunnel successfully initiated. You can start testing now`); return fnCallback(null, true); }); } }); self.proc.stderr.pipe(split()).on('data', function(data) { logger.log( self.options['user'], self.options['key'], { filename: __filename }, self.options, 'We are getting this error while we are trying to spawning process. Error : ' + data ); self.isProcessRunning = false; if (!isCallback) { var logFileIndex = binaryArguments.indexOf('--logFile'); if (logFileIndex === -1) { console.log( `Tunnel couldn't start. Please see ${__dirname}/${ binaryArguments[logFileIndex + 1] } for more details` ); } return fnCallback(null, false); } }); // On exit if process is exit due to unable to start local Server then // Retry else log and exit self.proc.on('exit', function(code) { if (code && code === 3) { globalForceRetries = retries - 1; sleep(500); return runBinary_(self, retries - 1, fnCallback); } console.log(`Tunnel successfully stopped`); logger.log( self.options['user'], self.options['key'], { filename: __filename }, self.options, 'Getting this message while tunnel is being closed. Exit code is : ' + code ); self.isProcessRunning = false; if (!isCallback) { return fnCallback(null, false); } }); }); } else { logger.log( self.options['user'], self.options['key'], { filename: __filename }, self.options, 'Number of retries to run binary exceeded.' ); return fnCallback(new Error('Number of retries to run binary exceeded.'), false); } } /** * verifyToken_ is used to verify user credentials by sending a POST request to the authentication server with retries and exponential backoff. * @param {Setting} options passed User arguments. * @param {Function} fnCallback is a Callable function. * @return {Object|Error} Return User Object or Error if any. */ function verifyToken_(options, fnCallback, retries = 3, delay = 1000) { const attempt = () => { try { var data = JSON.stringify({ username: options['user'], token: options['key'] }); var _httpAuthUrl = urlParse.parse(httpTunnelConfig.jsonResponse.AuthUrl); var reqOptions = { hostname: _httpAuthUrl.hostname, port: _httpAuthUrl.port, path: _httpAuthUrl.path, method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': data.length, Accept: 'application/json', client: 'npm-tunnel', version: packageVersion } }; var proxyOpts = util.getProxyOpts_(options); if (Object.keys(proxyOpts).length) { reqOptions.agent = new HttpsProxyAgent(proxyOpts); } var req = https.request(reqOptions, resp => { let json = ''; resp.on('data', chunk => { json += chunk; }); resp.on('end', () => { try { if (resp.statusCode >= 500) { throw new Error(`Server error with status code: ${resp.statusCode}`); } if (typeof json === 'string') { json = JSON.parse(json); } if (json && json.type === 'error') { logger.log( options['user'], options['key'], { filename: __filename }, options, 'Authentication failed: ' + json.message ); throw new Error(json.message); } return fnCallback(false, json); } catch (parseError) { if (retries > 0) { logger.log( options['user'], options['key'], { filename: __filename }, options, `Parse or server error on attempt ${4 - retries}, retrying... Error: ` + parseError ); setTimeout(() => { verifyToken_(options, fnCallback, retries - 1, delay * 2); }, delay); } else { logger.log( options['user'], options['key'], { filename: __filename }, options, 'Parse or server error, maximum retries reached. Error: ' + parseError ); return fnCallback(true, parseError); } } }); }); req.on('error', e => { if (e.code === 'ECONNRESET' || e.code === 'ENOTFOUND' || e.code === 'ETIMEDOUT' || retries > 0) { logger.log( options['user'], options['key'], { filename: __filename }, options, `Network error on attempt ${4 - retries}, retrying... Error: ` + e ); setTimeout(() => { verifyToken_(options, fnCallback, retries - 1, delay * 2); }, delay); } else { logger.log( options['user'], options['key'], { filename: __filename }, options, 'Network error, maximum retries reached. Error: ' + e ); return fnCallback(true, e); } }); req.write(data); req.end(); } catch (e) { logger.log( options['user'], options['key'], { filename: __filename }, options, 'Something unexpected while setting up the request. Error : ' + e ); return fnCallback(true, e); } }; attempt(); } /** * killRunningProcess_ is used to Kill passed current Running process * @param {Tunnel} that is Current Tunnel Instance. * @param {Function} fnCallback is a Callable function. * @return {null|Error} Return null if killing of process is successful or Error * if any. */ function killRunningProcess_(that, fnCallback) { // use try-catch as possibility of Error while killing process. try { // Check process is running before killing if (that.proc && that.infoAPIPort) { var url = 'http://127.0.0.1:' + that.infoAPIPort + '/api/v1.0/stop'; var parseURL = urlParse.parse(url); var reqOptions = { hostname: parseURL.hostname, port: parseURL.port, path: parseURL.path, method: 'DELETE' }; var req = http.request(reqOptions, resp => { resp.on('data', () => {}); resp.on('end', () => { if (resp.statusCode != 200) { killProcess_(that, fnCallback); } return fnCallback(); }); }); req.on('error', e => { killProcess_(that, fnCallback); logger.log( that.options['user'], that.options['key'], { filename: __filename }, that.options, 'Getting error while killing the running process. Error : ' + e ); return fnCallback(e); }); req.write(''); req.end(); } else { return fnCallback(); } } catch (e) { logger.log( that.options['user'], that.options['key'], { filename: __filename }, that.options, 'Getting error while killing the running process. Error : ' + e ); return fnCallback(e); } } /** * addArguments_ is used generate passed User argumants to formatted Argumants * as process is Required to Run * @param {Tunnel} self is Current Tunnel Instance. * @param {Function} fnCallback is a Callable function. * @return {Object|Error} Return Arguments Object or Error if any. */ function addArguments_(self, fnCallback) { try { var options = self.options; var binaryArgs = []; for (var key in options) { var value = options[key]; if (key) { key = key.toLowerCase().trim(); } switch (key) { case 'user': case 'key': case 'port': case 'dns': case 'pidfile': case 'controller': if (value) { binaryArgs.push('--' + key); binaryArgs.push(value); } break; case 'tunnelname': case 'n': if (value) { binaryArgs.push('--tunnelName'); binaryArgs.push(value); } break; case 'loadbalanced': if(value) { binaryArgs.push('--load-balanced'); } break; case 'proxyhost': if (value) { binaryArgs.push('--proxy-host'); binaryArgs.push(value); } break; case 'proxyport': if (value) { binaryArgs.push('--proxy-port'); binaryArgs.push(value); } break; case 'proxyuser': if (value) { binaryArgs.push('--proxy-user'); binaryArgs.push(value); } break; case 'proxypass': if (value) { binaryArgs.push('--proxy-pass'); binaryArgs.push(value); } break; case 'localdirectory': case 'localdir': case 'dir': case 'd': if (value) { binaryArgs.push('--dir'); binaryArgs.push(value); } break; case 'env': case 'environment': case 'e': if (value) { binaryArgs.push('--env'); binaryArgs.push(value); } break; case 'sharedtunnel': case 'shared-tunnel': if (value) { binaryArgs.push('--shared-tunnel'); } break; case 'logfile': if (value) { binaryArgs.push('--logFile'); binaryArgs.push(value); } break; case 'nows': if (value) { binaryArgs.push('--nows'); } break; case 'verbose': case 'v': if (value) { binaryArgs.push('-v'); } break; case 'mode': if (value && !options['legacy']) { binaryArgs.push('--' + key); binaryArgs.push(value); } break; case 'bypasshosts': if (value && !options['legacy']) { binaryArgs.push('--bypassHosts'); binaryArgs.push(value); } break; case 'callbackurl': if (value && !options['legacy']) { binaryArgs.push('--callbackURL'); binaryArgs.push(value); } break; case 'localdomains': case 'local-domains': if (value && !options['legacy']) { binaryArgs.push('--local-domains'); binaryArgs.push(value); } break; case 'basic-auth': case 'b': if (value && !options['legacy']) { binaryArgs.push('-b'); binaryArgs.push(value); } break; case 'mitm': case 'm': if (value && !options['legacy']) { binaryArgs.push('--mitm'); } break; case 'noproxy': case 'no-proxy': if (value && !options['legacy']) { binaryArgs.push('--no-proxy'); binaryArgs.push(value); } break; case 'ingressonly': if (value && !options['legacy']) { binaryArgs.push('--ingress-only'); } break; case 'egressonly': if (value && !options['legacy']) { binaryArgs.push('--egress-only'); } break; case 'sshconntype': if (value && !options['legacy']) { binaryArgs.push('--sshConnType'); binaryArgs.push(value); // will work only if mode === "ssh", allowed values (over_22, over_443, over_ws), can give error if any other value specified } break; case 'config': if (value) { binaryArgs.push('--config'); binaryArgs.push(value); } break; case 'clientcert': if (value) { binaryArgs.push('--clientCert'); binaryArgs.push(value); } break; case 'clientkey': if (value) { binaryArgs.push('--clientKey'); binaryArgs.push(value); } break; case 'mtlshosts': if (value) { binaryArgs.push('--mTLSHosts'); binaryArgs.push(value); } break; case 'pacfile': if (value) { binaryArgs.push('--pacfile'); binaryArgs.push(value); } break; case 'allowhosts': if (value) { binaryArgs.push('--allowHosts'); binaryArgs.push(value); } break; case 'serverdomain': if (value) { binaryArgs.push('--server-domain'); binaryArgs.push(value); } break; case 'ntlm': if (value) { binaryArgs.push('--ntlm'); } break; case 'ntlmusername': if (value) { binaryArgs.push('--ntlm-username'); binaryArgs.push(value) } break; case 'ntlmpassword': if (value) { binaryArgs.push('--ntlm-password'); binaryArgs.push(value) } break; case 'maxsshconnections': if (value) { binaryArgs.push('--maxSSHConnections'); binaryArgs.push(value) } break; case 'loglevel': case 'log-level': if (value) { binaryArgs.push('--log-level'); binaryArgs.push(value) } break; } } if (binaryArgs.indexOf('--controller') === -1) { binaryArgs.push('--controller'); binaryArgs.push('npm'); } // Get free port with max attempt (5) getFreePort_(options, 5, function(e, port) { if (typeof port === 'number') { binaryArgs.push('--infoAPIPort'); binaryArgs.push(port); // add tunnel name with hostname and port. var tunnelNameIndex = binaryArgs.indexOf('--tunnelName'); var hostnamePort = os.hostname() + port; if (tunnelNameIndex === -1) { binaryArgs.push('--tunnelName'); binaryArgs.push(hostnamePort); } // add logfile name if not given. var logFileIndex = binaryArgs.indexOf('--logFile'); if (logFileIndex === -1) { binaryArgs.push('--logFile'); binaryArgs.push(hostnamePort + '.log'); } } self.infoAPIPort = port; process.env.INFO_API_PORT = self.infoAPIPort; logger.log( options['user'], options['key'], { filename: __filename }, options, 'Info of passed tunnel arguments ' + binaryArgs ); getFreePort_(options, 5, function(e, port_) { if (typeof port_ === 'number') { binaryArgs.push('--port'); binaryArgs.push(port_); return fnCallback(false, binaryArgs); } }); }); } catch (e) { return fnCallback(true, e); } } /** * getFreePort_ is used to find free port on your system * @param {Setting} options is User passed arguments. * @param {number} retries max attempt, retries to get port. * @param {Function} fnCallback is a Callable function. * @return {number|Error} Return Free Port or Error if any. */ function getFreePort_(options, retries, fnCallback) { try { getPort(function(e, port) { if (e || usingPorts.indexOf(port) > -1) { if (retries >= 0) { getFreePort_(options, retries - 1, fnCallback); } else { logger.log( options['user'], options['key'], { filename: __filename }, options, 'Error trying to get Free Port on LambdaTest Tunnel' + e ); throw Error( 'Error trying to get Free Port on LambdaTest Tunnel, Please contact support' + e ); } } usingPorts.push(port); return fnCallback(false, port); }); } catch (e) { logger.log( options['user'], options['key'], { filename: __filename }, options, 'Error trying to get Free Port on LambdaTest Tunnel' + e ); throw Error('Error trying to get Free Port on LambdaTest Tunnel, Please contact support' + e); } } /** * getBinaryPath_ is used for getting binary Path * @param {Tunnel} self is Current Tunnel Instance. * @param {Setting} options is User passed arguments. * @param {Function} fnCallback is a Callable function. * @return {string|Error} Return binary path or Error if any. */ function getBinaryPath_(that, options, fnCallback) { // if path is there then Return path else get from Server if (typeof that.binaryPath == 'undefined') { that.binary = new TunnelBinary(httpTunnelConfig, options); var conf = { user: options['user'], key: options['key'] }; // Do this for getting binary using Proxy. if ( (options['proxyHost'] || options['proxyhost']) && (options['proxyPort'] || options['proxyport']) ) { conf.proxyHost = options['proxyHost'] || options['proxyhost']; conf.proxyPort = options['proxyPort'] || options['proxyport']; } if ( (options['proxyUser'] || options['proxyuser']) && (options['proxyPass'] || options['proxypass']) ) { conf.proxyUser = options['proxyUser'] || options['proxyuser']; conf.proxyPass = options['proxyPass'] || options['proxypass']; } that.binary.binaryPath_(conf, fnCallback); } else { return fnCallback(that.binaryPath); } } /** * retryTunnelName_ is used to get Running Tunnel Name and Retries to get this * atmost 5 attempt. * @param {Tunnel} self is Current Tunnel Instance. * @param {number} infoAPIPort running local Server Port. * @param {number} retries max attempt, retries to get port. * @param {Function} fnCallback is a Callable function. * @return {string|null} Return tunnelName or null if any. */ function retryTunnelName_(self, infoAPIPort, retries, fnCallback) { try { // Check whether max retries is reached ?. if (retries >= 0) { // local Server path for getting tunnelName var url = 'http://127.0.0.1:' + infoAPIPort + '/api/v1.0/info'; var reqOptions = urlParse.parse(url); http .get(reqOptions, response => { let json = ''; response.on('data', chunk => { json += chunk; }); response.on('end', () => { // After successfully getting name clear the timeout and return name if (response.statusCode === 200) { if (typeof json === 'string') json = JSON.parse(json); return fnCallback((json.data && json.data.tunnelName) || null); } else { sleep(500); return retryTunnelName_(self, infoAPIPort, retries - 1, fnCallback); } }); }) .on('error', e => { logger.log( self.options['user'], self.options['key'], { filename: __filename }, self.options, 'Getting error while trying to get the tunnel name from running local server. Error : ' + e ); // wait .5s for next retries sleep(500); return retryTunnelName_(self, infoAPIPort, retries - 1, fnCallback); }); } else { sleep(500); logger.log( self.options['user'], self.options['key'], { filename: __filename }, self.options, 'Number of retries to to get tunnel name exceeded.' ); if (globalForceRetries <= 0) { console.log(` Failed to run tunnel binary may be due to Tunnel server ts.lambdatest.com not reachable. Please run curl ts.lambdatest.com/health" to check OR Tunnel Binary not compatible to the platform. Please delete .lambdatest folder and run the test again. `); } return fnCallback(null); } } catch (e) { logger.log( self.options['user'], self.options['key'], { filename: __filename }, self.options, 'Something unexpected while trying to get tunnel name from local server. Error : ' + e ); return fnCallback(null); } } /** * clearTimeout_ is used to clear setTimeout method. * @param {string} timeoutId is setTimeout Instance. * @return {null} Return null. */ function clearTimeout_(timeoutId) { if (timeoutId) { clearTimeout(timeoutId); } } /** * killProcess_ is used to kill process method. * @param {Object} that is Instance. * @param {Function} fnCallback is Callback Method. * @return {null} Return null. */ function killProcess_(that, fnCallback) { that.proc.on('exit', function() { if (that.proc.pid) { sleep(500); try { process.kill(that.proc.pid); } catch (err) {} return fnCallback(); } return fnCallback(); }); if (os.platform() == 'win32') { childProcess.exec('taskkill /F /PID ' + that.proc.pid); } else { that.proc.stdin.pause(); that.proc.kill('SIGINT'); } } function sleep(sleepDuration) { var now = new Date().getTime(); while (new Date().getTime() < now + sleepDuration) { /* do nothing */ } } /** * getTunnelID is used to Run Tunnel Binary * @param {number} infoAPIPort running local Server Port. * @param {number} retries max attempt, retries to get port. * @return {number/boolean} Return id/false whether tunnel is Running Successfully or * not. */ async function getTunnelID(infoAPIPort, retries){ try { if (retries > 0) { var url = 'http://127.0.0.1:' + infoAPIPort + '/api/v1.0/info'; try { const response = await axios.get(url) if (response.data && response.data.data && response.data.data.id){ let tunnelID = response.data.data.id console.log("tunnel started with ID: ", tunnelID) return tunnelID } } catch (_) { console.log("Tunnel is not yet started. Retrying in 2 seconds") } await new Promise(r => setTimeout(r, 2000)); console.log("check tunnel status attempts left: ", retries - 1); return getTunnelID(infoAPIPort, retries - 1) } } catch (error){ console.error(error) } return false } /** * runBinaryV2_ is used to Run Tunnel Binary * @param {Tunnel} self is Current Tunnel Instance. * @param {number} retries max attempt try to Run tunnel. * @return {boolean} Return true/false whether tunnel is Running Successfully or * not. */ async function runBinaryV2_(self, retries) { console.log("Detached Mode Retries Left: ", retries) try{ if (retries >= 0) { // addArguments_ method return formatted argumants or error if any. // Run Binary with argumants in spawn process. let binaryArguments = [] await addArguments_(self, (bool, data)=>{ binaryArguments = [...data] }) var subprocess = childProcess.spawn(self.binaryPath, binaryArguments, { detached:true, stdio:'ignore' }); subprocess.unref() let indexOfInfoAPIPort = binaryArguments.indexOf("--infoAPIPort") if (indexOfInfoAPIPort != -1 && indexOfInfoAPIPort < binaryArguments.length){ let infoAPIPortValue = binaryArguments[indexOfInfoAPIPort + 1] let tunnelID = await getTunnelID(infoAPIPortValue, maxCheckTunnelStatusRetries) if (tunnelID){ console.log("You can start testing now. Tunnel Started Succesfully with ID: ", tunnelID) return true } } else { return Error("Invalid Arguments") } } } catch (error){ console.log("Error while starting tunnel, Retrying ....") runBinaryV2_(self, retries - 1) } } module.exports = Tunnel; module.exports.Tunnel = Tunnel;