cmux-ssh-here 0.4.1 → 0.5.0
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/bin.js +28 -33
- package/package.json +1 -1
package/bin.js
CHANGED
|
@@ -33,10 +33,10 @@ const { default: pty } = await import("node-pty");
|
|
|
33
33
|
// and an ssh client treats a leading-dash username as a flag. Hex is URL/ssh-safe.
|
|
34
34
|
const TTL_SECONDS = Number(process.env.CMUX_SSH_TTL) || 180; // link/token lifetime before regeneration
|
|
35
35
|
let token; // secret carried in user=<token>; rotated every TTL_SECONDS
|
|
36
|
-
let
|
|
36
|
+
let expiry; // epoch ms when the current token expires
|
|
37
37
|
const regenerateToken = () => {
|
|
38
38
|
token = randomBytes(12).toString("hex");
|
|
39
|
-
|
|
39
|
+
expiry = Date.now() + TTL_SECONDS * 1000;
|
|
40
40
|
};
|
|
41
41
|
regenerateToken();
|
|
42
42
|
|
|
@@ -251,13 +251,11 @@ const server = new Server(serverCfg, (client, info) => {
|
|
|
251
251
|
return ctx.username === token ? ctx.accept() : ctx.reject();
|
|
252
252
|
});
|
|
253
253
|
client.on("ready", () => {
|
|
254
|
+
// ponytail: just track the connection; the 5s timer redraws — no per-event
|
|
255
|
+
// repaint (that was the screen-churn that broke copying).
|
|
254
256
|
const id = nextSid++;
|
|
255
257
|
sessions.set(id, { ip: info?.ip || "?", since: Date.now() });
|
|
256
|
-
|
|
257
|
-
client.on("close", () => {
|
|
258
|
-
sessions.delete(id);
|
|
259
|
-
render();
|
|
260
|
-
});
|
|
258
|
+
client.on("close", () => sessions.delete(id));
|
|
261
259
|
});
|
|
262
260
|
client.on("session", (accept) => {
|
|
263
261
|
const session = accept();
|
|
@@ -324,16 +322,18 @@ server.listen(PORT, "0.0.0.0", function () {
|
|
|
324
322
|
|
|
325
323
|
// ponytail: in debug mode skip the dashboard so logs stay readable.
|
|
326
324
|
const liveUI = !process.env.CMUX_SSH_DEBUG;
|
|
325
|
+
const REFRESH_MS = 5000; // ponytail: 5s cadence — long enough to select & copy the link
|
|
327
326
|
const ago = (since) => `${Math.floor((Date.now() - since) / 1000)}s`;
|
|
327
|
+
const remainingSec = () => Math.max(0, Math.ceil((expiry - Date.now()) / 1000));
|
|
328
328
|
|
|
329
|
-
//
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
329
|
+
// Decreasing progress bar for the link's remaining lifetime.
|
|
330
|
+
const bar = (rem) => {
|
|
331
|
+
const W = 28;
|
|
332
|
+
const filled = Math.round((rem / TTL_SECONDS) * W);
|
|
333
|
+
return `[${"█".repeat(filled)}${"░".repeat(W - filled)}]`;
|
|
334
|
+
};
|
|
334
335
|
|
|
335
|
-
// Collapse
|
|
336
|
-
// ControlMaster, the daemon channel, short-lived probes) into one row per IP.
|
|
336
|
+
// Collapse cmux's several SSH connections from one machine into one row per IP.
|
|
337
337
|
const sessionRows = () => {
|
|
338
338
|
const byIp = new Map();
|
|
339
339
|
for (const s of sessions.values()) {
|
|
@@ -346,7 +346,9 @@ server.listen(PORT, "0.0.0.0", function () {
|
|
|
346
346
|
);
|
|
347
347
|
};
|
|
348
348
|
|
|
349
|
-
|
|
349
|
+
render = () => {
|
|
350
|
+
if (!liveUI) return;
|
|
351
|
+
const rem = remainingSec();
|
|
350
352
|
const lines = [
|
|
351
353
|
"",
|
|
352
354
|
` cmux-ssh-here — shell as ${user} over the LAN`,
|
|
@@ -354,33 +356,26 @@ server.listen(PORT, "0.0.0.0", function () {
|
|
|
354
356
|
" Open in cmux:",
|
|
355
357
|
` ${buildLink()}`,
|
|
356
358
|
"",
|
|
359
|
+
` Link valid ${bar(rem)} ${rem}s`,
|
|
360
|
+
"",
|
|
357
361
|
];
|
|
358
362
|
const rows = sessionRows();
|
|
359
363
|
if (rows.length) lines.push(` Connected (${rows.length}):`, ...rows);
|
|
360
364
|
else lines.push(" No active sessions yet.");
|
|
361
|
-
lines.push("");
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
// Repaint the block, then leave the cursor parked on the countdown line.
|
|
366
|
-
render = () => {
|
|
367
|
-
if (!liveUI) return;
|
|
368
|
-
process.stdout.write(`\n${block()}\n${countdownLine()}`);
|
|
365
|
+
lines.push("", " Updates every 5s · Ctrl-C to stop.", "");
|
|
366
|
+
// Home + clear-to-end (not full \x1b[2J): redraw in the same spot every 5s.
|
|
367
|
+
process.stdout.write(`\x1b[H\x1b[J${lines.join("\n")}\n`);
|
|
369
368
|
};
|
|
370
369
|
|
|
371
370
|
if (liveUI) render();
|
|
372
|
-
else console.log(`\n Open in cmux (regenerates in ${
|
|
371
|
+
else console.log(`\n Open in cmux (regenerates in ${remainingSec()}s):\n ${buildLink()}\n`);
|
|
373
372
|
|
|
373
|
+
// Refresh every 5s: regenerate the link when it expires, then redraw.
|
|
374
374
|
setInterval(() => {
|
|
375
|
-
|
|
376
|
-
if (remaining <= 0) {
|
|
375
|
+
if (remainingSec() <= 0) {
|
|
377
376
|
regenerateToken();
|
|
378
|
-
|
|
379
|
-
if (liveUI) render();
|
|
380
|
-
else console.log(`\n [link regenerated]\n ${buildLink()}\n`);
|
|
381
|
-
return;
|
|
377
|
+
if (!liveUI) console.log(`\n [link regenerated]\n ${buildLink()}\n`);
|
|
382
378
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
}, 1000);
|
|
379
|
+
render();
|
|
380
|
+
}, REFRESH_MS);
|
|
386
381
|
});
|