Skip to content

Commit 74c6059

Browse files
committed
Merge remote-tracking branch 'origin/website'
2 parents bcf7bc6 + 110aec9 commit 74c6059

2 files changed

Lines changed: 76 additions & 9 deletions

File tree

server/routes/_share-terminal.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { defineWebSocketHandler } from "h3";
2-
import { getShare, registerTerminalPeer, unregisterTerminalPeer, broadcastToTerminalPeers, removeTerminal, addActiveTerminal, removeActiveTerminal } from "../utils/share";
2+
import { getShare, registerTerminalPeer, unregisterTerminalPeer, broadcastToTerminalPeers, removeTerminal, addActiveTerminal, removeActiveTerminal, updateTerminalPeerDimensions, recalcTerminalDimensions, setTerminalDimensions } from "../utils/share";
33
import { createTerminalConnection } from "../utils/ssh";
44
import type { Client } from "ssh2";
55

@@ -49,8 +49,10 @@ export default defineWebSocketHandler({
4949
const terminalId = `st-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
5050
const name = `Terminal ${session.activeTerminals.size + 1}`;
5151

52-
// Subscribe the creator
53-
registerTerminalPeer(terminalId, peer);
52+
// Subscribe the creator and store their dimensions
53+
const creatorDims = { cols: data.cols || 80, rows: data.rows || 24 };
54+
registerTerminalPeer(terminalId, peer, creatorDims);
55+
setTerminalDimensions(terminalId, creatorDims.cols, creatorDims.rows);
5456
let subs = peerTerminals.get(peer.id);
5557
if (!subs) { subs = new Set(); peerTerminals.set(peer.id, subs); }
5658
subs.add(terminalId);
@@ -99,14 +101,18 @@ export default defineWebSocketHandler({
99101
}
100102
} else if (data.type === "resize") {
101103
const { terminalId, cols, rows } = data;
102-
if (typeof terminalId !== "string") return;
104+
if (typeof terminalId !== "string" || typeof cols !== "number" || typeof rows !== "number") return;
105+
106+
// Update this peer's dimensions and compute min across all peers
107+
const newMin = updateTerminalPeerDimensions(terminalId, peer.id, cols, rows);
108+
if (!newMin) return; // dimensions unchanged, no PTY resize needed
103109

104110
if (session.mode === "relay" && session.hostRelayPeer) {
105-
session.hostRelayPeer.send(JSON.stringify({ type: "terminal-resize", terminalId, cols, rows }));
111+
session.hostRelayPeer.send(JSON.stringify({ type: "terminal-resize", terminalId, cols: newMin.cols, rows: newMin.rows }));
106112
} else {
107113
const dt = directTerminals.get(terminalId);
108-
if (dt && dt.stream && typeof cols === "number" && typeof rows === "number") {
109-
dt.stream.setWindow(rows, cols, rows * 16, cols * 8);
114+
if (dt && dt.stream) {
115+
dt.stream.setWindow(newMin.rows, newMin.cols, newMin.rows * 16, newMin.cols * 8);
110116
}
111117
}
112118
} else if (data.type === "close") {
@@ -132,10 +138,24 @@ export default defineWebSocketHandler({
132138
});
133139

134140
function cleanupPeer(peer: any): void {
141+
const auth = peerAuth.get(peer.id);
135142
const subs = peerTerminals.get(peer.id);
136143
if (subs) {
137144
for (const terminalId of subs) {
138145
unregisterTerminalPeer(terminalId, peer);
146+
// Recalculate min dimensions — the leaving peer may have been the smallest
147+
const newMin = recalcTerminalDimensions(terminalId);
148+
if (newMin && auth) {
149+
const session = getShare(auth.shareId);
150+
if (session) {
151+
if (session.mode === "relay" && session.hostRelayPeer) {
152+
session.hostRelayPeer.send(JSON.stringify({ type: "terminal-resize", terminalId, cols: newMin.cols, rows: newMin.rows }));
153+
} else {
154+
const dt = directTerminals.get(terminalId);
155+
if (dt && dt.stream) dt.stream.setWindow(newMin.rows, newMin.cols, newMin.rows * 16, newMin.cols * 8);
156+
}
157+
}
158+
}
139159
}
140160
peerTerminals.delete(peer.id);
141161
}

server/utils/share.ts

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,15 +260,20 @@ const MAX_TERMINAL_BUFFER = 50 * 1024; // 50KB rolling replay buffer per termina
260260
interface SharedTerminalEntry {
261261
peers: Set<Peer>;
262262
outputBuffer: string; // rolling buffer for late subscribers
263+
peerDimensions: Map<string, { cols: number; rows: number }>; // peerId → last known dimensions
264+
currentDimensions: { cols: number; rows: number }; // current PTY dimensions
263265
}
264266
const sharedTerminals = new Map<string, SharedTerminalEntry>();
265267

266-
export function registerTerminalPeer(terminalId: string, peer: Peer): void {
268+
export function registerTerminalPeer(terminalId: string, peer: Peer, dims?: { cols: number; rows: number }): void {
267269
let entry = sharedTerminals.get(terminalId);
268270
if (!entry) {
269-
entry = { peers: new Set(), outputBuffer: "" };
271+
entry = { peers: new Set(), outputBuffer: "", peerDimensions: new Map(), currentDimensions: { cols: 80, rows: 24 } };
270272
sharedTerminals.set(terminalId, entry);
271273
}
274+
if (dims) {
275+
entry.peerDimensions.set(peer.id, dims);
276+
}
272277
// Replay buffered output to late subscriber
273278
if (entry.outputBuffer.length > 0) {
274279
try { peer.send(JSON.stringify({ type: "output", terminalId, data: entry.outputBuffer })); } catch {}
@@ -280,6 +285,7 @@ export function unregisterTerminalPeer(terminalId: string, peer: Peer): void {
280285
const entry = sharedTerminals.get(terminalId);
281286
if (entry) {
282287
entry.peers.delete(peer);
288+
entry.peerDimensions.delete(peer.id);
283289
if (entry.peers.size === 0) sharedTerminals.delete(terminalId);
284290
}
285291
}
@@ -304,6 +310,47 @@ export function removeTerminal(terminalId: string): void {
304310
sharedTerminals.delete(terminalId);
305311
}
306312

313+
/**
314+
* Update a peer's dimensions and compute the minimum across all peers.
315+
* Returns the new min dimensions if they differ from the current PTY dimensions, or null if unchanged.
316+
*/
317+
export function updateTerminalPeerDimensions(terminalId: string, peerId: string, cols: number, rows: number): { cols: number; rows: number } | null {
318+
const entry = sharedTerminals.get(terminalId);
319+
if (!entry) return null;
320+
entry.peerDimensions.set(peerId, { cols, rows });
321+
const min = computeMinDimensions(entry);
322+
if (min.cols === entry.currentDimensions.cols && min.rows === entry.currentDimensions.rows) return null;
323+
entry.currentDimensions = min;
324+
return min;
325+
}
326+
327+
/**
328+
* Recalculate min dimensions after a peer disconnects.
329+
*/
330+
export function recalcTerminalDimensions(terminalId: string): { cols: number; rows: number } | null {
331+
const entry = sharedTerminals.get(terminalId);
332+
if (!entry || entry.peerDimensions.size === 0) return null;
333+
const min = computeMinDimensions(entry);
334+
if (min.cols === entry.currentDimensions.cols && min.rows === entry.currentDimensions.rows) return null;
335+
entry.currentDimensions = min;
336+
return min;
337+
}
338+
339+
function computeMinDimensions(entry: SharedTerminalEntry): { cols: number; rows: number } {
340+
let minCols = Infinity;
341+
let minRows = Infinity;
342+
for (const dims of entry.peerDimensions.values()) {
343+
if (dims.cols < minCols) minCols = dims.cols;
344+
if (dims.rows < minRows) minRows = dims.rows;
345+
}
346+
return { cols: minCols === Infinity ? 80 : minCols, rows: minRows === Infinity ? 24 : minRows };
347+
}
348+
349+
export function setTerminalDimensions(terminalId: string, cols: number, rows: number): void {
350+
const entry = sharedTerminals.get(terminalId);
351+
if (entry) entry.currentDimensions = { cols, rows };
352+
}
353+
307354
// --- Path security ---
308355

309356
export function isPathWithinRoot(filePath: string, rootPath: string): boolean {

0 commit comments

Comments
 (0)