@duskim/http-tunnel 1.2.6
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.
- package/LICENSE.md +13 -0
- package/README.md +2 -0
- package/dist/client/BypassMessageFilter.js +1 -0
- package/dist/client/LocalTunnelWrapper.js +1 -0
- package/dist/client/RemoteTunnelWrapper.js +1 -0
- package/dist/client/TunnelClient.js +1 -0
- package/dist/client/TunnelCluster.js +1 -0
- package/dist/client/TunnelManager.js +1 -0
- package/dist/client/index.js +1 -0
- package/dist/constants.js +1 -0
- package/dist/i18n.js +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/CallbackQueue.js +1 -0
- package/dist/lib/SocketManager.js +1 -0
- package/dist/lib/TunnelAgent.js +1 -0
- package/dist/lib/utils.js +1 -0
- package/dist/server/TunnelServer.js +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/utils.js +1 -0
- package/dist/test/fixtures.js +1 -0
- package/dist/test/util.js +1 -0
- package/package.json +44 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright 2022 du s. kim
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +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)}}
|
|
@@ -0,0 +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()}}
|
|
@@ -0,0 +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}}
|
|
@@ -0,0 +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()}}
|
|
@@ -0,0 +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}}}
|
|
@@ -0,0 +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()}}
|
|
@@ -0,0 +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";
|
|
@@ -0,0 +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};
|
package/dist/i18n.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import i18n from"i18n";import path from"path";i18n.configure({locales:["en"],directory:path.join(__dirname,"./locales.json"),objectNotation:!0});
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{default as TunnelManager}from "./client/TunnelManager.js";export{default as TunnelServer}from "./server/TunnelServer.js";
|
|
@@ -0,0 +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}}}}
|
|
@@ -0,0 +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")}}
|
|
@@ -0,0 +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()}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import _ from"lodash";import log from"npmlog";log.enableColor();const MIN_LOWERCASE_ASCII=97,MAX_LOWERCASE_ASCII=122;function generateRandomString(e){let t="";for(let r=0;r<e;r++){var n=Math.floor(Math.random()*(MAX_LOWERCASE_ASCII-MIN_LOWERCASE_ASCII)+MIN_LOWERCASE_ASCII);t+=String.fromCharCode(n)}return t}function isJSON(r){try{return JSON.parse(r),!0}catch(r){return!1}}function difference(r,n,o){return r.filter(e=>{let t=!1;return n.forEach(r=>_.get(e,o)===_.get(r,o)&&(t=!0)),!t})}export{generateRandomString,isJSON,difference};
|
|
@@ -0,0 +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()}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{default as TunnelServer}from "./TunnelServer.js";export*from "./utils.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import fs from"fs";function getHttpsCerts(t,e){return{key:fs.readFileSync(t,"utf-8"),cert:fs.readFileSync(e,"utf-8")}}export{getHttpsCerts};
|
|
@@ -0,0 +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};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Transform}from"stream";class MessageInterceptor extends Transform{constructor(){super({readableObjectMode:!0,writableObjectMode:!0})}_transform(e,r,t){return t(null,e)}}async function sleep(r){await new Promise(e=>setTimeout(e,r))}async function waitFor(r,t=50){let o;for(let e=0;e<30;e++){o=[];try{return void r()}catch(e){o.push(e)}await sleep(t)}throw new Error(o?.toString())}export{MessageInterceptor,sleep,waitFor};
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@duskim/http-tunnel",
|
|
3
|
+
"description": "http tunneler",
|
|
4
|
+
"license": "Apache 2.0",
|
|
5
|
+
"version": "1.2.6",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"author": "Du Kim <dusgotmail@gmail.com>",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"module": "./dist/index.js",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE.md"
|
|
15
|
+
],
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"express": "^4.21.2",
|
|
24
|
+
"i18n": "^0.15.1",
|
|
25
|
+
"jsonwebtoken": "^9.0.2",
|
|
26
|
+
"localenv": "^0.2.2",
|
|
27
|
+
"lodash": "^4.17.21",
|
|
28
|
+
"npmlog": "^7.0.1",
|
|
29
|
+
"prettier": "^2.8.5",
|
|
30
|
+
"pump": "^3.0.0",
|
|
31
|
+
"socket.io": "^4.8.1",
|
|
32
|
+
"socketio-jwt": "^4.6.2",
|
|
33
|
+
"socket.io-client": "^4.8.1",
|
|
34
|
+
"socket.io.js": "^1.0.0",
|
|
35
|
+
"typescript": "^5.1.6",
|
|
36
|
+
"vite-tsconfig-paths": "^5.1.4",
|
|
37
|
+
"ws": "^8.18.2",
|
|
38
|
+
"yup": "^1.2.0"
|
|
39
|
+
},
|
|
40
|
+
"keywords": [
|
|
41
|
+
"tunnel",
|
|
42
|
+
"http"
|
|
43
|
+
]
|
|
44
|
+
}
|