
如何优雅拦截 fetch 与 SSE 流Claude Counter 注入层 bridge.js 源码实战【免费下载链接】claude-counterA minimal browser extension that shows token count, cache timer, and usage bars on claude.ai.项目地址: https://gitcode.com/gh_mirrors/cl/claude-counterClaude Counter 是一款轻量级浏览器扩展在 claude.ai 页面上实时显示 token 数、缓存倒计时和用量进度条。它实现这些功能的秘诀正是优雅地拦截 fetch 请求与 SSEServer-Sent Events流式响应——整个注入层由一个仅约 180 行的 bridge.js 完成。本文将带你读懂这套注入层 消息桥的经典架构新手也能轻松理解浏览器扩展拦截网络请求的完整套路。 为什么需要注入层Content Script 的权限盲区很多新手会踩第一个坑直接在 content script 里写window.fetch ...发现页面自己的请求根本拦不到。原因很简单——content script 运行在隔离世界isolated world它有独立的window对象。页面应用调用的是它自己世界里的fetch两者互不可见。Claude Counter 的解法非常干净通过script标签把 bridge.js 注入到页面主世界main world注入动作由 bridge-client.js 中的injectBridgeOnce()完成用runtime.getURL(src/injected/bridge.js)取出脚本地址且靠getElementById检查保证只注入一次SPA 不刷新页面脚本不能重复跑manifest.json 中把该文件声明为web_accessible_resources这是脚本能被页面加载的前提。一句话总结隔离世界负责搭桥主世界负责偷听网络。⏱️ 抢在框架之前先存原件再包一层拦截网络请求最忌讳包了又包、丢了原件。bridge.js 在文件最开头就做了两件关键动作const originalFetch window.fetch; // 抢在任何人之前保存原件 const originalPushState history.pushState.bind(history);随后再给window.fetch包一层 async 代理。这里有个容易忽略的细节包fetch的同时还包了history.pushState / replaceState——因为 claude.ai 是单页应用切换会话时 URL 会变但页面不刷新而前端框架可能会提前缓存这些方法。bridge.js 抢在框架初始化前包裹它们每次导航就派发一个cc:urlchange自定义事件main.js 侧监听该事件即可及时刷新 token 统计。 核心原则注入脚本一定要尽早执行保存原始引用再用调用原件 旁路处理的方式包装。 优雅拦截 fetch只挑自己关心的请求包裹后的fetch代理并不是无脑处理所有请求而是精准识别三类流量请求特征识别条件用途生成开始信号POST且 URL 含/completion或/retry_completion派发cc:generation_startUI 开始显示缓存倒计时SSE 流式响应content-type含event-stream进入流解析提取实时用量会话树数据URL 含/chat_conversations/且带tree参数用正则提取orgId与conversationId供 token 统计注意 bridge.js 第 70-78 行 的toAbsoluteUrl()fetch的入参可能是字符串、URL或Request对象三种形态都做了归一化——这种防御性处理是拦截层代码的标配。 解析 SSE 流克隆响应 逐行缓冲这是整篇文章最值得学的一段。页面正在消费一个流式响应你不能直接读它的 body会掐断页面的流。Claude Counter 的做法是response.clone()克隆一份响应让页面和插件各读各的在克隆体上调用body.getReader()拿流读取器用TextDecoder带{ stream: true }增量解码解决分片恰好把一个data:行切成两半的问题按\r\n|\r|\n切行最后一行不完整就留在 buffer 等下一片只挑data:开头的行解析 JSON命中type message_limit时通过消息桥转发出去。对应源码在 handleEventStream。这段流解析拿到的message_limit是未四舍五入的精确用量比 Claude 官方/usage页面的取整百分比更准——这正是插件进度条更精确的来源。 消息通道两个世界如何对话注入脚本主世界与 content script隔离世界之间没有共享变量只能靠window.postMessage。Claude Counter 定义了一套极简协议统一标记所有消息都带cc: ClaudeCounter字段双方收到消息先校验标记避免误吞页面其他脚本的 postMessage事件单向推送如cc:generation_start、cc:conversation、cc:message_limitcontent script 侧用bridge.on(type, fn)订阅请求-响应配对BridgeClient.request() 生成requestId把 Promise 的 resolve/reject 存进_pendingMap等cc:response回来时按 id 取走结算并附带超时自动清理。bridge.js 侧收到cc:request后按kind分发目前支持三种任务见 消息监听器hash用页面世界的crypto.subtle计算 SHA-256 摘要content script 拿不到该 APIusage以credentials: include调用/usage接口借用页面 Cookie 身份conversation拉取会话树 JSON 并转发。️ 容错哲学插件绝不能拖垮宿主页面翻遍 bridge.js 源码会发现大量try/catch且捕获后一律静默降级ignore parse failures、best-effort; dont break claude.ai。再对比 main.js 中刷新失败也只是 return 不报错——这体现了一个成熟扩展的底线思维拦截层旁路失败不影响主流程clone 解析失败跳过页面请求照常返回SSE 读取全程尽力而为任何异常都吞掉所有数据本地处理不上传任何外部服务器见 README 的 Privacy 一节。 动手看源码推荐阅读路径想完整走一遍数据流按这个顺序读src/injected/bridge.js —— 注入层fetch 拦截 SSE 解析 请求分发src/content/bridge-client.js —— 客户端注入脚本 消息桥src/content/main.js —— 调度层URL 变化、用量刷新、SSE 事件消费manifest.json —— 权限与资源声明另外不想装扩展的同学还可以直接看油猴版实现 userscript/claude-counter.user.js它把注入逻辑直接内嵌进脚本适合对比两种分发形态的差异。✅ 总结拦截 fetch 与 SSE 的四个黄金动作一句话记住全文尽早保存原始引用originalFetch再包一层异步代理精准识别目标流量用 URL/方法/content-type过滤别处理无关请求response.clone()getReader()无损旁读 SSE 流注意分片缓冲postMessage requestId 协议打通隔离世界并用标记字段防串扰。Claude Counter 用不到 200 行代码展示了浏览器扩展网络拦截的完整范式无论是做 AI 用量监控、接口调试工具还是页面增强插件这套注入层 消息桥的架构都值得直接借鉴。【免费下载链接】claude-counterA minimal browser extension that shows token count, cache timer, and usage bars on claude.ai.项目地址: https://gitcode.com/gh_mirrors/cl/claude-counter创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考