GT2/GT2-Android/node_modules/@expo/ngrok/index.js

264 lines
5.5 KiB
JavaScript

var request = require('request');
var spawn = require('child_process').spawn;
var Emitter = require('events').EventEmitter;
var platform = require('os').platform();
var lock = require('lock')();
var async = require('async');
var uuid = require('uuid');
var url = require('url');
var logfmt = require('logfmt');
var bin = './ngrok' + (platform === 'win32' ? '.exe' : '');
var noop = function() {};
var emitter = new Emitter().on('error', noop);
var ngrok, api, tunnels = {};
function connect(opts, cb) {
if (typeof opts === 'function') {
cb = opts;
}
cb = cb || noop;
opts = defaults(opts);
if (api) {
return runTunnel(opts, cb);
}
lock('ngrok', function(release) {
function run(err) {
if (err) {
emitter.emit('error', err);
return cb(err);
}
runNgrok(opts, release(function(err) {
if (err) {
emitter.emit('error', err);
return cb(err);
}
runTunnel(opts, cb)
}));
}
opts.authtoken ?
authtoken(opts.authtoken, opts.configPath, run) :
run(null);
});
}
function defaults(opts) {
opts = opts || {proto: 'http', addr: 80};
if (typeof opts === 'function') {
opts = {proto: 'http', addr: 80};
}
if (typeof opts !== 'object') {
opts = {proto: 'http', addr: opts};
}
if (!opts.proto) {
opts.proto = 'http';
}
if (!opts.addr) {
opts.addr = opts.port || opts.host || 80;
}
if (opts.httpauth) {
opts.auth = opts.httpauth;
}
if (['us', 'eu', 'au', 'ap'].indexOf(opts.region) === -1) {
opts.region = 'us';
}
if (!opts.configPath) {
opts.configPath = '~/.ngrok2/ngrok.yml';
}
return opts;
}
function runNgrok(opts, cb) {
if (api) {
return cb();
}
emitter.emit('statuschange', 'starting');
ngrok = spawn(
bin,
['start', '--none', '--log=stdout', '--region=' + opts.region, '-config=' + opts.configPath],
{cwd: __dirname + '/bin'});
ngrok.stdout.on('data', function (chunk) {
var lines = chunk.toString().split(/\r?\n/);
lines.forEach(function (line) {
if (!line) return;
var data = logfmt.parse(line);
if (data.obj === 'web' && data.msg === 'starting web service' && data.addr) {
api = request.defaults({
baseUrl: 'http://' + data.addr,
json: true
});
cb();
} else if (data.obj === 'csess' && data.msg === 'session closed, starting reconnect loop') {
emitter.emit('statuschange', 'reconnecting');
} else if (data.obj === 'csess' && data.msg === 'client session established') {
emitter.emit('statuschange', 'online');
}
});
});
ngrok.stderr.on('data', function (data) {
var info = data.toString().substring(0, 10000);
return cb(new Error(info));
});
ngrok.on('exit', function () {
ngrok = null;
api = null;
tunnels = {};
});
ngrok.on('close', function () {
return emitter.emit('close');
});
process.on('exit', function() {
kill();
});
ngrok.on('error', function(err) {
cb(err);
});
}
function runTunnel(opts, cb) {
_runTunnel(opts, function(err, publicUrl, uiUrl) {
if (err) {
emitter.emit('error', err);
return cb(err);
}
emitter.emit('connect', publicUrl, uiUrl);
return cb(null, publicUrl, uiUrl);
});
}
function _runTunnel(opts, cb) {
var retries = 100;
opts.name = String(opts.name || uuid.v4());
var retry = function() {
api.post(
{url: 'api/tunnels', json: opts},
function(err, resp, body) {
if (err) {
return cb(err);
}
var notReady = resp.statusCode === 500 && /panic/.test(body) ||
resp.statusCode === 502 && body.details &&
body.details.err === 'tunnel session not ready yet';
if (notReady) {
return retries-- ?
setTimeout(retry, 200) :
cb(new Error(body));
}
var publicUrl = body && body.public_url;
if (!publicUrl) {
var err = Object.assign(new Error(body.msg || 'failed to start tunnel'), body);
return cb(err);
}
tunnels[publicUrl] = body.uri;
if (opts.proto === 'http' && opts.bind_tls !== false) {
tunnels[publicUrl.replace('https', 'http')] = body.uri + ' (http)';
}
var uiUrl = url.parse(resp.request.uri);
uiUrl = uiUrl.resolve('/').slice(0, -1);
return cb(null, publicUrl, uiUrl);
});
};
retry();
}
function authtoken(token, configPath, cb) {
cb = cb || noop;
var a = spawn(
bin,
['authtoken', token, '-config=' + configPath],
{cwd: __dirname + '/bin'});
a.stdout.once('data', done.bind(null, null, token));
a.stderr.once('data', done.bind(null, new Error('cant set authtoken')));
a.on('error', function(err) {
cb(err);
});
function done(err, token) {
cb(err, token);
a.kill();
}
}
function disconnect(publicUrl, cb) {
cb = cb || noop;
if (typeof publicUrl === 'function') {
cb = publicUrl;
publicUrl = null;
}
if (!api) {
return cb();
}
if (publicUrl) {
return api.del(
tunnels[publicUrl],
function(err, resp, body) {
if (err || resp.statusCode !== 204) {
return cb(err || new Error(body));
}
delete tunnels[publicUrl];
emitter.emit('disconnect', publicUrl);
return cb();
});
}
return async.each(
Object.keys(tunnels),
disconnect,
function(err) {
if (err) {
emitter.emit('error', err);
return cb(err);
}
emitter.emit('disconnect');
return cb();
});
}
function kill(cb) {
cb = cb || noop;
if (!ngrok) {
return cb();
}
ngrok.on('exit', function() {
emitter.emit('disconnect');
return cb();
});
return ngrok.kill();
}
function ngrokProcess() {
return ngrok;
}
emitter.connect = connect;
emitter.disconnect = disconnect;
emitter.authtoken = authtoken;
emitter.kill = kill;
emitter.process = ngrokProcess;
module.exports = emitter;