diff --git a/.gitignore b/.gitignore index ceaea36..9303c34 100644 --- a/.gitignore +++ b/.gitignore @@ -1,132 +1,2 @@ -# ---> Node -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* -.pnpm-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories node_modules/ -jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) -web_modules/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional stylelint cache -.stylelintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variable files -.env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# Next.js build output -.next -out - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# vuepress v2.x temp and cache directory -.temp -.cache - -# Docusaurus cache and generated files -.docusaurus - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -# Stores VSCode versions used for testing VSCode extensions -.vscode-test - -# yarn v2 -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.* - +npm-debug.log \ No newline at end of file diff --git a/README.md b/README.md index c30fe3a..2b8a396 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ -# http-tunnel-client +# HTTP Tunnel Client +A lightweight http tunnel client using nodejs. +### Contributors +- Original [web-tunnel](https://github.com/web-tunnel/lite-http-tunnel-client) +- Sambo Chea \ No newline at end of file diff --git a/bin/http-tunnel b/bin/http-tunnel new file mode 100644 index 0000000..256e745 --- /dev/null +++ b/bin/http-tunnel @@ -0,0 +1,3 @@ +#!/usr/bin/env node + +require('../client'); \ No newline at end of file diff --git a/client.js b/client.js new file mode 100644 index 0000000..d024b93 --- /dev/null +++ b/client.js @@ -0,0 +1,179 @@ +const os = require("os"); +const fs = require("fs"); +const path = require("path"); +const http = require("http"); +const { io } = require("socket.io-client"); +const HttpsProxyAgent = require("https-proxy-agent"); +const { program, InvalidArgumentError, Argument } = require("commander"); +const { TunnelRequest, TunnelResponse } = require("./lib"); + +let socket = null; + +function keepAlive() { + setTimeout(() => { + if (socket && socket.connected) { + socket.send("ping"); + } + keepAlive(); + }, 5000); +} + +function initClient(options) { + const initParams = { + path: "/$web_tunnel", + transports: ["websocket"], + auth: { + token: options.token, + }, + }; + const http_proxy = process.env.https_proxy || process.env.http_proxy; + if (http_proxy) { + initParams.agent = new HttpsProxyAgent(http_proxy); + } + socket = io(options.server, initParams); + + socket.on("connect", () => { + if (socket.connected) { + console.log("client connect to server successfully"); + } + }); + + socket.on("connect_error", (e) => { + console.log("connect error", e && e.message); + }); + + socket.on("disconnect", () => { + console.log("client disconnected"); + }); + + socket.on("request", (requestId, request) => { + const isWebSocket = request.headers.upgrade === "websocket"; + console.log(`${isWebSocket ? "WS" : request.method}: `, request.path); + request.port = options.port; + request.hostname = options.host; + if (options.origin) { + request.headers.host = options.origin; + } + const tunnelRequest = new TunnelRequest({ + requestId, + socket: socket, + }); + const localReq = http.request(request); + tunnelRequest.pipe(localReq); + const onTunnelRequestError = (e) => { + tunnelRequest.off("end", onTunnelRequestEnd); + localReq.destroy(e); + }; + const onTunnelRequestEnd = () => { + tunnelRequest.off("error", onTunnelRequestError); + }; + tunnelRequest.once("error", onTunnelRequestError); + tunnelRequest.once("end", onTunnelRequestEnd); + const onLocalResponse = (localRes) => { + localReq.off("error", onLocalError); + if (isWebSocket && localRes.upgrade) { + return; + } + const tunnelResponse = new TunnelResponse({ + responseId: requestId, + socket: socket, + }); + tunnelResponse.writeHead( + localRes.statusCode, + localRes.statusMessage, + localRes.headers, + localRes.httpVersion + ); + localRes.pipe(tunnelResponse); + }; + const onLocalError = (error) => { + console.log(error); + localReq.off("response", onLocalResponse); + socket.emit("request-error", requestId, error && error.message); + tunnelRequest.destroy(error); + }; + const onUpgrade = (localRes, localSocket, localHead) => { + // localSocket.once('error', onTunnelRequestError); + if (localHead && localHead.length) localSocket.unshift(localHead); + const tunnelResponse = new TunnelResponse({ + responseId: requestId, + socket: socket, + duplex: true, + }); + tunnelResponse.writeHead(null, null, localRes.headers); + localSocket.pipe(tunnelResponse).pipe(localSocket); + }; + localReq.once("error", onLocalError); + localReq.once("response", onLocalResponse); + + if (isWebSocket) { + localReq.on("upgrade", onUpgrade); + } + }); + keepAlive(); +} + +program.name("http-tunnel").description("HTTP tunnel client"); + +program + .command("start") + .argument("", "local server port number", (value) => { + const port = parseInt(value, 10); + if (isNaN(port)) { + throw new InvalidArgumentError("Not a number."); + } + return port; + }) + .option("-p, --profile ", "setting profile name", "config") + .option("-h, --host ", "local host value", "localhost") + .option("-o, --origin ", "change request origin") + .action((port, options) => { + const configDir = path.resolve(os.homedir(), ".http-tunnel"); + if (!fs.existsSync(configDir)) { + fs.mkdirSync(configDir); + } + let config = {}; + const configFilePath = path.resolve(configDir, `${options.profile}.json`); + if (fs.existsSync(configFilePath)) { + config = JSON.parse(fs.readFileSync(configFilePath, "utf8")); + } + if (!config.server) { + console.log("Please set remote tunnel server firstly"); + return; + } + if (!config.token) { + console.log(`Please set jwt token for ${config.server} firstly`); + return; + } + options.port = port; + options.token = config.token; + options.server = config.server; + initClient(options); + }); + +program + .command("config") + .addArgument(new Argument("", "config type").choices(["jwt", "server"])) + .argument("", "config value") + .option("-p --profile ", "setting profile name", "config") + .action((type, value, options) => { + const configDir = path.resolve(os.homedir(), ".http-tunnel"); + if (!fs.existsSync(configDir)) { + fs.mkdirSync(configDir); + } + let config = {}; + const configFilePath = path.resolve(configDir, `${options.profile}.json`); + if (fs.existsSync(configFilePath)) { + config = JSON.parse(fs.readFileSync(configFilePath, "utf8")); + } + if (type === "jwt") { + config.token = value; + } + if (type === "server") { + config.server = value; + } + fs.writeFileSync(configFilePath, JSON.stringify(config, null, 2)); + console.log(`${type} config saved successfully`); + }); + +program.parse(); diff --git a/lib.js b/lib.js new file mode 100644 index 0000000..c2c07ab --- /dev/null +++ b/lib.js @@ -0,0 +1,146 @@ +const stream = require("stream"); + +class TunnelRequest extends stream.Readable { + constructor({ socket, requestId }) { + super(); + this._socket = socket; + this._requestId = requestId; + const onRequestPipe = (requestId, data) => { + if (this._requestId === requestId) { + this.push(data); + } + }; + const onRequestPipes = (requestId, data) => { + if (this._requestId === requestId) { + data.forEach((chunk) => { + this.push(chunk); + }); + } + }; + const onRequestPipeError = (requestId, error) => { + if (this._requestId === requestId) { + this._socket.off("request-pipe", onRequestPipe); + this._socket.off("request-pipes", onRequestPipes); + this._socket.off("request-pipe-error", onRequestPipeError); + this._socket.off("request-pipe-end", onRequestPipeEnd); + this.destroy(new Error(error)); + } + }; + const onRequestPipeEnd = (requestId, data) => { + if (this._requestId === requestId) { + this._socket.off("request-pipe", onRequestPipe); + this._socket.off("request-pipes", onRequestPipes); + this._socket.off("request-pipe-error", onRequestPipeError); + this._socket.off("request-pipe-end", onRequestPipeEnd); + if (data) { + this.push(data); + } + this.push(null); + } + }; + this._socket.on("request-pipe", onRequestPipe); + this._socket.on("request-pipes", onRequestPipes); + this._socket.on("request-pipe-error", onRequestPipeError); + this._socket.on("request-pipe-end", onRequestPipeEnd); + } + + _read() {} +} + +class TunnelResponse extends stream.Duplex { + constructor({ socket, responseId, duplex }) { + super(); + this._socket = socket; + this._responseId = responseId; + if (duplex) { + // for websocket request: bidirection + const onResponsePipe = (responseId, data) => { + if (this._responseId === responseId) { + this.push(data); + } + }; + const onResponsePipes = (responseId, data) => { + if (this._responseId === responseId) { + data.forEach((chunk) => { + this.push(chunk); + }); + } + }; + const onResponsePipeError = (responseId, error) => { + if (this._responseId === responseId) { + this._socket.off("response-pipe", onResponsePipe); + this._socket.off("response-pipes", onResponsePipes); + this._socket.off("response-pipe-error", onResponsePipeError); + this._socket.off("response-pipe-end", onResponsePipeEnd); + this.destroy(new Error(error)); + } + }; + const onResponsePipeEnd = (responseId, data) => { + if (this._responseId === responseId) { + this._socket.off("response-pipe", onResponsePipe); + this._socket.off("response-pipes", onResponsePipes); + this._socket.off("response-pipe-error", onResponsePipeError); + this._socket.off("response-pipe-end", onResponsePipeEnd); + if (data) { + this.push(data); + } + this.push(null); + } + }; + this._socket.on("response-pipe", onResponsePipe); + this._socket.on("response-pipes", onResponsePipes); + this._socket.on("response-pipe-error", onResponsePipeError); + this._socket.on("response-pipe-end", onResponsePipeEnd); + } + } + + _write(chunk, encoding, callback) { + this._socket.emit("response-pipe", this._responseId, chunk); + this._socket.io.engine.once("drain", () => { + callback(); + }); + } + + _writev(chunks, callback) { + this._socket.emit("response-pipes", this._responseId, chunks); + this._socket.io.engine.once("drain", () => { + callback(); + }); + } + + _final(callback) { + this._socket.emit("response-pipe-end", this._responseId); + this._socket.io.engine.once("drain", () => { + callback(); + }); + } + + _destroy(e, callback) { + if (e) { + this._socket.emit( + "response-pipe-error", + this._responseId, + e && e.message + ); + this._socket.io.engine.once("drain", () => { + callback(); + }); + return; + } + callback(); + } + + writeHead(statusCode, statusMessage, headers, httpVersion) { + this._socket.emit("response", this._responseId, { + statusCode, + statusMessage, + headers, + httpVersion, + }); + } + + _read(size) {} +} + +exports.TunnelRequest = TunnelRequest; +exports.TunnelResponse = TunnelResponse; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..582a4f6 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,239 @@ +{ + "name": "@cubetiq/http-tunnel-client", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@cubetiq/http-tunnel-client", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "commander": "^9.3.0", + "https-proxy-agent": "^5.0.1", + "socket.io-client": "^4.5.1" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/commander": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.3.0.tgz", + "integrity": "sha512-hv95iU5uXPbK83mjrJKuZyFM/LBAoCV/XhVGkS5Je6tl7sxr6A0ITMw5WoRV46/UaJ46Nllm3Xt7IaJhXTIkzw==", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-client": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.2.2.tgz", + "integrity": "sha512-8ZQmx0LQGRTYkHuogVZuGSpDqYZtCM/nv8zQ68VZ+JkOpazJ7ICdsSpaO6iXwvaU30oFg5QJOJWj8zWqhbKjkQ==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", + "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/socket.io-client": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.5.1.tgz", + "integrity": "sha512-e6nLVgiRYatS+AHXnOnGi4ocOpubvOUCGhyWw8v+/FxW8saHkinG6Dfhi9TU0Kt/8mwJIAASxvw6eujQmjdZVA==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.2.1", + "socket.io-parser": "~4.2.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.0.tgz", + "integrity": "sha512-tLfmEwcEwnlQTxFB7jibL/q2+q8dlVQzj4JdRLJ/W/G1+Fu9VSxCx1Lo+n1HvXxKnM//dUuD0xgiA7tQf57Vng==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "engines": { + "node": ">=0.4.0" + } + } + }, + "dependencies": { + "@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + } + }, + "commander": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.3.0.tgz", + "integrity": "sha512-hv95iU5uXPbK83mjrJKuZyFM/LBAoCV/XhVGkS5Je6tl7sxr6A0ITMw5WoRV46/UaJ46Nllm3Xt7IaJhXTIkzw==" + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "engine.io-client": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.2.2.tgz", + "integrity": "sha512-8ZQmx0LQGRTYkHuogVZuGSpDqYZtCM/nv8zQ68VZ+JkOpazJ7ICdsSpaO6iXwvaU30oFg5QJOJWj8zWqhbKjkQ==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "engine.io-parser": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", + "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==" + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "socket.io-client": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.5.1.tgz", + "integrity": "sha512-e6nLVgiRYatS+AHXnOnGi4ocOpubvOUCGhyWw8v+/FxW8saHkinG6Dfhi9TU0Kt/8mwJIAASxvw6eujQmjdZVA==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.2.1", + "socket.io-parser": "~4.2.0" + } + }, + "socket.io-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.0.tgz", + "integrity": "sha512-tLfmEwcEwnlQTxFB7jibL/q2+q8dlVQzj4JdRLJ/W/G1+Fu9VSxCx1Lo+n1HvXxKnM//dUuD0xgiA7tQf57Vng==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + } + }, + "ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "requires": {} + }, + "xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ba93f55 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "@cubetiq/http-tunnel-client", + "version": "1.0.0", + "description": "A lightweight http tunnel client", + "main": "client.js", + "bin": { + "http-tunnel": "bin/http-tunnel" + }, + "scripts": { + "start": "node client.js" + }, + "repository": { + "type": "git", + "url": "https://git.cubetiqs.com/cubetiq/http-tunnel-client.git" + }, + "keywords": [ + "http-tunnel-client" + ], + "author": "Sambo Chea ", + "license": "ISC", + "dependencies": { + "commander": "^9.3.0", + "https-proxy-agent": "^5.0.1", + "socket.io-client": "^4.5.1" + } +}