rostc 1.0.0 → 1.0.3
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/cli.js +133 -72
- package/package.json +3 -3
package/cli.js
CHANGED
|
@@ -1,96 +1,157 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const WebSocket = require('ws');
|
|
4
|
-
const chalk = require('chalk');
|
|
5
4
|
const http = require('http');
|
|
6
5
|
const { randomBytes } = require('crypto');
|
|
6
|
+
const { exec } = require('child_process');
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
let chalk;
|
|
9
|
+
try {
|
|
10
|
+
chalk = require('chalk');
|
|
11
|
+
} catch (e) {
|
|
12
|
+
chalk = { cyan: (t)=>t, green: (t)=>t, gray: (t)=>t, red: (t)=>t, yellow: (t)=>t };
|
|
13
|
+
}
|
|
10
14
|
|
|
15
|
+
const WORKER_URL = 'https://rostc.roygichira084.workers.dev';
|
|
11
16
|
const port = process.argv[2];
|
|
12
17
|
|
|
13
18
|
if (!port) {
|
|
14
19
|
console.log(chalk.red('✗ Error: Please specify a port'));
|
|
15
|
-
console.log(chalk.yellow('→ Usage:
|
|
16
|
-
process.exit(1);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (isNaN(port) || port < 1 || port > 65535) {
|
|
20
|
-
console.log(chalk.red('✗ Error: Invalid port number'));
|
|
20
|
+
console.log(chalk.yellow('→ Usage: rostc 8080'));
|
|
21
21
|
process.exit(1);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
console.log(chalk.cyan(`→
|
|
24
|
+
console.log(chalk.cyan(`→ Starting tunnel on port ${port}...`));
|
|
25
25
|
|
|
26
|
-
//
|
|
27
|
-
const
|
|
28
|
-
const
|
|
26
|
+
// Check if something is already running on the port
|
|
27
|
+
const net = require('net');
|
|
28
|
+
const tester = net.createServer()
|
|
29
|
+
.once('error', (err) => {
|
|
30
|
+
if (err.code === 'EADDRINUSE') {
|
|
31
|
+
// Port is in use — just tunnel to existing server
|
|
32
|
+
console.log(chalk.gray(`→ Port ${port} already in use. Tunneling to existing server.`));
|
|
33
|
+
startTunnel(false);
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
.once('listening', () => {
|
|
37
|
+
// Port is free — start our own simple HTTP server
|
|
38
|
+
tester.close();
|
|
39
|
+
console.log(chalk.gray(`→ No server found on port ${port}. Starting built-in file server...`));
|
|
40
|
+
startTunnel(true);
|
|
41
|
+
})
|
|
42
|
+
.listen(port);
|
|
29
43
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
ws.on('open', () => {
|
|
34
|
-
console.log(chalk.green(`✓ ${publicUrl}`));
|
|
35
|
-
console.log(chalk.gray(`⌛ Expires in 4 minutes (240 seconds)`));
|
|
36
|
-
console.log(chalk.gray(`→ Press Ctrl+C to destroy early\n`));
|
|
44
|
+
function startTunnel(startOwnServer) {
|
|
45
|
+
let server;
|
|
37
46
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
if (startOwnServer) {
|
|
48
|
+
// Start a simple HTTP file server
|
|
49
|
+
const http = require('http');
|
|
50
|
+
const fs = require('fs');
|
|
51
|
+
const path = require('path');
|
|
52
|
+
|
|
53
|
+
server = http.createServer((req, res) => {
|
|
54
|
+
let filePath = '.' + req.url;
|
|
55
|
+
if (filePath === './') filePath = './index.html';
|
|
45
56
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
const ext = path.extname(filePath);
|
|
58
|
+
const contentType = {
|
|
59
|
+
'.html': 'text/html',
|
|
60
|
+
'.js': 'text/javascript',
|
|
61
|
+
'.css': 'text/css',
|
|
62
|
+
'.json': 'application/json',
|
|
63
|
+
'.png': 'image/png',
|
|
64
|
+
'.jpg': 'image/jpeg',
|
|
65
|
+
'.gif': 'image/gif',
|
|
66
|
+
}[ext] || 'text/plain';
|
|
54
67
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
68
|
+
fs.readFile(filePath, (err, content) => {
|
|
69
|
+
if (err) {
|
|
70
|
+
res.writeHead(404);
|
|
71
|
+
res.end('File not found');
|
|
72
|
+
} else {
|
|
73
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
74
|
+
res.end(content);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
server.listen(port, () => {
|
|
80
|
+
console.log(chalk.gray(`→ Built-in server running on http://localhost:${port}`));
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Generate random tunnel ID
|
|
85
|
+
const tunnelId = randomBytes(8).toString('hex');
|
|
86
|
+
const publicUrl = `${WORKER_URL}/${tunnelId}`;
|
|
87
|
+
|
|
88
|
+
// Connect to Cloudflare Worker
|
|
89
|
+
const ws = new WebSocket(`${WORKER_URL.replace('https', 'wss')}/${tunnelId}`);
|
|
90
|
+
|
|
91
|
+
ws.on('open', () => {
|
|
92
|
+
console.log(chalk.green(`✓ ${publicUrl}`));
|
|
93
|
+
console.log(chalk.gray(`⌛ Expires in 4 minutes (240 seconds)`));
|
|
94
|
+
console.log(chalk.gray(`→ Press Ctrl+C to destroy\n`));
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Handle incoming requests through WebSocket
|
|
98
|
+
if (server) {
|
|
99
|
+
// Forward requests from the tunnel to our local server
|
|
100
|
+
ws.on('message', (data) => {
|
|
101
|
+
try {
|
|
102
|
+
const msg = JSON.parse(data);
|
|
103
|
+
if (msg.type === 'request') {
|
|
104
|
+
const options = {
|
|
105
|
+
hostname: 'localhost',
|
|
106
|
+
port: port,
|
|
107
|
+
path: msg.path,
|
|
108
|
+
method: msg.method,
|
|
109
|
+
headers: msg.headers
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const req = http.request(options, (res) => {
|
|
113
|
+
const chunks = [];
|
|
114
|
+
res.on('data', chunk => chunks.push(chunk));
|
|
115
|
+
res.on('end', () => {
|
|
116
|
+
ws.send(JSON.stringify({
|
|
117
|
+
type: 'response',
|
|
118
|
+
id: msg.id,
|
|
119
|
+
status: res.statusCode,
|
|
120
|
+
headers: res.headers,
|
|
121
|
+
body: Buffer.concat(chunks).toString('base64')
|
|
122
|
+
}));
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (msg.body) {
|
|
127
|
+
req.write(Buffer.from(msg.body, 'base64'));
|
|
62
128
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
129
|
+
req.end();
|
|
130
|
+
}
|
|
131
|
+
} catch(e) {}
|
|
66
132
|
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 4-minute timer
|
|
136
|
+
const timeout = setTimeout(() => {
|
|
137
|
+
console.log(chalk.red(`\n✗ Tunnel expired after 4 minutes`));
|
|
138
|
+
ws.close();
|
|
139
|
+
if (server) server.close();
|
|
140
|
+
process.exit(0);
|
|
141
|
+
}, 240000);
|
|
142
|
+
|
|
143
|
+
// Handle Ctrl+C
|
|
144
|
+
process.on('SIGINT', () => {
|
|
145
|
+
console.log(chalk.yellow(`\n→ Destroying tunnel...`));
|
|
146
|
+
clearTimeout(timeout);
|
|
147
|
+
ws.close();
|
|
148
|
+
if (server) server.close();
|
|
149
|
+
process.exit(0);
|
|
67
150
|
});
|
|
68
151
|
|
|
69
|
-
|
|
70
|
-
|
|
152
|
+
ws.on('error', (err) => {
|
|
153
|
+
console.log(chalk.red(`✗ Error: ${err.message}`));
|
|
154
|
+
if (server) server.close();
|
|
155
|
+
process.exit(1);
|
|
71
156
|
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// 4-minute timer
|
|
75
|
-
const timeout = setTimeout(() => {
|
|
76
|
-
console.log(chalk.red(`\n✗ Tunnel expired after 4 minutes`));
|
|
77
|
-
ws.close();
|
|
78
|
-
process.exit(0);
|
|
79
|
-
}, 240000);
|
|
80
|
-
|
|
81
|
-
// Handle Ctrl+C
|
|
82
|
-
process.on('SIGINT', () => {
|
|
83
|
-
console.log(chalk.yellow(`\n→ Destroying tunnel...`));
|
|
84
|
-
clearTimeout(timeout);
|
|
85
|
-
ws.close();
|
|
86
|
-
process.exit(0);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
ws.on('error', (err) => {
|
|
90
|
-
console.log(chalk.red(`✗ WebSocket error: ${err.message}`));
|
|
91
|
-
process.exit(1);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
ws.on('close', () => {
|
|
95
|
-
console.log(chalk.gray('\n→ Tunnel closed'));
|
|
96
|
-
});
|
|
157
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rostc",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "4-minute localhost tunnel",
|
|
5
5
|
"bin": {
|
|
6
6
|
"rostc": "./cli.js"
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"
|
|
10
|
-
"
|
|
9
|
+
"chalk": "^4.1.2",
|
|
10
|
+
"ws": "^8.14.0"
|
|
11
11
|
}
|
|
12
12
|
}
|