Repository Public
1/**2 * Registry of live terminal controllers. It deliberately has no runtime dependency on xterm so3 * that light components (toolbar, switcher, tabs) can talk to terminals without loading it.4 * Controllers outlive React views: a terminal keeps its WebSocket and buffer while its tab is5 * hidden or while another page is shown, and is re-parented into the view when it comes back.6 */7import type { TerminalController } from './controller';89const controllers = new Map<string, TerminalController>();1011export const getController = (id: string | null | undefined): TerminalController | undefined => (id ? controllers.get(id) : undefined);12export const allControllers = (): TerminalController[] => [...controllers.values()];13export function registerController(c: TerminalController): void {14 controllers.set(c.id, c);15 installGlobalListeners();16}17export function disposeController(id: string): void {18 controllers.get(id)?.dispose();19 controllers.delete(id);20}21export function disposeAllControllers(): void {22 for (const id of [...controllers.keys()]) disposeController(id);23}2425let installed = false;26function installGlobalListeners(): void {27 if (installed || typeof window === 'undefined') return;28 installed = true;29 const wake = () => {30 if (document.visibilityState !== 'visible') return;31 for (const c of controllers.values()) c.wake();32 };33 document.addEventListener('visibilitychange', wake);34 window.addEventListener('online', wake);35 window.addEventListener('pageshow', wake);36 window.addEventListener('offline', () => {37 for (const c of controllers.values()) c.wentOffline();38 });39 window.addEventListener('orientationchange', () => {40 setTimeout(() => {41 for (const c of controllers.values()) c.fit();42 }, 250);43 });4445 if (process.env.NEXT_PUBLIC_E2E === '1') {46 // Test hook (Playwright). Compiled out of production builds.47 (window as unknown as { __crTerm: unknown }).__crTerm = {48 ids: () => [...controllers.keys()],49 text: (id: string) => controllers.get(id)?.bufferText() ?? null,50 status: (id: string) => controllers.get(id)?.status ?? null,51 send: (id: string, data: string) => controllers.get(id)?.send(data),52 dropSocket: (id: string) => controllers.get(id)?.debugDropSocket(),53 sent: (id: string) => controllers.get(id)?.debugSent ?? null,54 size: (id: string) => {55 const c = controllers.get(id);56 return c ? { cols: c.term.cols, rows: c.term.rows, fontSize: c.term.options.fontSize } : null;57 },58 hasFocus: (id: string) => !!controllers.get(id)?.el.contains(document.activeElement),59 scroll: (id: string) => {60 const b = controllers.get(id)?.term.buffer.active;61 return b ? { viewportY: b.viewportY, baseY: b.baseY, type: b.type } : null;62 },63 };64 }65}66