1 | 'use strict';
|
2 |
|
3 | var async = require('async');
|
4 | var fs = require('fs');
|
5 | var os = require('os');
|
6 | var path = require('path');
|
7 | var spawn = require('child_process').spawn;
|
8 | var downloader = require('./downloader');
|
9 | var tunnelLocation = undefined;
|
10 | var activeTunnel = undefined;
|
11 | var started = false;
|
12 |
|
13 | function logger(msg) {
|
14 | console.log.apply(console, arguments);
|
15 | }
|
16 |
|
17 | function download(options, callback) {
|
18 | tunnelLocation = path.normalize(path.join(__dirname, '../testingbot-tunnel.jar'));
|
19 |
|
20 | var url = 'https://testingbot.com/tunnel/testingbot-tunnel.jar';
|
21 |
|
22 | if (options.tunnelVersion) {
|
23 | tunnelLocation = path.normalize(path.join(__dirname, '../testingbot-tunnel-' + options.tunnelVersion + '.jar'));
|
24 | url = 'https://testingbot.com/tunnel/testingbot-tunnel-' + options.tunnelVersion + '.jar';
|
25 | }
|
26 |
|
27 | try {
|
28 | var tunnelFile = fs.statSync(tunnelLocation);
|
29 | if (tunnelFile['size'] > 1024) {
|
30 | return callback(null);
|
31 | }
|
32 | } catch (ignore) {}
|
33 |
|
34 | downloader.get(url, { fileName: 'testingbot-tunnel', destination: tunnelLocation }, function (err, destination) {
|
35 | if (err) {
|
36 | return callback(new Error('Could not download the tunnel from TestingBot - please check your connection. ' + err.message));
|
37 | }
|
38 |
|
39 | return callback(null);
|
40 | });
|
41 | }
|
42 |
|
43 | function run(options, callback) {
|
44 | if (!fs.existsSync(tunnelLocation)) {
|
45 | return callback(new Error('Tunnel jar file is not present in ' + tunnelLocation));
|
46 | }
|
47 |
|
48 | var onReady = function onReady() {
|
49 | started = true;
|
50 | logger('Tunnel is ready');
|
51 | callback(null, activeTunnel);
|
52 | };
|
53 |
|
54 | var args = [];
|
55 |
|
56 | args.push('-jar');
|
57 | args.push(tunnelLocation);
|
58 |
|
59 | if (options.apiKey) {
|
60 | args.push(options.apiKey);
|
61 | }
|
62 |
|
63 | if (options.apiSecret) {
|
64 | args.push(options.apiSecret);
|
65 | }
|
66 |
|
67 | for (var option in options) {
|
68 | if (option === 'apiKey' || option === 'apiSecret' || option === 'verbose' || option === 'tunnelVersion') {
|
69 | continue;
|
70 | }
|
71 |
|
72 | if (options[option]) {
|
73 | args.push('--' + option);
|
74 | args.push(options[option]);
|
75 | } else {
|
76 | args.push('--' + option);
|
77 | }
|
78 | }
|
79 |
|
80 | var readyFile = path.join(os.tmpdir(), 'testingbot.ready');
|
81 | try {
|
82 | if (fs.statSync(readyFile).isFile()) {
|
83 | logger('Tunnel Readyfile already exists, removing');
|
84 | fs.unlinkSync(readyFile);
|
85 | }
|
86 | } catch (ignore) {}
|
87 |
|
88 | args.push('-f');
|
89 | args.push(readyFile);
|
90 |
|
91 | var readyFileChecker = setInterval(function () {
|
92 | fs.stat(readyFile, function (error, stat) {
|
93 | if (!error) {
|
94 | clearInterval(readyFileChecker);
|
95 | onReady();
|
96 | }
|
97 | });
|
98 | }, 800);
|
99 |
|
100 | if (options.verbose) {
|
101 | logger('Starting tunnel with options', args);
|
102 | }
|
103 | activeTunnel = spawn('java', args, {
|
104 | detached: true
|
105 | });
|
106 |
|
107 | activeTunnel.unref();
|
108 |
|
109 | activeTunnel.stderr.on('data', function (data) {
|
110 | data = data.toString().trim();
|
111 | if (options.verbose && data !== '') {
|
112 | logger(data);
|
113 | }
|
114 | if (data.indexOf('is available for download') > -1 || data.indexOf('An error ocurred') > -1) {
|
115 | logger(data);
|
116 | } else if (data.toLowerCase().indexOf('error') > -1) {
|
117 | logger(data);
|
118 | }
|
119 | });
|
120 |
|
121 | activeTunnel.stdout.on('data', function (data) {
|
122 | data = data.toString().trim();
|
123 | if (options.verbose && data !== '') {
|
124 | logger(data);
|
125 | }
|
126 | });
|
127 |
|
128 | activeTunnel.close = function (closeCallback) {
|
129 | if (closeCallback) {
|
130 | activeTunnel.on('close', function () {
|
131 | closeCallback();
|
132 | });
|
133 | }
|
134 | activeTunnel.kill('SIGINT');
|
135 | };
|
136 |
|
137 | activeTunnel.on('exit', function (code, signal) {
|
138 | logger('Closing TestingBot Tunnel');
|
139 | if (!started) {
|
140 | callback(new Error('Could not start TestingBot Tunnel. Exit code ' + code + ' signal: ' + signal));
|
141 | }
|
142 |
|
143 | started = false;
|
144 | activeTunnel = null;
|
145 | });
|
146 | }
|
147 |
|
148 | function killTunnel(callback) {
|
149 | if (!callback) {
|
150 | callback = function () {};
|
151 | }
|
152 |
|
153 | if (!activeTunnel) {
|
154 | return callback(new Error('no active tunnel'));
|
155 | }
|
156 |
|
157 | activeTunnel.kill('SIGINT');
|
158 | }
|
159 |
|
160 | function downloadAndRun(options, callback) {
|
161 | if (!options) {
|
162 | options = {};
|
163 | }
|
164 |
|
165 | if (!callback) {
|
166 | callback = function () {};
|
167 | }
|
168 |
|
169 | async.waterfall([async.apply(download, options), async.apply(run, options)], callback);
|
170 | }
|
171 |
|
172 | function exitHandler(options, err) {
|
173 | if (!activeTunnel) {
|
174 | return;
|
175 | }
|
176 |
|
177 | if (err) {
|
178 | logger(err.stack);
|
179 | }
|
180 |
|
181 | if (options.cleanup) {
|
182 | logger('Shutting down tunnel');
|
183 | killTunnel();
|
184 | }
|
185 |
|
186 | if (options.exit) {
|
187 | process.exit();
|
188 | }
|
189 | }
|
190 |
|
191 | process.on('exit', exitHandler.bind(null, { cleanup: true }));
|
192 | process.on('SIGINT', exitHandler.bind(null, { exit: true }));
|
193 | process.on('uncaughtException', exitHandler.bind(null, { exit: true }));
|
194 |
|
195 | module.exports = downloadAndRun;
|
196 | module.exports.kill = killTunnel;
|