@duskim/http-tunnel 1.2.6 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import{isJSON}from "../lib/utils.js";import{Transform}from"stream";export default class BypassMessageFilter extends Transform{constructor(r){super({readableObjectMode:!0,writableObjectMode:!0}),this.schema=r}_transform(r, s, e){var t=r.toString();if(isJSON(t)){t=JSON.parse(t);if(this.schema.isValidSync(t))return e(null,null)}return e(null,r)}}
1
+ import{isJSON}from"../lib/utils.js";import{Transform}from"stream";export default class BypassMessageFilter extends Transform{constructor(r){super({readableObjectMode:!0,writableObjectMode:!0}),this.schema=r}_transform(r,s,e){var t=r.toString();if(isJSON(t)){t=JSON.parse(t);if(this.schema.isValidSync(t))return e(null,null)}return e(null,r)}}
@@ -1 +1 @@
1
- import{EventEmitter}from"events";import net from"net";import log from"npmlog";export default class LocalTunnelWrapper extends EventEmitter{constructor(e){super(),this.socket=null,this.options=e}async openNewConnection(){var e=new URL(this.options.remoteUrl);return this.socket=net.connect({host:e.hostname,port:Number(e.port),noDelay:!0,keepAlive:!0}),new Promise(e=>{this.socket.once("connect",()=>{e(this.socket)}).once("close",e=>{this.close()}).on("error",e=>{e&&log.error("LocalTunnelWrapper: ",e.message)})})}close(){this.emit("dead",null),this.socket?.destroy()}}
1
+ import{EventEmitter}from"events";import net from"net";import log from"npmlog";export default class LocalTunnelWrapper extends EventEmitter{constructor(e){super(),this.socket=null,this.options=e}async openNewConnection(){var e=new URL(this.options.remoteUrl);return this.socket=net.connect({host:e.hostname,port:Number(e.port),noDelay:!0,keepAlive:!0}),new Promise((e,o)=>{this.socket.once("connect",()=>{e(this.socket)}).once("close",e=>{this.close()}).on("error",e=>{log.error("LocalTunnelWrapper: ","Connection error:",e.message),this.emit("error",e),o(e)})})}close(){this.emit("dead",null),this.socket?.destroy()}}
@@ -1 +1 @@
1
- import{generateRandomString}from "../lib/utils.js";import{EventEmitter}from"events";import log from"npmlog";import tls from"tls";import{DEFAULT_SOCKET_TIMEOUT}from "../constants.js";export default class RemoteTunnelWrapper extends EventEmitter{constructor(e){super(),this.options=e,this._socketId=generateRandomString(12)}async openConn(){var e=new URL(this.options.remoteUrl);return this._remote=tls.connect({key:this.options.serverPrivateKey,cert:this.options.serverPublicKey,host:e.hostname,port:Number(e.port),rejectUnauthorized:!1}),this._remote.setNoDelay(!0),this._remote.setKeepAlive(!0),new Promise((e, t)=>{this._handleUnresponsive=setTimeout(()=>{this.teardown(),t(null)},DEFAULT_SOCKET_TIMEOUT),this._remote.once("connect",()=>{clearTimeout(this._handleUnresponsive),this.emit("open",this),e(this._remote)}).once("close", e=>{this.teardown(this._err),t(!0)}).on("error", e=>{this._err=e,log.error("RemoteTunnelWrapper: ",e.message),clearTimeout(this._handleUnresponsive),t(!0)})})}teardown(e){this.socket?.destroy(),clearTimeout(this._handleUnresponsive),this.emit("dead",e,this)}get socket(){return this._remote}get id(){return this._socketId}}
1
+ import{generateRandomString}from"../lib/utils.js";import{EventEmitter}from"events";import log from"npmlog";import tls from"tls";import{DEFAULT_SOCKET_TIMEOUT}from"../constants.js";export default class RemoteTunnelWrapper extends EventEmitter{constructor(e){super(),this.options=e,this._socketId=generateRandomString(12)}async openConn(){var e=new URL(this.options.remoteUrl);return this._remote=tls.connect({key:this.options.serverPrivateKey,cert:this.options.serverPublicKey,host:e.hostname,port:Number(e.port),rejectUnauthorized:!1}),this._remote.setNoDelay(!0),this._remote.setKeepAlive(!0),new Promise((e,r)=>{this._handleUnresponsive=setTimeout(()=>{var e=new Error(`Connection timeout after ${DEFAULT_SOCKET_TIMEOUT}ms`);log.error("RemoteTunnelWrapper: ",e.message),this.teardown(e),r(e)},DEFAULT_SOCKET_TIMEOUT),this._remote.once("connect",()=>{clearTimeout(this._handleUnresponsive),this.emit("open",this),e(this._remote)}).once("close",e=>{var t=this._err||new Error("Connection closed unexpectedly");log.error("RemoteTunnelWrapper: ",t.message),this.teardown(this._err),r(t)}).on("error",e=>{this._err=e,log.error("RemoteTunnelWrapper: ","Connection error:",e.message),this.emit("error",e),clearTimeout(this._handleUnresponsive),r(e)})})}teardown(e){this.socket?.destroy(),clearTimeout(this._handleUnresponsive),this.emit("dead",e,this)}get socket(){return this._remote}get id(){return this._socketId}}
@@ -1 +1 @@
1
- import{EventEmitter}from"events";import jwt from"jsonwebtoken";import log from"npmlog";import io from"socket.io-client";export default class TunnelClient extends EventEmitter{constructor(e){super();var t=jwt.sign({},e.privateKey,{algorithm:"RS256"}),t=io(e.remoteUrl,{secure:!0,extraHeaders:{Authorization:"Bearer "+t},transports:["websocket"],rejectUnauthorized:!1});t.on("connect_error",e=>{log.error("TunnelClient: ",e)}),t.on("open-tunnel",async()=>{await e.cluster.open()}),this.socket=t}connect(){this.socket.connect()}close(){this.socket.close()}}
1
+ import{SOCKETIO_RECONNECTION_ATTEMPTS,SOCKETIO_RECONNECTION_DELAY,SOCKETIO_RECONNECTION_DELAY_MAX,SOCKETIO_TIMEOUT}from"../constants";import{EventEmitter}from"events";import jwt from"jsonwebtoken";import log from"npmlog";import io from"socket.io-client";export default class TunnelClient extends EventEmitter{constructor(e){super(),this.reconnectAttempts=0;var t=jwt.sign({},e.privateKey,{algorithm:"RS256"}),t=io(e.remoteUrl,{secure:!0,extraHeaders:{Authorization:"Bearer "+t},transports:["websocket"],rejectUnauthorized:!1,reconnection:!0,reconnectionAttempts:SOCKETIO_RECONNECTION_ATTEMPTS,reconnectionDelay:SOCKETIO_RECONNECTION_DELAY,reconnectionDelayMax:SOCKETIO_RECONNECTION_DELAY_MAX,timeout:SOCKETIO_TIMEOUT});t.on("connect",()=>{log.info("TunnelClient: ","Connected to server"),this.reconnectAttempts=0,this.emit("connected")}),t.on("disconnect",e=>{log.warn("TunnelClient: ","Disconnected: "+e),this.emit("disconnected",e)}),t.on("connect_error",e=>{this.reconnectAttempts++,log.error("TunnelClient: ",`Connection error (attempt ${this.reconnectAttempts}/${SOCKETIO_RECONNECTION_ATTEMPTS}):`,e.message),this.emit("connection-error",e)}),t.on("reconnect",e=>{log.info("TunnelClient: ",`Reconnected successfully after ${e} attempts`),this.reconnectAttempts=0,this.emit("reconnected",e)}),t.on("reconnect_attempt",e=>{log.info("TunnelClient: ",`Reconnection attempt ${e}/`+SOCKETIO_RECONNECTION_ATTEMPTS)}),t.on("reconnect_error",e=>{log.error("TunnelClient: ","Reconnection error:",e.message)}),t.on("reconnect_failed",()=>{log.error("TunnelClient: ",`Failed to reconnect after ${SOCKETIO_RECONNECTION_ATTEMPTS} attempts`),this.emit("fatal-error",new Error("Failed to reconnect to server"))}),t.on("open-tunnel",async()=>{await e.cluster.open()}),this.socket=t}connect(){this.socket.connect()}close(){this.socket.close()}}
@@ -1 +1 @@
1
- import{EventEmitter}from"events";import pump from"pump";import{DEFAULT_AGENT_PORT,DEFAULT_LOCALHOST_PORT}from "../constants.js";import{LocalTunnelWrapper,RemoteTunnelWrapper}from "./index.js";export default class TunnelCluster extends EventEmitter{constructor(e){super(),this.options=e}async open(){try{var e=new URL(this.options.remoteUrl);e.port=String(DEFAULT_AGENT_PORT);var r=await new RemoteTunnelWrapper({serverPrivateKey:this.options.serverPrivateKey,serverPublicKey:this.options.serverPublicKey,remoteUrl:e.href}).openConn();r.pause();var t=await new LocalTunnelWrapper({remoteUrl:"http://localhost:"+DEFAULT_LOCALHOST_PORT}).openNewConnection();return pump(r,t,r),r.resume(),!0}catch(e){return!1}}}
1
+ import{EventEmitter}from"events";import log from"npmlog";import pump from"pump";import{DEFAULT_AGENT_PORT,DEFAULT_LOCALHOST_PORT}from"../constants";import{LocalTunnelWrapper,RemoteTunnelWrapper}from"./index.js";export default class TunnelCluster extends EventEmitter{constructor(e){super(),this.options=e}async open(){try{var e=new URL(this.options.remoteUrl);e.port=String(DEFAULT_AGENT_PORT);var r=await new RemoteTunnelWrapper({serverPrivateKey:this.options.serverPrivateKey,serverPublicKey:this.options.serverPublicKey,remoteUrl:e.href}).openConn();r.pause();var t=await new LocalTunnelWrapper({remoteUrl:"http://localhost:"+DEFAULT_LOCALHOST_PORT}).openNewConnection();return pump(r,t,r),r.resume(),!0}catch(e){return log.error("TunnelCluster: ","Failed to open tunnel connection",e instanceof Error?e.message:e),this.emit("error",e),!1}}}
@@ -1 +1 @@
1
- import TunnelClient from "./TunnelClient.js";import{EventEmitter}from"events";import{getHttpsCerts}from "../server/utils.js";import{TunnelCluster}from "./index.js";export default class TunnelManager extends EventEmitter{constructor(e){super(),this.options=e,this.setMaxListeners(EventEmitter.defaultMaxListeners||10);var t=getHttpsCerts(e.serverPrivateKey,e.serverPublicKey),r=new TunnelCluster({remoteUrl:this.options.remoteUrl,serverPublicKey:t.cert,serverPrivateKey:t.key});this.tunnelClient=new TunnelClient({cluster:r,privateKey:t.key,remoteUrl:e.remoteUrl})}open(){this.tunnelClient.connect()}close(){this.tunnelClient.close()}}
1
+ import TunnelClient from"./TunnelClient.js";import{EventEmitter}from"events";import{getHttpsCerts}from"../server/utils";import{TunnelCluster}from"./index.js";export default class TunnelManager extends EventEmitter{constructor(e){super(),this.options=e,this.setMaxListeners(EventEmitter.defaultMaxListeners||10);var t=getHttpsCerts(e.serverPrivateKey,e.serverPublicKey),r=new TunnelCluster({remoteUrl:this.options.remoteUrl,serverPublicKey:t.cert,serverPrivateKey:t.key});this.tunnelClient=new TunnelClient({cluster:r,privateKey:t.key,remoteUrl:e.remoteUrl})}open(){this.tunnelClient.connect()}close(){this.tunnelClient.close()}}
@@ -1 +1 @@
1
- export{default as LocalTunnelWrapper}from "./LocalTunnelWrapper.js";export{default as RemoteTunnelWrapper}from "./RemoteTunnelWrapper.js";export{default as TunnelCluster}from "./TunnelCluster.js";export{default as TunnelManager}from "./TunnelManager.js";export{default as TunnelClient}from "./TunnelClient.js";
1
+ export{default as LocalTunnelWrapper}from"./LocalTunnelWrapper.js";export{default as RemoteTunnelWrapper}from"./RemoteTunnelWrapper.js";export{default as TunnelCluster}from"./TunnelCluster.js";export{default as TunnelManager}from"./TunnelManager.js";export{default as TunnelClient}from"./TunnelClient.js";
package/dist/constants.js CHANGED
@@ -1 +1 @@
1
- const DEFAULT_SOCKET_TIMEOUT=1e4,TIME_UNTIL_STALE=15e3,DEFAULT_SERVER_PORT=4e3,DEFAULT_AGENT_PORT=4001,DEFAULT_LOCALHOST_PORT=3001;export{DEFAULT_SOCKET_TIMEOUT,TIME_UNTIL_STALE,DEFAULT_SERVER_PORT,DEFAULT_AGENT_PORT,DEFAULT_LOCALHOST_PORT};
1
+ const DEFAULT_SOCKET_TIMEOUT=1e4,TIME_UNTIL_STALE=15e3,DEFAULT_REQUEST_TIMEOUT=3e4,MAX_SOCKET_POOL_SIZE=100,SOCKETIO_RECONNECTION_ATTEMPTS=10,SOCKETIO_RECONNECTION_DELAY=1e3,SOCKETIO_RECONNECTION_DELAY_MAX=1e4,SOCKETIO_TIMEOUT=2e4,DEFAULT_SERVER_PORT=4e3,DEFAULT_AGENT_PORT=4001,DEFAULT_LOCALHOST_PORT=3001;export{DEFAULT_SOCKET_TIMEOUT,TIME_UNTIL_STALE,DEFAULT_REQUEST_TIMEOUT,MAX_SOCKET_POOL_SIZE,SOCKETIO_RECONNECTION_ATTEMPTS,SOCKETIO_RECONNECTION_DELAY,SOCKETIO_RECONNECTION_DELAY_MAX,SOCKETIO_TIMEOUT,DEFAULT_SERVER_PORT,DEFAULT_AGENT_PORT,DEFAULT_LOCALHOST_PORT};
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export{default as TunnelManager}from "./client/TunnelManager.js";export{default as TunnelServer}from "./server/TunnelServer.js";
1
+ export{default as TunnelManager}from"./client/TunnelManager.js";export{default as TunnelServer}from"./server/TunnelServer.js";
@@ -1 +1 @@
1
- import{EventEmitter}from"events";import{TIME_UNTIL_STALE}from "../constants.js";export default class CallbackQueue extends EventEmitter{constructor(){super(),this.queue=[],setInterval(()=>{this.queue=this.queue.filter(e=>{var{callback:e,date:t}=e,t=Date.now()-t;return!(TIME_UNTIL_STALE<=t&&(e(new Error("closed"),null),1))})},1e4)}clear(){this.queue=[]}hasPendingCallbacks(){return 0<this.queue.length}enqueue(e){e={callback:e,date:Date.now()};this.queue.push(e),this.emit("push",e)}dequeue(){return this.queue.shift()?.callback}[Symbol.iterator](){let e=0;return{_first:!0,next:()=>e<this.queue.length?{value:this.queue[e++].callback,done:!1}:{done:!0}}}}
1
+ import{EventEmitter}from"events";import{TIME_UNTIL_STALE}from"../constants.js";export default class CallbackQueue extends EventEmitter{constructor(){super(),this.queue=[],this.cleanupInterval=setInterval(()=>{this.queue=this.queue.filter(e=>{var{callback:e,date:t}=e,t=Date.now()-t;return!(TIME_UNTIL_STALE<=t&&(e(new Error("closed"),null),1))})},1e4)}clear(){this.queue.splice(0,this.queue.length)}destroy(){clearInterval(this.cleanupInterval);for(const e of this.queue)e.callback(new Error("CallbackQueue destroyed"),null);this.clear(),this.removeAllListeners()}hasPendingCallbacks(){return 0<this.queue.length}getPendingCount(){return this.queue.length}enqueue(e){e={callback:e,date:Date.now()};this.queue.push(e),this.emit("push",e)}dequeue(){return this.queue.shift()?.callback}[Symbol.iterator](){let e=0;return{_first:!0,next:()=>e<this.queue.length?{value:this.queue[e++].callback,done:!1}:{done:!0}}}}
@@ -1 +1 @@
1
- import{DEFAULT_SOCKET_TIMEOUT}from "../constants.js";export default class SocketManager{constructor(){this.availableSockets=[],this.socketCounter=0}addSocket(t){t.setKeepAlive(!0),t.id=this.socketCounter++,this.availableSockets.push(t),t.setTimeout(DEFAULT_SOCKET_TIMEOUT,()=>{t.destroy()}),t.on("disconnect", e=>{this.handleSocketDisconnect(t,e)}),t.on("error", e=>{console.error("Socket error: "+t.id,e),this.handleSocketDisconnect(t,"error")})}handleSocketDisconnect(t, e){console.log(`Socket ${t.id} disconnected: `+e),this.availableSockets=this.availableSockets.filter(e=>t!==e),this.cleanupSocketOperations(t)}cleanupSocketOperations(e){}cleanupDeadSockets(){var e=this.availableSockets.length,e=(this.availableSockets=this.availableSockets.filter(e=>{var t=!e.closed&&"open"===e.readyState;return t||console.log("Removing dead socket: "+e.id),t}),e-this.availableSockets.length);0<e&&console.log(`Cleaned up ${e} dead sockets`)}getAvailableSocket(){this.cleanupDeadSockets();const e=this.availableSockets.shift();if(e)return e.setTimeout(1.5*DEFAULT_SOCKET_TIMEOUT,()=>{e.destroy()}),e;console.log("No available connected sockets")}}
1
+ import log from"npmlog";import{DEFAULT_SOCKET_TIMEOUT,MAX_SOCKET_POOL_SIZE}from"../constants";export default class SocketManager{constructor(e=MAX_SOCKET_POOL_SIZE){this.availableSockets=[],this.socketCounter=0,this.maxPoolSize=e}addSocket(t){return this.availableSockets.length>=this.maxPoolSize?(log.warn("SocketManager: ",`Pool at capacity (${this.maxPoolSize}), rejecting socket`),t.destroy(),!1):(t.setKeepAlive(!0),t.id=this.socketCounter++,this.availableSockets.push(t),log.info("SocketManager: ",`Added socket ${t.id}, pool size: ${this.availableSockets.length}/`+this.maxPoolSize),t.setTimeout(DEFAULT_SOCKET_TIMEOUT,()=>{t.destroy()}),t.on("disconnect",e=>{this.handleSocketDisconnect(t,e)}),t.on("error",e=>{console.error("Socket error: "+t.id,e),this.handleSocketDisconnect(t,"error")}),!0)}handleSocketDisconnect(t,e){console.log(`Socket ${t.id} disconnected: `+e),this.availableSockets=this.availableSockets.filter(e=>t!==e),this.cleanupSocketOperations(t)}cleanupSocketOperations(e){}isSocketHealthy(e){return!e.closed&&!e.destroyed&&"open"===e.readyState&&e.writable&&e.readable}cleanupDeadSockets(){var e=this.availableSockets.length,e=(this.availableSockets=this.availableSockets.filter(e=>{var t=this.isSocketHealthy(e);return t||(console.log("Removing dead socket: "+e.id),e.destroy()),t}),e-this.availableSockets.length);0<e&&console.log(`Cleaned up ${e} dead sockets`)}getAvailableSocket(){this.cleanupDeadSockets();const e=this.availableSockets.shift();if(e)return this.isSocketHealthy(e)?(log.info("SocketManager: ",`Using socket ${e.id}, remaining: `+this.availableSockets.length),e.setTimeout(1.5*DEFAULT_SOCKET_TIMEOUT,()=>{e.destroy()}),e):(console.log(`Socket ${e.id} became unhealthy, skipping`),e.destroy(),this.getAvailableSocket());console.log("No available connected sockets")}getPoolSize(){return this.availableSockets.length}getMaxPoolSize(){return this.maxPoolSize}}
@@ -1 +1 @@
1
- import{readFileSync}from"fs";import{Agent}from"http";import _ from"lodash";import log from"npmlog";import tls,{TLSSocket}from"tls";import{DEFAULT_AGENT_PORT}from "../constants.js";import CallbackQueue from "./CallbackQueue.js";import SocketManager from "./SocketManager.js";export default class TunnelAgent extends Agent{constructor(e){super({maxFreeSockets:256}),this.callbackQueue=new CallbackQueue,this.socketManager=new SocketManager,this.handleServerConnection= e=>{e=new TLSSocket(e,{...this.keys,isServer:!0,requestCert:!0}),this.socketManager.addSocket(e),this.callbackQueue.hasPendingCallbacks()&&this.createConnection(this,this.callbackQueue.dequeue())};e={key:readFileSync(e.serverPrivateKey,"utf-8"),cert:readFileSync(e.serverPublicKey,"utf-8")};this.keys=e,this.server=tls.createServer({...this.keys,requestCert:!0,keepAlive:!0}),this.started=!1}listen(){const r=this.server;return this.started?Promise.resolve():(r.on("close",()=>{for(const e of this.callbackQueue)e&&e(new Error("closed"),null)}),r.on("connection",this.handleServerConnection),r.on("error", e=>{log.error("TunnelAgent",e),"ECONNRESET"!=e.code&&e.code}),new Promise((e, t)=>{r.listen(DEFAULT_AGENT_PORT,()=>e(null))}))}createConnection(e, t){var r=this.socketManager.getAvailableSocket();if(_.isNil(r))return this.callbackQueue.enqueue(t),!1;t(null,r)}destroy(){super.destroy(),this.server.close()}}
1
+ import{readFileSync}from"fs";import{Agent}from"http";import _ from"lodash";import log from"npmlog";import tls,{TLSSocket}from"tls";import{DEFAULT_AGENT_PORT}from"../constants.js";import CallbackQueue from"./CallbackQueue.js";import SocketManager from"./SocketManager.js";export default class TunnelAgent extends Agent{constructor(e){super({maxFreeSockets:256}),this.callbackQueue=new CallbackQueue,this.socketManager=new SocketManager,this.handleServerConnection=e=>{e=new TLSSocket(e,{...this.keys,isServer:!0,requestCert:!0}),this.socketManager.addSocket(e),this.callbackQueue.hasPendingCallbacks()&&this.createConnection(this,this.callbackQueue.dequeue())};e={key:readFileSync(e.serverPrivateKey,"utf-8"),cert:readFileSync(e.serverPublicKey,"utf-8")};this.keys=e,this.server=tls.createServer({...this.keys,requestCert:!0,keepAlive:!0}),this.started=!1}listen(){const r=this.server;return this.started?Promise.resolve():(r.on("close",()=>{for(const e of this.callbackQueue)e&&e(new Error("closed"),null)}),r.on("connection",this.handleServerConnection),r.on("error",e=>{log.error("TunnelAgent",e),"ECONNRESET"!=e.code&&e.code}),new Promise((e,t)=>{r.listen(DEFAULT_AGENT_PORT,()=>e(null))}))}createConnection(e,t){var r=this.socketManager.getAvailableSocket();if(_.isNil(r))return this.callbackQueue.enqueue(t),!1;t(null,r)}destroy(){super.destroy(),this.callbackQueue.destroy(),this.server.close()}}
@@ -1 +1 @@
1
- import TunnelAgent from "../lib/TunnelAgent.js";import{EventEmitter}from"events";import express from"express";import{readFileSync}from"fs";import http from"http";import https from"https";import log from"npmlog";import pump from"pump";import{Server}from"socket.io";import socketioJwt from"socketio-jwt";import{getHttpsCerts}from "./utils.js";import{DEFAULT_AGENT_PORT,DEFAULT_SERVER_PORT}from "../constants.js";export default class TunnelServer extends EventEmitter{constructor(t){super(),this.serverPort=DEFAULT_SERVER_PORT,this.agentPort=DEFAULT_AGENT_PORT,this.handleAnyRequest=(t, e)=>{var r=http.request(this.buildRequestOptions(t),async t=>{e.writeHead(t.statusCode??200,{...t.headers}),pump(t,e)});r.setNoDelay(!0),pump(t,r)},this.open=()=>{this.tunnelAgent.listen().then(()=>{log.info("TunnelAgent: ","listening on port: %d",this.agentPort)}),this.httpsServer.listen(this.serverPort,()=>{log.info("TunnelServer: ","listening on port: %d",this.serverPort)})};var{httpsPublicKey:e,httpsPrivateKey:r,serverPublicKey:n}=t,t=(this.tunnelAgent=new TunnelAgent(t),this.callbackQueue=this.tunnelAgent.callbackQueue,express()),r=(t.all("*",this.handleAnyRequest),https.createServer({...getHttpsCerts(r,e),requestTimeout:0,noDelay:!0},t)),e=(this.httpsServer=r,this.io=new Server(r,{cors:{origin:"*"}}),readFileSync(n,{encoding:"utf-8"}));this.io.use(socketioJwt.authorize({secret:e,handshake:!0})),this.io.on("connection", t=>{function e(){t.emit("open-tunnel")}log.info("TunnelServer: ","new connection"),this.callbackQueue.addListener("push",async()=>{e()});for(const r of this.callbackQueue)e()})}buildRequestOptions(t){var{url:t,method:e,headers:r}=t;return{path:t,agent:this.tunnelAgent,method:e,headers:{...r,"x-forwarded-proto":"https"}}}async close(){this.tunnelAgent.destroy(),await this.io.close()}}
1
+ import TunnelAgent from"../lib/TunnelAgent.js";import{EventEmitter}from"events";import express from"express";import{readFileSync}from"fs";import http from"http";import https from"https";import log from"npmlog";import pump from"pump";import{Server}from"socket.io";import socketioJwt from"socketio-jwt";import{getHttpsCerts}from"utils";import{DEFAULT_AGENT_PORT,DEFAULT_REQUEST_TIMEOUT,DEFAULT_SERVER_PORT}from"../constants";export default class TunnelServer extends EventEmitter{constructor(e){super(),this.serverPort=DEFAULT_SERVER_PORT,this.agentPort=DEFAULT_AGENT_PORT,this.shuttingDown=!1,this.handleAnyRequest=(e,n)=>{if(this.shuttingDown)n.writeHead(503,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"Server is shutting down"}));else{let t=!1;const o=setTimeout(()=>{t=!0,n.headersSent||(log.warn("TunnelServer: ",`Request timeout after ${DEFAULT_REQUEST_TIMEOUT}ms`,e.method,e.url),n.writeHead(504,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"Gateway Timeout"})))},DEFAULT_REQUEST_TIMEOUT);var r=http.request(this.buildRequestOptions(e),async e=>{clearTimeout(o),t||(n.writeHead(e.statusCode??200,{...e.headers}),pump(e,n))});r.on("error",e=>{clearTimeout(o),t||(log.error("TunnelServer: ","Request error:",e.message),n.headersSent)||(n.writeHead(502,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"Bad Gateway"})))}),r.setNoDelay(!0),pump(e,r)}},this.open=()=>{this.tunnelAgent.listen().then(()=>{log.info("TunnelAgent: ","listening on port: %d",this.agentPort)}),this.httpsServer.listen(this.serverPort,()=>{log.info("TunnelServer: ","listening on port: %d",this.serverPort)})};var{httpsPublicKey:t,httpsPrivateKey:n,serverPublicKey:r}=e,e=(this.tunnelAgent=new TunnelAgent(e),this.callbackQueue=this.tunnelAgent.callbackQueue,express()),n=(e.all("*",this.handleAnyRequest),https.createServer({...getHttpsCerts(n,t),requestTimeout:0,noDelay:!0},e)),t=(this.httpsServer=n,this.io=new Server(n,{cors:{origin:"*"}}),readFileSync(r,{encoding:"utf-8"}));this.io.use(socketioJwt.authorize({secret:t,handshake:!0})),this.io.on("connection",e=>{function t(){e.emit("open-tunnel")}log.info("TunnelServer: ","new connection"),this.callbackQueue.addListener("push",async()=>{t()});for(const n of this.callbackQueue)t()})}buildRequestOptions(e){var{url:e,method:t,headers:n}=e;return{path:e,agent:this.tunnelAgent,method:t,headers:{...n,"x-forwarded-proto":"https"}}}async close(){log.info("TunnelServer: ","Initiating graceful shutdown"),this.shuttingDown=!0;var e=Date.now();for(;this.callbackQueue.hasPendingCallbacks()&&Date.now()-e<3e4;)log.info("TunnelServer: ",`Waiting for ${this.callbackQueue.getPendingCount()} pending requests to complete`),await new Promise(e=>setTimeout(e,100));this.callbackQueue.hasPendingCallbacks()?log.warn("TunnelServer: ",`Shutdown timeout reached with ${this.callbackQueue.getPendingCount()} pending requests`):log.info("TunnelServer: ","All pending requests completed"),this.tunnelAgent.destroy(),await this.io.close(),log.info("TunnelServer: ","Shutdown complete")}}
@@ -1 +1 @@
1
- export{default as TunnelServer}from "./TunnelServer.js";export*from "./utils.js";
1
+ export{default as TunnelServer}from"./TunnelServer.js";export*from"./utils.js";
@@ -1 +1 @@
1
- import path from"path";import{DEFAULT_SERVER_PORT}from "../constants.js";const options={remoteUrl:`https://localhost:${DEFAULT_SERVER_PORT}/`,serverPrivateKey:path.join(__dirname,"./../../keys/server.key"),serverPublicKey:path.join(__dirname,"./../../keys/server.crt"),httpsPrivateKey:path.join(__dirname,"./../../keys/server.key"),httpsPublicKey:path.join(__dirname,"./../../keys/server.crt")};export{options};
1
+ import path from"path";import{DEFAULT_SERVER_PORT}from"../constants";const options={remoteUrl:`https://localhost:${DEFAULT_SERVER_PORT}/`,serverPrivateKey:path.join(__dirname,"./../../keys/server.key"),serverPublicKey:path.join(__dirname,"./../../keys/server.crt"),httpsPrivateKey:path.join(__dirname,"./../../keys/server.key"),httpsPublicKey:path.join(__dirname,"./../../keys/server.crt")};export{options};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@duskim/http-tunnel",
3
3
  "description": "http tunneler",
4
4
  "license": "Apache 2.0",
5
- "version": "1.2.6",
5
+ "version": "1.3.0",
6
6
  "type": "module",
7
7
  "author": "Du Kim <dusgotmail@gmail.com>",
8
8
  "main": "./dist/index.js",