Files
koring-launcher/electron/preload.ts
T
dream_pep 8fc6fcaf05 feat(update): 实现更新通道管理系统与更新调试页面
本次提交完成以下变更:
1. 实现完整的更新通道管理功能,支持慢走/跑步两种更新模式,可在设置页切换且配置持久化生效
2. 新增更新调试页面,集成检查更新、版本比对、设置测试版本等调试工具
3. 新增快速打开Chromium DevTools的按钮,方便开发调试
4. 完善自动更新后端逻辑,新增版本比对、测试版本覆盖、通道动态获取等IPC接口
5. 调整GitHub Release构建脚本,修复prerelease版本的命名与发布问题
6. 添加semver依赖与类型定义,完善全量类型声明
7. 更新自动更新文档,补充更新通道相关的设计说明
8. 微调默认配置,添加主题暗黑模式的初始配置
2026-08-31 02:20:25 +08:00

118 lines
4.8 KiB
TypeScript

import electron from 'electron';
import path from 'path';
import fs from 'fs';
const { contextBridge, ipcRenderer } = electron;
const MIME_MAP: Record<string, string> = {
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.webp': 'image/webp',
'.gif': 'image/gif',
'.bmp': 'image/bmp',
};
function getFileAsDataUrl(filePath: string): string | null {
try {
const buffer = fs.readFileSync(filePath);
const ext = path.extname(filePath).toLowerCase();
const mime = MIME_MAP[ext] || 'image/png';
return `data:${mime};base64,${buffer.toString('base64')}`;
} catch {
return null;
}
}
contextBridge.exposeInMainWorld('electronAPI', {
// Generic IPC
invoke: (channel: string, ...args: unknown[]) =>
ipcRenderer.invoke(channel, ...args),
on: (channel: string, callback: (...args: unknown[]) => void) => {
const handler = (_event: Electron.IpcRendererEvent, ...args: unknown[]) => callback(...args);
ipcRenderer.on(channel, handler);
return () => ipcRenderer.removeListener(channel, handler);
},
send: (channel: string, ...args: unknown[]) =>
ipcRenderer.send(channel, ...args),
// Window controls
minimize: () => ipcRenderer.invoke('window:minimize'),
maximize: () => ipcRenderer.invoke('window:maximize'),
close: () => ipcRenderer.invoke('window:close'),
isMaximized: () => ipcRenderer.invoke('window:isMaximized'),
openDevTools: () => ipcRenderer.invoke('window:openDevTools'),
onResized: (callback: () => void) => {
const handler = () => callback();
ipcRenderer.on('window:resized', handler);
return () => ipcRenderer.removeListener('window:resized', handler);
},
// Theme
getTheme: () => ipcRenderer.invoke('window:getTheme'),
// Config preloading
onConfigPreload: (callback: (data: { config: unknown; isFirstLaunch: boolean }) => void) => {
const handler = (_event: Electron.IpcRendererEvent, data: { config: unknown; isFirstLaunch: boolean }) => callback(data);
ipcRenderer.on('config:preload', handler);
return () => ipcRenderer.removeListener('config:preload', handler);
},
// Config changed broadcast (authoritative full config from main process)
onConfigChanged: (callback: (config: unknown) => void) => {
const handler = (_event: Electron.IpcRendererEvent, config: unknown) => callback(config);
ipcRenderer.on('config:changed', handler);
return () => ipcRenderer.removeListener('config:changed', handler);
},
// Background image — pick file, copy to userData, return base64 data URL
pickBackgroundImage: async (): Promise<string | null> => {
const result = await ipcRenderer.invoke('dialog:openFile', {
filters: [{ name: '图片', extensions: ['png', 'jpg', 'jpeg', 'webp', 'gif', 'bmp'] }],
});
if (!result) return null;
const { srcPath, ext } = result as { srcPath: string; ext: string };
// Copy to userData via main process
const destPath = await ipcRenderer.invoke('background:copyToUserData', srcPath, ext);
if (!destPath) return null;
return getFileAsDataUrl(destPath);
},
// Get cached background as base64 data URL
getBackgroundDataUrl: async (): Promise<string | null> => {
const filePath = await ipcRenderer.invoke('background:getCachedPath');
if (!filePath) return null;
return getFileAsDataUrl(filePath);
},
// Open external URL in system browser
openExternal: (url: string) => ipcRenderer.invoke('shell:openExternal', url),
// Crash monitoring — devtools only
simulateCrash: () => ipcRenderer.invoke('crash:simulate'),
testCrashDialog: () => ipcRenderer.invoke('crash:testDialog'),
// Config reset
resetConfig: () => ipcRenderer.invoke('config:reset'),
// Auto-update (main process: electron/updater.ts)
checkForUpdates: (manual = false) => ipcRenderer.invoke('update:check', { manual }),
downloadUpdate: () => ipcRenderer.invoke('update:download'),
pauseUpdate: () => ipcRenderer.invoke('update:pause'),
resumeUpdate: () => ipcRenderer.invoke('update:resume'),
cancelUpdate: () => ipcRenderer.invoke('update:cancel'),
quitAndInstall: () => ipcRenderer.invoke('update:quitAndInstall'),
getUpdateState: () => ipcRenderer.invoke('update:getState'),
getReleaseNotes: (tag?: string) => ipcRenderer.invoke('update:getReleaseNotes', { tag }),
getUpdateChannels: () => ipcRenderer.invoke('update:getChannels'),
setUpdateChannel: (channel: string) => ipcRenderer.invoke('update:setChannel', { channel }),
setTestVersion: (version: string) => ipcRenderer.invoke('update:setTestVersion', { version }),
compareVersions: (a: string, b: string) => ipcRenderer.invoke('update:compareVersions', { a, b }),
onUpdateStatus: (callback: (data: unknown) => void) => {
const handler = (_event: Electron.IpcRendererEvent, data: unknown) => callback(data);
ipcRenderer.on('update:status', handler);
return () => ipcRenderer.removeListener('update:status', handler);
},
});