@lambdatest/node-tunnel
Version:
Nodejs bindings for LambdaTest Tunnel
349 lines (333 loc) • 12 kB
JavaScript
var localTunnelConfig_ = require('./cfg/node-tunnel-config-v3-latest.json'),
logger,
https = require('https'),
urlParse = require('url'),
HttpsProxyAgent = require('https-proxy-agent'),
util = require('./util'),
AdmZip = require('adm-zip'),
fs = require('fs'),
path = require('path'),
os = require('os'),
executableName = 'LT';
/**
* TunnelBinary is a function based Class.
*/
function TunnelBinary(httpTunnelConfig, options) {
/**
* Try to find out binary based on platform arch.
*/
this.binaryVersion = 'v3';
// if (options['legacy']) {
// this.binaryVersion = 'v2';
// executableName = 'ltcomponent';
// }
this.httpTunnelConfig = httpTunnelConfig;
this.hostOS = process.platform;
this.bits = process.arch === 'x64' || process.arch === 'arm64' ? '64bit' : '32bit';
if (this.hostOS.match(/darwin|mac os/i)) {
this.platform = 'mac';
} else if (this.hostOS.match(/mswin|msys|mingw|cygwin|bccwin|wince|emc|win32/i)) {
this.platform = 'win';
this.windows = true;
} else if (this.hostOS.match(/freebsd/i)) {
this.platform = 'freebsd';
} else {
this.platform = 'linux';
}
this.httpPath = this.httpTunnelConfig.jsonResponse.binaryLinks[this.platform][this.binaryVersion][
this.bits
].httpPath;
this.binaryName = this.httpTunnelConfig.jsonResponse.binaryLinks[this.platform][
this.binaryVersion
][this.bits].binaryName;
this.httpHashContents = this.httpTunnelConfig.jsonResponse.binaryLinks[this.platform][
this.binaryVersion
][this.bits].hash;
this.localHashContents =
localTunnelConfig_.binaryLinks[this.platform][this.binaryVersion][this.bits].hash;
logger = this.httpTunnelConfig.logger;
/**
* retryBinaryDownload_ is used to download binary while getting Error duing
* download.
* @param {!Setting} conf is command line argumants passed during Initialize
* the Tunnel
* @param {Object} destParentDir is home Dir for Downloaded binary e.g.
* `.lambdatest`.
* @param {number} retries max tries for download.
* @param {number} binaryPath path of downloaded binary.
* @param {Function} fnCallback is a Callable function.
* @return {Method<download_>|Error} Return download_ method or Error of max
* count Exceeded.
*/
this.retryBinaryDownload_ = function(conf, destParentDir, retries, binaryPath, fnCallback) {
var self = this;
if (retries > 0) {
console.log('Retrying Download. Retries left', retries);
fs.stat(binaryPath, function(e) {
if (!e) {
fs.unlinkSync(binaryPath);
}
self.download_(conf, destParentDir, retries - 1, fnCallback);
});
} else {
console.error('Number of retries to download exceeded.');
}
};
/**
* download_ is used to download binary as zip File.
* @param {!Setting} conf is command line argumants passed during Initialize
* the Tunnel
* @param {Object} destParentDir is home Dir for Downloaded binary e.g.
* `.lambdatest`.
* @param {number} retries max tries for download.
* @param {Function} fnCallback is a Callable function.
* @return {string|Error} Return BinaryPath or Error while Extraction Failed.
*/
this.download_ = function(conf, destParentDir, retries, fnCallback) {
try {
console.log(`Downloading latest binary`);
// make Dir if not there.
if (!this.checkPath_(destParentDir)) {
fs.mkdirSync(destParentDir, { recursive: true });
}
// Generate binary path.
var binaryPath = path.join(destParentDir, this.binaryName);
var fileStream = fs.createWriteStream(binaryPath);
var self = this;
// Set Proxy If User passed this to in arguments.
var options = urlParse.parse(this.httpPath);
var proxyOpts = util.getProxyOpts_(conf);
if (Object.keys(proxyOpts).length) {
options.agent = new HttpsProxyAgent(proxyOpts);
}
// Get binary as zip File from https Server and put this to local folder.
// After fully download, unzip and change mode to excutable.
https
.get(options, function(response) {
response.pipe(fileStream);
response.on('error', function(e) {
logger.log(
conf['user'],
conf['key'],
{ filename: __filename },
conf,
'Got Error while unzip downloading binary' + e
);
self.retryBinaryDownload_(conf, destParentDir, retries, binaryPath, fnCallback);
});
fileStream.on('error', function(e) {
logger.log(
conf['user'],
conf['key'],
{ filename: __filename },
conf,
'Got Error while unzip downloading binary' + e
);
self.retryBinaryDownload_(conf, destParentDir, retries, binaryPath, fnCallback);
});
fileStream.on('close', function() {
if (self.checkPath_(binaryPath)) {
var unzipBinaryPath = path.join(destParentDir, executableName);
var destBinaryName = executableName;
if (self.windows) {
destBinaryName += '.exe';
}
var destBinaryPath = path.join(destParentDir, destBinaryName);
// Reading and Unzipping binary zip File
console.log(`Extracting binary`);
try {
var zip = new AdmZip(binaryPath);
zip.extractAllTo(destParentDir, false);
fs.chmod(destBinaryPath, '0755', function() {
return fnCallback(destBinaryPath)
});
} catch (e) {
console.log("Error log ------> ", e);
logger.log(
conf['user'],
conf['key'],
{ filename: __filename },
conf,
'Got Error while unzip downloading binary' + e
);
self.retryBinaryDownload_(conf, destParentDir, retries, binaryPath, fnCallback);
}
} else {
console.error('Got Error while downloading binary zip');
logger.log(
conf['user'],
conf['key'],
{ filename: __filename },
conf,
'Got Error while downloading binary zip'
);
self.retryBinaryDownload_(conf, destParentDir, retries, binaryPath, fnCallback);
}
});
})
.on('error', function(e) {
logger.log(
conf['user'],
conf['key'],
{ filename: __filename },
conf,
'Got Error while unzip downloading binary' + e
);
self.retryBinaryDownload_(conf, destParentDir, retries, binaryPath, fnCallback);
});
} catch (e) {
console.error('Got Error while downloading binary zip', e);
logger.log(conf['user'], conf['key'], { filename: __filename }, conf, {
message: 'Got Error while Extracting binary zip',
e: e
});
}
};
/**
* binaryPath_ is used to find Executable binary File Path.
* @param {!Setting} conf is command line argumants passed during Initialize
* the Tunnel
* @param {Function} fnCallback is a Callable function.
* @return {string|Error} Return BinaryPath or Error.
*/
this.binaryPath_ = function(conf, fnCallback) {
try {
var destParentDir = this.availableDirs_();
var destBinaryName = executableName;
if (this.windows) {
destBinaryName += '.exe';
}
var binaryPath = path.join(destParentDir, destBinaryName);
// Check whether executable binary File is exist or need to Downlaod.
if (this.checkPath_(binaryPath, fs.X_OK) && !process.env.LT_FORCE_DOWNLOAD) {
var that = this;
console.log(`Checking for updates`);
// Comparing with local hash to find out binary changes.
if (this.httpHashContents === this.localHashContents) {
console.log(`Binary already at latest version`);
return fnCallback(binaryPath);
} else {
console.log(`Binary is deprecated`);
try {
fs.writeFileSync(
__dirname + '/cfg/node-tunnel-config-v3-latest.json',
JSON.stringify(this.httpTunnelConfig.jsonResponse)
);
} catch (e) {
console.log(
'Permission denied! Please execute the command using sudo or provide the required read & write permissions to file : ',
__dirname + '/cfg/node-tunnel-config-v3-latest.json'
);
throw e;
}
localTunnelConfig_ = this.httpTunnelConfig.jsonResponse;
that.download_(conf, destParentDir, 5, fnCallback);
}
} else {
delete process.env.LT_FORCE_DOWNLOAD;
this.download_(conf, destParentDir, 5, fnCallback);
}
} catch (e) {
logger.log(conf['user'], conf['key'], { filename: __filename }, conf, e);
}
};
/**
* binaryPath_ is used for verifying exist of File.
* @param {!string} path is File path
* @param {!string} mode is File mode
* @return {boolean}
*/
this.checkPath_ = function(path, mode) {
try {
mode = mode || fs.R_OK | fs.W_OK;
fs.accessSync(path, mode);
return true;
} catch (e) {
// console.log('Error checkPath ----->', e);
if (typeof fs.accessSync !== 'undefined') {
return false;
}
try {
fs.statSync(path);
return true;
} catch (e) {
return false;
}
}
};
/**
* availableDirs_ Check whether ordered Dir is priviledged for creation.
* @return {string} Dir that has Required priviledge for creation of Dir.
*/
this.availableDirs_ = function() {
var orderedPathLength = this.orderedPaths.length;
var _path,
iCounter = 0;
for (var i = 0; i < orderedPathLength; i++) {
iCounter++;
_path = this.orderedPaths[i];
if (this.makePath_(_path)) {
break;
}
}
if (iCounter < orderedPathLength) {
return _path;
}
throw Error('Error trying to download LambdaTest Tunnel binary');
};
/**
* makePath_ Make Dir if not there.
* @return {boolean} Dir is Created or not.
*/
this.makePath_ = function(path) {
try {
if (!this.checkPath_(path)) {
fs.mkdirSync(path, { recursive: true });
}
return true;
} catch (e) {
return false;
}
};
/**
* homeDir_ Find out home Dir of Platform.
* @return {string|null} home Dir or null.
*/
this.homeDir_ = function() {
if (typeof process.cwd === 'function') return process.cwd();
if (typeof os.homedir === 'function') return os.homedir();
var env = process.env;
var home = env.HOME;
var user = env.LOGNAME || env.USER || env.LNAME || env.USERNAME;
if (process.platform === 'win32') {
return env.USERPROFILE || env.HOMEDRIVE + env.HOMEPATH || home || null;
}
if (process.platform === 'darwin') {
return home || (user ? '/Users/' + user : null);
}
if (process.platform === 'linux') {
return home || (process.getuid() === 0 ? '/root' : user ? '/home/' + user : null);
}
return home || null;
};
/**
* rmDir remove directory and sub-directory.
*/
this.rmDir = function(dir) {
var self = arguments.callee;
if (fs.existsSync(dir)) {
fs.readdirSync(dir).forEach(function(file) {
var C = dir + '/' + file;
if (fs.statSync(C).isDirectory()) self(C);
else fs.unlinkSync(C);
});
fs.rmdirSync(dir);
}
};
this.orderedPaths = [
path.join(this.homeDir_(), '.lambdatest', this.binaryVersion),
path.join(process.cwd(), this.binaryVersion),
path.join(os.tmpdir(), this.binaryVersion)
];
}
module.exports = TunnelBinary;