feat(electron): 多账号第一纵切 —— 账号存储/选择器/聚合收件箱

按 docs/MULTI-ACCOUNT-PLAN.md 实现客户端多账号的前半段(SSE 多连接与
写信账号切换留作下一轮)。

- `src/lib/accounts.ts`:纯逻辑(地址规范化、身份判重、默认账号、聚合合并),
  16 条测试钉住每条判据(含反向对照)。
- 持久化在主进程:`userData/accounts.json`,**原子写**(临时文件 + rename)+
  0600。不落 localStorage:那份存储渲染层任何脚本都可读,且 file:// 与
  http:// 是两套。无 IPC 时(浏览器)退到 localStorage 并在界面**如实写明**。
- 取信:单账号走原路径(逐字节不变);聚合时**每账号各一次请求、各带自己的
  令牌**(`fetchWithAuth`,不碰认证单例,避免并发串号)。
- ★ 只合并**同一网关**的账号:跨网关的邮件混进列表后点开会去问当前账号的
  服务器(404,或 mail_id 撞上就打开了别人的信)。如实排除 + 列表上方说明。
- ★ 部分失败可见:某账号取不到时给出账号名与原因 —— 静默丢掉它会让聚合列表
  少一整份邮件而界面看起来完全正常。
- `API_BASE` 改为 `let`(切换账号要换网关),api 层不得缓存它
  (`client.ts` 的 `const BASE` 快照已改成每次读)。
- UI:列表头下拉(≥2 个可用账号才出现「全部邮箱」)+ 账号徽标 + 账号页
  「多账号」一段(添加前调 /auth/me 验证,401 当场拒绝,不写进列表)。
- 测试:vitest 230 通过(原 222 + 新 8)、`test/lib/accounts.test.mjs` 16 通过、
  typecheck 通过。新增 `test/manual/multi-account-verify.mjs`(真起打包产物 +
  两个真实账号,判据落在网络层:聚合必须每账号各一次请求且各带自己的令牌)。
This commit is contained in:
2026-09-13 06:16:59 +08:00
parent c7cb88d9aa
commit addde97600
18 changed files with 1671 additions and 22 deletions

View File

@ -149,10 +149,27 @@ AccountManager
| 维度 | 鸿蒙dsh | elepi |
|---|---|---|
| 账号存储 | preferences JSON | electron-storeencrypted |
| SSE 多连接 | 改造中(当前单连接) | 需新增 |
| 聚合收件箱 | 已实现M7 | 需新增 |
| 账号选择器 UI | 需新增 | 需新增 |
| 写信账号切换 | 需新增 | 需新增 |
| 账号存储 | preferences JSON | ✅ 主进程 `userData/accounts.json`(原子写 + 0600无 IPC 时退 localStorage |
| 账号增删/验证 | 需新增 | ✅ 账号页"多账号"一段:`/auth/me` 验证通过才写入 |
| 聚合收件箱 | 已实现M7 | ✅ 收件箱/发件箱列表头 + 账号徽标 + 部分失败的可见告警 |
| 账号选择器 UI | 需新增 | ✅ 列表头下拉≥2 个可用账号才出现「全部邮箱」) |
| SSE 多连接 | 改造中(当前单连接) | ❌ 未做(下一轮) |
| 写信账号切换 | 需新增 | ❌ 未做(下一轮) |
两边都以此文档为准,不以 cc 里的讨论为依据。
### 实现备注ele 侧2026-09-12
- **"当前账号"复用 api 单例**`config.ts``API_BASE`/`bearerToken` 仍是
模块级状态,含义变成"当前选中账号的认证",切换时由 `accountStore` 同步
`API_BASE` 因此改成 `let`,且 api 层不得在模块作用域缓存它)。
单账号视图的代码路径逐字节不变。
- **只有两处走显式认证**`fetchWithAuth`):聚合收件箱、以及将来的多账号 SSE。
借用单例来回切会让并发请求串号A 的请求带上 B 的令牌)。
- **只合并同一网关的账号**。跨网关的邮件混进列表后,点开会去问当前账号的
服务器(要么 404要么 mail_id 撞上就打开了别人的信)—— 所以如实排除并在
列表上方说明,而不是静默少一份。
- **部分失败必须可见**:某个账号取不到时列表上方给出账号名与原因,
这正是"聚合"最容易骗人的失败方式(看起来一切正常,只是少了一整份邮件)。
- 加密存储:本机 `safeStorage` 依赖系统 keyring本环境无故未启用
现状是有据可查的 0600 明文文件,界面上如实写明存放位置。