-
Notifications
You must be signed in to change notification settings - Fork 62
Expand file tree
/
Copy pathlocal.js
More file actions
220 lines (182 loc) · 6.58 KB
/
local.js
File metadata and controls
220 lines (182 loc) · 6.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
var Log = require('./logger'),
logger = new Log(global.logLevel || 'info'),
exec = require('child_process').execFile,
fs = require('fs'),
path = require('path'),
https = require('https'),
utils = require('./utils'),
windows = ((process.platform.match(/win32/) || process.platform.match(/win64/)) !== null),
localBinary = __dirname + '/BrowserStackLocal' + (windows ? '.exe' : '');
var Tunnel = function Tunnel(key, port, uniqueIdentifier, config, callback) {
var that = {};
localBinary = process.env.BROWSERSTACK_LOCAL_BINARY_PATH || localBinary;
function tunnelLauncher() {
var tunnelOptions = getTunnelOptions(key, uniqueIdentifier);
if (typeof callback !== 'function') {
callback = function(){};
}
logger.debug('[%s] Launching tunnel', new Date());
var subProcess = exec(localBinary, tunnelOptions, function(error, stdout, stderr) {
logger.debug(stderr);
logger.debug(error);
if (stdout.indexOf('Error') >= 0 || error) {
logger.debug('[%s] Tunnel launching failed', new Date());
logger.debug(stdout);
callback(new Error(new Date() + ': Tunnel launching failed'));
}
});
var data = '';
var running = false;
var runMatchers = [ 'You can now access your local server(s)', 'Press Ctrl-C to exit' ];
setTimeout(function() {
if (!running) {
logger.error('BrowserStackLocal failed to launch within 30 seconds.');
callback(new Error('BrowserStackLocal failed to launch within 30 seconds.'));
}
}, 30 * 1000);
subProcess.stdout.on('data', function(_data) {
if (running) {
return;
}
data += _data;
if (data.indexOf(runMatchers[0]) >= 0 && data.indexOf(runMatchers[1]) >= 0) {
running = true;
logger.debug('[%s] Tunnel launched', new Date());
setTimeout(function(){
callback();
}, 2000);
}
});
if (config.tunnel_pid_file) {
utils.mkdirp(path.dirname(config.tunnel_pid_file));
fs.writeFile(config.tunnel_pid_file, subProcess.pid);
}
that.process = subProcess;
}
function getTunnelOptions(key, uniqueIdentifier) {
var options = [key];
if (config.debug) {
options.push('-v');
}
if (!uniqueIdentifier) {
options.push('-force');
options.push('-onlyAutomate');
} else {
options.push('-localIdentifier');
options.push(uniqueIdentifier);
}
var proxy = config.proxy;
if (proxy) {
options.push('-proxyHost ' + proxy.host);
options.push('-proxyPort ' + proxy.port);
if (proxy.username && proxy.password) {
options.push('-proxyUser ' + proxy.username);
options.push('-proxyPass ' + proxy.password);
}
}
return options;
}
function runTunnelCmd(tunnelOptions, subProcessTimeout, processOutputHook, callback) {
var isRunning, subProcess, timeoutHandle;
var callbackOnce = function (err, result) {
clearTimeout(timeoutHandle);
if (subProcess && isRunning) {
try {
process.kill(subProcess.pid, 'SIGKILL');
subProcess = null;
} catch (e) {
logger.debug('[%s] failed to kill process:', new Date(), e);
} finally {
if (config.tunnel_pid_file) {
fs.unlink(config.tunnel_pid_file, function () {});
}
}
}
callback && callback(err, result);
callback = null;
};
isRunning = true;
try {
subProcess = exec(localBinary, tunnelOptions, function (error, stdout) {
isRunning = false;
if (error) {
callbackOnce(new Error('failed to get process output: ' + error));
} else if (stdout) {
processOutputHook(stdout, callbackOnce);
}
});
subProcess.stdout.on('data', function (data) {
processOutputHook(data, callbackOnce);
});
} catch (e) {
// Handles EACCESS and other errors when binary file exists,
// but doesn't have necessary permissions (among other issues)
callbackOnce(new Error('failed to get process output: ' + e));
}
if (subProcessTimeout > 0) {
timeoutHandle = setTimeout(function () {
callbackOnce(new Error('failed to get process output: command timeout'));
}, subProcessTimeout);
}
}
function getTunnelBinaryVersion(callback) {
var subProcessTimeout = 3000;
runTunnelCmd([ '--version' ], subProcessTimeout, function (data, done) {
var matches = /version\s+(\d+(\.\d+)*)/.exec(data);
var version = (matches && matches.length > 2) && matches[1];
logger.debug('[%s] Tunnel binary: found version', new Date(), version);
done(isFinite(version) ? null : new Error('failed to get binary version'), parseFloat(version));
}, callback);
}
function verifyTunnelBinary(callback) {
logger.debug('[%s] Verifying tunnel binary', new Date());
fs.exists(localBinary, function (exists) {
if (!exists) {
logger.debug('[%s] Verifying tunnel binary: file does not exist', new Date());
callback(false);
} else {
getTunnelBinaryVersion(function (err, version) {
callback(!err && isFinite(version));
});
}
});
}
function fetchTunnelBinary(retryDarwinArm64) {
var file = fs.createWriteStream(localBinary);
var isDarwinArm64 = process.platform === 'darwin' && process.arch === 'arm64';
var arch = isDarwinArm64 && retryDarwinArm64 ? 'x64' : process.arch;
https.get('https://s3.amazonaws.com/browserStack/browserstack-local/BrowserStackLocal' + (windows ? '.exe' : '-' + process.platform + '-' + arch),
function(response) {
if (response.statusCode !== 200) {
if (isDarwinArm64 && !retryDarwinArm64) {
fetchTunnelBinary(true);
} else {
var message = 'Got error while downloading binary: ' + response.statusCode + ' ' + response.statusMessage;
logger.info(message);
throw new Error(message);
}
} else {
response.pipe(file);
response.on('end', function() {
fs.chmodSync(localBinary, 0700);
setTimeout(function() {
tunnelLauncher();
}, 100);
}).on('error', function(e) {
logger.info('Got error while downloading binary: ' + e.message);
throw new Error('Got error while downloading binary: ' + e.message);
});
}
});
}
verifyTunnelBinary(function (exists) {
if (exists) {
tunnelLauncher();
return;
}
logger.debug('Downloading BrowserStack Local to "%s"', localBinary);
fetchTunnelBinary(false);
});
return that;
};
exports.Tunnel = Tunnel;