| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- var url = require('url');
- var EventEmitter = require('events').EventEmitter;
- var axios = require('axios');
- var debug = require('debug')('localtunnel:client');
- var TunnelCluster = require('./TunnelCluster');
- var Tunnel = function(opt) {
- if (!(this instanceof Tunnel)) {
- return new Tunnel(opt);
- }
- var self = this;
- self._closed = false;
- self._opt = opt || {};
- self._opt.host = self._opt.host || 'https://localtunnel.me';
- };
- Tunnel.prototype.__proto__ = EventEmitter.prototype;
- // initialize connection
- // callback with connection info
- Tunnel.prototype._init = function(cb) {
- var self = this;
- var opt = self._opt;
- var params = {
- responseType: 'json'
- };
- var base_uri = opt.host + '/';
- // optionally override the upstream server
- var upstream = url.parse(opt.host);
- // no subdomain at first, maybe use requested domain
- var assigned_domain = opt.subdomain;
- // where to quest
- var uri = base_uri + ((assigned_domain) ? assigned_domain : '?new');
- (function get_url() {
- axios.get(uri, params)
- .then(function(res){
- var body = res.data;
- if (res.status !== 200) {
- var err = new Error((body && body.message) || 'localtunnel server returned an error, please try again');
- return cb(err);
- }
- var port = body.port;
- var host = upstream.hostname;
- var max_conn = body.max_conn_count || 1;
- cb(null, {
- remote_host: upstream.hostname,
- remote_port: body.port,
- name: body.id,
- url: body.url,
- max_conn: max_conn
- });
- })
- .catch(function(err){
- // TODO (shtylman) don't print to stdout?
- console.log('tunnel server offline: ' + err.message + ', retry 1s');
- return setTimeout(get_url, 1000);
- })
- })();
- };
- Tunnel.prototype._establish = function(info) {
- var self = this;
- var opt = self._opt;
- // increase max event listeners so that localtunnel consumers don't get
- // warning messages as soon as they setup even one listener. See #71
- self.setMaxListeners(info.max_conn + (EventEmitter.defaultMaxListeners || 10));
- info.local_host = opt.local_host;
- info.local_port = opt.port;
- var tunnels = self.tunnel_cluster = TunnelCluster(info);
- // only emit the url the first time
- tunnels.once('open', function() {
- self.emit('url', info.url);
- });
- // re-emit socket error
- tunnels.on('error', function(err) {
- self.emit('error', err);
- });
- var tunnel_count = 0;
- // track open count
- tunnels.on('open', function(tunnel) {
- tunnel_count++;
- debug('tunnel open [total: %d]', tunnel_count);
- var close_handler = function() {
- tunnel.destroy();
- };
- if (self._closed) {
- return close_handler();
- }
- self.once('close', close_handler);
- tunnel.once('close', function() {
- self.removeListener('close', close_handler);
- });
- });
- // when a tunnel dies, open a new one
- tunnels.on('dead', function(tunnel) {
- tunnel_count--;
- debug('tunnel dead [total: %d]', tunnel_count);
- if (self._closed) {
- return;
- }
- tunnels.open();
- });
- tunnels.on('request', function(info) {
- self.emit('request', info);
- });
- // establish as many tunnels as allowed
- for (var count = 0 ; count < info.max_conn ; ++count) {
- tunnels.open();
- }
- };
- Tunnel.prototype.open = function(cb) {
- var self = this;
- self._init(function(err, info) {
- if (err) {
- return cb(err);
- }
- self.url = info.url;
- self._establish(info);
- cb();
- });
- };
- // shutdown tunnels
- Tunnel.prototype.close = function() {
- var self = this;
- self._closed = true;
- self.emit('close');
- };
- module.exports = Tunnel;
|