cli-tunnel 1.3.1-beta.1 → 1.3.1-beta.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/dist/index.js +32 -20
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -23,6 +23,10 @@ import http from 'node:http';
|
|
|
23
23
|
import readline from 'node:readline';
|
|
24
24
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
25
25
|
import os from 'node:os';
|
|
26
|
+
import headlessPkg from '@xterm/headless';
|
|
27
|
+
const HeadlessTerminal = headlessPkg.Terminal;
|
|
28
|
+
import serializePkg from '@xterm/addon-serialize';
|
|
29
|
+
const SerializeAddon = serializePkg.SerializeAddon;
|
|
26
30
|
import { redactSecrets } from './redact.js';
|
|
27
31
|
// F-15: Global error handlers to prevent unclean crashes
|
|
28
32
|
process.on('uncaughtException', (err) => {
|
|
@@ -58,7 +62,7 @@ ${BOLD}Options:${RESET}
|
|
|
58
62
|
--local Disable devtunnel (localhost only)
|
|
59
63
|
--port <n> Bridge port (default: random)
|
|
60
64
|
--name <name> Session name (shown in dashboard)
|
|
61
|
-
--
|
|
65
|
+
--replay (deprecated, screen buffer is always on)
|
|
62
66
|
--help, -h Show this help
|
|
63
67
|
|
|
64
68
|
${BOLD}Examples:${RESET}
|
|
@@ -187,7 +191,6 @@ setInterval(() => {
|
|
|
187
191
|
}, 30000);
|
|
188
192
|
// ─── Security: Redact secrets from replay events ────────────
|
|
189
193
|
// ─── Bridge server ──────────────────────────────────────────
|
|
190
|
-
const acpEventLog = [];
|
|
191
194
|
const connections = new Map();
|
|
192
195
|
let localResizeAt = 0; // Timestamp of last local terminal resize
|
|
193
196
|
// #10: Session TTL enforcement — periodically close expired connections
|
|
@@ -526,12 +529,10 @@ wss.on('connection', (ws, req) => {
|
|
|
526
529
|
// F-10: WS ping/pong heartbeat
|
|
527
530
|
ws._isAlive = true;
|
|
528
531
|
ws.on('pong', () => { ws._isAlive = true; });
|
|
529
|
-
//
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
}
|
|
534
|
-
ws.send(JSON.stringify({ type: '_replay_done' }));
|
|
532
|
+
// Send current screen state so new clients see what's on the terminal now
|
|
533
|
+
const screenState = getScreenState();
|
|
534
|
+
if (screenState) {
|
|
535
|
+
ws.send(JSON.stringify({ type: 'pty', data: screenState }));
|
|
535
536
|
}
|
|
536
537
|
ws.on('message', (data) => {
|
|
537
538
|
// F-13: Enforce WS message rate limit (100 msg/sec)
|
|
@@ -565,6 +566,8 @@ wss.on('connection', (ws, req) => {
|
|
|
565
566
|
const localRecentlyResized = Date.now() - localResizeAt < 2000;
|
|
566
567
|
if (Number.isFinite(cols) && Number.isFinite(rows) && ptyProcess && !localRecentlyResized) {
|
|
567
568
|
ptyProcess.resize(Math.max(1, Math.min(500, cols)), Math.max(1, Math.min(200, rows)));
|
|
569
|
+
if (screenTerminal)
|
|
570
|
+
screenTerminal.resize(Math.max(1, Math.min(500, cols)), Math.max(1, Math.min(200, rows)));
|
|
568
571
|
}
|
|
569
572
|
}
|
|
570
573
|
}
|
|
@@ -587,20 +590,26 @@ setInterval(() => {
|
|
|
587
590
|
ws.ping();
|
|
588
591
|
}
|
|
589
592
|
}, 30000);
|
|
590
|
-
//
|
|
591
|
-
|
|
593
|
+
// Screen buffer: headless xterm.js terminal that tracks the current screen state
|
|
594
|
+
// On client connect, serialize the screen and send it — renders correctly regardless of dimensions
|
|
595
|
+
let screenTerminal = null;
|
|
596
|
+
let serializeAddon = null;
|
|
597
|
+
function initScreenBuffer(cols, rows) {
|
|
598
|
+
screenTerminal = new HeadlessTerminal({ cols, rows, scrollback: 1000, allowProposedApi: true });
|
|
599
|
+
serializeAddon = new SerializeAddon();
|
|
600
|
+
screenTerminal.loadAddon(serializeAddon);
|
|
601
|
+
}
|
|
602
|
+
function getScreenState() {
|
|
603
|
+
if (!serializeAddon)
|
|
604
|
+
return '';
|
|
605
|
+
return serializeAddon.serialize();
|
|
606
|
+
}
|
|
592
607
|
function broadcast(data) {
|
|
593
|
-
// F-01: Redact secrets from live broadcast (not just replay)
|
|
594
608
|
const redacted = redactSecrets(data);
|
|
609
|
+
// Feed into headless terminal to track screen state
|
|
610
|
+
if (screenTerminal)
|
|
611
|
+
screenTerminal.write(redacted);
|
|
595
612
|
const msg = JSON.stringify({ type: 'pty', data: redacted });
|
|
596
|
-
if (hasReplay) {
|
|
597
|
-
// F-12: Cap per-entry size to prevent memory exhaustion
|
|
598
|
-
if (msg.length <= MAX_REPLAY_ENTRY_SIZE) {
|
|
599
|
-
acpEventLog.push(msg);
|
|
600
|
-
}
|
|
601
|
-
if (acpEventLog.length > 2000)
|
|
602
|
-
acpEventLog.splice(0, acpEventLog.length - 2000);
|
|
603
|
-
}
|
|
604
613
|
for (const [, ws] of connections) {
|
|
605
614
|
if (ws.readyState === WebSocket.OPEN)
|
|
606
615
|
ws.send(msg);
|
|
@@ -869,6 +878,8 @@ async function main() {
|
|
|
869
878
|
cols, rows, cwd,
|
|
870
879
|
env: safeEnv,
|
|
871
880
|
});
|
|
881
|
+
// Initialize headless terminal for screen state tracking
|
|
882
|
+
initScreenBuffer(cols, rows);
|
|
872
883
|
// Detect CSPRNG crash (rare Node.js + PTY issue) and show helpful message
|
|
873
884
|
let earlyExitCode = null;
|
|
874
885
|
const earlyExitCheck = new Promise((resolve) => {
|
|
@@ -906,6 +917,7 @@ async function main() {
|
|
|
906
917
|
process.stdin.setRawMode(true);
|
|
907
918
|
process.stdin.resume();
|
|
908
919
|
process.stdin.on('data', (data) => ptyProcess.write(data.toString()));
|
|
909
|
-
process.stdout.on('resize', () => { localResizeAt = Date.now();
|
|
920
|
+
process.stdout.on('resize', () => { localResizeAt = Date.now(); const c = process.stdout.columns || 120; const r = process.stdout.rows || 30; ptyProcess.resize(c, r); if (screenTerminal)
|
|
921
|
+
screenTerminal.resize(c, r); });
|
|
910
922
|
}
|
|
911
923
|
main().catch((err) => { console.error(err); process.exit(1); });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cli-tunnel",
|
|
3
|
-
"version": "1.3.1-beta.
|
|
3
|
+
"version": "1.3.1-beta.3",
|
|
4
4
|
"description": "Tunnel any CLI app to your phone — PTY + devtunnel + xterm.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -36,6 +36,8 @@
|
|
|
36
36
|
"node": ">=22.0.0"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
+
"@xterm/addon-serialize": "^0.14.0",
|
|
40
|
+
"@xterm/headless": "^6.0.0",
|
|
39
41
|
"node-pty": "1.1.0",
|
|
40
42
|
"qrcode-terminal": "0.12.0",
|
|
41
43
|
"ws": "8.19.0"
|