You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
104 lines
2.9 KiB
104 lines
2.9 KiB
const request = require('request-promise-native');
|
|
const uuid = require('uuid');
|
|
const {getProcess, killProcess, setAuthtoken} = require('./process');
|
|
|
|
let processUrl;
|
|
let internalApi;
|
|
let tunnels = {};
|
|
|
|
async function connect (opts) {
|
|
opts = defaults(opts);
|
|
validate(opts);
|
|
if (opts.authtoken) {
|
|
await setAuthtoken(opts);
|
|
}
|
|
|
|
processUrl = await getProcess(opts);
|
|
internalApi = request.defaults({baseUrl: processUrl});
|
|
return connectRetry(opts);
|
|
}
|
|
|
|
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;
|
|
return opts
|
|
}
|
|
|
|
function validate (opts) {
|
|
if (opts.web_addr === false || opts.web_addr === 'false') {
|
|
throw new Error('web_addr:false is not supported, module depends on internal ngrok api')
|
|
}
|
|
}
|
|
|
|
async function connectRetry (opts, retryCount = 0) {
|
|
opts.name = String(opts.name || uuid.v4());
|
|
try {
|
|
const response = await internalApi.post({url: 'api/tunnels', json: opts});
|
|
const publicUrl = response.public_url;
|
|
if (!publicUrl) {
|
|
throw new Error('failed to start tunnel');
|
|
}
|
|
tunnels[publicUrl] = response.uri;
|
|
if (opts.proto === 'http' && opts.bind_tls !== false) {
|
|
tunnels[publicUrl.replace('https', 'http')] = response.uri + ' (http)';
|
|
}
|
|
return publicUrl;
|
|
} catch (err) {
|
|
if (!isRetriable(err) || retryCount >= 100) {
|
|
throw err.error || err.response;
|
|
}
|
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
return connectRetry(opts, ++retryCount);
|
|
}
|
|
}
|
|
|
|
function isRetriable (err) {
|
|
if (!err.response) return false;
|
|
const body = err.response.body;
|
|
const notReady500 = err.statusCode === 500 && /panic/.test(body)
|
|
const notReady502 = err.statusCode === 502 && body.details && body.details.err === 'tunnel session not ready yet';
|
|
const notReady503 = err.statusCode === 503 && body.details && body.details.err === 'a successful ngrok tunnel session has not yet been established';
|
|
return notReady500 || notReady502 || notReady503;
|
|
}
|
|
|
|
async function disconnect (publicUrl) {
|
|
if (!internalApi) return;
|
|
if (!publicUrl) {
|
|
const disconnectAll = Object.keys(tunnels).map(disconnect);
|
|
return Promise.all(disconnectAll);
|
|
}
|
|
const tunnelUrl = tunnels[publicUrl];
|
|
if (!tunnelUrl) {
|
|
throw new Error(`there is no tunnel with url: ${publicUrl}`)
|
|
}
|
|
await internalApi.del(tunnelUrl)
|
|
delete tunnels[publicUrl];
|
|
}
|
|
|
|
async function kill () {
|
|
if (!internalApi) return;
|
|
await killProcess();
|
|
internalApi = null;
|
|
tunnels = {}
|
|
}
|
|
|
|
function getUrl() {
|
|
return processUrl;
|
|
}
|
|
|
|
function getApi() {
|
|
return internalApi;
|
|
}
|
|
|
|
module.exports = {
|
|
connect,
|
|
disconnect,
|
|
authtoken: setAuthtoken,
|
|
kill,
|
|
getUrl,
|
|
getApi
|
|
};
|
|
|