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.
164 lines
4.0 KiB
164 lines
4.0 KiB
2 years ago
|
/* 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');
|
||
|
}
|
||
|
};
|