[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)¶
task-view.tsx STATUS_META加 'intercepted'(红色 ⚠️ 被拦截 chip)- 类型扩展:
Record<TaskStatus | 'intercepted', ...>—— 因为 'intercepted' 不在 TaskStatus 联合 - chip 渲染:
task.status === 'running' && progress.status === 'intercepted'→ 显示 intercepted - 新建
interception-banner.tsx: - 轮询
storage.scheduler.status1.5s - intercepted 时显示红色横幅 + 呼吸动画
- 两个按钮:[打开验证页] [我已验证完,继续]
- 应用内 sonner toast:边沿检测
idle → intercepted弹 warning toast(避免重复弹) - 集成到 main-layout:DataBar 上方插入 InterceptionBanner
Phase 2 — 复用 tab + 一键恢复 + 防再撞(D/E/F)¶
openVerifyTab改造:返回 tab id;优先级 1. 已有 sorry tab → 激活 2. 已有 maps tab → 激活 3. 都没才开新resumeAllIntercepted()新方法:把所有 intercepted task 改 running + globalStatus=running + manageQueue- manageQueue 防再撞:已有
if (globalStatus !== 'running') return(L310/347/498)—— 已防 ✅ - message 路由:'resume-all-intercepted' → resumeAllIntercepted
Phase 3 — 自动检测 + 冷却(G/H)¶
background/index.ts加 tabs.onUpdated 监听:- 仅在
isInterceptedNow()且 tabId === verifyTabId 时 - changeInfo.url 跳走 sorry → 触发 resumeAllIntercepted
- 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 |
验证方式¶
- 制造拦截:跑大批量 atm 任务直到 Google 触发 sorry/index
- 预期反馈:
- 系统通知弹出(已有)
- 应用内 sonner toast 一闪(新增)
- 主面板顶部红色呼吸横幅 + 任务卡 chip 变"⚠️ 被拦截"
- 点"打开验证页"按钮 → 激活已有 sorry tab(不开新 maps)
- 完成验证 → sorry tab url 跳走 → background 自动检测 → 30s 后自动恢复所有任务
- 或手动点"我已验证完,继续" → 立即触发恢复(仍走 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 显示