跳转至

[ISSUE-0025] Google 验证拦截后用户感知 + 恢复体验差

相关源码: - 检测/上报:src/sections/content-button/index.tsx(looksIntercepted) - 处理:src/entrypoints/background/batch-controller.ts:onInterception - 验证 tab:src/utils/scrape-window.ts:openVerifyTab - UI 横幅:src/sections/layout/interception-banner.tsx(v0.10.37 新建)

用户感知的现象

用户截图:Google 弹出"请选择包含的所有图片"验证码(URL: google.com/sorry/index),问:

"请告诉我如何交互,要弹窗的吧?任务要暂停的吧?这个优先级要高"

根因 / 现状盘点

已有但不到位

已有 缺失
① 检测:looksInterceptedUrl + looksIntercepted ✗ STATUS_META 缺 'intercepted' → chip 显示"进行中"误导
② 上报:reportInterception → onInterception ✗ 应用内无横幅,仅 browser.notifications 系统通知(切窗口易错过)
③ 暂停:globalStatus='intercepted' + manageQueue 检测后 return(已防再撞) ✗ openVerifyTab 又开 maps 新 tab(用户面前已有 sorry 页)
✗ 恢复要逐个任务点"继续",没有"我已验证完"一键按钮
✗ 用户验证完 sorry 跳走,无自动监听 → 用户得手动操作
✗ 立即恢复后可能立即再撞墙,需要冷却期

修复方案(双轨)

Phase 1 — 立即可见的反馈(A/B/C)

  1. task-view.tsx STATUS_META 加 'intercepted'(红色 ⚠️ 被拦截 chip)
  2. 类型扩展:Record<TaskStatus | 'intercepted', ...> —— 因为 'intercepted' 不在 TaskStatus 联合
  3. chip 渲染:task.status === 'running' && progress.status === 'intercepted' → 显示 intercepted
  4. 新建 interception-banner.tsx
  5. 轮询 storage.scheduler.status 1.5s
  6. intercepted 时显示红色横幅 + 呼吸动画
  7. 两个按钮:[打开验证页] [我已验证完,继续]
  8. 应用内 sonner toast:边沿检测 idle → intercepted 弹 warning toast(避免重复弹)
  9. 集成到 main-layout:DataBar 上方插入 InterceptionBanner

Phase 2 — 复用 tab + 一键恢复 + 防再撞(D/E/F)

  1. openVerifyTab 改造:返回 tab id;优先级 1. 已有 sorry tab → 激活 2. 已有 maps tab → 激活 3. 都没才开新
  2. resumeAllIntercepted() 新方法:把所有 intercepted task 改 running + globalStatus=running + manageQueue
  3. manageQueue 防再撞:已有 if (globalStatus !== 'running') return(L310/347/498)—— 已防 ✅
  4. message 路由:'resume-all-intercepted' → resumeAllIntercepted

Phase 3 — 自动检测 + 冷却(G/H)

  1. background/index.ts 加 tabs.onUpdated 监听
  2. 仅在 isInterceptedNow() 且 tabId === verifyTabId 时
  3. changeInfo.url 跳走 sorry → 触发 resumeAllIntercepted
  4. 30s 冷却:resumeAllIntercepted 内部检查距 lastInterceptedAt 不足 30s → setTimeout 等齐 30s 再恢复(让 Google 消气)

改动文件

文件 改了什么
src/sections/layout/interception-banner.tsx 🆕 横幅 + toast 边沿 + 两个 action 按钮
src/sections/layout/main-layout.tsx 集成 InterceptionBanner
src/sections/task/task-view.tsx STATUS_META 加 intercepted + chip 渲染优先 progress.status
src/utils/scrape-window.ts openVerifyTab 改造:优先复用 sorry/maps tab
src/entrypoints/background/batch-controller.ts + resumeAllIntercepted / getVerifyTabId / isInterceptedNow + lastInterceptedAt + 30s cooldown
src/entrypoints/background/index.ts message 路由 resume-all-intercepted + tabs.onUpdated 监听 sorry 跳走
package.json 0.10.36 → 0.10.37

验证方式

  1. 制造拦截:跑大批量 atm 任务直到 Google 触发 sorry/index
  2. 预期反馈
  3. 系统通知弹出(已有)
  4. 应用内 sonner toast 一闪(新增)
  5. 主面板顶部红色呼吸横幅 + 任务卡 chip 变"⚠️ 被拦截"
  6. 点"打开验证页"按钮 → 激活已有 sorry tab(不开新 maps)
  7. 完成验证 → sorry tab url 跳走 → background 自动检测 → 30s 后自动恢复所有任务
  8. 或手动点"我已验证完,继续" → 立即触发恢复(仍走 30s 冷却以防 Google 立即再封)

如何避免再犯

  • 暴露给用户的状态枚举要全 —— STATUS_META 缺一个值 = chip 看不到该状态
  • 系统通知 + 应用内 toast 双轨 —— 用户切窗口/静音时系统通知静默,应用内横幅是兜底
  • tab 复用前先 query —— 不要假设需要新开,先看用户面前已有什么
  • 一键操作 > 多次点击 —— 一个事件触发的批量恢复,写一个 resumeAll API
  • 冷却期 > 立即恢复 —— 反爬场景下立即恢复 = 立即再撞,至少留 30s

相关问题

  • 同类风险:log-view 实开标签"含拦截"提示(v0.10.36 ISSUE 同期讨论)
  • 未来可加:拦截累计统计 chip 到 DataBar、拦截趋势 7 日图(用户深度反馈后再做)

已排除(v0.10.37 后复核)

  • ~~deep-scrape 抓官网遇 Cloudflare 拦截没触发横幅~~ 设计正确不是 bug
  • dynamic-scraper.ts:isCloudflareChallenge 已检测 CF "5 秒盾",最多重试 2 轮放行
  • 单网站 CF 拦截只影响该网站,不应停所有任务(与 Google maps 拦截全局停的语义不同)
  • 失败已写入 page-log(type: 'website' + status 码),日志「官网/社媒」tab 可见
  • 若未来需要"CF 拦截率"统计,再考虑独立 chip 显示