cmux-ssh-here 0.4.0 → 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 +42 -24
- 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();
|
|
@@ -322,42 +320,62 @@ server.listen(PORT, "0.0.0.0", function () {
|
|
|
322
320
|
return `https://cmux.com/deeplink/ssh?${params}`;
|
|
323
321
|
};
|
|
324
322
|
|
|
325
|
-
// ponytail: in debug mode skip the
|
|
326
|
-
// just print the link whenever it rotates.
|
|
323
|
+
// ponytail: in debug mode skip the dashboard so logs stay readable.
|
|
327
324
|
const liveUI = !process.env.CMUX_SSH_DEBUG;
|
|
325
|
+
const REFRESH_MS = 5000; // ponytail: 5s cadence — long enough to select & copy the link
|
|
328
326
|
const ago = (since) => `${Math.floor((Date.now() - since) / 1000)}s`;
|
|
327
|
+
const remainingSec = () => Math.max(0, Math.ceil((expiry - Date.now()) / 1000));
|
|
328
|
+
|
|
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
|
+
};
|
|
335
|
+
|
|
336
|
+
// Collapse cmux's several SSH connections from one machine into one row per IP.
|
|
337
|
+
const sessionRows = () => {
|
|
338
|
+
const byIp = new Map();
|
|
339
|
+
for (const s of sessions.values()) {
|
|
340
|
+
const e = byIp.get(s.ip);
|
|
341
|
+
if (e) { e.count++; e.since = Math.min(e.since, s.since); }
|
|
342
|
+
else byIp.set(s.ip, { count: 1, since: s.since });
|
|
343
|
+
}
|
|
344
|
+
return [...byIp.entries()].map(
|
|
345
|
+
([ipAddr, e]) => ` • ${ipAddr} connected ${ago(e.since)} ago${e.count > 1 ? ` (${e.count} connections)` : ""}`
|
|
346
|
+
);
|
|
347
|
+
};
|
|
329
348
|
|
|
330
349
|
render = () => {
|
|
331
350
|
if (!liveUI) return;
|
|
351
|
+
const rem = remainingSec();
|
|
332
352
|
const lines = [
|
|
333
353
|
"",
|
|
334
354
|
` cmux-ssh-here — shell as ${user} over the LAN`,
|
|
335
355
|
"",
|
|
336
|
-
|
|
356
|
+
" Open in cmux:",
|
|
337
357
|
` ${buildLink()}`,
|
|
338
358
|
"",
|
|
359
|
+
` Link valid ${bar(rem)} ${rem}s`,
|
|
360
|
+
"",
|
|
339
361
|
];
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
}
|
|
346
|
-
lines.push("", " Ctrl-C to stop.", "");
|
|
347
|
-
// Clear screen + home, then redraw.
|
|
348
|
-
process.stdout.write(`\x1b[2J\x1b[3J\x1b[H${lines.join("\n")}\n`);
|
|
362
|
+
const rows = sessionRows();
|
|
363
|
+
if (rows.length) lines.push(` Connected (${rows.length}):`, ...rows);
|
|
364
|
+
else lines.push(" No active sessions yet.");
|
|
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`);
|
|
349
368
|
};
|
|
350
369
|
|
|
351
370
|
if (liveUI) render();
|
|
352
|
-
else console.log(`\n Open in cmux (regenerates in ${
|
|
371
|
+
else console.log(`\n Open in cmux (regenerates in ${remainingSec()}s):\n ${buildLink()}\n`);
|
|
353
372
|
|
|
354
|
-
//
|
|
373
|
+
// Refresh every 5s: regenerate the link when it expires, then redraw.
|
|
355
374
|
setInterval(() => {
|
|
356
|
-
|
|
357
|
-
if (remaining <= 0) {
|
|
375
|
+
if (remainingSec() <= 0) {
|
|
358
376
|
regenerateToken();
|
|
359
377
|
if (!liveUI) console.log(`\n [link regenerated]\n ${buildLink()}\n`);
|
|
360
378
|
}
|
|
361
379
|
render();
|
|
362
|
-
},
|
|
380
|
+
}, REFRESH_MS);
|
|
363
381
|
});
|