-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathagent.js
More file actions
123 lines (106 loc) · 3.26 KB
/
Copy pathagent.js
File metadata and controls
123 lines (106 loc) · 3.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#!/usr/bin/env node
const WebSocket = require('ws');
const pty = require('node-pty');
const crypto = require('crypto');
const os = require('os');
const qrcode = require('qrcode-terminal');
const RELAY = process.argv[2] || 'ws://localhost:3100';
const token = crypto.randomBytes(16).toString('hex');
const httpUrl = RELAY.replace('ws://', 'http://').replace('wss://', 'https://');
const fullUrl = `${httpUrl}?token=${token}`;
// Spawn a real terminal
const shell = process.env.SHELL || (os.platform() === 'win32' ? 'powershell.exe' : 'bash');
const ptyProcess = pty.spawn(shell, [], {
name: 'xterm-256color',
cols: 80,
rows: 24,
cwd: process.env.HOME || process.cwd(),
env: process.env,
});
let ws = null;
let reconnectAttempts = 0;
const MAX_RECONNECT = 20;
let shown = false;
function connectToRelay() {
ws = new WebSocket(`${RELAY}?type=agent&token=${token}`);
ws.on('open', () => {
reconnectAttempts = 0;
if (!shown) {
shown = true;
console.log('\n ========================================');
console.log(' Open Tunnel — Remote Terminal Active');
console.log(' ========================================\n');
console.log(' Scan this QR code with your phone:\n');
qrcode.generate(fullUrl, { small: true }, (qr) => {
console.log(qr);
console.log(` Or open this URL:\n`);
console.log(` ${fullUrl}\n`);
console.log(' Waiting for connections...\n');
});
} else {
console.log(' [*] Reconnected to relay.\n');
}
});
ws.on('message', (data) => {
try {
const msg = JSON.parse(data.toString());
if (msg.type === 'input') {
ptyProcess.write(msg.data);
} else if (msg.type === 'resize') {
ptyProcess.resize(msg.cols, msg.rows);
} else if (msg.type === 'client-joined') {
console.log(' [+] Client connected to your session');
ptyProcess.write('\n');
}
} catch (e) {
ptyProcess.write(data.toString());
}
});
ws.on('close', (code) => {
if (code === 1000) {
// Normal close
console.log('\n Session ended.');
ptyProcess.kill();
process.exit(0);
}
// Unexpected close — try to reconnect
reconnect();
});
ws.on('error', (err) => {
// Don't log every error, reconnect handles it
});
// Respond to pings from relay to keep connection alive
ws.on('ping', () => {
ws.pong();
});
}
function reconnect() {
if (reconnectAttempts >= MAX_RECONNECT) {
console.error('\n Failed to reconnect after 20 attempts. Exiting.');
ptyProcess.kill();
process.exit(1);
}
reconnectAttempts++;
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts - 1), 30000);
console.log(` [*] Reconnecting in ${Math.round(delay / 1000)}s (attempt ${reconnectAttempts}/${MAX_RECONNECT})...`);
setTimeout(connectToRelay, delay);
}
// PTY output → relay → browser clients
ptyProcess.onData((data) => {
if (ws && ws.readyState === 1) {
ws.send(JSON.stringify({ type: 'output', data }));
}
});
ptyProcess.onExit(() => {
console.log('\n Shell exited. Closing tunnel.');
if (ws) ws.close(1000);
process.exit(0);
});
// Clean shutdown
process.on('SIGINT', () => {
ptyProcess.kill();
if (ws) ws.close(1000);
process.exit(0);
});
// Start
connectToRelay();