Skip to content

Commit f30c80a

Browse files
committed
fix: terminals for share
1 parent 3aa43fe commit f30c80a

1 file changed

Lines changed: 89 additions & 46 deletions

File tree

app/pages/index.vue

Lines changed: 89 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -630,9 +630,9 @@ function onShareLeft() {
630630
}
631631
632632
function onShareStopped() {
633-
// Host stopped sharing: close relay terminal WebSockets
634-
for (const ws of relayTerminalWs.values()) ws.close();
635-
relayTerminalWs.clear();
633+
// Host stopped sharing: close all relay terminals
634+
for (const handle of relayTerminals.values()) handle.close();
635+
relayTerminals.clear();
636636
}
637637
638638
async function autoJoinShare(sid: string) {
@@ -651,71 +651,114 @@ async function autoJoinShare(sid: string) {
651651
652652
// Register callbacks for share lifecycle + relay terminal handling
653653
import { setOnShareClosed, setOnRelayTerminalCreate, setOnRelayTerminalInput, setOnRelayTerminalResize, setOnRelayTerminalClose } from '~/composables/useShare';
654-
// Map of relay terminalId → local WebSocket (desktop host bridges guest terminals to local PTY)
655-
const relayTerminalWs = new Map<string, WebSocket>();
654+
655+
// Unified relay terminal handle — either IPC (Electron) or WebSocket (web/SSH)
656+
interface RelayTerminalHandle {
657+
sendInput(data: string): void;
658+
sendResize(cols: number, rows: number): void;
659+
close(): void;
660+
}
661+
const relayTerminals = new Map<string, RelayTerminalHandle>();
656662
657663
if (import.meta.client) {
658664
setOnShareClosed(() => {
659665
resetToFolderSelector();
660666
});
661667
662-
// Desktop host relay: guest creates a terminal → spawn local terminal, pipe I/O through relay WS
668+
// Desktop host relay: guest creates a terminal → spawn local terminal, pipe I/O back through relay WS
663669
setOnRelayTerminalCreate((msg: any) => {
664670
const { terminalId, cwd, cols, rows } = msg;
665671
const { getLocalWsUrl, getSessionId } = useApi();
666672
const { sendRelayMessage } = useShare();
667-
// Connect to the local terminal WS (bypasses share routing to avoid loop)
668-
const localWs = new WebSocket(getLocalWsUrl());
669-
relayTerminalWs.set(terminalId, localWs);
670-
671-
localWs.onopen = () => {
672-
localWs.send(JSON.stringify({
673-
type: "create",
674-
cwd: cwd || rootPath.value,
673+
const electronTerminal = (window as any).electronTerminal as {
674+
create: (opts: any) => Promise<{ ok: boolean; error?: string }>;
675+
write: (id: string, data: string) => void;
676+
resize: (id: string, cols: number, rows: number) => void;
677+
kill: (id: string) => void;
678+
onData: (cb: (p: { id: string; data: string }) => void) => () => void;
679+
onExit: (cb: (p: { id: string; code: number }) => void) => () => void;
680+
} | undefined;
681+
682+
if (electronTerminal) {
683+
// Electron: use IPC to main process (node-pty runs there, not in Nuxt subprocess)
684+
const ipcId = `relay-${terminalId}`;
685+
const offData = electronTerminal.onData(({ id, data }: { id: string; data: string }) => {
686+
if (id === ipcId) sendRelayMessage({ type: "terminal-output", terminalId, data });
687+
});
688+
const offExit = electronTerminal.onExit(({ id, code }: { id: string; code: number }) => {
689+
if (id !== ipcId) return;
690+
sendRelayMessage({ type: "terminal-exit", terminalId, code });
691+
offData();
692+
offExit();
693+
relayTerminals.delete(terminalId);
694+
});
695+
electronTerminal.create({
696+
id: ipcId,
675697
cols: cols || 80,
676698
rows: rows || 24,
677-
sessionId: getSessionId(),
678-
}));
679-
};
680-
681-
localWs.onmessage = (ev) => {
682-
try {
683-
const data = JSON.parse(ev.data);
684-
if (data.type === "output") {
685-
sendRelayMessage({ type: "terminal-output", terminalId, data: data.data });
686-
} else if (data.type === "exit") {
687-
sendRelayMessage({ type: "terminal-exit", terminalId, code: data.code ?? 0 });
688-
localWs.close();
689-
relayTerminalWs.delete(terminalId);
699+
cwd: cwd || rootPath.value,
700+
}).then((result: { ok: boolean; error?: string }) => {
701+
if (!result.ok) {
702+
sendRelayMessage({ type: "terminal-output", terminalId, data: `\r\n\x1b[31m[Terminal error: ${result.error}]\x1b[0m\r\n` });
703+
offData();
704+
offExit();
705+
relayTerminals.delete(terminalId);
690706
}
691-
} catch {}
692-
};
693-
694-
localWs.onclose = () => {
695-
relayTerminalWs.delete(terminalId);
696-
};
707+
});
708+
relayTerminals.set(terminalId, {
709+
sendInput(data) { electronTerminal.write(ipcId, data); },
710+
sendResize(c, r) { electronTerminal.resize(ipcId, c, r); },
711+
close() { electronTerminal.kill(ipcId); offData(); offExit(); relayTerminals.delete(terminalId); },
712+
});
713+
} else {
714+
// Web/SSH: use WebSocket to local terminal server
715+
const localWs = new WebSocket(getLocalWsUrl());
716+
localWs.onopen = () => {
717+
localWs.send(JSON.stringify({
718+
type: "create",
719+
cwd: cwd || rootPath.value,
720+
cols: cols || 80,
721+
rows: rows || 24,
722+
sessionId: getSessionId(),
723+
}));
724+
};
725+
localWs.onmessage = (ev) => {
726+
try {
727+
const data = JSON.parse(ev.data);
728+
if (data.type === "output") {
729+
sendRelayMessage({ type: "terminal-output", terminalId, data: data.data });
730+
} else if (data.type === "exit") {
731+
sendRelayMessage({ type: "terminal-exit", terminalId, code: data.code ?? 0 });
732+
localWs.close();
733+
relayTerminals.delete(terminalId);
734+
}
735+
} catch {}
736+
};
737+
localWs.onclose = () => { relayTerminals.delete(terminalId); };
738+
relayTerminals.set(terminalId, {
739+
sendInput(data) {
740+
if (localWs.readyState === WebSocket.OPEN)
741+
localWs.send(JSON.stringify({ type: "input", data }));
742+
},
743+
sendResize(c, r) {
744+
if (localWs.readyState === WebSocket.OPEN)
745+
localWs.send(JSON.stringify({ type: "resize", cols: c, rows: r }));
746+
},
747+
close() { localWs.close(); relayTerminals.delete(terminalId); },
748+
});
749+
}
697750
});
698751
699752
setOnRelayTerminalInput((msg: any) => {
700-
const ws = relayTerminalWs.get(msg.terminalId);
701-
if (ws && ws.readyState === WebSocket.OPEN) {
702-
ws.send(JSON.stringify({ type: "input", data: msg.data }));
703-
}
753+
relayTerminals.get(msg.terminalId)?.sendInput(msg.data);
704754
});
705755
706756
setOnRelayTerminalResize((msg: any) => {
707-
const ws = relayTerminalWs.get(msg.terminalId);
708-
if (ws && ws.readyState === WebSocket.OPEN) {
709-
ws.send(JSON.stringify({ type: "resize", cols: msg.cols, rows: msg.rows }));
710-
}
757+
relayTerminals.get(msg.terminalId)?.sendResize(msg.cols, msg.rows);
711758
});
712759
713760
setOnRelayTerminalClose((msg: any) => {
714-
const ws = relayTerminalWs.get(msg.terminalId);
715-
if (ws) {
716-
ws.close();
717-
relayTerminalWs.delete(msg.terminalId);
718-
}
761+
relayTerminals.get(msg.terminalId)?.close();
719762
});
720763
}
721764

0 commit comments

Comments
 (0)