The tab list lived only in per-tab sessionStorage, so a new browser tab, another device, or cleared storage showed an empty terminal sidebar while PTYs kept running server-side, and orphans leaked until the idle sweep. Add GET /api/terminal/sessions and adopt unknown server sessions into the local tab projection (additive only; a failed listing changes nothing). The idle sweep also reaped terminals in background tabs because only the active tab holds a WebSocket attachment. Add POST /api/terminal/touch and have open clients periodically refresh activity for every session their tabs reference.
88 lines
2.2 KiB
TypeScript
88 lines
2.2 KiB
TypeScript
import {
|
|
connectTerminalStream,
|
|
createTerminalSession,
|
|
resizeTerminal,
|
|
updateTerminalAppearance,
|
|
sendTerminalInput,
|
|
closeTerminal,
|
|
restartTerminalSession,
|
|
forceKillTerminal,
|
|
listTerminalShells,
|
|
listTerminalSessions,
|
|
touchTerminalSessions,
|
|
} from '@openchamber/ui/lib/terminalApi';
|
|
import type {
|
|
TerminalAPI,
|
|
TerminalHandlers,
|
|
CreateTerminalOptions,
|
|
ResizeTerminalPayload,
|
|
TerminalSession,
|
|
ForceKillOptions,
|
|
} from '@openchamber/ui/lib/api/types';
|
|
|
|
export const createWebTerminalAPI = (): TerminalAPI => ({
|
|
async listShells() {
|
|
return listTerminalShells();
|
|
},
|
|
|
|
async listSessions(cwd: string) {
|
|
return listTerminalSessions(cwd);
|
|
},
|
|
|
|
async touchSessions(sessionIds: string[]) {
|
|
await touchTerminalSessions(sessionIds);
|
|
},
|
|
|
|
async createSession(options: CreateTerminalOptions): Promise<TerminalSession> {
|
|
return createTerminalSession(options);
|
|
},
|
|
|
|
connect(sessionId: string, handlers: TerminalHandlers) {
|
|
const unsubscribe = connectTerminalStream(
|
|
sessionId,
|
|
handlers.onEvent,
|
|
handlers.onError
|
|
);
|
|
|
|
return {
|
|
close: () => unsubscribe(),
|
|
};
|
|
},
|
|
|
|
async sendInput(sessionId: string, input: string): Promise<void> {
|
|
await sendTerminalInput(sessionId, input);
|
|
},
|
|
|
|
async resize(payload: ResizeTerminalPayload): Promise<void> {
|
|
await resizeTerminal(payload.sessionId, payload.cols, payload.rows);
|
|
},
|
|
|
|
async updateAppearance(sessionId, appearance): Promise<void> {
|
|
await updateTerminalAppearance(sessionId, appearance);
|
|
},
|
|
|
|
async close(sessionId: string): Promise<void> {
|
|
await closeTerminal(sessionId);
|
|
},
|
|
|
|
async restartSession(
|
|
currentSessionId: string,
|
|
options: CreateTerminalOptions
|
|
): Promise<TerminalSession> {
|
|
return restartTerminalSession(currentSessionId, {
|
|
cwd: options.cwd ?? '',
|
|
cols: options.cols,
|
|
rows: options.rows,
|
|
themeMode: options.themeMode,
|
|
terminalBackground: options.terminalBackground,
|
|
terminalForeground: options.terminalForeground,
|
|
shell: options.shell,
|
|
loginShell: options.loginShell,
|
|
});
|
|
},
|
|
|
|
async forceKill(options: ForceKillOptions): Promise<void> {
|
|
await forceKillTerminal(options);
|
|
},
|
|
});
|