Skip to content

Commit c313f86

Browse files
committed
fix: shared terminals
1 parent 24536c2 commit c313f86

5 files changed

Lines changed: 36 additions & 20 deletions

File tree

app/components/ShareModal.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
<input class="field-input share-link" :value="shareUrl" readonly
4242
@click="($event.target as HTMLInputElement).select()" />
4343
<button class="dialog-btn copy" @click="copyLink" :class="{ copied }">
44-
{{ copied ? 'Copied!' : 'Copy' }}
44+
{{ copied ? 'Copied' : 'Copy' }}
4545
</button>
4646
</div>
4747

app/components/TerminalPanel.vue

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
</template>
6060

6161
<script setup lang="ts">
62-
const { isSharing, sharedTerminals } = useShare();
62+
const { isSharing, sharedTerminals, addSharedTerminal } = useShare();
6363
6464
const props = defineProps<{
6565
rootPath: string;
@@ -95,16 +95,17 @@ function createSessions(count: number): TerminalSession[] {
9595
return arr;
9696
}
9797
98-
const sessions = ref<TerminalSession[]>(createSessions(1));
98+
// In share mode, start empty — sharedTerminals watcher will populate sessions
99+
const sessions = ref<TerminalSession[]>(isSharing.value ? [] : createSessions(1));
99100
100101
// Track server terminal IDs we've already registered (to avoid duplicates on terminal-added)
101102
const knownShareIds = new Set<string>();
102103
103104
// activeId = left terminal in split (or the single visible terminal)
104-
const activeId = ref(sessions.value[0]!.id);
105+
const activeId = ref(sessions.value[0]?.id ?? '');
105106
const splitId = ref<string | null>(null);
106107
// focusedId = which terminal is highlighted/selected in sidebar
107-
const focusedId = ref(sessions.value[0]!.id);
108+
const focusedId = ref(sessions.value[0]?.id ?? '');
108109
const splitRatio = ref(50);
109110
110111
// Map: terminalId → { left, right } for ALL saved (non-active) split pairs
@@ -125,7 +126,7 @@ watch(isSharing, (sharing) => {
125126
126127
watch(sharedTerminals, (list) => {
127128
if (!isSharing.value) return;
128-
// Add new terminals
129+
// Add new terminals we don't have yet
129130
for (const t of list) {
130131
if (!knownShareIds.has(t.id)) {
131132
knownShareIds.add(t.id);
@@ -137,26 +138,25 @@ watch(sharedTerminals, (list) => {
137138
}
138139
}
139140
}
140-
// Remove gone terminals
141-
const activeIds = new Set(list.map((t: any) => t.id));
141+
// Remove terminals no longer on server
142+
const serverIds = new Set(list.map((t: any) => t.id));
142143
const before = sessions.value.length;
143144
sessions.value = sessions.value.filter(s => {
144-
if (s.shareTerminalId && !activeIds.has(s.shareTerminalId)) {
145+
if (s.shareTerminalId && !serverIds.has(s.shareTerminalId)) {
145146
knownShareIds.delete(s.shareTerminalId);
146147
return false;
147148
}
148149
return true;
149150
});
150151
if (sessions.value.length !== before) {
151-
// Re-select if active was removed
152152
if (!sessions.value.find(s => s.id === activeId.value) && sessions.value.length > 0) {
153153
activeId.value = sessions.value[0]!.id;
154154
focusedId.value = activeId.value;
155155
} else if (sessions.value.length === 0) {
156156
emit("close");
157157
}
158158
}
159-
}, { deep: true });
159+
}, { deep: true, immediate: true });
160160
161161
watch(() => props.initialTerminalHeight, (val) => {
162162
if (val !== undefined) panelHeight.value = val;
@@ -179,6 +179,8 @@ function onShareCreated(localId: string, serverTerminalId: string) {
179179
if (s) {
180180
s.shareTerminalId = serverTerminalId;
181181
knownShareIds.add(serverTerminalId);
182+
// Add to sharedTerminals so terminal-removed can clean it up
183+
addSharedTerminal(serverTerminalId, s.name);
182184
}
183185
}
184186
@@ -520,10 +522,13 @@ function resetSessions(count: number, splitIndex: number, savedPairsData?: [numb
520522
521523
function ensureSession() {
522524
if (sessions.value.length === 0) {
525+
// In share mode, if the server already has terminals they'll appear via the watcher.
526+
// Only create a new local session if there are genuinely no shared terminals yet.
527+
if (isSharing.value && sharedTerminals.value.length > 0) return;
523528
nextId = 1;
524529
epoch = Date.now();
525530
savedPairsMap.clear();
526-
sessions.value = createSessions(1);
531+
sessions.value = isSharing.value ? [{ id: `t${epoch}-${nextId++}`, name: 'Terminal 1' }] : createSessions(1);
527532
activeId.value = sessions.value[0]!.id;
528533
focusedId.value = activeId.value;
529534
splitId.value = null;

app/composables/useShare.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,13 @@ export function useShare() {
147147
}
148148
}
149149

150+
// --- Add a terminal to sharedTerminals (called by creator after terminal-ready) ---
151+
function addSharedTerminal(id: string, name: string): void {
152+
if (!sharedTerminals.value.find((t: any) => t.id === id)) {
153+
sharedTerminals.value = [...sharedTerminals.value, { id, name }];
154+
}
155+
}
156+
150157
// --- Host: refresh info ---
151158
async function refreshInfo(): Promise<void> {
152159
if (!shareId.value) return;
@@ -208,11 +215,14 @@ export function useShare() {
208215
allowTerminal.value = msg.allowTerminal;
209216
}
210217
break;
211-
case "terminal-added":
212-
if (msg.terminal && !sharedTerminals.value.find((t: any) => t.id === msg.terminal.id)) {
218+
case "terminal-added": {
219+
// Skip if we are the creator — TerminalPanel.onShareCreated will add it via addSharedTerminal
220+
const myId = guestId.value || "host";
221+
if (msg.terminal && msg.creatorId !== myId && !sharedTerminals.value.find((t: any) => t.id === msg.terminal.id)) {
213222
sharedTerminals.value = [...sharedTerminals.value, msg.terminal];
214223
}
215224
break;
225+
}
216226
case "terminal-removed":
217227
sharedTerminals.value = sharedTerminals.value.filter((t: any) => t.id !== msg.terminalId);
218228
break;
@@ -350,6 +360,7 @@ export function useShare() {
350360
updateSettings,
351361
refreshInfo,
352362
getShareTerminalWsUrl,
363+
addSharedTerminal,
353364
sendRelayMessage,
354365
cleanup,
355366
};

server/routes/_share-terminal.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,11 @@ export default defineWebSocketHandler({
6868
cols: data.cols || 80,
6969
rows: data.rows || 24,
7070
}));
71-
addActiveTerminal(auth.shareId, terminalId, name);
71+
addActiveTerminal(auth.shareId, terminalId, name, auth.guestId || "host");
7272
peer.send(JSON.stringify({ type: "terminal-ready", terminalId, name }));
7373
} else {
7474
// Direct mode: create SSH shell channel on this server
75-
createDirectTerminal(auth.shareId, session, terminalId, name, data, peer);
75+
createDirectTerminal(auth.shareId, session, terminalId, name, auth.guestId || "host", data, peer);
7676
}
7777
} else if (data.type === "subscribe") {
7878
const { terminalId } = data;
@@ -139,7 +139,7 @@ function cleanupPeer(peer: any): void {
139139
peerAuth.delete(peer.id);
140140
}
141141

142-
async function createDirectTerminal(shareId: string, session: any, terminalId: string, name: string, data: any, peer: any): Promise<void> {
142+
async function createDirectTerminal(shareId: string, session: any, terminalId: string, name: string, creatorId: string, data: any, peer: any): Promise<void> {
143143
if (!session.hostSessionId) {
144144
peer.send(JSON.stringify({ type: "output", terminalId, data: "\r\n\x1b[31m[No SSH session]\x1b[0m\r\n" }));
145145
return;
@@ -155,7 +155,7 @@ async function createDirectTerminal(shareId: string, session: any, terminalId: s
155155
}
156156

157157
directTerminals.set(terminalId, { shareId, sshConn: conn, stream });
158-
addActiveTerminal(shareId, terminalId, name);
158+
addActiveTerminal(shareId, terminalId, name, creatorId);
159159

160160
// Mute initial MOTD
161161
let muted = true;

server/utils/share.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,11 @@ export function updateShareRootPath(shareId: string, rootPath: string): void {
135135
session.rootPath = rootPath;
136136
}
137137

138-
export function addActiveTerminal(shareId: string, terminalId: string, name: string): void {
138+
export function addActiveTerminal(shareId: string, terminalId: string, name: string, creatorId?: string): void {
139139
const session = shares.get(shareId);
140140
if (!session) return;
141141
session.activeTerminals.set(terminalId, name);
142-
broadcastControl(shareId, { type: "terminal-added", terminal: { id: terminalId, name } });
142+
broadcastControl(shareId, { type: "terminal-added", terminal: { id: terminalId, name }, creatorId });
143143
}
144144

145145
export function removeActiveTerminal(shareId: string, terminalId: string): void {

0 commit comments

Comments
 (0)