UNPKG

@onboardbase/cli

Version:

[![Version](https://img.shields.io/npm/v/@onboardbase/cli.svg)](https://www.npmjs.com/package/@onboardbase/cli) [![Downloads/week](https://img.shields.io/npm/dw/@onboardbase/cli.svg)](https://www.npmjs.com/package/@onboardbase/cli) [![License](https://img

121 lines (120 loc) 5.07 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const events_1 = require("events"); const _debuger = require("debug"); const fs = require("fs"); const net = require("net"); const tls = require("tls"); const headerHostTransformer_1 = require("./headerHostTransformer"); const debug = _debuger.debug("tunnel:client"); // manages groups of tunnels class TunnelCluster extends events_1.EventEmitter { constructor(opts) { // @ts-ignore super(opts); this.opts = opts; } open() { const opt = this.opts; // Prefer IP if returned by the server const remoteHostOrIp = opt.remote_ip || opt.remote_host; const remotePort = opt.remote_port; const localHost = opt.local_host || "localhost"; const localPort = opt.local_port; const localProtocol = opt.local_https ? "https" : "http"; const allowInvalidCert = opt.allow_invalid_cert; debug("establishing tunnel %s://%s:%s <> %s:%s", localProtocol, localHost, localPort, remoteHostOrIp, remotePort); // connection to localtunnel server const remote = net.connect({ host: remoteHostOrIp, port: remotePort, }); remote.setKeepAlive(true); remote.on("error", (err) => { debug("got remote connection error", err.message); // emit connection refused errors immediately, because they // indicate that the tunnel can't be established. // @ts-ignore if (err.code === "ECONNREFUSED") { this.emit("error", new Error(`connection refused: ${remoteHostOrIp}:${remotePort} (check your firewall settings)`)); } remote.end(); }); const connLocal = () => { if (remote.destroyed) { debug("remote destroyed"); this.emit("dead"); return; } debug("connecting locally to %s://%s:%d", localProtocol, localHost, localPort); remote.pause(); if (allowInvalidCert) { debug("allowing invalid certificates"); } const getLocalCertOpts = () => allowInvalidCert ? { rejectUnauthorized: false } : { cert: fs.readFileSync(opt.local_cert), key: fs.readFileSync(opt.local_key), ca: opt.local_ca ? [fs.readFileSync(opt.local_ca)] : undefined, }; // connection to local http server const local = opt.local_https ? tls.connect(Object.assign({ host: localHost, port: localPort }, getLocalCertOpts())) : net.connect({ host: localHost, port: localPort }); const remoteClose = () => { debug("remote close"); this.emit("dead"); local.end(); }; remote.once("close", remoteClose); // TODO some languages have single threaded servers which makes opening up // multiple local connections impossible. We need a smarter way to scale // and adjust for such instances to avoid beating on the door of the server local.once("error", (err) => { console.log(err); debug("local error %s", err.message); local.end(); remote.removeListener("close", remoteClose); // @ts-ignore if (err.code !== "ECONNREFUSED") { return remote.end(); } // retrying connection to local server setTimeout(connLocal, 1000); }); local.once("connect", () => { debug("connected locally"); remote.resume(); let stream = remote; // if user requested specific local host // then we use host header transform to replace the host header if (opt.local_host) { debug("transform Host header to %s", opt.local_host); // @ts-ignore stream = remote.pipe(new headerHostTransformer_1.HeaderHostTransformer({ host: opt.local_host })); } stream.pipe(local).pipe(remote); // when local closes, also get a new remote local.once("close", (hadError) => { debug("local connection closed [%s]", hadError); }); }); }; remote.on("data", (data) => { const match = data.toString().match(/^(\w+) (\S+)/); if (match) { this.emit("request", { method: match[1], path: match[2], }); } }); // tunnel is considered open when remote connects remote.once("connect", () => { this.emit("open", remote); connLocal(); }); } } exports.default = TunnelCluster;