import { contextBridge, ipcRenderer } from 'electron'; import { Command, Query } from './cqrs/types'; const LINUX_SCREEN_SHARE_MONITOR_AUDIO_CHUNK_CHANNEL = 'linux-screen-share-monitor-audio-chunk'; const LINUX_SCREEN_SHARE_MONITOR_AUDIO_ENDED_CHANNEL = 'linux-screen-share-monitor-audio-ended'; const AUTO_UPDATE_STATE_CHANGED_CHANNEL = 'auto-update-state-changed'; const DEEP_LINK_RECEIVED_CHANNEL = 'deep-link-received'; const WINDOW_STATE_CHANGED_CHANNEL = 'window-state-changed'; export interface LinuxScreenShareAudioRoutingInfo { available: boolean; active: boolean; monitorCaptureSupported: boolean; screenShareSinkName: string; screenShareMonitorSourceName: string; voiceSinkName: string; reason?: string; } export interface LinuxScreenShareMonitorCaptureInfo { bitsPerSample: number; captureId: string; channelCount: number; sampleRate: number; sourceName: string; } export interface LinuxScreenShareMonitorAudioChunkPayload { captureId: string; chunk: Uint8Array; } export interface LinuxScreenShareMonitorAudioEndedPayload { captureId: string; reason?: string; } export interface ClipboardFilePayload { data: string; lastModified: number; mime: string; name: string; path?: string; } export type DesktopUpdateServerVersionStatus = 'unknown' | 'reported' | 'missing' | 'unavailable'; export interface DesktopUpdateServerContext { manifestUrls: string[]; serverVersion: string | null; serverVersionStatus: DesktopUpdateServerVersionStatus; } export interface DesktopUpdateServerHealthSnapshot { manifestUrl: string | null; serverVersion: string | null; serverVersionStatus: DesktopUpdateServerVersionStatus; } export interface DesktopUpdateState { autoUpdateMode: 'auto' | 'off' | 'version'; availableVersions: string[]; configuredManifestUrls: string[]; currentVersion: string; defaultManifestUrls: string[]; isSupported: boolean; lastCheckedAt: number | null; latestVersion: string | null; manifestUrl: string | null; manifestUrls: string[]; minimumServerVersion: string | null; preferredVersion: string | null; restartRequired: boolean; serverBlocked: boolean; serverBlockMessage: string | null; serverVersion: string | null; serverVersionStatus: DesktopUpdateServerVersionStatus; status: | 'idle' | 'disabled' | 'checking' | 'downloading' | 'up-to-date' | 'restart-required' | 'unsupported' | 'no-manifest' | 'target-unavailable' | 'target-older-than-installed' | 'error'; statusMessage: string | null; targetVersion: string | null; } export interface DesktopNotificationPayload { body: string; requestAttention: boolean; title: string; } export interface WindowStateSnapshot { isFocused: boolean; isMinimized: boolean; } function readLinuxDisplayServer(): string { if (process.platform !== 'linux') { return 'N/A'; } try { const displayServer = ipcRenderer.sendSync('get-linux-display-server'); return typeof displayServer === 'string' && displayServer.trim().length > 0 ? displayServer : 'Unknown (Linux)'; } catch { return 'Unknown (Linux)'; } } export interface ElectronAPI { linuxDisplayServer: string; minimizeWindow: () => void; maximizeWindow: () => void; closeWindow: () => void; openExternal: (url: string) => Promise; getSources: () => Promise<{ id: string; name: string; thumbnail: string }[]>; prepareLinuxScreenShareAudioRouting: () => Promise; activateLinuxScreenShareAudioRouting: () => Promise; deactivateLinuxScreenShareAudioRouting: () => Promise; startLinuxScreenShareMonitorCapture: () => Promise; stopLinuxScreenShareMonitorCapture: (captureId?: string) => Promise; onLinuxScreenShareMonitorAudioChunk: (listener: (payload: LinuxScreenShareMonitorAudioChunkPayload) => void) => () => void; onLinuxScreenShareMonitorAudioEnded: (listener: (payload: LinuxScreenShareMonitorAudioEndedPayload) => void) => () => void; getAppDataPath: () => Promise; consumePendingDeepLink: () => Promise; getDesktopSettings: () => Promise<{ autoUpdateMode: 'auto' | 'off' | 'version'; autoStart: boolean; closeToTray: boolean; hardwareAcceleration: boolean; manifestUrls: string[]; preferredVersion: string | null; runtimeHardwareAcceleration: boolean; restartRequired: boolean; }>; showDesktopNotification: (payload: DesktopNotificationPayload) => Promise; requestWindowAttention: () => Promise; clearWindowAttention: () => Promise; onWindowStateChanged: (listener: (state: WindowStateSnapshot) => void) => () => void; getAutoUpdateState: () => Promise; getAutoUpdateServerHealth: (serverUrl: string) => Promise; configureAutoUpdateContext: (context: Partial) => Promise; checkForAppUpdates: () => Promise; restartToApplyUpdate: () => Promise; onAutoUpdateStateChanged: (listener: (state: DesktopUpdateState) => void) => () => void; setDesktopSettings: (patch: { autoUpdateMode?: 'auto' | 'off' | 'version'; autoStart?: boolean; closeToTray?: boolean; hardwareAcceleration?: boolean; manifestUrls?: string[]; preferredVersion?: string | null; vaapiVideoEncode?: boolean; }) => Promise<{ autoUpdateMode: 'auto' | 'off' | 'version'; autoStart: boolean; closeToTray: boolean; hardwareAcceleration: boolean; manifestUrls: string[]; preferredVersion: string | null; runtimeHardwareAcceleration: boolean; restartRequired: boolean; }>; relaunchApp: () => Promise; onDeepLinkReceived: (listener: (url: string) => void) => () => void; readClipboardFiles: () => Promise; readFile: (filePath: string) => Promise; writeFile: (filePath: string, data: string) => Promise; saveFileAs: (defaultFileName: string, data: string) => Promise<{ saved: boolean; cancelled: boolean }>; fileExists: (filePath: string) => Promise; deleteFile: (filePath: string) => Promise; ensureDir: (dirPath: string) => Promise; command: (command: Command) => Promise; query: (query: Query) => Promise; } const electronAPI: ElectronAPI = { linuxDisplayServer: readLinuxDisplayServer(), minimizeWindow: () => ipcRenderer.send('window-minimize'), maximizeWindow: () => ipcRenderer.send('window-maximize'), closeWindow: () => ipcRenderer.send('window-close'), openExternal: (url) => ipcRenderer.invoke('open-external', url), getSources: () => ipcRenderer.invoke('get-sources'), prepareLinuxScreenShareAudioRouting: () => ipcRenderer.invoke('prepare-linux-screen-share-audio-routing'), activateLinuxScreenShareAudioRouting: () => ipcRenderer.invoke('activate-linux-screen-share-audio-routing'), deactivateLinuxScreenShareAudioRouting: () => ipcRenderer.invoke('deactivate-linux-screen-share-audio-routing'), startLinuxScreenShareMonitorCapture: () => ipcRenderer.invoke('start-linux-screen-share-monitor-capture'), stopLinuxScreenShareMonitorCapture: (captureId) => ipcRenderer.invoke('stop-linux-screen-share-monitor-capture', captureId), onLinuxScreenShareMonitorAudioChunk: (listener) => { const wrappedListener = (_event: Electron.IpcRendererEvent, payload: LinuxScreenShareMonitorAudioChunkPayload) => { const chunk = payload.chunk instanceof Uint8Array ? payload.chunk : Uint8Array.from((payload as { chunk?: Iterable }).chunk || []); listener({ ...payload, chunk }); }; ipcRenderer.on(LINUX_SCREEN_SHARE_MONITOR_AUDIO_CHUNK_CHANNEL, wrappedListener); return () => { ipcRenderer.removeListener(LINUX_SCREEN_SHARE_MONITOR_AUDIO_CHUNK_CHANNEL, wrappedListener); }; }, onLinuxScreenShareMonitorAudioEnded: (listener) => { const wrappedListener = (_event: Electron.IpcRendererEvent, payload: LinuxScreenShareMonitorAudioEndedPayload) => { listener(payload); }; ipcRenderer.on(LINUX_SCREEN_SHARE_MONITOR_AUDIO_ENDED_CHANNEL, wrappedListener); return () => { ipcRenderer.removeListener(LINUX_SCREEN_SHARE_MONITOR_AUDIO_ENDED_CHANNEL, wrappedListener); }; }, getAppDataPath: () => ipcRenderer.invoke('get-app-data-path'), consumePendingDeepLink: () => ipcRenderer.invoke('consume-pending-deep-link'), getDesktopSettings: () => ipcRenderer.invoke('get-desktop-settings'), showDesktopNotification: (payload) => ipcRenderer.invoke('show-desktop-notification', payload), requestWindowAttention: () => ipcRenderer.invoke('request-window-attention'), clearWindowAttention: () => ipcRenderer.invoke('clear-window-attention'), onWindowStateChanged: (listener) => { const wrappedListener = (_event: Electron.IpcRendererEvent, state: WindowStateSnapshot) => { listener(state); }; ipcRenderer.on(WINDOW_STATE_CHANGED_CHANNEL, wrappedListener); return () => { ipcRenderer.removeListener(WINDOW_STATE_CHANGED_CHANNEL, wrappedListener); }; }, getAutoUpdateState: () => ipcRenderer.invoke('get-auto-update-state'), getAutoUpdateServerHealth: (serverUrl) => ipcRenderer.invoke('get-auto-update-server-health', serverUrl), configureAutoUpdateContext: (context) => ipcRenderer.invoke('configure-auto-update-context', context), checkForAppUpdates: () => ipcRenderer.invoke('check-for-app-updates'), restartToApplyUpdate: () => ipcRenderer.invoke('restart-to-apply-update'), onAutoUpdateStateChanged: (listener) => { const wrappedListener = (_event: Electron.IpcRendererEvent, state: DesktopUpdateState) => { listener(state); }; ipcRenderer.on(AUTO_UPDATE_STATE_CHANGED_CHANNEL, wrappedListener); return () => { ipcRenderer.removeListener(AUTO_UPDATE_STATE_CHANGED_CHANNEL, wrappedListener); }; }, setDesktopSettings: (patch) => ipcRenderer.invoke('set-desktop-settings', patch), relaunchApp: () => ipcRenderer.invoke('relaunch-app'), onDeepLinkReceived: (listener) => { const wrappedListener = (_event: Electron.IpcRendererEvent, url: string) => { listener(url); }; ipcRenderer.on(DEEP_LINK_RECEIVED_CHANNEL, wrappedListener); return () => { ipcRenderer.removeListener(DEEP_LINK_RECEIVED_CHANNEL, wrappedListener); }; }, readClipboardFiles: () => ipcRenderer.invoke('read-clipboard-files'), readFile: (filePath) => ipcRenderer.invoke('read-file', filePath), writeFile: (filePath, data) => ipcRenderer.invoke('write-file', filePath, data), saveFileAs: (defaultFileName, data) => ipcRenderer.invoke('save-file-as', defaultFileName, data), fileExists: (filePath) => ipcRenderer.invoke('file-exists', filePath), deleteFile: (filePath) => ipcRenderer.invoke('delete-file', filePath), ensureDir: (dirPath) => ipcRenderer.invoke('ensure-dir', dirPath), command: (command) => ipcRenderer.invoke('cqrs:command', command), query: (query) => ipcRenderer.invoke('cqrs:query', query) }; contextBridge.exposeInMainWorld('electronAPI', electronAPI); declare global { interface Window { electronAPI: ElectronAPI; } }