import { contextBridge, ipcRenderer } from 'electron' import { IPC } from '@shared/ipc' import type { AppState, Challenge, Exercise, GameId, GameStatus, HistoryEntry, MatchSummary, Settings, Tick, UpdaterStatus } from '@shared/types' type Unsub = () => void type Handler = (payload: T) => void function on(channel: string, handler: Handler): Unsub { const listener = (_e: Electron.IpcRendererEvent, payload: T): void => handler(payload) ipcRenderer.on(channel, listener) return () => ipcRenderer.removeListener(channel, listener) } const api = { getState: (): Promise => ipcRenderer.invoke(IPC.getState), addExercise: ( input: Omit ): Promise => ipcRenderer.invoke(IPC.addExercise, input), updateExercise: (id: string, patch: Partial): Promise => ipcRenderer.invoke(IPC.updateExercise, id, patch), deleteExercise: (id: string): Promise => ipcRenderer.invoke(IPC.deleteExercise, id), toggleExercise: (id: string, enabled: boolean): Promise => ipcRenderer.invoke(IPC.toggleExercise, id, enabled), markDone: (id: string, actualReps?: number): Promise => ipcRenderer.invoke(IPC.markDone, id, actualReps), snooze: (id: string, minutes: number): Promise => ipcRenderer.invoke(IPC.snooze, id, minutes), skip: (id: string): Promise => ipcRenderer.invoke(IPC.skip, id), updateSettings: (patch: Partial): Promise => ipcRenderer.invoke(IPC.updateSettings, patch), getAccentColor: (): Promise => ipcRenderer.invoke(IPC.getAccentColor), getOsTheme: (): Promise<'light' | 'dark'> => ipcRenderer.invoke(IPC.getOsTheme), getAppVersion: (): Promise => ipcRenderer.invoke(IPC.getAppVersion), getMeetingActive: (): Promise => ipcRenderer.invoke(IPC.getMeetingActive), pauseAll: (): Promise => ipcRenderer.invoke(IPC.pauseAll), resumeAll: (): Promise => ipcRenderer.invoke(IPC.resumeAll), quit: (): Promise => ipcRenderer.invoke(IPC.quit), reminderClose: (): Promise => ipcRenderer.invoke(IPC.reminderClose), minimizeMain: (): void => ipcRenderer.send(IPC.minimizeMain), toggleMaximizeMain: (): void => ipcRenderer.send(IPC.toggleMaximizeMain), isMaximizedMain: (): Promise => ipcRenderer.invoke(IPC.isMaximizedMain), closeMain: (): void => ipcRenderer.send(IPC.closeMain), hideMain: (): void => ipcRenderer.send(IPC.hideMain), // Games listGames: (): Promise => ipcRenderer.invoke(IPC.gamesList), installGame: (id: GameId): Promise => ipcRenderer.invoke(IPC.gameInstall, id), uninstallGame: (id: GameId): Promise => ipcRenderer.invoke(IPC.gameUninstall, id), toggleGame: (id: GameId, enabled: boolean): Promise => ipcRenderer.invoke(IPC.gameToggle, id, enabled), openGameLaunchOptions: (id: GameId): Promise => ipcRenderer.invoke(IPC.gameOpenLaunchOptions, id), // Challenges addChallenge: (input: Omit): Promise => ipcRenderer.invoke(IPC.addChallenge, input), updateChallenge: ( id: string, patch: Partial ): Promise => ipcRenderer.invoke(IPC.updateChallenge, id, patch), deleteChallenge: (id: string): Promise => ipcRenderer.invoke(IPC.deleteChallenge, id), toggleChallenge: (id: string, enabled: boolean): Promise => ipcRenderer.invoke(IPC.toggleChallenge, id, enabled), markChallengeDone: (id: string, reps: number): Promise => ipcRenderer.invoke(IPC.markChallengeDone, id, reps), closeMatchSummary: (): Promise => ipcRenderer.invoke(IPC.closeMatchSummary), // Dev-only: synthesize a match-end event from the renderer. The channel is // not registered in production builds (see src/main/ipc.ts), so this // function will reject in shipped binaries even though it's exposed. // Gated at the preload level too so the bundler can dead-code-eliminate it. ...(import.meta.env.MODE !== 'production' ? { simulateMatchEnd: ( id: GameId, stats: Record ): Promise => ipcRenderer.invoke(IPC.devSimulateMatchEnd, id, stats) } : {}), // Auto-updater updaterStatus: (): Promise => ipcRenderer.invoke(IPC.updaterStatus), updaterCheck: (): Promise => ipcRenderer.invoke(IPC.updaterCheck), // Fire-and-forget. Прогресс и завершение прилетают через onUpdaterStatus — // renderer не должен `await`'ить, иначе busy-state висит весь download. updaterDownload: (): void => ipcRenderer.send(IPC.updaterDownload), updaterInstall: (): void => ipcRenderer.send(IPC.updaterInstall), // History getHistory: (sinceMs?: number): Promise => ipcRenderer.invoke(IPC.getHistory, sinceMs), clearHistory: (beforeTs?: number): Promise => ipcRenderer.invoke(IPC.clearHistory, beforeTs), // Export / Import — открывают native save/open dialogs из main process. exportState: (): Promise<{ ok: boolean canceled: boolean path: string | null error?: string }> => ipcRenderer.invoke(IPC.exportState), importState: (): Promise<{ ok: boolean canceled: boolean error?: string }> => ipcRenderer.invoke(IPC.importState), onTick: (h: Handler): Unsub => on(IPC.evtTick, h), onFire: (h: Handler): Unsub => on(IPC.evtFire, h), onMatchEnd: (h: Handler): Unsub => on(IPC.evtMatchEnd, h), onStateChanged: (h: Handler): Unsub => on(IPC.evtStateChanged, h), onThemeChanged: (h: Handler<'light' | 'dark'>): Unsub => on(IPC.evtThemeChanged, h), onAccentChanged: (h: Handler): Unsub => on(IPC.evtAccentChanged, h), onGamesChanged: (h: Handler): Unsub => on(IPC.evtGamesChanged, h), onUpdaterStatus: (h: Handler): Unsub => on(IPC.evtUpdaterStatus, h), onMaximizeChanged: (h: Handler): Unsub => on(IPC.evtMaximizeChanged, h), onMeetingChanged: (h: Handler): Unsub => on(IPC.evtMeetingChanged, h), onHistoryChanged: (h: Handler): Unsub => on(IPC.evtHistoryChanged, h) } contextBridge.exposeInMainWorld('api', api) export type Api = typeof api