版控体系重构(13 文件):
- 移除 build.mjs --personal/--enterprise 参数和 EDITION 环境变量
- 统一安装包文件名:船长-HeiXiu-{os}-{版本号}-Setup.{ext}
- 简化 package.json 构建脚本,移除 :enterprise 变体
- scripts/*.mjs 不再接受 edition 参数
- 移除 shared/constants/app.ts 的 parseEdition()
窗口标题动态版控:
- 新格式:船长-HeiXiu-{os}-{版本号}(登录前)/ ·{版控} 后缀(登录后)
- 链路:AppProvider(account_type) → useEffect → appRuntime IPC → main process
- 移除 updater.ts 更新检查 API 的 edition 查询参数
本地存储生命周期修正:
- 退出登录不再自动清空缓存(user_id 隔离,重新登录命中缓存)
- clearUserStorage 添加 isOpen() 竞态守卫
- 移除 closeDatabase() 避免退出→重新登录后数据库不可用
- 手动「清理缓存」按钮保留
文档:
- CHANGELOG.md 新增 0.0.24 条目
- TODO.md 新增已完成项
101 lines
4.3 KiB
TypeScript
101 lines
4.3 KiB
TypeScript
import { ipcRenderer, contextBridge } from 'electron';
|
||
import { BIDIRECTIONAL, RENDERER_TO_MAIN } from '../shared/constants/ipc-channels';
|
||
|
||
// --------- Expose some API to the Renderer process ---------
|
||
contextBridge.exposeInMainWorld('ipcRenderer', {
|
||
on(...args: Parameters<typeof ipcRenderer.on>) {
|
||
const [channel, listener] = args;
|
||
return ipcRenderer.on(channel, (event, ...args) => listener(event, ...args));
|
||
},
|
||
off(...args: Parameters<typeof ipcRenderer.off>) {
|
||
const [channel, ...omit] = args;
|
||
return ipcRenderer.off(channel, ...omit);
|
||
},
|
||
send(...args: Parameters<typeof ipcRenderer.send>) {
|
||
const [channel, ...omit] = args;
|
||
return ipcRenderer.send(channel, ...omit);
|
||
},
|
||
invoke(...args: Parameters<typeof ipcRenderer.invoke>) {
|
||
const [channel, ...omit] = args;
|
||
return ipcRenderer.invoke(channel, ...omit);
|
||
},
|
||
});
|
||
|
||
// --------- 安全加密 API(基于 Electron safeStorage)---------
|
||
contextBridge.exposeInMainWorld('safeStorage', {
|
||
/**
|
||
* 加密明文字符串
|
||
* @param plaintext - 待加密的明文
|
||
* @returns Base64 编码的密文,失败返回 null
|
||
*/
|
||
async encrypt(plaintext: string): Promise<string | null> {
|
||
const result = await ipcRenderer.invoke(BIDIRECTIONAL.SAFESTORAGE_ENCRYPT, plaintext);
|
||
return result?.success ? result.data : null;
|
||
},
|
||
|
||
/**
|
||
* 解密 Base64 密文
|
||
* @param encryptedBase64 - Base64 编码的密文
|
||
* @returns 解密后的明文,失败返回 null
|
||
*/
|
||
async decrypt(encryptedBase64: string): Promise<string | null> {
|
||
const result = await ipcRenderer.invoke(BIDIRECTIONAL.SAFESTORAGE_DECRYPT, encryptedBase64);
|
||
return result?.success ? result.data : null;
|
||
},
|
||
});
|
||
|
||
// --------- 本地存储文件 API(主进程 fs 桥接)---------
|
||
contextBridge.exposeInMainWorld('fileStorage', {
|
||
/** 获取 heixiu-data 根目录路径 */
|
||
getDataDir(): Promise<string | null> {
|
||
return ipcRenderer.invoke(BIDIRECTIONAL.GET_DATA_DIR)
|
||
.then((r: { success: boolean; data: string }) => r?.success ? r.data : null);
|
||
},
|
||
/** 写入文件(Base64 → 磁盘),返回写入的绝对路径 */
|
||
writeFile(relativePath: string, data: string, encoding?: 'base64' | 'utf8'): Promise<string | null> {
|
||
return ipcRenderer.invoke(BIDIRECTIONAL.FILE_WRITE, { relativePath, data, encoding })
|
||
.then((r: { success: boolean; data: string }) => r?.success ? r.data : null);
|
||
},
|
||
/** 读取文件(磁盘 → Base64) */
|
||
readFile(relativePath: string): Promise<string | null> {
|
||
return ipcRenderer.invoke(BIDIRECTIONAL.FILE_READ, { relativePath })
|
||
.then((r: { success: boolean; data: string }) => r?.success ? r.data : null);
|
||
},
|
||
/** 删除文件 */
|
||
deleteFile(relativePath: string): Promise<boolean> {
|
||
return ipcRenderer.invoke(BIDIRECTIONAL.FILE_DELETE, { relativePath })
|
||
.then((r: { success: boolean }) => r?.success ?? false);
|
||
},
|
||
/** 递归创建目录 */
|
||
makeDir(relativePath: string): Promise<string | null> {
|
||
return ipcRenderer.invoke(BIDIRECTIONAL.MAKE_DIR, { relativePath })
|
||
.then((r: { success: boolean; data: string }) => r?.success ? r.data : null);
|
||
},
|
||
/** 在文件管理器中显示文件(绝对路径) */
|
||
showItemInFolder(filePath: string): Promise<boolean> {
|
||
return ipcRenderer.invoke(BIDIRECTIONAL.SHOW_ITEM_IN_FOLDER, { filePath })
|
||
.then((r: { success: boolean }) => r?.success ?? false);
|
||
},
|
||
/** 获取文件大小(字节) */
|
||
fileSize(relativePath: string): Promise<number | null> {
|
||
return ipcRenderer.invoke(BIDIRECTIONAL.FILE_SIZE, { relativePath })
|
||
.then((r: { success: boolean; data: number }) => r?.success ? r.data : null);
|
||
},
|
||
/** 检查文件/目录是否存在 */
|
||
fileExists(relativePath: string): Promise<boolean> {
|
||
return ipcRenderer.invoke(BIDIRECTIONAL.FILE_EXISTS, { relativePath })
|
||
.then((r: { success: boolean; data: boolean }) => r?.success ? r.data : false);
|
||
},
|
||
});
|
||
|
||
// --------- 应用运行时 API ---------
|
||
contextBridge.exposeInMainWorld('appRuntime', {
|
||
/**
|
||
* 登录/登出后更新窗口标题中的版控后缀
|
||
* @param edition - 'personal' | 'enterprise' | null(null 清除后缀)
|
||
*/
|
||
setWindowEdition(edition: string | null): void {
|
||
ipcRenderer.send(RENDERER_TO_MAIN.SET_WINDOW_TITLE, edition);
|
||
},
|
||
});
|