cli-tunnel 1.3.1-beta.3 → 1.3.1-beta.5
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 +8 -42
- package/package.json +1 -1
- package/remote-ui/app.js +25 -8
package/dist/index.js
CHANGED
|
@@ -23,10 +23,6 @@ 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;
|
|
30
26
|
import { redactSecrets } from './redact.js';
|
|
31
27
|
// F-15: Global error handlers to prevent unclean crashes
|
|
32
28
|
process.on('uncaughtException', (err) => {
|
|
@@ -83,12 +79,13 @@ pass through to the underlying app. cli-tunnel's own flags
|
|
|
83
79
|
const hasLocal = args.includes('--local');
|
|
84
80
|
const hasTunnel = !hasLocal;
|
|
85
81
|
const hasReplay = !args.includes('--no-replay');
|
|
82
|
+
const noWait = args.includes('--no-wait');
|
|
86
83
|
const portIdx = args.indexOf('--port');
|
|
87
84
|
const port = (portIdx !== -1 && args[portIdx + 1]) ? parseInt(args[portIdx + 1], 10) : 0;
|
|
88
85
|
const nameIdx = args.indexOf('--name');
|
|
89
86
|
const sessionName = (nameIdx !== -1 && args[nameIdx + 1]) ? args[nameIdx + 1] : '';
|
|
90
87
|
// Everything that's not our flags is the command
|
|
91
|
-
const ourFlags = new Set(['--local', '--tunnel', '--port', '--name', '--no-replay']);
|
|
88
|
+
const ourFlags = new Set(['--local', '--tunnel', '--port', '--name', '--no-replay', '--no-wait']);
|
|
92
89
|
const cmdArgs = [];
|
|
93
90
|
let skip = false;
|
|
94
91
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -100,7 +97,7 @@ for (let i = 0; i < args.length; i++) {
|
|
|
100
97
|
skip = true;
|
|
101
98
|
continue;
|
|
102
99
|
}
|
|
103
|
-
if (args[i] === '--local' || args[i] === '--tunnel' || args[i] === '--no-replay')
|
|
100
|
+
if (args[i] === '--local' || args[i] === '--tunnel' || args[i] === '--no-replay' || args[i] === '--no-wait')
|
|
104
101
|
continue;
|
|
105
102
|
cmdArgs.push(args[i]);
|
|
106
103
|
}
|
|
@@ -529,11 +526,6 @@ wss.on('connection', (ws, req) => {
|
|
|
529
526
|
// F-10: WS ping/pong heartbeat
|
|
530
527
|
ws._isAlive = true;
|
|
531
528
|
ws.on('pong', () => { ws._isAlive = true; });
|
|
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 }));
|
|
536
|
-
}
|
|
537
529
|
ws.on('message', (data) => {
|
|
538
530
|
// F-13: Enforce WS message rate limit (100 msg/sec)
|
|
539
531
|
const now = Date.now();
|
|
@@ -559,16 +551,10 @@ wss.on('connection', (ws, req) => {
|
|
|
559
551
|
ptyProcess.write(msg.data);
|
|
560
552
|
}
|
|
561
553
|
}
|
|
562
|
-
//
|
|
554
|
+
// pty_resize from remote clients is ignored — PTY stays at local terminal size
|
|
555
|
+
// The phone's xterm.js handles display via its own viewport/scrolling
|
|
563
556
|
if (msg.type === 'pty_resize') {
|
|
564
|
-
|
|
565
|
-
const rows = Number(msg.rows);
|
|
566
|
-
const localRecentlyResized = Date.now() - localResizeAt < 2000;
|
|
567
|
-
if (Number.isFinite(cols) && Number.isFinite(rows) && ptyProcess && !localRecentlyResized) {
|
|
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)));
|
|
571
|
-
}
|
|
557
|
+
// Only log, don't resize — prevents breaking local terminal layout
|
|
572
558
|
}
|
|
573
559
|
}
|
|
574
560
|
catch {
|
|
@@ -590,25 +576,8 @@ setInterval(() => {
|
|
|
590
576
|
ws.ping();
|
|
591
577
|
}
|
|
592
578
|
}, 30000);
|
|
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
|
-
}
|
|
607
579
|
function broadcast(data) {
|
|
608
580
|
const redacted = redactSecrets(data);
|
|
609
|
-
// Feed into headless terminal to track screen state
|
|
610
|
-
if (screenTerminal)
|
|
611
|
-
screenTerminal.write(redacted);
|
|
612
581
|
const msg = JSON.stringify({ type: 'pty', data: redacted });
|
|
613
582
|
for (const [, ws] of connections) {
|
|
614
583
|
if (ws.readyState === WebSocket.OPEN)
|
|
@@ -810,7 +779,7 @@ async function main() {
|
|
|
810
779
|
await new Promise(() => { });
|
|
811
780
|
}
|
|
812
781
|
// Wait for user to scan QR / copy URL before starting the CLI tool
|
|
813
|
-
if (hasTunnel) {
|
|
782
|
+
if (hasTunnel && !noWait) {
|
|
814
783
|
console.log(` ${BOLD}Press any key to start ${command}...${RESET}`);
|
|
815
784
|
await new Promise((resolve) => {
|
|
816
785
|
if (process.stdin.isTTY)
|
|
@@ -878,8 +847,6 @@ async function main() {
|
|
|
878
847
|
cols, rows, cwd,
|
|
879
848
|
env: safeEnv,
|
|
880
849
|
});
|
|
881
|
-
// Initialize headless terminal for screen state tracking
|
|
882
|
-
initScreenBuffer(cols, rows);
|
|
883
850
|
// Detect CSPRNG crash (rare Node.js + PTY issue) and show helpful message
|
|
884
851
|
let earlyExitCode = null;
|
|
885
852
|
const earlyExitCheck = new Promise((resolve) => {
|
|
@@ -917,7 +884,6 @@ async function main() {
|
|
|
917
884
|
process.stdin.setRawMode(true);
|
|
918
885
|
process.stdin.resume();
|
|
919
886
|
process.stdin.on('data', (data) => ptyProcess.write(data.toString()));
|
|
920
|
-
process.stdout.on('resize', () => { localResizeAt = Date.now(); const c = process.stdout.columns || 120; const r = process.stdout.rows || 30; ptyProcess.resize(c, r);
|
|
921
|
-
screenTerminal.resize(c, r); });
|
|
887
|
+
process.stdout.on('resize', () => { localResizeAt = Date.now(); const c = process.stdout.columns || 120; const r = process.stdout.rows || 30; ptyProcess.resize(c, r); });
|
|
922
888
|
}
|
|
923
889
|
main().catch((err) => { console.error(err); process.exit(1); });
|
package/package.json
CHANGED
package/remote-ui/app.js
CHANGED
|
@@ -63,9 +63,18 @@
|
|
|
63
63
|
if (currentView === 'grid' && gridMode === 'fullscreen' && gridTerminals[focusedIndex]) {
|
|
64
64
|
canvas = gridTerminals[focusedIndex].panel.querySelector('canvas');
|
|
65
65
|
} else {
|
|
66
|
-
|
|
66
|
+
var tc = document.getElementById('terminal-container');
|
|
67
|
+
if (tc) canvas = tc.querySelector('canvas');
|
|
68
|
+
}
|
|
69
|
+
if (!canvas) {
|
|
70
|
+
// Fallback: try to find any canvas in the xterm container
|
|
71
|
+
var xtermEl = document.querySelector('.xterm');
|
|
72
|
+
if (xtermEl) canvas = xtermEl.querySelector('canvas');
|
|
73
|
+
}
|
|
74
|
+
if (!canvas || !canvas.captureStream) {
|
|
75
|
+
if (statusText) { var prev = statusText.textContent; statusText.textContent = 'Recording not supported'; setTimeout(function() { statusText.textContent = prev; }, 3000); }
|
|
76
|
+
return false;
|
|
67
77
|
}
|
|
68
|
-
if (!canvas) { console.warn('No terminal canvas found'); return false; }
|
|
69
78
|
try {
|
|
70
79
|
var stream = canvas.captureStream(30); // 30 fps
|
|
71
80
|
var mimeType = MediaRecorder.isTypeSupported('video/webm;codecs=vp9') ? 'video/webm;codecs=vp9'
|
|
@@ -140,7 +149,13 @@
|
|
|
140
149
|
if (currentView === 'grid' && gridMode === 'fullscreen' && gridTerminals[focusedIndex]) {
|
|
141
150
|
canvas = gridTerminals[focusedIndex].panel.querySelector('canvas');
|
|
142
151
|
} else {
|
|
143
|
-
|
|
152
|
+
var tc = document.getElementById('terminal-container');
|
|
153
|
+
if (tc) canvas = tc.querySelector('canvas');
|
|
154
|
+
}
|
|
155
|
+
if (!canvas) {
|
|
156
|
+
// Fallback: try to find any canvas in the xterm container
|
|
157
|
+
var xtermEl = document.querySelector('.xterm');
|
|
158
|
+
if (xtermEl) canvas = xtermEl.querySelector('canvas');
|
|
144
159
|
}
|
|
145
160
|
if (!canvas) {
|
|
146
161
|
if (statusText) { var prev = statusText.textContent; statusText.textContent = 'No terminal to capture'; setTimeout(function() { statusText.textContent = prev; }, 2000); }
|
|
@@ -239,13 +254,15 @@
|
|
|
239
254
|
|
|
240
255
|
async function loadSessions() {
|
|
241
256
|
try {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
257
|
+
var tokenParam = new URLSearchParams(window.location.search).get('token');
|
|
258
|
+
var headers = tokenParam ? { 'Authorization': 'Bearer ' + tokenParam } : {};
|
|
259
|
+
var resp = await fetch('/api/sessions', { headers: headers });
|
|
260
|
+
if (!resp.ok) throw new Error('Status ' + resp.status);
|
|
261
|
+
var data = await resp.json();
|
|
246
262
|
renderDashboard(data.sessions || []);
|
|
247
263
|
} catch (err) {
|
|
248
|
-
dashboard.innerHTML = '<div style="padding:12px;color:var(--
|
|
264
|
+
dashboard.innerHTML = '<div style="padding:20px 12px;color:var(--text-dim);text-align:center">' +
|
|
265
|
+
escapeHtml('Sessions unavailable. Use Hub mode (cli-tunnel with no command) to see all sessions.') + '</div>';
|
|
249
266
|
}
|
|
250
267
|
}
|
|
251
268
|
|