ysb-sauce-tunnel
Version:
A wrapper around the Sauce Labs tunnel jar
162 lines (143 loc) • 4.18 kB
JavaScript
/* npm */
var chalk = require('chalk');
var request = require('request').defaults({jar:false});
var split = require('split');
/* core */
var util = require('util');
var os = require('os');
var path = require('path');
var proc = require('child_process');
var EventEmitter = require('events').EventEmitter;
var binaries = {
'darwin': 'sc',
'linux': 'sc',
'linux32': 'sc',
'win32': 'sc.exe'
};
module.exports = SauceTunnel;
function SauceTunnel(user, key, identifier, tunneled, extraFlags) {
EventEmitter.call(this);
this.user = user;
this.key = key;
this.identifier = identifier || 'Tunnel'+new Date().getTime();
this.tunneled = (tunneled == null) ? true : tunneled;
this.baseUrl = ["https://", this.user, ':', this.key, '@saucelabs.com', '/rest/v1/', this.user].join("");
this.extraFlags = extraFlags;
this.id = null;
}
util.inherits(SauceTunnel, EventEmitter);
SauceTunnel.prototype.openTunnel = function(callback) {
var me = this;
// win32, darwin or linux
var platform = os.platform();
// Special case: 32bit linux?
platform += (platform === 'linux' && os.arch() === 'ia32') ? '32' : '';
var executable = binaries[platform];
if (!executable) {
console.log("Platform:"+platform+" is not supported");
throw new Error(platform + ' platform is not supported');
}
var args = ['-u', this.user, '-k', this.key];
if (this.identifier) {
args.push("-i", this.identifier);
}
if (this.extraFlags) {
args = args.concat(this.extraFlags);
}
var cmd = path.join(__dirname, 'vendor', platform, 'bin/', executable);
console.log("CMD:"+cmd);
console.log("ARGS:"+args);
this.proc = proc.spawn(cmd, args,{ shell: true });
callback.called = false;
this.proc.stdout.pipe(split()).on('data', function(data) {
if (!data.match(/^\[-u,/g)) {
console.log("DATA:"+data);
me.emit('verbose:debug', data);
}
if (data.match(/Sauce Connect is up, you may start your tests/)) {
me.emit('verbose:ok', '=> Sauce Labs Tunnel established');
if (!callback.called) {
callback.called = true;
callback(true);
}
}
var match = data.match(/Tunnel ID\: ([a-z0-9]{32})/);
if (match) {
me.id = match[1];
}
});
this.proc.stderr.pipe(split()).on('data', function(data) {
me.emit('log:error', data);
});
var self = this;
this.proc.on('exit', function(code) {
me.emit('verbose:ok', 'Sauce Labs Tunnel disconnected ', code);
if (!callback.called) {
callback.called = true;
callback(false);
}
});
};
SauceTunnel.prototype.getTunnels = function(callback) {
request({
url: this.baseUrl + '/tunnels',
json: true
}, function(err, resp, body) {
callback(body);
});
};
SauceTunnel.prototype.killTunnel = function(callback) {
if (!this.tunneled) {
return callback();
}
this.emit('verbose:debug', 'Trying to kill tunnel');
request({
method: "DELETE",
url: this.baseUrl + "/tunnels/" + this.id,
json: true
}, function (err, resp, body) {
if (!err && resp.statusCode === 200) {
this.emit('verbose:debug', 'Tunnel Closed');
}
else {
this.emit('log:error', 'Error closing tunnel');
}
callback(err);
}.bind(this));
};
SauceTunnel.prototype.start = function(callback) {
var me = this;
if (!this.tunneled) {
return callback(true);
}
this.emit('verbose:writeln', chalk.inverse("=> Sauce Labs trying to open tunnel"));
this.openTunnel(function(status) {
callback(status);
});
};
SauceTunnel.prototype.stop = function (callback) {
var self = this,
callbackArg;
this.proc.on('exit', function () {
callback(callbackArg);
});
this.killTunnel(function (err) {
// When deleting the tunnel via the REST API succeeds, then Sauce Connect exits automatically.
// Otherwise kill the process. Don't care with the tunnel, it will time out.
if (err) {
callbackArg = err;
self.proc.kill();
}
});
};
SauceTunnel.prototype.kill = function(callback) {
if (this.proc) {
this.proc.on('exit', function () {
callback();
});
this.proc.kill();
}
else {
callback();
}
};