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.
163 lines
4.0 KiB
163 lines
4.0 KiB
/* eslint-disable consistent-return, no-underscore-dangle */ |
|
|
|
const { parse } = require('url'); |
|
const { EventEmitter } = require('events'); |
|
const axios = require('axios'); |
|
const debug = require('debug')('localtunnel:client'); |
|
|
|
const TunnelCluster = require('./TunnelCluster'); |
|
|
|
module.exports = class Tunnel extends EventEmitter { |
|
constructor(opts = {}) { |
|
super(opts); |
|
this.opts = opts; |
|
this.closed = false; |
|
if (!this.opts.host) { |
|
this.opts.host = 'https://localtunnel.me'; |
|
} |
|
} |
|
|
|
_getInfo(body) { |
|
/* eslint-disable camelcase */ |
|
const { id, ip, port, url, cached_url, max_conn_count } = body; |
|
const { host, port: local_port, local_host } = this.opts; |
|
const { local_https, local_cert, local_key, local_ca, allow_invalid_cert } = this.opts; |
|
return { |
|
name: id, |
|
url, |
|
cached_url, |
|
max_conn: max_conn_count || 1, |
|
remote_host: parse(host).hostname, |
|
remote_ip: ip, |
|
remote_port: port, |
|
local_port, |
|
local_host, |
|
local_https, |
|
local_cert, |
|
local_key, |
|
local_ca, |
|
allow_invalid_cert, |
|
}; |
|
/* eslint-enable camelcase */ |
|
} |
|
|
|
// initialize connection |
|
// callback with connection info |
|
_init(cb) { |
|
const opt = this.opts; |
|
const getInfo = this._getInfo.bind(this); |
|
|
|
const params = { |
|
responseType: 'json', |
|
}; |
|
|
|
const baseUri = `${opt.host}/`; |
|
// no subdomain at first, maybe use requested domain |
|
const assignedDomain = opt.subdomain; |
|
// where to quest |
|
const uri = baseUri + (assignedDomain || '?new'); |
|
|
|
(function getUrl() { |
|
axios |
|
.get(uri, params) |
|
.then(res => { |
|
const body = res.data; |
|
debug('got tunnel information', res.data); |
|
if (res.status !== 200) { |
|
const err = new Error( |
|
(body && body.message) || 'localtunnel server returned an error, please try again' |
|
); |
|
return cb(err); |
|
} |
|
cb(null, getInfo(body)); |
|
}) |
|
.catch(err => { |
|
debug(`tunnel server offline: ${err.message}, retry 1s`); |
|
return setTimeout(getUrl, 1000); |
|
}); |
|
})(); |
|
} |
|
|
|
_establish(info) { |
|
// increase max event listeners so that localtunnel consumers don't get |
|
// warning messages as soon as they setup even one listener. See #71 |
|
this.setMaxListeners(info.max_conn + (EventEmitter.defaultMaxListeners || 10)); |
|
|
|
this.tunnelCluster = new TunnelCluster(info); |
|
|
|
// only emit the url the first time |
|
this.tunnelCluster.once('open', () => { |
|
this.emit('url', info.url); |
|
}); |
|
|
|
// re-emit socket error |
|
this.tunnelCluster.on('error', err => { |
|
debug('got socket error', err.message); |
|
this.emit('error', err); |
|
}); |
|
|
|
let tunnelCount = 0; |
|
|
|
// track open count |
|
this.tunnelCluster.on('open', tunnel => { |
|
tunnelCount++; |
|
debug('tunnel open [total: %d]', tunnelCount); |
|
|
|
const closeHandler = () => { |
|
tunnel.destroy(); |
|
}; |
|
|
|
if (this.closed) { |
|
return closeHandler(); |
|
} |
|
|
|
this.once('close', closeHandler); |
|
tunnel.once('close', () => { |
|
this.removeListener('close', closeHandler); |
|
}); |
|
}); |
|
|
|
// when a tunnel dies, open a new one |
|
this.tunnelCluster.on('dead', () => { |
|
tunnelCount--; |
|
debug('tunnel dead [total: %d]', tunnelCount); |
|
if (this.closed) { |
|
return; |
|
} |
|
this.tunnelCluster.open(); |
|
}); |
|
|
|
this.tunnelCluster.on('request', req => { |
|
this.emit('request', req); |
|
}); |
|
|
|
// establish as many tunnels as allowed |
|
for (let count = 0; count < info.max_conn; ++count) { |
|
this.tunnelCluster.open(); |
|
} |
|
} |
|
|
|
open(cb) { |
|
this._init((err, info) => { |
|
if (err) { |
|
return cb(err); |
|
} |
|
|
|
this.clientId = info.name; |
|
this.url = info.url; |
|
|
|
// `cached_url` is only returned by proxy servers that support resource caching. |
|
if (info.cached_url) { |
|
this.cachedUrl = info.cached_url; |
|
} |
|
|
|
this._establish(info); |
|
cb(); |
|
}); |
|
} |
|
|
|
close() { |
|
this.closed = true; |
|
this.emit('close'); |
|
} |
|
};
|
|
|