跳转至

Popup 认证状态机

类型:wiki(知识沉淀) 描述:浏览器扩展 popup 的登录态机制 — 4 态状态机 + 2 秒超时兜底 + 多重登录入口 最后更新:2026-05-26(v0.10.16) 相关源码src/sections/popup/index.tsxsrc/utils/auto-login.tssrc/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 秒后必须切到 stalelogged-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-loginforce=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 秒是体感和容错的平衡点

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 也提供手动登录入口