直接代理 window 无法劫持 fetch,因其是 writable: false、configurable: false 的原生方法;需用 Object.defineProperty 覆盖并调用 originalFetch.apply(this, args),响应需 clone 避免 body 流锁定。

直接代理 window 或 globalThis 无法劫持 fetch,必须重定义 fetch 函数本身。
为什么不能用 Proxy 代理 window 来拦截 fetch
因为 fetch 是挂载在 window 上的不可写(writable: false)、不可配置(configurable: false)原生方法。对 window 做 new Proxy(window, {}) 不会生效,甚至在 iframe 等环境中触发 TypeError: Cannot create proxy with a non-object as target。Proxy 只能代理标准对象,而 window 在部分上下文里根本不是可代理对象。
如何安全替换原生 fetch 并保留原始行为
核心是用 Object.defineProperty 覆盖 window.fetch,并在新函数中显式调用原始 fetch,同时确保 this 和参数传递正确:
安装并运行 Aperture(Lightning Labs 的 L402 闪电反向代理),用于创建 L402 付费墙、配置付费 API 端点、为其他代理托管付费内容或测试 L402 认证流程。
- 先缓存原始
fetch:const originalFetch = window.fetch; - 用
Object.defineProperty重写,避免被某些框架(如 React DevTools)意外还原 - 调用时必须用
originalFetch.apply(this, args),不能只写originalFetch(args)——后者会丢失this绑定,虽然fetch不依赖this,但统一写法可避免后续扩展出错 - 在调用前后插入日志、计时、Trace ID 注入等逻辑,不干扰返回值结构
常见错误:劫持后 Promise 链断裂或 response 无法 clone
劫持后返回的 Response 对象如果被多次读取(如 res.json() + res.text()),会抛 TypeError: Failed to execute 'json' on 'Response': body stream is locked。这是因为原生 fetch 返回的 Response body 是单次流。
- 不要直接 return
originalFetch(...)的原始响应;需手动克隆并注入元数据 - 推荐做法:在
then中拿到Response后,用response.clone()备份一份用于日志采集,再把原始response返回给业务代码 - 若需修改响应头,只能在 Service Worker 中做;客户端 JS 无法修改已发出的
Response.headers
兼容性与部署注意事项
该劫持方式在主流浏览器(Chrome/Firefox/Safari/Edge ≥ v80)均有效,但要注意以下几点:
- 必须在页面最早期执行(如放在
<head>内的<script>),晚于第三方 SDK(如 Sentry、Bugsnag)可能被覆盖 - 不适用于 Web Worker,因其无
window,需单独处理self.fetch - 某些安全策略(如 CSP 的
script-src 'unsafe-eval'限制)不影响此方案,因未使用eval或内联字符串函数 - 若项目用了
webpack的externals或module federation,注意全局作用域是否被隔离
真正难的不是写几行 Proxy,而是确保劫持后的 fetch 行为和原生完全一致——尤其是 Response.body 的流状态、redirect 语义、signal 中断传播,这些细节一旦错位,就会导致业务请求静默失败或重复触发。

















