import { useState, useEffect, useCallback } from 'react'; import { useIsElectron } from './use-is-electron'; // ─── Types ────────────────────────────────────────────────────── export type UpdateStatus = 'idle' | 'checking' | 'available' | 'not-available' | 'downloading' | 'ready' | 'error'; export interface UseElectronUpdaterReturn { /** Current status of the auto-updater lifecycle */ status: UpdateStatus; /** Download progress percentage (0–100) */ progress: number; /** Download speed in bytes per second */ bytesPerSecond: number; /** Information about the available/downloaded update */ updateInfo: ElectronUpdateInfo | null; /** Error message if the updater encountered an issue */ errorMessage: string | null; /** Whether the app is running inside Electron */ isElectron: boolean; /** Trigger a manual update check */ checkForUpdates: () => void; /** Quit the app and install the downloaded update */ installUpdate: () => void; } /** * React hook for the Electron auto-updater. * * Subscribes to all update lifecycle events via `window.electronAPI` * and provides reactive state for building an update notification UI. * * Safe to use in both Electron and browser environments — all * Electron-specific calls are gated behind `window.electronAPI` checks. * * @example * ```tsx * function UpdateBanner() { * const { status, progress, updateInfo, checkForUpdates, installUpdate } = useElectronUpdater(); * * if (status === 'available') { * return
Update {updateInfo?.version} available! Downloading...
; * } * if (status === 'downloading') { * return
Downloading... {progress.toFixed(0)}%
; * } * if (status === 'ready') { * return ; * } * return ; * } * ``` */ export function useElectronUpdater(): UseElectronUpdaterReturn { const [status, setStatus] = useState('idle'); const [progress, setProgress] = useState(0); const [bytesPerSecond, setBytesPerSecond] = useState(0); const [updateInfo, setUpdateInfo] = useState(null); const [errorMessage, setErrorMessage] = useState(null); const isElectron = useIsElectron(); useEffect(() => { if (!window.electronAPI) return; const api = window.electronAPI; const unsubChecking = api.onUpdateChecking(() => { setStatus('checking'); setErrorMessage(null); }); const unsubAvailable = api.onUpdateAvailable((info) => { setStatus('available'); setUpdateInfo(info); }); const unsubNotAvailable = api.onUpdateNotAvailable((info) => { setStatus('not-available'); setUpdateInfo(info); }); const unsubProgress = api.onDownloadProgress((progressInfo) => { setStatus('downloading'); setProgress(progressInfo.percent); setBytesPerSecond(progressInfo.bytesPerSecond); }); const unsubDownloaded = api.onUpdateDownloaded((info) => { setStatus('ready'); setProgress(100); setUpdateInfo(info); }); const unsubError = api.onUpdateError((error) => { setStatus('error'); setErrorMessage(error); }); // Cleanup all listeners on unmount return () => { unsubChecking(); unsubAvailable(); unsubNotAvailable(); unsubProgress(); unsubDownloaded(); unsubError(); }; }, []); const checkForUpdates = useCallback(() => { if (!window.electronAPI) return; setStatus('checking'); setErrorMessage(null); window.electronAPI.checkForUpdates(); }, []); const installUpdate = useCallback(() => { if (!window.electronAPI) return; window.electronAPI.installUpdate(); }, []); return { status, progress, bytesPerSecond, updateInfo, errorMessage, isElectron, checkForUpdates, installUpdate, }; }