From ac9f9c91fceb91d749dd5eac3c367a14f20a953b Mon Sep 17 00:00:00 2001 From: amaanbs Date: Fri, 6 Jun 2025 04:27:16 +0530 Subject: [PATCH 1/2] change binary download distribution --- lib/Local.js | 4 +- lib/LocalBinary.js | 78 ++++++++++++++++++++++++++++------- lib/fetchDownloadSourceUrl.js | 57 +++++++++++++++++++++++++ test/local.js | 6 +-- 4 files changed, 126 insertions(+), 19 deletions(-) create mode 100644 lib/fetchDownloadSourceUrl.js diff --git a/lib/Local.js b/lib/Local.js index 9876c6d..41945c8 100644 --- a/lib/Local.js +++ b/lib/Local.js @@ -254,9 +254,9 @@ function Local(){ conf.useCaCertificate = this.useCaCertificate; } if(!callback) { - return this.binary.binaryPath(conf); + return this.binary.binaryPath(conf, this.key); } - this.binary.binaryPath(conf, callback); + this.binary.binaryPath(conf, this.key, callback); } else { console.log('BINARY PATH IS DEFINED'); if(!callback) { diff --git a/lib/LocalBinary.js b/lib/LocalBinary.js index 073bf61..d5bb22d 100644 --- a/lib/LocalBinary.js +++ b/lib/LocalBinary.js @@ -14,9 +14,44 @@ const packageName = 'browserstack-local-nodejs'; function LocalBinary(){ this.hostOS = process.platform; this.is64bits = process.arch == 'x64'; + this.baseRetries = 10; + this.sourceURL = null; + this.downloadErrorMessage = null; + + this.getSourceUrl = function(conf, retries) { + /* Request for an endpoint from Rails no more than twice with 5 retries each */ + if (![5, 10].includes(retries) && this.sourceURL) return this.sourceURL; + + let cmd, opts; + cmd = 'node'; + opts = [path.join(__dirname, 'fetchDownloadSourceUrl.js'), this.key]; + 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); + } - this.getDownloadPath = function () { - let sourceURL = 'https://www.browserstack.com/local-testing/downloads/binaries/'; + if (retries == 5) { + opts.push(true, this.binaryDownloadError); + } + + 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; + 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'; @@ -43,9 +78,10 @@ function LocalBinary(){ } }; - this.httpPath = this.getDownloadPath(); - - + this.binaryDownloadError = function(errorMessagePrefix, errorObject) { + console.error(errorMessagePrefix, errorObject); + this.downloadErrorMessage = errorMessagePrefix + " : " + errorObject ? errorObject.getMessage() : "null"; + } this.retryBinaryDownload = function(conf, destParentDir, callback, retries, binaryPath) { var that = this; @@ -66,6 +102,12 @@ function LocalBinary(){ }; 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)) @@ -96,21 +138,27 @@ function LocalBinary(){ fs.chmodSync(binaryPath, '0755'); return binaryPath; }else{ - console.log('failed to download'); + 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(); - console.error(output); + that.binaryDownloadError(output); return that.retryBinaryDownload(conf, destParentDir, null, retries, binaryPath); } } catch(err) { - console.error('Download failed with error', err); + that.binaryDownloadError('Download failed with error', 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); @@ -152,11 +200,11 @@ function LocalBinary(){ } response.on('error', function(err) { - console.error('Got Error in binary download response', err); + that.binaryDownloadError('Got Error in binary download response', err); that.retryBinaryDownload(conf, destParentDir, callback, retries, binaryPath); }); fileStream.on('error', function (err) { - console.error('Got Error while downloading binary file', err); + that.binaryDownloadError('Got Error while downloading binary file', err); that.retryBinaryDownload(conf, destParentDir, callback, retries, binaryPath); }); fileStream.on('close', function () { @@ -165,12 +213,13 @@ function LocalBinary(){ }); }); }).on('error', function(err) { - console.error('Got Error in binary downloading request', err); + that.binaryDownloadError('Got Error in binary downloading request', err); that.retryBinaryDownload(conf, destParentDir, callback, retries, binaryPath); }); }; - this.binaryPath = function(conf, callback){ + this.binaryPath = function(conf, key, callback){ + this.key = key; var destParentDir = this.getAvailableDirs(); var destBinaryName = (this.windows) ? 'BrowserStackLocal.exe' : 'BrowserStackLocal'; var binaryPath = path.join(destParentDir, destBinaryName); @@ -180,10 +229,11 @@ function LocalBinary(){ } callback(binaryPath); } else { + let retries = this.baseRetries; if(!callback) { - return this.downloadSync(conf, destParentDir, 5); + return this.downloadSync(conf, destParentDir, retries); } - this.download(conf, destParentDir, callback, 5); + this.download(conf, destParentDir, callback, retries); } }; diff --git a/lib/fetchDownloadSourceUrl.js b/lib/fetchDownloadSourceUrl.js new file mode 100644 index 0000000..503b3de --- /dev/null +++ b/lib/fetchDownloadSourceUrl.js @@ -0,0 +1,57 @@ +const https = require('https'), + fs = require('fs'), + HttpsProxyAgent = require('https-proxy-agent'); + +const authToken = process.argv[2], proxyHost = process.argv[3], proxyPort = process.argv[4], useCaCertificate = process.argv[5], downloadFallback = process.argv[6], downloadErrorMessage = process.argv[7]; + +let body = '', data = {"auth_token": authToken}; +const options = { + hostname: 'k8s-devlocal.bsstag.com', + port: 443, + path: '/binary/api/v1/endpoint', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'user-agent': process.env.USER_AGENT + } +}; +if (downloadFallback) { + options.headers['X-Local-Fallback-Cloudflare'] = true; + data["error_message"] = downloadErrorMessage; +} + +if(proxyHost && proxyPort) { + options.agent = new HttpsProxyAgent({ + host: proxyHost, + port: proxyPort + }); +} +if (useCaCertificate) { + try { + options.ca = fs.readFileSync(useCaCertificate); + } catch(err) { + console.log('failed to read cert file', err); + } +} + +const req = https.request(options, res => { + res.on('data', d => { + body += d; + }); + res.on('end', () => { + try { + const url = JSON.parse(body).data.endpoint; + console.log(url); + } catch (e) { + console.error(e); + } + }); + res.on('error', (err) => { + console.error(err); + }) +}); +req.on('error', e => { + console.error(e); +}); +req.write(JSON.stringify(data)); +req.end(); diff --git a/test/local.js b/test/local.js index cdda649..5405a93 100644 --- a/test/local.js +++ b/test/local.js @@ -280,7 +280,7 @@ describe('LocalBinary', function () { // ensure that we have a valid binary downloaded // removeIfInvalid(); - (new LocalBinary()).binaryPath({}, function(binaryPath) { + (new LocalBinary()).binaryPath({}, 'abc', function(binaryPath) { defaultBinaryPath = binaryPath; tempfs.mkdir({ recursive: true @@ -313,7 +313,7 @@ describe('LocalBinary', function () { fs.writeFile(defaultBinaryPath, 'Random String', function() { fs.chmod(defaultBinaryPath, '0755', function() { localBinary.binaryPath({ - }, function(binaryPath) { + }, 'abc', function(binaryPath) { expect(downloadStub.called).to.be.true; done(); }); @@ -331,7 +331,7 @@ describe('LocalBinary', function () { }); localBinary.binaryPath({ - }, function(binaryPath) { + }, 'abc', function(binaryPath) { expect(downloadStub.called).to.be.true; done(); }); From 66f260ef0195d36bd0b4f0aa36f5c7476f4cd05b Mon Sep 17 00:00:00 2001 From: amaanbs Date: Fri, 6 Jun 2025 05:26:02 +0530 Subject: [PATCH 2/2] fix hostname --- lib/fetchDownloadSourceUrl.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fetchDownloadSourceUrl.js b/lib/fetchDownloadSourceUrl.js index 503b3de..829f37e 100644 --- a/lib/fetchDownloadSourceUrl.js +++ b/lib/fetchDownloadSourceUrl.js @@ -6,7 +6,7 @@ const authToken = process.argv[2], proxyHost = process.argv[3], proxyPort = proc let body = '', data = {"auth_token": authToken}; const options = { - hostname: 'k8s-devlocal.bsstag.com', + hostname: 'local.browserstack.com', port: 443, path: '/binary/api/v1/endpoint', method: 'POST',