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()。
方法
LFNetwork.start(options?): Promise<NetworkState>开始监听(可配置 frameScope 与事件回调);已在监听时直接返回当前状态,本次传入的 frameScope 被忽略(事件回调仍会注册)
LFNetwork.stop(): Promise<NetworkState>停止监听
LFNetwork.isListening(): boolean是否正在监听
LFNetwork.getState(): NetworkState读取监听状态
LFNetwork.clear(): NetworkState清空已捕获的记录,返回清空后的状态快照(reason: 'clear',不触发 onStateChange)
LFNetwork.getRequest(requestId): RequestRecord | null按 requestId 取单条请求记录
LFNetwork.getRequests(): RequestRecord[]取全部已捕获请求
LFNetwork.getResponseBody(requestId): Promise<{ body, base64Encoded }>读取指定请求的响应体(未缓存时必须处于监听状态,stop() 后调用会 reject)
LFNetwork.onEvent / onRequest / onResponse / onFinished / onFailed(callback): () => void订阅事件(返回取消函数)
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' | 事件类别 |
method | string | 底层 CDP 方法名(如 Network.responseReceived) |
requestId | string | 请求 id,用于 getRequest / getResponseBody |
record | NetworkRequestRecord / null | 事件发生时的请求记录快照(深拷贝) |
raw | object | 原始 CDP 参数(深拷贝) |
事件对象本身没有 url 字段,URL 在 event.record.url。回调里抛出的同步异常只会打印到页面 console,不会中断监听,也不会上抛给调用方。
请求记录 NetworkRequestRecord 常用字段
| 字段 | 类型 | 说明 |
|---|---|---|
requestId / method / url | string | 基本信息(method 为实际发送的方法名:DELETE/GET/HEAD/OPTIONS/POST/PUT 会被浏览器规范为大写,小写 patch 等其他写法保持原样) |
status / statusText | number / null / string | 响应到达前 status 为 null |
resourceType | string | CDP 资源类型:XHR / Fetch / Document / Script / Image / Media 等 |
requestHeaders / responseHeaders | Record<string, string> | 请求/响应头 |
requestBody | string / null | POST 数据(CDP 捕获到时) |
mimeType / protocol / remoteIPAddress | string | 内容类型 / 协议(h2 等)/ 远端 IP |
servedFromCache / fromServiceWorker | boolean | 是否来自缓存 / Service Worker |
finished / failed / errorText / canceled | — | 完成/失败状态与错误文本 |
startedAtMs / finishedAtMs / durationMs | number / 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, ... }。
示例
// @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 拦截示例
// 入口头部需要声明: // @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 },
};
});