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.
134 lines
3.1 KiB
134 lines
3.1 KiB
const { spawn } = require('child_process');
|
|
const platform = require('os').platform();
|
|
|
|
const defaultDir = __dirname + '/bin';
|
|
const bin = './ngrok' + (platform === 'win32' ? '.exe' : '');
|
|
const ready = /starting web service.*addr=(\d+\.\d+\.\d+\.\d+:\d+)/;
|
|
const inUse = /address already in use/;
|
|
|
|
let processPromise, activeProcess;
|
|
|
|
/*
|
|
ngrok process runs internal ngrok api
|
|
and should be spawned only ONCE
|
|
(respawn allowed if it fails or .kill method called)
|
|
*/
|
|
|
|
async function getProcess(opts) {
|
|
if (processPromise) return processPromise;
|
|
try {
|
|
processPromise = startProcess(opts);
|
|
return await processPromise;
|
|
}
|
|
catch(ex) {
|
|
processPromise = null;
|
|
throw ex;
|
|
}
|
|
}
|
|
|
|
async function startProcess (opts) {
|
|
let dir = defaultDir;
|
|
const start = ['start', '--none', '--log=stdout'];
|
|
if (opts.region) start.push('--region=' + opts.region);
|
|
if (opts.configPath) start.push('--config=' + opts.configPath);
|
|
if (opts.binPath) dir = opts.binPath(dir);
|
|
|
|
const ngrok = spawn(bin, start, {cwd: dir});
|
|
|
|
let resolve, reject;
|
|
const apiUrl = new Promise((res, rej) => {
|
|
resolve = res;
|
|
reject = rej;
|
|
});
|
|
|
|
ngrok.stdout.on('data', data => {
|
|
const msg = data.toString();
|
|
const addr = msg.match(ready);
|
|
if (opts.onLogEvent) {
|
|
opts.onLogEvent(msg.trim());
|
|
}
|
|
if (opts.onStatusChange) {
|
|
if (msg.match('client session established')) {
|
|
opts.onStatusChange('connected');
|
|
} else if (msg.match('session closed, starting reconnect loop')) {
|
|
opts.onStatusChange('closed');
|
|
}
|
|
}
|
|
if (addr) {
|
|
resolve(`http://${addr[1]}`);
|
|
} else if (msg.match(inUse)) {
|
|
reject(new Error(msg.substring(0, 10000)));
|
|
}
|
|
});
|
|
|
|
ngrok.stderr.on('data', data => {
|
|
const msg = data.toString().substring(0, 10000);
|
|
reject(new Error(msg));
|
|
});
|
|
|
|
ngrok.on('exit', () => {
|
|
processPromise = null;
|
|
activeProcess = null;
|
|
});
|
|
|
|
process.on('exit', async () => await killProcess());
|
|
|
|
try {
|
|
const url = await apiUrl;
|
|
activeProcess = ngrok;
|
|
return url;
|
|
}
|
|
catch(ex) {
|
|
ngrok.kill();
|
|
throw ex;
|
|
}
|
|
finally {
|
|
// Remove the stdout listeners if nobody is interested in the content.
|
|
if (!opts.onLogEvent && !opts.onStatusChange) {
|
|
ngrok.stdout.removeAllListeners('data');
|
|
}
|
|
ngrok.stderr.removeAllListeners('data');
|
|
}
|
|
}
|
|
|
|
function killProcess () {
|
|
if (!activeProcess) return;
|
|
return new Promise(resolve => {
|
|
activeProcess.on('exit', () => resolve());
|
|
activeProcess.kill();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {string | INgrokOptions} optsOrToken
|
|
*/
|
|
async function setAuthtoken (optsOrToken) {
|
|
const isOpts = typeof optsOrToken !== 'string'
|
|
const opts = isOpts ? optsOrToken : {}
|
|
const token = isOpts ? opts.authtoken : optsOrToken
|
|
|
|
const authtoken = ['authtoken', token];
|
|
if (opts.configPath) authtoken.push('--config=' + opts.configPath);
|
|
|
|
let dir = defaultDir;
|
|
if (opts.binPath) dir = opts.binPath(dir)
|
|
const ngrok = spawn(bin, authtoken, {cwd: dir});
|
|
|
|
const killed = new Promise((resolve, reject) => {
|
|
ngrok.stdout.once('data', () => resolve());
|
|
ngrok.stderr.once('data', () => reject(new Error('cant set authtoken')));
|
|
});
|
|
|
|
try {
|
|
return await killed;
|
|
}
|
|
finally {
|
|
ngrok.kill();
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
getProcess,
|
|
killProcess,
|
|
setAuthtoken
|
|
};
|
|
|