cli-tunnel 1.3.1-beta.3 → 1.3.1-beta.4
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 +5 -35
- 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();
|
|
@@ -566,8 +558,6 @@ wss.on('connection', (ws, req) => {
|
|
|
566
558
|
const localRecentlyResized = Date.now() - localResizeAt < 2000;
|
|
567
559
|
if (Number.isFinite(cols) && Number.isFinite(rows) && ptyProcess && !localRecentlyResized) {
|
|
568
560
|
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
561
|
}
|
|
572
562
|
}
|
|
573
563
|
}
|
|
@@ -590,25 +580,8 @@ setInterval(() => {
|
|
|
590
580
|
ws.ping();
|
|
591
581
|
}
|
|
592
582
|
}, 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
583
|
function broadcast(data) {
|
|
608
584
|
const redacted = redactSecrets(data);
|
|
609
|
-
// Feed into headless terminal to track screen state
|
|
610
|
-
if (screenTerminal)
|
|
611
|
-
screenTerminal.write(redacted);
|
|
612
585
|
const msg = JSON.stringify({ type: 'pty', data: redacted });
|
|
613
586
|
for (const [, ws] of connections) {
|
|
614
587
|
if (ws.readyState === WebSocket.OPEN)
|
|
@@ -810,7 +783,7 @@ async function main() {
|
|
|
810
783
|
await new Promise(() => { });
|
|
811
784
|
}
|
|
812
785
|
// Wait for user to scan QR / copy URL before starting the CLI tool
|
|
813
|
-
if (hasTunnel) {
|
|
786
|
+
if (hasTunnel && !noWait) {
|
|
814
787
|
console.log(` ${BOLD}Press any key to start ${command}...${RESET}`);
|
|
815
788
|
await new Promise((resolve) => {
|
|
816
789
|
if (process.stdin.isTTY)
|
|
@@ -878,8 +851,6 @@ async function main() {
|
|
|
878
851
|
cols, rows, cwd,
|
|
879
852
|
env: safeEnv,
|
|
880
853
|
});
|
|
881
|
-
// Initialize headless terminal for screen state tracking
|
|
882
|
-
initScreenBuffer(cols, rows);
|
|
883
854
|
// Detect CSPRNG crash (rare Node.js + PTY issue) and show helpful message
|
|
884
855
|
let earlyExitCode = null;
|
|
885
856
|
const earlyExitCheck = new Promise((resolve) => {
|
|
@@ -917,7 +888,6 @@ async function main() {
|
|
|
917
888
|
process.stdin.setRawMode(true);
|
|
918
889
|
process.stdin.resume();
|
|
919
890
|
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); });
|
|
891
|
+
process.stdout.on('resize', () => { localResizeAt = Date.now(); const c = process.stdout.columns || 120; const r = process.stdout.rows || 30; ptyProcess.resize(c, r); });
|
|
922
892
|
}
|
|
923
893
|
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
|
|