@karthik_yk/ws-tunnel-cli 1.0.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.
- package/LICENCE +0 -0
- package/README.md +10 -0
- package/bin/tunnel.js +2 -0
- package/package.json +32 -0
- package/src/httpClient.js +97 -0
- package/src/index.js +25 -0
package/LICENCE
ADDED
|
File without changes
|
package/README.md
ADDED
package/bin/tunnel.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@karthik_yk/ws-tunnel-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "websocket tunnel cli",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git@github-personal:karthik617/ws-tunnel-cli.git"
|
|
11
|
+
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"ws-tunnel": "bin/tunnel.js"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"ws": "^8.16.0"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"tunnel",
|
|
20
|
+
"ws",
|
|
21
|
+
"ngrok",
|
|
22
|
+
"http",
|
|
23
|
+
"cli"
|
|
24
|
+
],
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"author": "karthik617",
|
|
27
|
+
"type": "module",
|
|
28
|
+
"main": "src/index.js",
|
|
29
|
+
"scripts": {
|
|
30
|
+
"start": "node src/index.js"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import WebSocket from "ws";
|
|
2
|
+
import http from "http";
|
|
3
|
+
|
|
4
|
+
export function createHttpTunnel(localPort, remoteHost) {
|
|
5
|
+
if (!remoteHost || !/^wss?:\/\//.test(remoteHost)) {
|
|
6
|
+
throw new Error("remoteHost must be ws:// or wss:// url");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const ws = new WebSocket(remoteHost);
|
|
10
|
+
|
|
11
|
+
ws.on("open", () => {
|
|
12
|
+
ws.send(JSON.stringify({
|
|
13
|
+
type: "register",
|
|
14
|
+
localPort
|
|
15
|
+
}));
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
ws.on("message", async (msg) => {
|
|
19
|
+
const data = JSON.parse(msg.toString());
|
|
20
|
+
|
|
21
|
+
// 🌍 PUBLIC URL LOGIC (IMPORTANT)
|
|
22
|
+
if (data.type === "registered") {
|
|
23
|
+
const url = getPublicUrl(remoteHost, data.id);
|
|
24
|
+
console.log(`🌍 Public URL: ${url}`);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (data.type === "http_request") {
|
|
29
|
+
const response = await forwardToLocal(data, localPort);
|
|
30
|
+
ws.send(JSON.stringify({
|
|
31
|
+
type: "http_response",
|
|
32
|
+
requestId: data.requestId,
|
|
33
|
+
...response
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
ws.on("close", () => {
|
|
39
|
+
console.error("❌ Tunnel disconnected");
|
|
40
|
+
process.exit(1);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
ws.on("error", (err) => {
|
|
44
|
+
console.error("❌ Tunnel error:", err.message);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getPublicUrl(remoteHost, id) {
|
|
50
|
+
const { protocol, host } = new URL(remoteHost.replace("ws", "http"));
|
|
51
|
+
|
|
52
|
+
// Local dev → path based
|
|
53
|
+
if (host.startsWith("localhost") || host.startsWith("127.")) {
|
|
54
|
+
return `${protocol}//${host}/tunnel/${id}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Production (Render) → subdomain based
|
|
58
|
+
return `${protocol}//${id}.${host}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function forwardToLocal(req, localPort) {
|
|
62
|
+
return new Promise((resolve) => {
|
|
63
|
+
const headers = { ...req.headers };
|
|
64
|
+
delete headers.host; // 🔥 IMPORTANT
|
|
65
|
+
|
|
66
|
+
const localReq = http.request(
|
|
67
|
+
{
|
|
68
|
+
host: "localhost",
|
|
69
|
+
port: localPort,
|
|
70
|
+
method: req.method,
|
|
71
|
+
path: req.path,
|
|
72
|
+
headers
|
|
73
|
+
},
|
|
74
|
+
(res) => {
|
|
75
|
+
let body = "";
|
|
76
|
+
res.on("data", (c) => (body += c));
|
|
77
|
+
res.on("end", () => {
|
|
78
|
+
resolve({
|
|
79
|
+
status: res.statusCode,
|
|
80
|
+
headers: res.headers,
|
|
81
|
+
body
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
localReq.on("error", (err) => {
|
|
88
|
+
resolve({
|
|
89
|
+
status: 502,
|
|
90
|
+
body: err.message
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (req.body) localReq.write(req.body);
|
|
95
|
+
localReq.end();
|
|
96
|
+
});
|
|
97
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createHttpTunnel } from "./httpClient.js";
|
|
2
|
+
|
|
3
|
+
const args = process.argv.slice(2);
|
|
4
|
+
if (args.length < 2) {
|
|
5
|
+
console.log(`
|
|
6
|
+
Usage:
|
|
7
|
+
ws-tunnel http <localPort> <wss:remoteHost>
|
|
8
|
+
|
|
9
|
+
Examples:
|
|
10
|
+
ws-tunnel http 8080 wss://localhost:8080
|
|
11
|
+
`);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
const [mode, localPort, remoteHost] = args;
|
|
15
|
+
|
|
16
|
+
if (mode !== "http" || !localPort || !remoteHost) {
|
|
17
|
+
console.log("Usage: ws-tunnel http <localPort> <wss:remoteHost>");
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
createHttpTunnel(Number(localPort), remoteHost);
|
|
22
|
+
} catch (err) {
|
|
23
|
+
console.error(err.message);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|