| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163 | /* 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');  }};
 |