// ============================================================ // useUpdater — 渲染进程中的更新状态管理(单例 IPC 监听) // // 用法: // const { status, progress, checkForUpdates, installUpdate } = useUpdater(); // // 状态机:idle → checking → (no-update | available → downloading → downloaded → idle) // // 关键设计: // - IPC 监听器在模块级别只注册一次(单例),避免多个组件同时 // 调用 useUpdater() 导致 MaxListenersExceededWarning // - 所有 useUpdater() 实例通过订阅机制共享同一份状态 // ============================================================ import { useState, useEffect, useCallback, useRef } from 'react'; import { safeIpcInvoke, safeIpcOn, safeIpcSend } from '@/utils/ipc'; // ---------- 类型 ---------- export type UpdaterStatus = | 'idle' | 'checking' | 'no-update' | 'available' | 'downloading' | 'downloaded' | 'error'; export interface UpdateInfo { version: string; releaseDate?: string; releaseNotes?: string; forceUpdate?: boolean; } export interface UpdateProgress { percent: number; bytesPerSecond?: number; transferred?: number; total?: number; } export interface UpdateError { message: string; } // ---------- 模块级共享状态 ---------- interface UpdaterState { status: UpdaterStatus; info: UpdateInfo | null; progress: UpdateProgress; error: UpdateError | null; } type Subscriber = (state: UpdaterState) => void; let sharedState: UpdaterState = { status: 'idle', info: null, progress: { percent: 0 }, error: null, }; const subscribers = new Set(); let listenersRegistered = false; function notifyAll(): void { const snapshot = { ...sharedState }; subscribers.forEach((fn) => { try { fn(snapshot); } catch { /* 单个订阅者异常不影响其他 */ } }); } function setSharedState(patch: Partial): void { sharedState = { ...sharedState, ...patch }; notifyAll(); } // ---------- IPC 通道名(与主进程 updater.ts 保持一致)---------- const CH = { UPDATE_AVAILABLE: 'update-available', UPDATE_PROGRESS: 'update-progress', UPDATE_DOWNLOADED: 'update-downloaded', UPDATE_ERROR: 'update-error', UPDATE_NOT_AVAILABLE: 'update-not-available', INSTALL_UPDATE: 'install-update', CHECK_FOR_UPDATE: 'check-for-update', } as const; // ---------- 注册 IPC 监听器(模块级,仅一次)---------- function ensureListeners(): void { if (listenersRegistered) return; listenersRegistered = true; safeIpcOn(CH.UPDATE_AVAILABLE, (_event: unknown, ...args: unknown[]) => { setSharedState({ info: args[0] as UpdateInfo, status: 'available' }); }); safeIpcOn(CH.UPDATE_PROGRESS, (_event: unknown, ...args: unknown[]) => { setSharedState({ status: 'downloading', progress: args[0] as UpdateProgress }); }); safeIpcOn(CH.UPDATE_DOWNLOADED, () => { setSharedState({ status: 'downloaded' }); }); safeIpcOn(CH.UPDATE_ERROR, (_event: unknown, ...args: unknown[]) => { setSharedState({ error: args[0] as UpdateError, status: 'error' }); }); safeIpcOn(CH.UPDATE_NOT_AVAILABLE, () => { setSharedState({ status: 'no-update' }); }); } // ---------- Hook ---------- export function useUpdater() { const [state, setState] = useState(sharedState); const subscribed = useRef(false); // 确保 IPC 监听器只注册一次 useEffect(() => { ensureListeners(); }, []); // 订阅模块级状态变更 useEffect(() => { if (subscribed.current) return; subscribed.current = true; const sub: Subscriber = (newState) => setState(newState); subscribers.add(sub); return () => { subscribers.delete(sub); subscribed.current = false; }; }, []); // 同步初始状态(防止在订阅之前状态已变更) useEffect(() => { setState(sharedState); }, []); /** 手动检查更新 */ const checkForUpdates = useCallback(async () => { setSharedState({ status: 'checking', error: null }); try { await safeIpcInvoke(CH.CHECK_FOR_UPDATE); } catch (err) { setSharedState({ status: 'error', error: { message: (err as Error).message || '检查更新失败' }, }); } }, []); /** 确认安装更新(退出 → 安装 → 重启) */ const installUpdate = useCallback(() => { safeIpcSend(CH.INSTALL_UPDATE); }, []); /** 重置状态 */ const reset = useCallback(() => { setSharedState({ status: 'idle', info: null, progress: { percent: 0 }, error: null, }); }, []); return { status: state.status, info: state.info, progress: state.progress, error: state.error, checkForUpdates, installUpdate, reset, } as const; }