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.
Files changed (2) hide show
  1. package/dist/index.js +32 -20
  2. 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
- --no-replay Disable replay buffer (on by default)
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
- // Replay history with secrets redacted (only if replay is enabled)
530
- if (hasReplay) {
531
- for (const event of acpEventLog) {
532
- ws.send(JSON.stringify({ type: '_replay', data: redactSecrets(event) }));
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
- // F-12: Cap per-entry replay buffer size (64KB)
591
- const MAX_REPLAY_ENTRY_SIZE = 65536;
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(); ptyProcess.resize(process.stdout.columns || 120, process.stdout.rows || 30); });
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.1",
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"