/** * Preload Script * Exposes safe APIs to the renderer process via contextBridge */ import { contextBridge, ipcRenderer } from 'electron'; /** * IPC renderer methods exposed to the renderer process */ const electronAPI = { /** * IPC invoke (request-response pattern) */ ipcRenderer: { invoke: (channel: string, ...args: unknown[]) => { const validChannels = [ // Gateway 'gateway:status', 'gateway:isConnected', 'gateway:start', 'gateway:stop', 'gateway:restart', 'gateway:rpc', 'gateway:httpProxy', 'hostapi:fetch', 'gateway:health', 'gateway:getControlUiUrl', // OpenClaw 'openclaw:status', 'openclaw:isReady', // Shell 'shell:openExternal', 'shell:showItemInFolder', 'shell:openPath', // Dialog 'dialog:open', 'dialog:save', 'dialog:message', // App 'app:version', 'app:name', 'app:getPath', 'app:platform', 'app:quit', 'app:relaunch', 'app:request', // Window controls 'window:minimize', 'window:maximize', 'window:close', 'window:isMaximized', // Settings 'settings:get', 'settings:set', 'settings:setMany', 'settings:getAll', 'settings:reset', 'usage:recentTokenHistory', // Update 'update:status', 'update:version', 'update:check', 'update:download', 'update:install', 'update:setChannel', 'update:setAutoDownload', 'update:cancelAutoInstall', // Env 'env:getConfig', 'env:setApiKey', 'env:deleteApiKey', // Provider 'provider:list', 'provider:get', 'provider:save', 'provider:delete', 'provider:setApiKey', 'provider:updateWithKey', 'provider:deleteApiKey', 'provider:hasApiKey', 'provider:getApiKey', 'provider:setDefault', 'provider:getDefault', 'provider:validateKey', 'provider:requestOAuth', 'provider:cancelOAuth', // Cron 'cron:list', 'cron:create', 'cron:update', 'cron:delete', 'cron:toggle', 'cron:trigger', // Channel Config 'channel:saveConfig', 'channel:getConfig', 'channel:getFormValues', 'channel:deleteConfig', 'channel:listConfigured', 'channel:setEnabled', 'channel:validate', 'channel:validate', 'channel:validateCredentials', // WhatsApp 'channel:requestWhatsAppQr', 'channel:cancelWhatsAppQr', // ClawHub 'clawhub:search', 'clawhub:install', 'clawhub:uninstall', 'clawhub:list', 'clawhub:openSkillReadme', // UV 'uv:check', 'uv:install-all', // Skill config (direct file access) 'skill:updateConfig', 'skill:getConfig', 'skill:getAllConfigs', // Logs 'log:getRecent', 'log:readFile', 'log:getFilePath', 'log:getDir', 'log:listFiles', // File staging & media 'file:stage', 'file:stageBuffer', 'media:getThumbnails', 'media:saveImage', // Chat send with media (reads staged files in main process) 'chat:sendWithMedia', // Session management 'session:delete', // OpenClaw extras 'openclaw:getDir', 'openclaw:getConfigDir', 'openclaw:getSkillsDir', 'openclaw:getCliCommand', ]; if (validChannels.includes(channel)) { return ipcRenderer.invoke(channel, ...args); } throw new Error(`Invalid IPC channel: ${channel}`); }, /** * Listen for events from main process */ on: (channel: string, callback: (...args: unknown[]) => void) => { const validChannels = [ 'gateway:status-changed', 'gateway:message', 'gateway:notification', 'gateway:channel-status', 'gateway:chat-message', 'channel:whatsapp-qr', 'channel:whatsapp-success', 'channel:whatsapp-error', 'gateway:exit', 'gateway:error', 'navigate', 'update:status-changed', 'update:checking', 'update:available', 'update:not-available', 'update:progress', 'update:downloaded', 'update:error', 'update:auto-install-countdown', 'cron:updated', 'oauth:code', 'oauth:success', 'oauth:error', 'openclaw:cli-installed', ]; if (validChannels.includes(channel)) { // Wrap the callback to strip the event const subscription = (_event: Electron.IpcRendererEvent, ...args: unknown[]) => { callback(...args); }; ipcRenderer.on(channel, subscription); // Return unsubscribe function return () => { ipcRenderer.removeListener(channel, subscription); }; } throw new Error(`Invalid IPC channel: ${channel}`); }, /** * Listen for a single event from main process */ once: (channel: string, callback: (...args: unknown[]) => void) => { const validChannels = [ 'gateway:status-changed', 'gateway:message', 'gateway:notification', 'gateway:channel-status', 'gateway:chat-message', 'gateway:exit', 'gateway:error', 'navigate', 'update:status-changed', 'update:checking', 'update:available', 'update:not-available', 'update:progress', 'update:downloaded', 'update:error', 'update:auto-install-countdown', 'oauth:code', 'oauth:success', 'oauth:error', ]; if (validChannels.includes(channel)) { ipcRenderer.once(channel, (_event, ...args) => callback(...args)); return; } throw new Error(`Invalid IPC channel: ${channel}`); }, /** * Remove all listeners for a channel */ off: (channel: string, callback?: (...args: unknown[]) => void) => { if (callback) { // eslint-disable-next-line @typescript-eslint/no-explicit-any ipcRenderer.removeListener(channel, callback as any); } else { ipcRenderer.removeAllListeners(channel); } }, }, /** * Open external URL in default browser */ openExternal: (url: string) => { return ipcRenderer.invoke('shell:openExternal', url); }, /** * Get current platform */ platform: process.platform, /** * Check if running in development */ isDev: process.env.NODE_ENV === 'development' || !!process.env.VITE_DEV_SERVER_URL, }; // Expose the API to the renderer process contextBridge.exposeInMainWorld('electron', electronAPI); // Type declarations for the renderer process export type ElectronAPI = typeof electronAPI;