"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;
