import Electron, { contextBridge, ipcRenderer, webUtils } from 'electron'; import { Recipe } from './recipe'; import { GooseApp } from './api'; interface NotificationData { title: string; body: string; } interface MessageBoxOptions { type?: 'none' | 'info' | 'error' | 'question' | 'warning'; buttons?: string[]; defaultId?: number; title?: string; message: string; detail?: string; } interface MessageBoxResponse { response: number; checkboxChecked?: boolean; } interface SaveDialogOptions { title?: string; defaultPath?: string; buttonLabel?: string; filters?: Array<{ name: string; extensions: string[] }>; message?: string; nameFieldLabel?: string; showsTagField?: boolean; } interface SaveDialogResponse { canceled: boolean; filePath?: string; } interface FileResponse { file: string; filePath: string; error: string | null; found: boolean; } interface SaveDataUrlResponse { id: string; filePath?: string; error?: string; } const config = JSON.parse(process.argv.find((arg) => arg.startsWith('{')) || '{}'); interface UpdaterEvent { event: string; data?: unknown; } // Define the API types in a single place type ElectronAPI = { platform: string; reactReady: () => void; getConfig: () => Record; hideWindow: () => void; directoryChooser: () => Promise; createChatWindow: ( query?: string, dir?: string, version?: string, resumeSessionId?: string, viewType?: string, recipeId?: string ) => void; logInfo: (txt: string) => void; showNotification: (data: NotificationData) => void; showMessageBox: (options: MessageBoxOptions) => Promise; showSaveDialog: (options: SaveDialogOptions) => Promise; openInChrome: (url: string) => void; fetchMetadata: (url: string) => Promise; reloadApp: () => void; checkForOllama: () => Promise; selectFileOrDirectory: (defaultPath?: string) => Promise; getBinaryPath: (binaryName: string) => Promise; readFile: (directory: string) => Promise; writeFile: (directory: string, content: string) => Promise; ensureDirectory: (dirPath: string) => Promise; listFiles: (dirPath: string, extension?: string) => Promise; getAllowedExtensions: () => Promise; getPathForFile: (file: File) => string; setMenuBarIcon: (show: boolean) => Promise; getMenuBarIconState: () => Promise; setDockIcon: (show: boolean) => Promise; getDockIconState: () => Promise; getSettings: () => Promise; saveSettings: (settings: unknown) => Promise; getSecretKey: () => Promise; getGoosedHostPort: () => Promise; setWakelock: (enable: boolean) => Promise; getWakelockState: () => Promise; setSpellcheck: (enable: boolean) => Promise; getSpellcheckState: () => Promise; openNotificationsSettings: () => Promise; onMouseBackButtonClicked: (callback: () => void) => void; offMouseBackButtonClicked: (callback: () => void) => void; on: ( channel: string, callback: (event: Electron.IpcRendererEvent, ...args: unknown[]) => void ) => void; off: ( channel: string, callback: (event: Electron.IpcRendererEvent, ...args: unknown[]) => void ) => void; emit: (channel: string, ...args: unknown[]) => void; broadcastThemeChange: (themeData: { mode: string; useSystemTheme: boolean; theme: string; }) => void; // Functions for image pasting saveDataUrlToTemp: (dataUrl: string, uniqueId: string) => Promise; deleteTempFile: (filePath: string) => void; // Function for opening external URLs securely openExternal: (url: string) => Promise; // Update-related functions getVersion: () => string; checkForUpdates: () => Promise<{ updateInfo: unknown; error: string | null }>; downloadUpdate: () => Promise<{ success: boolean; error: string | null }>; installUpdate: () => void; restartApp: () => void; onUpdaterEvent: (callback: (event: UpdaterEvent) => void) => void; getUpdateState: () => Promise<{ updateAvailable: boolean; latestVersion?: string } | null>; isUsingGitHubFallback: () => Promise; // Recipe warning functions closeWindow: () => void; hasAcceptedRecipeBefore: (recipe: Recipe) => Promise; recordRecipeHash: (recipe: Recipe) => Promise; openDirectoryInExplorer: (directoryPath: string) => Promise; launchApp: (app: GooseApp) => Promise; refreshApp: (app: GooseApp) => Promise; closeApp: (appName: string) => Promise; addRecentDir: (dir: string) => Promise; }; type AppConfigAPI = { get: (key: string) => unknown; getAll: () => Record; }; const electronAPI: ElectronAPI = { platform: process.platform, reactReady: () => ipcRenderer.send('react-ready'), getConfig: () => { if (!config || Object.keys(config).length === 0) { console.warn( 'No config provided by main process. This may indicate an initialization issue.' ); } return config; }, hideWindow: () => ipcRenderer.send('hide-window'), directoryChooser: () => ipcRenderer.invoke('directory-chooser'), createChatWindow: ( query?: string, dir?: string, version?: string, resumeSessionId?: string, viewType?: string, recipeId?: string ) => ipcRenderer.send( 'create-chat-window', query, dir, version, resumeSessionId, viewType, recipeId ), logInfo: (txt: string) => ipcRenderer.send('logInfo', txt), showNotification: (data: NotificationData) => ipcRenderer.send('notify', data), showMessageBox: (options: MessageBoxOptions) => ipcRenderer.invoke('show-message-box', options), showSaveDialog: (options: SaveDialogOptions) => ipcRenderer.invoke('show-save-dialog', options), openInChrome: (url: string) => ipcRenderer.send('open-in-chrome', url), fetchMetadata: (url: string) => ipcRenderer.invoke('fetch-metadata', url), reloadApp: () => ipcRenderer.send('reload-app'), checkForOllama: () => ipcRenderer.invoke('check-ollama'), selectFileOrDirectory: (defaultPath?: string) => ipcRenderer.invoke('select-file-or-directory', defaultPath), getBinaryPath: (binaryName: string) => ipcRenderer.invoke('get-binary-path', binaryName), readFile: (filePath: string) => ipcRenderer.invoke('read-file', filePath), writeFile: (filePath: string, content: string) => ipcRenderer.invoke('write-file', filePath, content), ensureDirectory: (dirPath: string) => ipcRenderer.invoke('ensure-directory', dirPath), listFiles: (dirPath: string, extension?: string) => ipcRenderer.invoke('list-files', dirPath, extension), getPathForFile: (file: File) => webUtils.getPathForFile(file), getAllowedExtensions: () => ipcRenderer.invoke('get-allowed-extensions'), setMenuBarIcon: (show: boolean) => ipcRenderer.invoke('set-menu-bar-icon', show), getMenuBarIconState: () => ipcRenderer.invoke('get-menu-bar-icon-state'), setDockIcon: (show: boolean) => ipcRenderer.invoke('set-dock-icon', show), getDockIconState: () => ipcRenderer.invoke('get-dock-icon-state'), getSettings: () => ipcRenderer.invoke('get-settings'), saveSettings: (settings: unknown) => ipcRenderer.invoke('save-settings', settings), getSecretKey: () => ipcRenderer.invoke('get-secret-key'), getGoosedHostPort: () => ipcRenderer.invoke('get-goosed-host-port'), setWakelock: (enable: boolean) => ipcRenderer.invoke('set-wakelock', enable), getWakelockState: () => ipcRenderer.invoke('get-wakelock-state'), setSpellcheck: (enable: boolean) => ipcRenderer.invoke('set-spellcheck', enable), getSpellcheckState: () => ipcRenderer.invoke('get-spellcheck-state'), openNotificationsSettings: () => ipcRenderer.invoke('open-notifications-settings'), onMouseBackButtonClicked: (callback: () => void) => { // Wrapper that ignores the event parameter. const wrappedCallback = (_event: Electron.IpcRendererEvent) => callback(); ipcRenderer.on('mouse-back-button-clicked', wrappedCallback); return wrappedCallback; }, offMouseBackButtonClicked: (callback: () => void) => { ipcRenderer.removeListener('mouse-back-button-clicked', callback); }, on: ( channel: string, callback: (event: Electron.IpcRendererEvent, ...args: unknown[]) => void ) => { ipcRenderer.on(channel, callback); }, off: ( channel: string, callback: (event: Electron.IpcRendererEvent, ...args: unknown[]) => void ) => { ipcRenderer.off(channel, callback); }, emit: (channel: string, ...args: unknown[]) => { ipcRenderer.emit(channel, ...args); }, broadcastThemeChange: (themeData: { mode: string; useSystemTheme: boolean; theme: string }) => { ipcRenderer.send('broadcast-theme-change', themeData); }, saveDataUrlToTemp: (dataUrl: string, uniqueId: string): Promise => { return ipcRenderer.invoke('save-data-url-to-temp', dataUrl, uniqueId); }, deleteTempFile: (filePath: string): void => { ipcRenderer.send('delete-temp-file', filePath); }, openExternal: (url: string): Promise => { return ipcRenderer.invoke('open-external', url); }, getVersion: (): string => { return config.GOOSE_VERSION || ipcRenderer.sendSync('get-app-version') || ''; }, checkForUpdates: (): Promise<{ updateInfo: unknown; error: string | null }> => { return ipcRenderer.invoke('check-for-updates'); }, downloadUpdate: (): Promise<{ success: boolean; error: string | null }> => { return ipcRenderer.invoke('download-update'); }, installUpdate: (): void => { ipcRenderer.invoke('install-update'); }, restartApp: (): void => { ipcRenderer.send('restart-app'); }, onUpdaterEvent: (callback: (event: UpdaterEvent) => void): void => { ipcRenderer.on('updater-event', (_event, data) => callback(data)); }, getUpdateState: (): Promise<{ updateAvailable: boolean; latestVersion?: string } | null> => { return ipcRenderer.invoke('get-update-state'); }, isUsingGitHubFallback: (): Promise => { return ipcRenderer.invoke('is-using-github-fallback'); }, closeWindow: () => ipcRenderer.send('close-window'), hasAcceptedRecipeBefore: (recipe: Recipe) => ipcRenderer.invoke('has-accepted-recipe-before', recipe), recordRecipeHash: (recipe: Recipe) => ipcRenderer.invoke('record-recipe-hash', recipe), openDirectoryInExplorer: (directoryPath: string) => ipcRenderer.invoke('open-directory-in-explorer', directoryPath), launchApp: (app: GooseApp) => ipcRenderer.invoke('launch-app', app), refreshApp: (app: GooseApp) => ipcRenderer.invoke('refresh-app', app), closeApp: (appName: string) => ipcRenderer.invoke('close-app', appName), addRecentDir: (dir: string) => ipcRenderer.invoke('add-recent-dir', dir), }; const appConfigAPI: AppConfigAPI = { get: (key: string) => config[key], getAll: () => config, }; // Expose the APIs contextBridge.exposeInMainWorld('electron', electronAPI); contextBridge.exposeInMainWorld('appConfig', appConfigAPI); // Type declaration for TypeScript declare global { interface Window { electron: ElectronAPI; appConfig: AppConfigAPI; } }