跳转到正文
返回门户

LFNetwork ​

全局变量:LFNetwork · 启用方式:// @require network

基于 CDP 的网络监听:观察当前标签页的请求/响应并读取响应体。三种页面运行环境均可用:已保存 bundle、liveDebug 主路径(USER_SCRIPT 世界),以及 run --tab MAIN world 临时脚本(编辑器会自动安装 ISOLATED 世界网络中继桥)。后台应用(// @match background / run --target background)与不带 --tab 的 run 没有标签页,start() 与 getResponseBody() 会立即抛出 error.code = 'LF_NETWORK_NO_TAB' 的错误。@require dom 只在运行时为 LFDOM 注入 network,源码直接调用 LFNetwork 须自己声明 @require network。

监听期间 LFNetwork 通过 chrome.debugger 附加到标签页,浏览器会显示「正在调试此浏览器」横幅(会改变视口高度)。进程结束 / liveDebug 热重载时会自动 stop()。

方法 ​

ts
LFNetwork.start(options?): Promise<NetworkState>

开始监听(可配置 frameScope 与事件回调);已在监听时直接返回当前状态,本次传入的 frameScope 被忽略(事件回调仍会注册)

ts
LFNetwork.stop(): Promise<NetworkState>

停止监听

ts
LFNetwork.isListening(): boolean

是否正在监听

ts
LFNetwork.getState(): NetworkState

读取监听状态

ts
LFNetwork.clear(): NetworkState

清空已捕获的记录,返回清空后的状态快照(reason: 'clear',不触发 onStateChange)

ts
LFNetwork.getRequest(requestId): RequestRecord | null

按 requestId 取单条请求记录

ts
LFNetwork.getRequests(): RequestRecord[]

取全部已捕获请求

ts
LFNetwork.getResponseBody(requestId): Promise<{ body, base64Encoded }>

读取指定请求的响应体(未缓存时必须处于监听状态,stop() 后调用会 reject)

ts
LFNetwork.onEvent / onRequest / onResponse / onFinished / onFailed(callback): () => void

订阅事件(返回取消函数)

ts
LFNetwork.onStateChange(callback): () => void

订阅监听状态变化

参数详解 ​

start(options?) ​

选项类型默认值说明
frameScope'page' / 'tab''page'page 派发本脚本主 frame 及其所有子 iframe(含跨域 iframe)的请求,无 frame 归属的请求(如 worker)也会放行;与 tab 的差别主要在于:附加时尚未登记到 frame 树的新 frame 的请求会被保守丢弃。从子 iframe 调用 start() 时自动退化为 'tab';非法值按 'page' 处理
onEvent / onRequest / onResponse / onFinished / onFailed(event) => void—事件回调。强烈建议在 start 时传入而不是 start 后再 onXxx() 注册——两步式会与附加后立刻到达的首批事件竞态而丢事件
onStateChange(state) => void—监听状态变化回调

start() 失败最常见的原因是该标签页已附加了其他调试器(如 DevTools)——关掉该页的 DevTools 再试。

事件对象 LFNetworkEvent ​

字段类型说明
type'request' / 'response' / 'finished' / 'failed'事件类别
methodstring底层 CDP 方法名(如 Network.responseReceived)
requestIdstring请求 id,用于 getRequest / getResponseBody
recordNetworkRequestRecord / null事件发生时的请求记录快照(深拷贝)
rawobject原始 CDP 参数(深拷贝)

事件对象本身没有 url 字段,URL 在 event.record.url。回调里抛出的同步异常只会打印到页面 console,不会中断监听,也不会上抛给调用方。

请求记录 NetworkRequestRecord 常用字段 ​

字段类型说明
requestId / method / urlstring基本信息(method 为实际发送的方法名:DELETE/GET/HEAD/OPTIONS/POST/PUT 会被浏览器规范为大写,小写 patch 等其他写法保持原样)
status / statusTextnumber / null / string响应到达前 status 为 null
resourceTypestringCDP 资源类型:XHR / Fetch / Document / Script / Image / Media 等
requestHeaders / responseHeadersRecord<string, string>请求/响应头
requestBodystring / nullPOST 数据(CDP 捕获到时)
mimeType / protocol / remoteIPAddressstring内容类型 / 协议(h2 等)/ 远端 IP
servedFromCache / fromServiceWorkerboolean是否来自缓存 / Service Worker
finished / failed / errorText / canceled—完成/失败状态与错误文本
startedAtMs / finishedAtMs / durationMsnumber / null毫秒时间戳与耗时
body / bodyLoaded{ body: string, base64Encoded: boolean } / null / boolean调用过 getResponseBody() 后缓存的响应体

记录保留上限 1000 条,最旧的先被淘汰;getRequest / getRequests 返回深拷贝,可放心修改。getResponseBody(requestId) 返回 { body: string, base64Encoded: boolean }——base64Encoded 为 true 时 body 是 base64 编码的二进制;响应体在 finished 事件后才完整,建议在 onFinished 里读取。body 并非对每个请求都可用(未加载完、已从缓存淘汰、204/重定向等无响应体时 reject);已缓存的 body 直接返回,否则必须处于监听状态;单次命令超时 10 秒;错误对象带 requestId、rawError,以及可能存在的 CDP 错误码 code。getState() 返回 { listening, connected, tabId, requestCount, frameScope, lastError, ... }。

示例

js
// @require network

await LFNetwork.start({
  onFinished: async (event) => {
    if (!event.record || !event.record.url.includes("/api/orders")) return;
    const { body, base64Encoded } = await LFNetwork.getResponseBody(event.requestId);
    if (!base64Encoded) LFRuntime.print("orders payload", JSON.parse(body));
  },
});

与 LFRuntime.network 的区别 ​

LFNetwork 是「旁路观察」:看请求、读响应体,不改变页面行为。LFRuntime.network 是「在线拦截」:mock 或替换页面自己的 fetch/XHR 响应。只拦截页面自身 JS 发起的请求,应用自己调用的 fetch 与 LFHttp 不经过。已安装应用必须在入口头部声明 @intercept <url-pattern>(正式 Compile 会校验,与运行时使用同一解析器:写在 "use strict" 等前置语句之后也能识别;// @intercept "" 这类空值视为未声明);run --tab 与 liveDebug 会自动注入拦截宿主,调试期可不声明。

拦截回调返回 { body, init? }:

  • 省略 init 时按 { status: 200, statusText: 'OK', headers: { 'Content-Type': 'application/json' } } 构造,原响应的状态码与响应头会丢失;需要保留时传 init: { status: response.status, headers: response.headers }。init.headers 可以是对象、[name, value][] 数组或 Headers 实例。
  • body 可以是字符串、ArrayBuffer、TypedArray 或 DataView(二进制原样送达页面);普通对象/数组会被 JSON.stringify;Blob / FormData / URLSearchParams / ReadableStream 不支持——会被拒绝并在页面 console 报错,该请求按原样放行。
  • 回调收到的 request.body / response.body 恒为字符串,bodyType 为 'base64' 时是二进制的 base64,不能直接 JSON.parse。

LFRuntime.network 拦截示例

js
// 入口头部需要声明: // @intercept /api/orders

LFRuntime.network.onResponse("/api/orders", (record) => {
  LFRuntime.print("orders response", record.response.body);
});

LFRuntime.network.interceptResponse("/api/orders", ({ response }) => {
  if (response.bodyType === "base64") return; // 返回 undefined 保持原响应
  const data = JSON.parse(response.body);
  data.items = data.items.filter((item) => item.status !== "hidden");
  return {
    body: JSON.stringify(data),
    init: { status: response.status, headers: response.headers },
  };
});