diff --git a/bin/probot-run.js b/bin/probot-run.js index e35964c8..ea6ad920 100755 --- a/bin/probot-run.js +++ b/bin/probot-run.js @@ -36,9 +36,13 @@ if (!program.privateKey) { } if (program.tunnel) { - const setupTunnel = require('../lib/tunnel'); try { - setupTunnel(program.tunnel, program.port); + const setupTunnel = require('../lib/tunnel'); + setupTunnel(program.tunnel, program.port).then(tunnel => { + console.log('Listening on ' + tunnel.url); + }).catch(err => { + console.warn('Could not open tunnel: ', err.message); + }); } catch (err) { console.warn('Run `npm install --save-dev localtunnel` to enable localtunnel.'); } diff --git a/lib/tunnel.js b/lib/tunnel.js index c3bd600a..5ef9f16f 100644 --- a/lib/tunnel.js +++ b/lib/tunnel.js @@ -1,22 +1,44 @@ // eslint-disable-next-line import/no-extraneous-dependencies, import/no-unresolved const localtunnel = require('localtunnel'); +const https = require('https'); -module.exports = function (subdomain, port) { - if(subdomain !== 'string') { +module.exports = function setupTunnel(subdomain, port, retries = 0) { + if(typeof subdomain !== 'string') { subdomain = require('os').userInfo().username; } - const tunnel = localtunnel(port, {subdomain}, (err, tunnel) => { - if (err) { - console.warn('Could not open tunnel: ', err.message); - } else { - console.log('Listening on ' + tunnel.url); - } + return new Promise((resolve, reject) => { + localtunnel(port, {subdomain}, (err, tunnel) => { + if (err) { + reject(err); + } else { + testTunnel(subdomain).then(() => resolve(tunnel)).catch(err => { + if(retries < 3) { + console.warn(`Failed to connect to localtunnel.me. Trying again (tries: ${retries + 1})`); + resolve(setupTunnel(subdomain, port, retries + 1)); + } else { + reject('Failed to connect to localtunnel.me. Giving up.'); + } + }); + } + }); + }); +} + +// When a tunnel is closed and then immediately reopened (e.g. restarting the +// server to reload changes), then localtunnel.me may connect but not pass +// requests through. This test that the tunnel returns 200 for /ping. +function testTunnel(subdomain) { + const options = { + host: `${subdomain}.localtunnel.me`, + port: 443, + path: '/ping', + method: 'GET' + }; + + return new Promise((resolve, reject) => { + https.request(options, function(res) { + res.statusCode == 200 ? resolve() : reject(); + }).end(); }); - - tunnel.on('close', () => { - console.warn('Local tunnel closed'); - }); - - return tunnel; }