Popup 认证状态机¶
类型:wiki(知识沉淀) 描述:浏览器扩展 popup 的登录态机制 — 4 态状态机 + 2 秒超时兜底 + 多重登录入口 最后更新:2026-05-26(v0.10.16) 相关源码:
src/sections/popup/index.tsx、src/utils/auto-login.ts、src/entrypoints/background/index.ts
是什么¶
popup 打开瞬间需要判断 3 件事: 1. 用户是否已登录 2. token 是否还有效 3. 如果没登录,怎么提供登录入口
这 3 个判断不能瞬时完成(要查 storage、要拉 user API),所以需要一个状态机管理「中间态」。
4 个状态(v0.10.16)¶
┌─→ logged-in ← user.uid 已加载
loading ───┤ (正常)
├─→ logged-out ← storage 无 uid
│ (从未登录 / 主动登出)
└─→ stale ← storage 有 uid 但 2 秒内 user 拉不到
(token 过期 / API 挂 / 网络问题)
关键设计:loading 是临时态,不能永久停留。最多 2 秒后必须切到 stale 或 logged-in,不能死等。
历次踩坑¶
v0.10.7(首次引入 3 态)¶
最初设计: - 'loading' / 'logged-in' / 'logged-out' - 有 uid → 等 user - 无 uid → logged-out
漏的场景:storage 里有 uid(之前登过留的)但 useAuthContext 拿不到 user(token 过期),authState 永远卡在 'loading',用户看到骨架屏永远不动,没有任何登录入口。
v0.10.16 修复¶
加 2 秒超时 + 第 4 态 stale:
useEffect(() => {
let mounted = true;
let timeoutId;
storage.getItem('local:uid').then((storedUid) => {
if (!storedUid) {
setAuthState('logged-out');
return;
}
// 有 storedUid,给 user 2 秒;超时切 stale
timeoutId = setTimeout(() => {
if (mounted) setAuthState((s) => (s === 'loading' ? 'stale' : s));
}, 2000);
});
return () => { mounted = false; if (timeoutId) clearTimeout(timeoutId); };
}, []);
并且 loading 状态也提供「手动登录」按钮,2 秒等不了的用户能立即手动登录。
4 个状态对应的 UI¶
| 状态 | UI | 何时出现 |
|---|---|---|
loading |
骨架屏 + 「手动登录」小按钮 | popup 打开瞬间,最多 2 秒 |
logged-in |
会员状态条(chip + 到期日) | 正常登录 |
logged-out |
[Outlined] 未登录 · 点击登录 |
storage 无 uid |
stale |
[Warning Contained] 会话失效 · 重新登录 |
有 uid 但拉不到 user |
登录按钮统一处理(handleLoginClick)¶
1. 防重入(loginBusy)
2. 先调 background 的 try-cookie-login(force=true)
├─ 成功 → window.location.reload() 重载 popup
└─ 失败 → 调 open-login → background 开登录窗 → window.close() popup
3. 用户在登录窗里完成登录 → background 写 storage → 下次开 popup 直接 logged-in
关键代码位置¶
| 文件 | 位置 | 作用 |
|---|---|---|
popup/index.tsx |
useEffect (line 87-) |
启动时检 storage + 2 秒超时 |
popup/index.tsx |
handleLoginClick |
统一登录入口(try-cookie 优先) |
popup/index.tsx |
4 个 JSX 分支 | 4 态对应 UI |
background/index.ts |
try-cookie-login 处理 |
autoLoginViaCookies(true) |
background/index.ts |
open-login 处理 |
失败兜底开登录窗 |
auto-login.ts |
autoLoginViaCookies(force) |
从 web.laifaxin.com 读 cookie 自动登录 |
易踩坑¶
⚠️ 坑 1:loading 不能死等¶
任何"等异步结果再判断"的状态都必须有超时。否则 API 挂了 / 网络断了 → 用户卡死无法操作。2 秒是体感和容错的平衡点。
⚠️ 坑 2:try-cookie-login 必须 force=true¶
autoLoginViaCookies(false) 检查 storage 已有 uid 就直接返回 ok —— 但这正是 stale 场景(uid 在但 token 过期)!必须 force=true 强制重新拉 cookie。
⚠️ 坑 3:popup 关闭后 SW 仍要处理¶
window.close() 后 popup JS 死掉,但 background SW 继续运行。所以 open-login 消息是 fire-and-forget,不依赖 popup 反馈。
⚠️ 坑 4:reload 后状态重新跑一遍¶
cookie 自动登录成功 → reload popup → 重新走 useEffect → storage 已有最新 uid → useAuthContext 拉 user → logged-in。不要手动 setState('logged-in'),让状态机自己跑。
修改这块时要 / 不要做什么¶
- ✅ 加新登录方式 → 在
handleLoginClick串行尝试(cookie → 其它 → 开窗) - ✅ 加新 auth 状态 → 必须有进入 / 离开它的明确条件,不能"等"
- ❌ 不要让
loading没有超时 - ❌ 不要在
loading状态隐藏所有登录入口(v0.10.7 的错) - ❌ 不要把 try-cookie 改成 force=false(v0.10.16 的修复重点)
版本里程碑¶
| 版本 | 事件 |
|---|---|
| v0.9.x | 直接判断 user,闪现"未登录" 1 秒 |
| v0.10.2 | 加 cookie 自动登录(autoLoginViaCookies) |
| v0.10.7 | 引入 3 态状态机,避免闪现"未登录" —— 埋下死循环坑 |
| v0.10.10 | popup 重构使用 signal pattern |
| v0.10.16 | 修死循环 + 加 stale 态 + loading 也提供手动登录入口 |