Tunnel.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. var url = require('url');
  2. var EventEmitter = require('events').EventEmitter;
  3. var axios = require('axios');
  4. var debug = require('debug')('localtunnel:client');
  5. var TunnelCluster = require('./TunnelCluster');
  6. var Tunnel = function(opt) {
  7. if (!(this instanceof Tunnel)) {
  8. return new Tunnel(opt);
  9. }
  10. var self = this;
  11. self._closed = false;
  12. self._opt = opt || {};
  13. self._opt.host = self._opt.host || 'https://localtunnel.me';
  14. };
  15. Tunnel.prototype.__proto__ = EventEmitter.prototype;
  16. // initialize connection
  17. // callback with connection info
  18. Tunnel.prototype._init = function(cb) {
  19. var self = this;
  20. var opt = self._opt;
  21. var params = {
  22. responseType: 'json'
  23. };
  24. var base_uri = opt.host + '/';
  25. // optionally override the upstream server
  26. var upstream = url.parse(opt.host);
  27. // no subdomain at first, maybe use requested domain
  28. var assigned_domain = opt.subdomain;
  29. // where to quest
  30. var uri = base_uri + ((assigned_domain) ? assigned_domain : '?new');
  31. (function get_url() {
  32. axios.get(uri, params)
  33. .then(function(res){
  34. var body = res.data;
  35. if (res.status !== 200) {
  36. var err = new Error((body && body.message) || 'localtunnel server returned an error, please try again');
  37. return cb(err);
  38. }
  39. var port = body.port;
  40. var host = upstream.hostname;
  41. var max_conn = body.max_conn_count || 1;
  42. cb(null, {
  43. remote_host: upstream.hostname,
  44. remote_port: body.port,
  45. name: body.id,
  46. url: body.url,
  47. max_conn: max_conn
  48. });
  49. })
  50. .catch(function(err){
  51. // TODO (shtylman) don't print to stdout?
  52. console.log('tunnel server offline: ' + err.message + ', retry 1s');
  53. return setTimeout(get_url, 1000);
  54. })
  55. })();
  56. };
  57. Tunnel.prototype._establish = function(info) {
  58. var self = this;
  59. var opt = self._opt;
  60. // increase max event listeners so that localtunnel consumers don't get
  61. // warning messages as soon as they setup even one listener. See #71
  62. self.setMaxListeners(info.max_conn + (EventEmitter.defaultMaxListeners || 10));
  63. info.local_host = opt.local_host;
  64. info.local_port = opt.port;
  65. var tunnels = self.tunnel_cluster = TunnelCluster(info);
  66. // only emit the url the first time
  67. tunnels.once('open', function() {
  68. self.emit('url', info.url);
  69. });
  70. // re-emit socket error
  71. tunnels.on('error', function(err) {
  72. self.emit('error', err);
  73. });
  74. var tunnel_count = 0;
  75. // track open count
  76. tunnels.on('open', function(tunnel) {
  77. tunnel_count++;
  78. debug('tunnel open [total: %d]', tunnel_count);
  79. var close_handler = function() {
  80. tunnel.destroy();
  81. };
  82. if (self._closed) {
  83. return close_handler();
  84. }
  85. self.once('close', close_handler);
  86. tunnel.once('close', function() {
  87. self.removeListener('close', close_handler);
  88. });
  89. });
  90. // when a tunnel dies, open a new one
  91. tunnels.on('dead', function(tunnel) {
  92. tunnel_count--;
  93. debug('tunnel dead [total: %d]', tunnel_count);
  94. if (self._closed) {
  95. return;
  96. }
  97. tunnels.open();
  98. });
  99. tunnels.on('request', function(info) {
  100. self.emit('request', info);
  101. });
  102. // establish as many tunnels as allowed
  103. for (var count = 0 ; count < info.max_conn ; ++count) {
  104. tunnels.open();
  105. }
  106. };
  107. Tunnel.prototype.open = function(cb) {
  108. var self = this;
  109. self._init(function(err, info) {
  110. if (err) {
  111. return cb(err);
  112. }
  113. self.url = info.url;
  114. self._establish(info);
  115. cb();
  116. });
  117. };
  118. // shutdown tunnels
  119. Tunnel.prototype.close = function() {
  120. var self = this;
  121. self._closed = true;
  122. self.emit('close');
  123. };
  124. module.exports = Tunnel;