UNPKG

browserstack-local

Version:
321 lines (281 loc) 10.6 kB
var https = require('https'), url = require('url'), fs = require('fs'), path = require('path'), os = require('os'), util = require('util'), childProcess = require('child_process'), zlib = require('zlib'), HttpsProxyAgent = require('https-proxy-agent'), version = require('../package.json').version, LocalError = require('./LocalError'); const packageName = 'browserstack-local-nodejs'; function LocalBinary(){ this.hostOS = process.platform; this.is64bits = process.arch == 'x64'; this.baseRetries = 9; this.sourceURL = null; this.downloadErrorMessage = null; this.getSourceUrl = function(conf, retries) { /* Request for an endpoint to download the local binary from Rails no more than twice with 5 retries each */ if (![4, 9].includes(retries) && this.sourceURL != null) { return this.sourceURL; } if (process.env.BINARY_DOWNLOAD_SOURCE_URL !== undefined && process.env.BINARY_DOWNLOAD_FALLBACK_ENABLED == 'true' && this.parentRetries != 4) { /* This is triggered from Local.js if there's an error executing the downloaded binary */ return process.env.BINARY_DOWNLOAD_SOURCE_URL; } let cmd, opts; cmd = 'node'; opts = [path.join(__dirname, 'fetchDownloadSourceUrl.js'), this.key]; if (retries == 4 || (process.env.BINARY_DOWNLOAD_FALLBACK_ENABLED == 'true' && this.parentRetries == 4)) { opts.push(true, this.downloadErrorMessage || process.env.BINARY_DOWNLOAD_ERROR_MESSAGE); } else { opts.push(false, null); } if(conf.proxyHost && conf.proxyPort) { opts.push(conf.proxyHost, conf.proxyPort); if (conf.useCaCertificate) { opts.push(conf.useCaCertificate); } } else if (conf.useCaCertificate) { opts.push(undefined, undefined, conf.useCaCertificate); } const userAgent = [packageName, version].join('/'); const env = Object.assign({ 'USER_AGENT': userAgent }, process.env); const obj = childProcess.spawnSync(cmd, opts, { env: env }); if(obj.stdout.length > 0) { this.sourceURL = obj.stdout.toString().replace(/\n+$/, ''); process.env.BINARY_DOWNLOAD_SOURCE_URL = this.sourceURL; return this.sourceURL; } else if(obj.stderr.length > 0) { let output = Buffer.from(JSON.parse(JSON.stringify(obj.stderr)).data).toString(); throw(output); } }; this.getDownloadPath = function (conf, retries) { let sourceURL = this.getSourceUrl(conf, retries) + '/'; if(this.hostOS.match(/darwin|mac os/i)){ return sourceURL + 'BrowserStackLocal-darwin-x64'; } else if(this.hostOS.match(/mswin|msys|mingw|cygwin|bccwin|wince|emc|win32/i)) { this.windows = true; return sourceURL + 'BrowserStackLocal.exe'; } else { if(this.is64bits) { if(this.isAlpine()) return sourceURL + 'BrowserStackLocal-alpine'; else return sourceURL + 'BrowserStackLocal-linux-x64'; } else { return sourceURL + 'BrowserStackLocal-linux-ia32'; } } }; this.isAlpine = function() { try { return childProcess.execSync('grep -w "NAME" /etc/os-release').includes('Alpine'); } catch(e) { return false; } }; this.binaryDownloadError = function(errorMessagePrefix, errorMessage) { console.error(errorMessagePrefix, errorMessage); this.downloadErrorMessage = errorMessagePrefix + ' : ' + errorMessage; }; this.retryBinaryDownload = function(conf, destParentDir, callback, retries, binaryPath) { var that = this; if(retries > 0) { console.log('Retrying Download. Retries left', retries); fs.stat(binaryPath, function(err) { if(err == null) { fs.unlinkSync(binaryPath); } if(!callback) { return that.downloadSync(conf, destParentDir, retries - 1); } that.download(conf, destParentDir, callback, retries - 1); }); } else { console.error('Number of retries to download exceeded.'); } }; this.downloadSync = function(conf, destParentDir, retries) { try { this.httpPath = this.getDownloadPath(conf, retries); } catch (e) { return console.error(`Unable to fetch the source url to download the binary with error: ${e}`); } console.log('Downloading in sync'); var that = this; if(!this.checkPath(destParentDir)) fs.mkdirSync(destParentDir); var destBinaryName = (this.windows) ? 'BrowserStackLocal.exe' : 'BrowserStackLocal'; var binaryPath = path.join(destParentDir, destBinaryName); let cmd, opts; cmd = 'node'; opts = [path.join(__dirname, 'download.js'), binaryPath, this.httpPath]; if(conf.proxyHost && conf.proxyPort) { opts.push(conf.proxyHost, conf.proxyPort); if (conf.useCaCertificate) { opts.push(conf.useCaCertificate); } } else if (conf.useCaCertificate) { opts.push(undefined, undefined, conf.useCaCertificate); } try{ const userAgent = [packageName, version].join('/'); const env = Object.assign({ 'USER_AGENT': userAgent }, process.env); const obj = childProcess.spawnSync(cmd, opts, { env: env }); let output; if(obj.stdout.length > 0) { if(fs.existsSync(binaryPath)){ fs.chmodSync(binaryPath, '0755'); return binaryPath; }else{ that.binaryDownloadError('failed to download'); return that.retryBinaryDownload(conf, destParentDir, null, retries, binaryPath); } } else if(obj.stderr.length > 0) { output = Buffer.from(JSON.parse(JSON.stringify(obj.stderr)).data).toString(); that.binaryDownloadError(output); return that.retryBinaryDownload(conf, destParentDir, null, retries, binaryPath); } } catch(err) { that.binaryDownloadError('Download failed with error', util.format(err)); return that.retryBinaryDownload(conf, destParentDir, null, retries, binaryPath); } }; this.download = function(conf, destParentDir, callback, retries){ try { this.httpPath = this.getDownloadPath(conf, retries); } catch (e) { return console.error(`Unable to fetch the source url to download the binary with error: ${e}`); } var that = this; if(!this.checkPath(destParentDir)) fs.mkdirSync(destParentDir); var destBinaryName = (this.windows) ? 'BrowserStackLocal.exe' : 'BrowserStackLocal'; var binaryPath = path.join(destParentDir, destBinaryName); var fileStream = fs.createWriteStream(binaryPath); var options = url.parse(this.httpPath); if(conf.proxyHost && conf.proxyPort) { options.agent = new HttpsProxyAgent({ host: conf.proxyHost, port: conf.proxyPort }); } if (conf.useCaCertificate) { try { options.ca = fs.readFileSync(conf.useCaCertificate); } catch(err) { console.log('failed to read cert file', err); } } options.headers = Object.assign({}, options.headers, { 'accept-encoding': 'gzip, *', 'user-agent': [packageName, version].join('/'), }); https.get(options, function (response) { const contentEncoding = response.headers['content-encoding']; if (typeof contentEncoding === 'string' && contentEncoding.match(/gzip/i)) { if (process.env.BROWSERSTACK_LOCAL_DEBUG_GZIP) { console.info('Using gzip in ' + options.headers['user-agent']); } response.pipe(zlib.createGunzip()).pipe(fileStream); } else { response.pipe(fileStream); } response.on('error', function(err) { that.binaryDownloadError('Got Error in binary download response', util.format(err)); that.retryBinaryDownload(conf, destParentDir, callback, retries, binaryPath); }); fileStream.on('error', function (err) { that.binaryDownloadError('Got Error while downloading binary file', util.format(err)); that.retryBinaryDownload(conf, destParentDir, callback, retries, binaryPath); }); fileStream.on('close', function () { fs.chmod(binaryPath, '0755', function() { callback(binaryPath); }); }); }).on('error', function(err) { that.binaryDownloadError('Got Error in binary downloading request', util.format(err)); that.retryBinaryDownload(conf, destParentDir, callback, retries, binaryPath); }); }; this.binaryPath = function(conf, key, parentRetries, callback){ this.key = key; this.parentRetries = parentRetries; var destParentDir = this.getAvailableDirs(); var destBinaryName = (this.windows) ? 'BrowserStackLocal.exe' : 'BrowserStackLocal'; var binaryPath = path.join(destParentDir, destBinaryName); if(this.checkPath(binaryPath, fs.X_OK)){ if(!callback) { return binaryPath; } callback(binaryPath); } else { let retries = this.baseRetries; if(!callback) { return this.downloadSync(conf, destParentDir, retries); } this.download(conf, destParentDir, callback, retries); } }; this.checkPath = function(path, mode){ mode = mode || (fs.R_OK | fs.W_OK); try { fs.accessSync(path, mode); return true; } catch(e){ if(typeof fs.accessSync !== 'undefined') return false; // node v0.10 try { fs.statSync(path); return true; } catch (e){ return false; } } }; this.getAvailableDirs = function(){ for(var i=0; i < this.orderedPaths.length; i++){ var path = this.orderedPaths[i]; if(this.makePath(path)) return path; } throw new LocalError('Error trying to download BrowserStack Local binary'); }; this.makePath = function(path){ try { if(!this.checkPath(path)){ fs.mkdirSync(path); } return true; } catch(e){ return false; } }; this.homedir = function() { 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; }; this.orderedPaths = [ path.join(this.homedir(), '.browserstack'), process.cwd(), os.tmpdir() ]; } module.exports = LocalBinary;