Skip to content

Commit 56d7b62

Browse files
authored
Merge pull request #161 from browserstack/Change_Binary_Download_Distribution
Change Binary Download Distribution
2 parents 034d04c + d2f8aa8 commit 56d7b62

File tree

4 files changed

+153
-22
lines changed

4 files changed

+153
-22
lines changed

lib/Local.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
var childProcess = require('child_process'),
22
os = require('os'),
33
fs = require('fs'),
4+
util = require('util'),
45
path = require('path'),
56
running = require('is-running'),
67
LocalBinary = require('./LocalBinary'),
@@ -17,7 +18,7 @@ function Local(){
1718
this.windows = os.platform().match(/mswin|msys|mingw|cygwin|bccwin|wince|emc|win32/i);
1819
this.pid = undefined;
1920
this.isProcessRunning = false;
20-
this.retriesLeft = 5;
21+
this.retriesLeft = 9;
2122
this.key = process.env.BROWSERSTACK_ACCESS_KEY;
2223
this.logfile = this.sanitizePath(path.join(process.cwd(), 'local.log'));
2324
this.opcode = 'start';
@@ -59,12 +60,15 @@ function Local(){
5960
return;
6061
}
6162
}catch(error){
62-
console.error('Error while trying to execute binary', error);
63+
const binaryDownloadErrorMessage = `Error while trying to execute binary: ${util.format(error)}`;
64+
console.error(binaryDownloadErrorMessage);
6365
if(that.retriesLeft > 0) {
6466
console.log('Retrying Binary Download. Retries Left', that.retriesLeft);
6567
that.retriesLeft -= 1;
6668
fs.unlinkSync(that.binaryPath);
6769
delete(that.binaryPath);
70+
process.env.BINARY_DOWNLOAD_ERROR_MESSAGE = binaryDownloadErrorMessage;
71+
process.env.BINARY_DOWNLOAD_FALLBACK_ENABLED = true;
6872
return that.startSync(options);
6973
} else {
7074
throw new LocalError(error.toString());
@@ -87,12 +91,15 @@ function Local(){
8791
that.opcode = 'start';
8892
that.tunnel = childProcess.execFile(that.binaryPath, that.getBinaryArgs(), function(error, stdout, stderr){
8993
if(error) {
90-
console.error('Error while trying to execute binary', error);
94+
const binaryDownloadErrorMessage = `Error while trying to execute binary: ${util.format(error)}`;
95+
console.error(binaryDownloadErrorMessage);
9196
if(that.retriesLeft > 0) {
9297
console.log('Retrying Binary Download. Retries Left', that.retriesLeft);
9398
that.retriesLeft -= 1;
9499
fs.unlinkSync(that.binaryPath);
95100
delete(that.binaryPath);
101+
process.env.BINARY_DOWNLOAD_ERROR_MESSAGE = binaryDownloadErrorMessage;
102+
process.env.BINARY_DOWNLOAD_FALLBACK_ENABLED = true;
96103
that.start(options, callback);
97104
return;
98105
} else {
@@ -254,9 +261,9 @@ function Local(){
254261
conf.useCaCertificate = this.useCaCertificate;
255262
}
256263
if(!callback) {
257-
return this.binary.binaryPath(conf);
264+
return this.binary.binaryPath(conf, this.key, this.retriesLeft);
258265
}
259-
this.binary.binaryPath(conf, callback);
266+
this.binary.binaryPath(conf, this.key, this.retriesLeft, callback);
260267
} else {
261268
console.log('BINARY PATH IS DEFINED');
262269
if(!callback) {

lib/LocalBinary.js

Lines changed: 77 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ var https = require('https'),
33
fs = require('fs'),
44
path = require('path'),
55
os = require('os'),
6+
util = require('util'),
67
childProcess = require('child_process'),
78
zlib = require('zlib'),
89
HttpsProxyAgent = require('https-proxy-agent'),
@@ -14,9 +15,55 @@ const packageName = 'browserstack-local-nodejs';
1415
function LocalBinary(){
1516
this.hostOS = process.platform;
1617
this.is64bits = process.arch == 'x64';
18+
this.baseRetries = 9;
19+
this.sourceURL = null;
20+
this.downloadErrorMessage = null;
21+
22+
this.getSourceUrl = function(conf, retries) {
23+
/* Request for an endpoint to download the local binary from Rails no more than twice with 5 retries each */
24+
if (![4, 9].includes(retries) && this.sourceURL != null) {
25+
return this.sourceURL;
26+
}
27+
28+
if (process.env.BINARY_DOWNLOAD_SOURCE_URL !== undefined && process.env.BINARY_DOWNLOAD_FALLBACK_ENABLED == 'true' && this.parentRetries != 4) {
29+
/* This is triggered from Local.js if there's an error executing the downloaded binary */
30+
return process.env.BINARY_DOWNLOAD_SOURCE_URL;
31+
}
32+
33+
let cmd, opts;
34+
cmd = 'node';
35+
opts = [path.join(__dirname, 'fetchDownloadSourceUrl.js'), this.key];
36+
37+
if (retries == 4 || (process.env.BINARY_DOWNLOAD_FALLBACK_ENABLED == 'true' && this.parentRetries == 4)) {
38+
opts.push(true, this.downloadErrorMessage || process.env.BINARY_DOWNLOAD_ERROR_MESSAGE);
39+
} else {
40+
opts.push(false, null);
41+
}
42+
43+
if(conf.proxyHost && conf.proxyPort) {
44+
opts.push(conf.proxyHost, conf.proxyPort);
45+
if (conf.useCaCertificate) {
46+
opts.push(conf.useCaCertificate);
47+
}
48+
} else if (conf.useCaCertificate) {
49+
opts.push(undefined, undefined, conf.useCaCertificate);
50+
}
51+
52+
const userAgent = [packageName, version].join('/');
53+
const env = Object.assign({ 'USER_AGENT': userAgent }, process.env);
54+
const obj = childProcess.spawnSync(cmd, opts, { env: env });
55+
if(obj.stdout.length > 0) {
56+
this.sourceURL = obj.stdout.toString().replace(/\n+$/, '');
57+
process.env.BINARY_DOWNLOAD_SOURCE_URL = this.sourceURL;
58+
return this.sourceURL;
59+
} else if(obj.stderr.length > 0) {
60+
let output = Buffer.from(JSON.parse(JSON.stringify(obj.stderr)).data).toString();
61+
throw(output);
62+
}
63+
};
1764

18-
this.getDownloadPath = function () {
19-
let sourceURL = 'https://www.browserstack.com/local-testing/downloads/binaries/';
65+
this.getDownloadPath = function (conf, retries) {
66+
let sourceURL = this.getSourceUrl(conf, retries) + '/';
2067

2168
if(this.hostOS.match(/darwin|mac os/i)){
2269
return sourceURL + 'BrowserStackLocal-darwin-x64';
@@ -43,9 +90,10 @@ function LocalBinary(){
4390
}
4491
};
4592

46-
this.httpPath = this.getDownloadPath();
47-
48-
93+
this.binaryDownloadError = function(errorMessagePrefix, errorMessage) {
94+
console.error(errorMessagePrefix, errorMessage);
95+
this.downloadErrorMessage = errorMessagePrefix + ' : ' + errorMessage;
96+
};
4997

5098
this.retryBinaryDownload = function(conf, destParentDir, callback, retries, binaryPath) {
5199
var that = this;
@@ -66,6 +114,12 @@ function LocalBinary(){
66114
};
67115

68116
this.downloadSync = function(conf, destParentDir, retries) {
117+
try {
118+
this.httpPath = this.getDownloadPath(conf, retries);
119+
} catch (e) {
120+
return console.error(`Unable to fetch the source url to download the binary with error: ${e}`);
121+
}
122+
69123
console.log('Downloading in sync');
70124
var that = this;
71125
if(!this.checkPath(destParentDir))
@@ -96,21 +150,27 @@ function LocalBinary(){
96150
fs.chmodSync(binaryPath, '0755');
97151
return binaryPath;
98152
}else{
99-
console.log('failed to download');
153+
that.binaryDownloadError('failed to download');
100154
return that.retryBinaryDownload(conf, destParentDir, null, retries, binaryPath);
101155
}
102156
} else if(obj.stderr.length > 0) {
103157
output = Buffer.from(JSON.parse(JSON.stringify(obj.stderr)).data).toString();
104-
console.error(output);
158+
that.binaryDownloadError(output);
105159
return that.retryBinaryDownload(conf, destParentDir, null, retries, binaryPath);
106160
}
107161
} catch(err) {
108-
console.error('Download failed with error', err);
162+
that.binaryDownloadError('Download failed with error', util.format(err));
109163
return that.retryBinaryDownload(conf, destParentDir, null, retries, binaryPath);
110164
}
111165
};
112166

113167
this.download = function(conf, destParentDir, callback, retries){
168+
try {
169+
this.httpPath = this.getDownloadPath(conf, retries);
170+
} catch (e) {
171+
return console.error(`Unable to fetch the source url to download the binary with error: ${e}`);
172+
}
173+
114174
var that = this;
115175
if(!this.checkPath(destParentDir))
116176
fs.mkdirSync(destParentDir);
@@ -152,11 +212,11 @@ function LocalBinary(){
152212
}
153213

154214
response.on('error', function(err) {
155-
console.error('Got Error in binary download response', err);
215+
that.binaryDownloadError('Got Error in binary download response', util.format(err));
156216
that.retryBinaryDownload(conf, destParentDir, callback, retries, binaryPath);
157217
});
158218
fileStream.on('error', function (err) {
159-
console.error('Got Error while downloading binary file', err);
219+
that.binaryDownloadError('Got Error while downloading binary file', util.format(err));
160220
that.retryBinaryDownload(conf, destParentDir, callback, retries, binaryPath);
161221
});
162222
fileStream.on('close', function () {
@@ -165,12 +225,14 @@ function LocalBinary(){
165225
});
166226
});
167227
}).on('error', function(err) {
168-
console.error('Got Error in binary downloading request', err);
228+
that.binaryDownloadError('Got Error in binary downloading request', util.format(err));
169229
that.retryBinaryDownload(conf, destParentDir, callback, retries, binaryPath);
170230
});
171231
};
172232

173-
this.binaryPath = function(conf, callback){
233+
this.binaryPath = function(conf, key, parentRetries, callback){
234+
this.key = key;
235+
this.parentRetries = parentRetries;
174236
var destParentDir = this.getAvailableDirs();
175237
var destBinaryName = (this.windows) ? 'BrowserStackLocal.exe' : 'BrowserStackLocal';
176238
var binaryPath = path.join(destParentDir, destBinaryName);
@@ -180,10 +242,11 @@ function LocalBinary(){
180242
}
181243
callback(binaryPath);
182244
} else {
245+
let retries = this.baseRetries;
183246
if(!callback) {
184-
return this.downloadSync(conf, destParentDir, 5);
247+
return this.downloadSync(conf, destParentDir, retries);
185248
}
186-
this.download(conf, destParentDir, callback, 5);
249+
this.download(conf, destParentDir, callback, retries);
187250
}
188251
};
189252

lib/fetchDownloadSourceUrl.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
const https = require('https'),
2+
fs = require('fs'),
3+
HttpsProxyAgent = require('https-proxy-agent');
4+
5+
const authToken = process.argv[2], proxyHost = process.argv[5], proxyPort = process.argv[6], useCaCertificate = process.argv[7], downloadFallback = process.argv[3], downloadErrorMessage = process.argv[4];
6+
7+
let body = '', data = {'auth_token': authToken};
8+
const options = {
9+
hostname: 'local.browserstack.com',
10+
port: 443,
11+
path: '/binary/api/v1/endpoint',
12+
method: 'POST',
13+
headers: {
14+
'Content-Type': 'application/json',
15+
'user-agent': process.env.USER_AGENT
16+
}
17+
};
18+
if (downloadFallback == 'true') {
19+
options.headers['X-Local-Fallback-Cloudflare'] = true;
20+
data['error_message'] = downloadErrorMessage;
21+
}
22+
23+
if(proxyHost && proxyPort) {
24+
options.agent = new HttpsProxyAgent({
25+
host: proxyHost,
26+
port: proxyPort
27+
});
28+
}
29+
if (useCaCertificate) {
30+
try {
31+
options.ca = fs.readFileSync(useCaCertificate);
32+
} catch(err) {
33+
console.log('failed to read cert file', err);
34+
}
35+
}
36+
37+
const req = https.request(options, res => {
38+
res.on('data', d => {
39+
body += d;
40+
});
41+
res.on('end', () => {
42+
try {
43+
const reqBody = JSON.parse(body);
44+
if(reqBody.error) {
45+
throw reqBody.error;
46+
}
47+
console.log(reqBody.data.endpoint);
48+
} catch (e) {
49+
console.error(e);
50+
}
51+
});
52+
res.on('error', (err) => {
53+
console.error(err);
54+
});
55+
});
56+
req.on('error', e => {
57+
console.error(e);
58+
});
59+
req.write(JSON.stringify(data));
60+
req.end();
61+

test/local.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ describe('LocalBinary', function () {
280280
// ensure that we have a valid binary downloaded
281281

282282
// removeIfInvalid();
283-
(new LocalBinary()).binaryPath({}, function(binaryPath) {
283+
(new LocalBinary()).binaryPath({}, 'abc', 9, function(binaryPath) {
284284
defaultBinaryPath = binaryPath;
285285
tempfs.mkdir({
286286
recursive: true
@@ -313,7 +313,7 @@ describe('LocalBinary', function () {
313313
fs.writeFile(defaultBinaryPath, 'Random String', function() {
314314
fs.chmod(defaultBinaryPath, '0755', function() {
315315
localBinary.binaryPath({
316-
}, function(binaryPath) {
316+
}, 'abc', 9, function(binaryPath) {
317317
expect(downloadStub.called).to.be.true;
318318
done();
319319
});
@@ -331,7 +331,7 @@ describe('LocalBinary', function () {
331331
});
332332

333333
localBinary.binaryPath({
334-
}, function(binaryPath) {
334+
}, 'abc', 9, function(binaryPath) {
335335
expect(downloadStub.called).to.be.true;
336336
done();
337337
});

0 commit comments

Comments
 (0)