设置存储
带版本迁移的设置存储、设置中心界面、备份与安全存储。
用户偏好以带版本的 JSON 文件形式存放在主进程中。渲染进程只能通过 Conveyor 读写
它们,设置中心界面是一组可搜索的标签页,而秘密从不触及这个存储——它们改走
safeStorage。
存储
lib/main/settings.ts(SettingsStore)掌管 Electron userData 目录下的
settings.json。它以带版本的信封形式写入:
{
"version": 10,
"settings": { "theme": "dark", "locale": "system", "...": "..." }
}| 操作系统 | 位置 |
|---|---|
| macOS | ~/Library/Application Support/<AppName>/settings.json |
| Windows | %APPDATA%\<AppName>\settings.json |
| Linux | ~/.config/<AppName>/settings.json |
每个字段都由 appSettingsSchema(lib/conveyor/schemas/app-schema.ts)定义,
并在读写时校验,因此手动编辑或损坏的文件会回退到默认值,而不会让应用崩溃。
向前迁移
存储以一串向前迁移进行版本管理。CURRENT_SETTINGS_VERSION 是目标版本;
SETTINGS_MIGRATIONS 中每一项把某个版本升级到下一个。加载时,migrateForward
从文件版本一路运行到当前版本的每个迁移器,随后重新校验:
// lib/main/settings.ts
export const CURRENT_SETTINGS_VERSION = 10
const SETTINGS_MIGRATIONS = {
// ...
7: (v7) => ({ ...v7, accentColor: 'default' }),
8: (v8) => ({ ...v8, onboardingCompleted: false, eulaAcceptedVersion: 0 }),
9: (v9) => ({ ...v9, zoomLevel: 0 }),
}新增一个设置是一次小而机械的改动:
把字段加入 schema
在 app-schema.ts 中扩展 appSettingsSchema 与 appSettingsPatchSchema,并把它
加入 DEFAULT_SETTINGS。
提升版本并添加迁移器
递增 CURRENT_SETTINGS_VERSION,并添加一个 SETTINGS_MIGRATIONS 项,在上一个
载荷之上补种新字段。
在 update() 中合并
添加 if (patch.<field> !== undefined) 一行,让渲染进程能写入它。
比应用更新的文件会被原样保留,存储回退到默认值,而不是悄悄降级它看不懂的数据。 一个遗留的无版本文件会被视作 v1 并向前迁移。
设置中心
app/routes/SettingsPage.tsx 以标签页形式渲染设置中心。共有八个标签页;
账户(Account)是它自己的页面(/account),并非设置标签页。
| 标签页 | Section 文件 | 控件 |
|---|---|---|
| 通用 | GeneralSection | 语言、开机自启、最小化到托盘 |
| 外观 | AppearanceSection | 主题、强调色 |
| 通知 | NotificationsSection | 原生通知开关 |
| 快捷键 | ShortcutsSection | 键盘快捷键列表 |
| 代理 | ProxySection | 网络代理模式 + URL |
| 存储 | StorageSection | 数据目录、备份导出/导入 |
| 更新 | UpdatesSection | 自动更新开关、更新通道(受标志开关控制) |
| 高级 | AdvancedSection | 错误上报、分析、恢复默认 |
更新标签页仅在 features.autoUpdate 开启时出现,因此剥离了自动更新的构建
不会留下孤立的标签页。
设置搜索
settings-search.ts 是一份逐项控件的小索引,每一项都指向其所属标签页。
SettingsSearch 用查询词去匹配已翻译的标签、描述与关键词,因此用户无需知道
某控件在哪个标签页也能跳转过去。隐藏的标签页(例如标志关闭时的"更新")会被排除
在结果之外。同一份索引也为命令面板的"设置"分组供数——参见
界面与页面。
use-settings-form 模式
use-settings-form.ts 通过 Conveyor 加载一次设置,并暴露 updateSettings /
resetSettings。每个 section 接收 settings 与 updateSettings,并写入一个
局部补丁。端到端添加一个控件:
- 把字段加入存储(见上文)。
- 在正确的 section 中渲染控件,调用
updateSettings({ field })。 - 添加一条
settings-search.ts记录,使其可被搜索并出现在命令面板中。
备份
lib/main/backup.ts 驱动存储标签页中的导出/导入(app-export-data /
app-import-data Conveyor 通道)。一份备份是一个 JSON 文件,包含你的设置外加
一份设备上存在哪些安全键的清单。
备份从不包含解密后的秘密。Supabase 会话、令牌与许可证密钥通过 safeStorage
绑定到设备;以明文导出它们会破坏操作系统的加密。在新设备上恢复意味着重新登录
以重建它们。
导入用 parseBackup(Zod)校验文件,并通过 settingsStore.restore() 恢复设置,
重新应用托盘与更新器偏好。无效文件会返回一个干净的错误,而非恢复出垃圾数据。
安全存储
lib/main/secure-storage.ts(SecureStorage)是设置存储的加密对应物,由
Electron 的 safeStorage(基于操作系统钥匙串的加密)支撑。它持久化到 userData
下的 secrets.bin,以 0o600 权限写入。渲染进程通过 secure Conveyor 领域
(window.conveyor.secure.get/set/delete)访问它。
已知的安全键是一个固定枚举:
// lib/conveyor/schemas/secure-schema.ts
export const secureKeySchema = z.enum([
'access-token', 'refresh-token', 'supabase-session', 'license-key',
])实践中,本模板在此存放 Supabase 会话——关于为什么 localStorage 在桌面端不够 用,参见认证。
当加密不可用时(例如没有钥匙串的 Linux 机器),写入会抛出一个类型化的
SecureStorageError,错误码为 ENCRYPTION_UNAVAILABLE,而不是悄悄写入明文。
调用方会将其表现为一次无法跨重启保留的登录;参见
疑难排查。
