| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 |
- "use strict";
- var __importDefault = (this && this.__importDefault) || function (mod) {
- return (mod && mod.__esModule) ? mod : { "default": mod };
- };
- Object.defineProperty(exports, "__esModule", { value: true });
- const events_1 = require("events");
- const debug_1 = __importDefault(require("debug"));
- const fs_1 = __importDefault(require("fs"));
- const net_1 = __importDefault(require("net"));
- const tls_1 = __importDefault(require("tls"));
- const HeaderHostTransformer_1 = __importDefault(require("./HeaderHostTransformer"));
- const debugLog = (0, debug_1.default)('localtunnel:client');
- class TunnelCluster extends events_1.EventEmitter {
- constructor(opts = {}) {
- super();
- this.isRegistered = false;
- this.opts = opts;
- this.remoteHost = opts.remote_ip || opts.remote_host || 'localhost';
- this.remotePort = opts.remote_port || 7002;
- this.clientId = opts.name || '';
- }
- open() {
- const opt = this.opts;
- const remoteHostOrIp = opt.remote_ip || opt.remote_host;
- const remotePort = opt.remote_port;
- const localHost = opt.local_host || 'localhost';
- const localPort = opt.local_port;
- if (!localPort || typeof localPort !== 'number') {
- this.emit('error', new Error('Invalid local port configuration'));
- return;
- }
- const localProtocol = opt.local_https ? 'https' : 'http';
- if (!remoteHostOrIp || !remotePort) {
- this.emit('error', new Error('Missing remote host/port configuration'));
- return;
- }
- debugLog('establishing tunnel %s://%s:%s <> %s:%s', localProtocol, localHost, localPort, remoteHostOrIp, remotePort);
- const remote = net_1.default.connect({
- host: remoteHostOrIp,
- port: remotePort
- });
- remote.setKeepAlive(true);
- const registrationTimeout = setTimeout(() => {
- if (!this.isRegistered) {
- debugLog('registration timeout');
- remote.destroy(new Error('Registration timeout'));
- }
- }, 5000);
- remote.once('connect', () => {
- debugLog('发送身份识别信息');
- remote.write(JSON.stringify({
- clientId: this.clientId,
- type: 'register'
- }) + '\n');
- let buffer = '';
- const messageHandler = (data) => {
- buffer += data.toString();
- if (buffer.includes('\n')) {
- try {
- const response = JSON.parse(buffer);
- if (response.type === 'registered' && response.success) {
- clearTimeout(registrationTimeout);
- this.isRegistered = true;
- remote.removeListener('data', messageHandler);
- this.emit('open', remote);
- connLocal();
- }
- else if (response.type === 'registered' && !response.success) {
- remote.destroy(new Error(response.message || 'Registration failed'));
- }
- }
- catch (err) {
- debugLog('解析注册响应失败:', err);
- remote.destroy();
- }
- buffer = '';
- }
- };
- remote.on('data', messageHandler);
- });
- remote.on('error', (err) => {
- clearTimeout(registrationTimeout);
- debugLog('remote connection error:', err.message);
- if (err.code === 'ECONNREFUSED') {
- this.emit('error', new Error(`Connection refused: ${remoteHostOrIp}:${remotePort} (check your firewall settings)`));
- }
- remote.end();
- });
- const connLocal = () => {
- if (remote.destroyed) {
- debugLog('remote destroyed');
- this.emit('dead');
- return;
- }
- debugLog('connecting locally to %s://%s:%d', localProtocol, localHost, localPort);
- remote.pause();
- const local = this.createLocalConnection(localPort);
- local.once('error', (err) => {
- debugLog('local error %s', err.message);
- local.end();
- if (err.code !== 'ECONNREFUSED'
- && err.code !== 'ECONNRESET') {
- return remote.end();
- }
- setTimeout(connLocal, 1000);
- });
- local.once('connect', () => {
- debugLog('connected locally');
- remote.resume();
- this.setupPipes(remote, local);
- });
- };
- }
- createLocalConnection(localPort) {
- const { local_host, local_https, local_cert, local_key, local_ca, allow_invalid_cert } = this.opts;
- const localConnectOpts = {
- host: local_host || 'localhost',
- port: localPort
- };
- if (!local_https) {
- return net_1.default.connect(localConnectOpts);
- }
- const tlsOpts = allow_invalid_cert ?
- { rejectUnauthorized: false } :
- {
- cert: local_cert ? fs_1.default.readFileSync(local_cert) : undefined,
- key: local_key ? fs_1.default.readFileSync(local_key) : undefined,
- ca: local_ca ? [fs_1.default.readFileSync(local_ca)] : undefined,
- };
- return tls_1.default.connect({ ...localConnectOpts, ...tlsOpts });
- }
- setupPipes(remote, local) {
- let stream = remote;
- if (this.opts.local_host) {
- debugLog('transform Host header to %s', this.opts.local_host);
- const transformer = new HeaderHostTransformer_1.default({ host: this.opts.local_host });
- stream = remote.pipe(transformer);
- }
- stream.pipe(local).pipe(remote);
- local.once('close', (hadError) => {
- debugLog('local connection closed [%s]', hadError);
- if (!remote.destroyed) {
- remote.end();
- }
- });
- remote.once('close', () => {
- debugLog('remote close');
- this.emit('dead');
- if (!local.destroyed) {
- local.end();
- }
- });
- }
- }
- exports.default = TunnelCluster;
|