feat: 重构更新流程 — 无更新不抛异常、用户决定下载、更新详情展示(md文件中的)
【核心重构】
- getLatestVersion() 不再 throw Error,无更新时返回 { version: APP_VERSION }
- checkForUpdates() 先自行预检 → 无更新直发 IPC,有更新才交 electron-updater
- 移除 NO_UPDATE_MESSAGE 常量及所有字符串匹配拦截代码
- HeiXiuProvider 增加 500ms TTL 缓存,避免预检+electron-updater 重复请求 API
【用户体验】
- 检查到新版本后不再自动下载,展示更新内容供用户决定
- 新增"下载更新"按钮(与"安装更新"分离为两步操作)
- 设置页新增"查看详情"弹窗,Markdown 格式化渲染更新日志
- available / downloading / downloaded 三个状态 UI 独立展示
【Bug 修复】
- 测试服务器版本排序:字符串序 → 语义版本序(_version_key),0.0.10 正确 > 0.0.9
- 版本比较:!= → <(_version_key),防止高版本误判为有更新
- get_best_release fallback 同样改为语义版本排序
- Windows cmd set 命令尾部空格 → Invalid URL(.trim() 修复)
- electron-updater 'error' 事件中拦截 NO_UPDATE_MESSAGE → UPDATE_NOT_AVAILABLE
【文档更新】
- CHANGELOG.md、PROJECT.md、UpdateA.md 同步更新架构图和流程说明
This commit is contained in:
81
electron/main/safe-storage-ipc.ts
Normal file
81
electron/main/safe-storage-ipc.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
// ============================================================
|
||||
// safe-storage-ipc — safeStorage 加密/解密 IPC 处理器
|
||||
//
|
||||
// 在主进程注册,渲染进程通过 preload 暴露的安全方法调用。
|
||||
// 加密后的数据为 Base64 字符串,方便存储在 localStorage。
|
||||
//
|
||||
// 平台对应:
|
||||
// Windows → DPAPI
|
||||
// macOS → Keychain
|
||||
// Linux → libsecret
|
||||
//
|
||||
// 注意:
|
||||
// - safeStorage 加密绑定当前 OS 用户账户,无法跨用户/跨设备解密
|
||||
// - 用户重装 OS 后加密数据将无法恢复(需重新登录)
|
||||
// - safeStorage.isEncryptionAvailable() 在部分 Linux 发行版可能返回 false
|
||||
// ============================================================
|
||||
|
||||
import { ipcMain, safeStorage } from 'electron';
|
||||
import { BIDIRECTIONAL } from '../../shared/constants/ipc-channels';
|
||||
import { logger } from './logger';
|
||||
|
||||
/** 检查 safeStorage 是否可用,不可用时记录警告 */
|
||||
function checkAvailability(): boolean {
|
||||
if (!safeStorage.isEncryptionAvailable()) {
|
||||
logger.warn('safe-storage', 'safeStorage 加密不可用(可能缺少 libsecret 等系统密钥服务)');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册 safeStorage IPC 处理器
|
||||
* 在 app.whenReady() 中调用
|
||||
*/
|
||||
export function registerSafeStorageIpcHandlers(): void {
|
||||
// ---------- 加密 ----------
|
||||
ipcMain.handle(BIDIRECTIONAL.SAFESTORAGE_ENCRYPT, (_event, plaintext: string) => {
|
||||
if (typeof plaintext !== 'string' || plaintext.length === 0) {
|
||||
logger.warn('safe-storage', '加密参数无效');
|
||||
return { success: false, error: '参数无效:需要非空字符串' };
|
||||
}
|
||||
|
||||
if (!checkAvailability()) {
|
||||
return { success: false, error: 'safeStorage 不可用' };
|
||||
}
|
||||
|
||||
try {
|
||||
const encrypted = safeStorage.encryptString(plaintext);
|
||||
const base64 = encrypted.toString('base64');
|
||||
return { success: true, data: base64 };
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
logger.error('safe-storage', '加密失败', err instanceof Error ? err : new Error(message));
|
||||
return { success: false, error: message };
|
||||
}
|
||||
});
|
||||
|
||||
// ---------- 解密 ----------
|
||||
ipcMain.handle(BIDIRECTIONAL.SAFESTORAGE_DECRYPT, (_event, encryptedBase64: string) => {
|
||||
if (typeof encryptedBase64 !== 'string' || encryptedBase64.length === 0) {
|
||||
logger.warn('safe-storage', '解密参数无效');
|
||||
return { success: false, error: '参数无效:需要非空字符串' };
|
||||
}
|
||||
|
||||
if (!checkAvailability()) {
|
||||
return { success: false, error: 'safeStorage 不可用' };
|
||||
}
|
||||
|
||||
try {
|
||||
const buffer = Buffer.from(encryptedBase64, 'base64');
|
||||
const plaintext = safeStorage.decryptString(buffer);
|
||||
return { success: true, data: plaintext };
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
logger.error('safe-storage', '解密失败', err instanceof Error ? err : new Error(message));
|
||||
return { success: false, error: message };
|
||||
}
|
||||
});
|
||||
|
||||
logger.info('safe-storage', 'safeStorage IPC 处理器已注册');
|
||||
}
|
||||
Reference in New Issue
Block a user