feat(update-center): 实现集中式软件更新中心功能

新增更新中心模块,支持管理 APM 和传统 deb 软件更新任务
- 添加更新任务队列管理、状态跟踪和日志记录功能
- 实现更新项忽略配置持久化存储
- 新增更新确认对话框和迁移提示
- 优化主窗口关闭时的任务保护机制
- 添加单元测试覆盖核心逻辑
This commit is contained in:
2026-04-09 08:19:51 +08:00
parent 97bb8e5f59
commit 0b17ada45a
37 changed files with 6389 additions and 342 deletions

View File

@@ -1,4 +1,47 @@
import { ipcRenderer, contextBridge } from "electron";
import { ipcRenderer, contextBridge, type IpcRendererEvent } from "electron";
type UpdateCenterSnapshot = {
items: Array<{
taskKey: string;
packageName: string;
displayName: string;
currentVersion: string;
newVersion: string;
source: "aptss" | "apm";
ignored?: boolean;
}>;
tasks: Array<{
taskKey: string;
packageName: string;
source: "aptss" | "apm";
status:
| "queued"
| "downloading"
| "installing"
| "completed"
| "failed"
| "cancelled";
progress: number;
logs: Array<{ time: number; message: string }>;
errorMessage: string;
}>;
warnings: string[];
hasRunningTasks: boolean;
};
type IpcRendererFacade = {
on: typeof ipcRenderer.on;
off: typeof ipcRenderer.off;
send: typeof ipcRenderer.send;
invoke: typeof ipcRenderer.invoke;
};
type UpdateCenterStateListener = (snapshot: UpdateCenterSnapshot) => void;
const updateCenterStateListeners = new Map<
UpdateCenterStateListener,
(_event: IpcRendererEvent, snapshot: UpdateCenterSnapshot) => void
>();
// --------- Expose some API to the Renderer process ---------
contextBridge.exposeInMainWorld("ipcRenderer", {
@@ -23,7 +66,7 @@ contextBridge.exposeInMainWorld("ipcRenderer", {
// You can expose other APTs you need here.
// ...
});
} satisfies IpcRendererFacade);
contextBridge.exposeInMainWorld("apm_store", {
arch: (() => {
@@ -38,6 +81,46 @@ contextBridge.exposeInMainWorld("apm_store", {
})(),
});
contextBridge.exposeInMainWorld("updateCenter", {
open: (): Promise<UpdateCenterSnapshot> =>
ipcRenderer.invoke("update-center-open"),
refresh: (): Promise<UpdateCenterSnapshot> =>
ipcRenderer.invoke("update-center-refresh"),
ignore: (payload: {
packageName: string;
newVersion: string;
}): Promise<void> => ipcRenderer.invoke("update-center-ignore", payload),
unignore: (payload: {
packageName: string;
newVersion: string;
}): Promise<void> => ipcRenderer.invoke("update-center-unignore", payload),
start: (taskKeys: string[]): Promise<void> =>
ipcRenderer.invoke("update-center-start", taskKeys),
cancel: (taskKey: string): Promise<void> =>
ipcRenderer.invoke("update-center-cancel", taskKey),
getState: (): Promise<UpdateCenterSnapshot> =>
ipcRenderer.invoke("update-center-get-state"),
onState: (listener: UpdateCenterStateListener): void => {
const wrapped = (
_event: IpcRendererEvent,
snapshot: UpdateCenterSnapshot,
) => {
listener(snapshot);
};
updateCenterStateListeners.set(listener, wrapped);
ipcRenderer.on("update-center-state", wrapped);
},
offState: (listener: UpdateCenterStateListener): void => {
const wrapped = updateCenterStateListeners.get(listener);
if (!wrapped) {
return;
}
ipcRenderer.off("update-center-state", wrapped);
updateCenterStateListeners.delete(listener);
},
});
// --------- Preload scripts loading ---------
function domReady(
condition: DocumentReadyState[] = ["complete", "interactive"],