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.
Files changed (2) hide show
  1. package/cli.js +133 -72
  2. 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
- // YOUR WORKER URL FROM DEPLOYMENT
9
- const WORKER_URL = 'https://rostc.roygichira084.workers.dev';
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: npx rostc 8080'));
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(`→ Building tunnel on port ${port}...`));
24
+ console.log(chalk.cyan(`→ Starting tunnel on port ${port}...`));
25
25
 
26
- // Generate random tunnel ID
27
- const tunnelId = randomBytes(8).toString('hex');
28
- const publicUrl = `${WORKER_URL}/${tunnelId}`;
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
- // Connect to Cloudflare Worker
31
- const ws = new WebSocket(`${WORKER_URL.replace('https', 'wss')}/${tunnelId}`);
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
- // Start local HTTP server
39
- const server = http.createServer((req, res) => {
40
- const chunks = [];
41
- req.on('data', chunk => chunks.push(chunk));
42
- req.on('end', () => {
43
- const body = Buffer.concat(chunks);
44
- const requestId = randomBytes(8).toString('hex');
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
- ws.send(JSON.stringify({
47
- type: 'request',
48
- id: requestId,
49
- method: req.method,
50
- path: req.url,
51
- headers: req.headers,
52
- body: body.toString('base64')
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
- const handler = (data) => {
56
- try {
57
- const msg = JSON.parse(data);
58
- if (msg.type === 'response' && msg.id === requestId) {
59
- ws.removeListener('message', handler);
60
- res.writeHead(msg.status || 200, msg.headers || {});
61
- res.end(msg.body ? Buffer.from(msg.body, 'base64') : null);
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
- } catch(e) {}
64
- };
65
- ws.on('message', handler);
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
- server.listen(port, () => {
70
- // Server ready
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.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
- "ws": "^8.14.0",
10
- "chalk": "^4.1.2"
9
+ "chalk": "^4.1.2",
10
+ "ws": "^8.14.0"
11
11
  }
12
12
  }