TunnelCluster.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. "use strict";
  2. var __importDefault = (this && this.__importDefault) || function (mod) {
  3. return (mod && mod.__esModule) ? mod : { "default": mod };
  4. };
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. const events_1 = require("events");
  7. const debug_1 = __importDefault(require("debug"));
  8. const fs_1 = __importDefault(require("fs"));
  9. const net_1 = __importDefault(require("net"));
  10. const tls_1 = __importDefault(require("tls"));
  11. const HeaderHostTransformer_1 = __importDefault(require("./HeaderHostTransformer"));
  12. const debugLog = (0, debug_1.default)('localtunnel:client');
  13. class TunnelCluster extends events_1.EventEmitter {
  14. constructor(opts = {}) {
  15. super();
  16. this.isRegistered = false;
  17. this.opts = opts;
  18. this.remoteHost = opts.remote_ip || opts.remote_host || 'localhost';
  19. this.remotePort = opts.remote_port || 7002;
  20. this.clientId = opts.name || '';
  21. }
  22. open() {
  23. const opt = this.opts;
  24. const remoteHostOrIp = opt.remote_ip || opt.remote_host;
  25. const remotePort = opt.remote_port;
  26. const localHost = opt.local_host || 'localhost';
  27. const localPort = opt.local_port;
  28. if (!localPort || typeof localPort !== 'number') {
  29. this.emit('error', new Error('Invalid local port configuration'));
  30. return;
  31. }
  32. const localProtocol = opt.local_https ? 'https' : 'http';
  33. if (!remoteHostOrIp || !remotePort) {
  34. this.emit('error', new Error('Missing remote host/port configuration'));
  35. return;
  36. }
  37. debugLog('establishing tunnel %s://%s:%s <> %s:%s', localProtocol, localHost, localPort, remoteHostOrIp, remotePort);
  38. const remote = net_1.default.connect({
  39. host: remoteHostOrIp,
  40. port: remotePort
  41. });
  42. remote.setKeepAlive(true);
  43. const registrationTimeout = setTimeout(() => {
  44. if (!this.isRegistered) {
  45. debugLog('registration timeout');
  46. remote.destroy(new Error('Registration timeout'));
  47. }
  48. }, 5000);
  49. remote.once('connect', () => {
  50. debugLog('发送身份识别信息');
  51. remote.write(JSON.stringify({
  52. clientId: this.clientId,
  53. type: 'register'
  54. }) + '\n');
  55. let buffer = '';
  56. const messageHandler = (data) => {
  57. buffer += data.toString();
  58. if (buffer.includes('\n')) {
  59. try {
  60. const response = JSON.parse(buffer);
  61. if (response.type === 'registered' && response.success) {
  62. clearTimeout(registrationTimeout);
  63. this.isRegistered = true;
  64. remote.removeListener('data', messageHandler);
  65. this.emit('open', remote);
  66. connLocal();
  67. }
  68. else if (response.type === 'registered' && !response.success) {
  69. remote.destroy(new Error(response.message || 'Registration failed'));
  70. }
  71. }
  72. catch (err) {
  73. debugLog('解析注册响应失败:', err);
  74. remote.destroy();
  75. }
  76. buffer = '';
  77. }
  78. };
  79. remote.on('data', messageHandler);
  80. });
  81. remote.on('error', (err) => {
  82. clearTimeout(registrationTimeout);
  83. debugLog('remote connection error:', err.message);
  84. if (err.code === 'ECONNREFUSED') {
  85. this.emit('error', new Error(`Connection refused: ${remoteHostOrIp}:${remotePort} (check your firewall settings)`));
  86. }
  87. remote.end();
  88. });
  89. const connLocal = () => {
  90. if (remote.destroyed) {
  91. debugLog('remote destroyed');
  92. this.emit('dead');
  93. return;
  94. }
  95. debugLog('connecting locally to %s://%s:%d', localProtocol, localHost, localPort);
  96. remote.pause();
  97. const local = this.createLocalConnection(localPort);
  98. local.once('error', (err) => {
  99. debugLog('local error %s', err.message);
  100. local.end();
  101. if (err.code !== 'ECONNREFUSED'
  102. && err.code !== 'ECONNRESET') {
  103. return remote.end();
  104. }
  105. setTimeout(connLocal, 1000);
  106. });
  107. local.once('connect', () => {
  108. debugLog('connected locally');
  109. remote.resume();
  110. this.setupPipes(remote, local);
  111. });
  112. };
  113. }
  114. createLocalConnection(localPort) {
  115. const { local_host, local_https, local_cert, local_key, local_ca, allow_invalid_cert } = this.opts;
  116. const localConnectOpts = {
  117. host: local_host || 'localhost',
  118. port: localPort
  119. };
  120. if (!local_https) {
  121. return net_1.default.connect(localConnectOpts);
  122. }
  123. const tlsOpts = allow_invalid_cert ?
  124. { rejectUnauthorized: false } :
  125. {
  126. cert: local_cert ? fs_1.default.readFileSync(local_cert) : undefined,
  127. key: local_key ? fs_1.default.readFileSync(local_key) : undefined,
  128. ca: local_ca ? [fs_1.default.readFileSync(local_ca)] : undefined,
  129. };
  130. return tls_1.default.connect({ ...localConnectOpts, ...tlsOpts });
  131. }
  132. setupPipes(remote, local) {
  133. let stream = remote;
  134. if (this.opts.local_host) {
  135. debugLog('transform Host header to %s', this.opts.local_host);
  136. const transformer = new HeaderHostTransformer_1.default({ host: this.opts.local_host });
  137. stream = remote.pipe(transformer);
  138. }
  139. stream.pipe(local).pipe(remote);
  140. local.once('close', (hadError) => {
  141. debugLog('local connection closed [%s]', hadError);
  142. if (!remote.destroyed) {
  143. remote.end();
  144. }
  145. });
  146. remote.once('close', () => {
  147. debugLog('remote close');
  148. this.emit('dead');
  149. if (!local.destroyed) {
  150. local.end();
  151. }
  152. });
  153. }
  154. }
  155. exports.default = TunnelCluster;