@lifelessrasel/devtunnel-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.
Files changed (3) hide show
  1. package/README.md +48 -0
  2. package/bin/devtunnel.js +104 -0
  3. package/package.json +38 -0
package/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # Tunnel Client (CLI)
2
+
3
+ The `tunnel-client` is a Node.js CLI tool that exposes your local server to the internet.
4
+
5
+ ## How it Works
6
+
7
+ 1. **Connection**: The CLI connects to the **Tunnel Server** via a WebSocket (`wss://tunnel.mohammadrasel.com`).
8
+ 2. **Registration**: It sends a `register` event to claim a subdomain (e.g., `myapp`).
9
+ 3. **Listening**: It keeps the WebSocket open, waiting for requests.
10
+ 4. **Forwarding**:
11
+ * When the Tunnel Server receives a public HTTP request (e.g., `https://myapp.tunnel.mohammadrasel.com`), it serializes the request and sends it to the CLI over the WebSocket.
12
+ * The CLI receives this message.
13
+ * The CLI uses `axios` to make the **actual HTTP request** to your local service (e.g., `http://localhost:3000`).
14
+ * The CLI captures the response (status, headers, body).
15
+ * The CLI sends the response back to the Tunnel Server over the WebSocket.
16
+ 5. **Response**: The Tunnel Server deserializes the response and sends it to the original public user.
17
+
18
+ ## Usage
19
+
20
+ ```bash
21
+ # Start a tunnel forwarding to localhost:3000
22
+ dt start --port 3000 --subdomain myapp --server wss://tunnel.mohammadrasel.com
23
+
24
+ # Options
25
+ --port, -p Local port to expose (default: 3000)
26
+ --subdomain, -s Requested subdomain (default: random)
27
+ --server Tunnel server URL (default: ws://localhost:8000)
28
+ ```
29
+
30
+ ## Architecture Diagram
31
+
32
+ ```mermaid
33
+ sequenceDiagram
34
+ participant PublicUser
35
+ participant TunnelServer
36
+ participant CLI
37
+ participant LocalApp
38
+
39
+ CLI->>TunnelServer: WebSocket Connect (Register 'myapp')
40
+ TunnelServer-->>CLI: Registered 'myapp.devtunnel.net'
41
+
42
+ PublicUser->>TunnelServer: HTTP GET https://myapp.devtunnel.net/api/users
43
+ TunnelServer->>CLI: WS Message (type: request, path: /api/users)
44
+ CLI->>LocalApp: HTTP GET http://localhost:3000/api/users
45
+ LocalApp-->>CLI: JSON Response { "users": [] }
46
+ CLI->>TunnelServer: WS Message (type: response, body: {...})
47
+ TunnelServer-->>PublicUser: HTTP 200 OK { "users": [] }
48
+ ```
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { Command } = require('commander');
4
+ const WebSocket = require('ws');
5
+ const axios = require('axios');
6
+ const program = new Command();
7
+
8
+ program
9
+ .name('devtunnel')
10
+ .description('Expose your local server to the internet')
11
+ .version('1.0.0');
12
+
13
+ program.command('start')
14
+ .description('Start a tunnel')
15
+ .option('-p, --port <number>', 'Local port to expose', '3000')
16
+ .option('-s, --subdomain <string>', 'Requested subdomain')
17
+ .option('--server <string>', 'Tunnel server URL', 'ws://localhost:8000')
18
+ .action(async (options) => {
19
+ const localPort = options.port;
20
+ const serverUrl = options.server;
21
+ const requestedSubdomain = options.subdomain;
22
+
23
+ console.log(`Connecting to ${serverUrl} requesting subdomain: ${requestedSubdomain || '(random)'}...`);
24
+
25
+ const ws = new WebSocket(serverUrl);
26
+
27
+ ws.on('open', () => {
28
+ console.log('Connected to Tunnel Server.');
29
+ ws.send(JSON.stringify({
30
+ type: 'register',
31
+ subdomain: requestedSubdomain
32
+ }));
33
+ });
34
+
35
+ ws.on('message', async (data) => {
36
+ try {
37
+ const message = JSON.parse(data);
38
+
39
+ if (message.type === 'registered') {
40
+ console.log(`\nTunnel Connected! 🚀`);
41
+ console.log(`Public URL: ${message.url}`);
42
+ console.log(`Forwarding to: http://localhost:${localPort}\n`);
43
+ }
44
+
45
+ else if (message.type === 'request') {
46
+ const { requestId, method, path, headers, body } = message;
47
+
48
+ // Forward to local app
49
+ try {
50
+ // Filter headers (host header usually causes issues)
51
+ const { host, ...forwardHeaders } = headers;
52
+
53
+ const response = await axios({
54
+ method: method,
55
+ url: `http://localhost:${localPort}${path}`,
56
+ headers: forwardHeaders,
57
+ data: body ? Buffer.from(body, 'base64') : undefined,
58
+ validateStatus: () => true, // Accept all status codes
59
+ responseType: 'arraybuffer' // Get raw response
60
+ });
61
+
62
+ // Send response back
63
+ ws.send(JSON.stringify({
64
+ type: 'response',
65
+ requestId,
66
+ status: response.status,
67
+ headers: response.headers,
68
+ body: response.data.toString('base64')
69
+ }));
70
+
71
+ console.log(`[${method}] ${path} -> ${response.status}`);
72
+
73
+ } catch (err) {
74
+ console.error(`Error forwarding request: ${err.message}`);
75
+ ws.send(JSON.stringify({
76
+ type: 'response',
77
+ requestId,
78
+ status: 502,
79
+ body: Buffer.from('Bad Gateway: Local server Error').toString('base64')
80
+ }));
81
+ }
82
+ }
83
+
84
+ else if (message.type === 'error') {
85
+ console.error(`Server Error: ${message.message}`);
86
+ process.exit(1);
87
+ }
88
+
89
+ } catch (e) {
90
+ console.error('Error parsing message:', e);
91
+ }
92
+ });
93
+
94
+ ws.on('close', () => {
95
+ console.log('Disconnected from server. Retrying in 3s...');
96
+ setTimeout(() => process.exit(1), 3000); // Simple restart for now
97
+ });
98
+
99
+ ws.on('error', (err) => {
100
+ console.error('Connection error:', err.message);
101
+ });
102
+ });
103
+
104
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@lifelessrasel/devtunnel-cli",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "version": "1.0.0",
7
+ "description": "A specialized tunneling tool to expose local servers to the internet via DevTunnel.net",
8
+ "main": "bin/devtunnel.js",
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ },
12
+ "keywords": [
13
+ "tunnel",
14
+ "localhost",
15
+ "proxy",
16
+ "devtunnel",
17
+ "cli"
18
+ ],
19
+ "author": "Mohammad Rasel",
20
+ "license": "ISC",
21
+ "type": "commonjs",
22
+ "bin": {
23
+ "dt": "bin/devtunnel.js",
24
+ "devtunnel": "bin/devtunnel.js"
25
+ },
26
+ "dependencies": {
27
+ "axios": "^1.13.2",
28
+ "commander": "^14.0.2",
29
+ "ws": "^8.19.0"
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/lifelessrasel/devtunnel.git"
34
+ },
35
+ "engines": {
36
+ "node": ">=16.0.0"
37
+ }
38
+ }