前端轮子(2)--diy响应数据

优雅 DIY 响应数据(XHR / fetch 拦截器)

前端轮子系列:

  1. 前端部署后-判断页面是否为最新
  2. 如何优雅diy响应数据
  3. 如何优雅进行webview调试
  4. 如何实现测试环境的自动登录

一个面向前端联调/测试的 Chrome 扩展:在 页面中的xhr\fetch请求,命中规则时直接返回自定义响应,从而"让业务代码以为请求成功了"。😂

你能得到什么

  • 按规则拦截 XHR:请求 URL 正则 + 方法(ANY/GET/POST...)匹配后返回自定义文本/JSON
  • DevTools 面板可视化管理:新增 / 编辑 / 删除 / 搜索 / 开关
  • 实时生效 :规则与开关存储在 chrome.storage.local,变更后同步到页面

一、快速开始(安装与使用)

1) 安装(开发者模式)

  1. 打开 Chrome 扩展管理页:chrome://extensions/
  2. 右上角开启 开发者模式
  3. 点击 加载已解压的扩展程序 ,选择 Interceptors/ 目录

2) 使用(DevTools 面板)

  1. 打开要调试的网站
  2. 打开 DevTools(F12)
  3. 切到面板 "XHR拦截器"
  4. 打开右上角 拦截开关
  5. 新增规则(pattern / method / response),保存即可生效

二、实现思路

1. 构建插件目录

如果是第一次开发浏览器插件看过来 官方的 hello world

  1. manifest.json 文件 => 定义插件的权限、内容脚本、web_accessible_resources 等
  2. devtools.html 文件 => 定义插件的 DevTools 入口页面
  3. devtools.js 文件 => 创建 DevTools 面板
  4. panel.html 文件 => 定义插件的面板页面
  5. panel.js 文件 => 定义插件的面板脚本
  6. scripts/background.js 文件 => 定义插件的后台脚本 / 内容注入逻辑
  7. scripts/page-inject.js 文件 => 定义插件的页面上下文拦截逻辑

2. 建立插件与页面的通信

因为插件和页面是不同的域,所以需要建立插件与页面的通信

  1. 在页面中注入 page-inject.js 脚本
    • 负责拦截、重写页面中的 XMLHttpRequest 请求
    • 通过 监听 message 事件 接收插件的规则、自定义返回内容
    • 匹配到规则后,构建响应数据并返回数据(阻止请求的发送)
  2. 在插件中注入 background.js 脚本 => 负责 插件与页面通信 => 通过 postMessage 发送配置到页面
    • 通过 chrome.storage.onChanged 监听 storage 变化
    • 当 storage 变化时,发送配置到页面

三、具体实现(上菜)

1. 构建项目

sh 复制代码
mkdir interceptor && cd interceptor
  1. manifest.json 文件 => 定义插件的权限、内容脚本、web_accessible_resources 等 声明:版本、名字、描述、权限、主机权限、脚本、资源访问权限
json 复制代码
{
    "manifest_version": 3,
    "name": "XHR Interceptor",
    "description": "通过可配置的规则拦截并替换 XHR/fetch 请求结果。",
    "version": "1.0.0",
    "permissions": ["storage"],
    "host_permissions": ["<all_urls>"],
    "devtools_page": "devtools.html",
    "content_scripts": [
      {
        "matches": ["<all_urls>"],
        "js": ["scripts/background.js"],
        "run_at": "document_start"
      }
    ],
    "web_accessible_resources": [
      {
        "resources": ["scripts/page-inject.js"],
        "matches": ["<all_urls>"]
      }
    ]
  }
  1. devtools.html 文件 => 定义插件的 DevTools 入口页面
html 复制代码
  <!DOCTYPE html>
  <html lang="zh-CN">
  <head>
      <meta charset="UTF-8" />
  </head>
  <body>
      <!-- 引入 devtools.js 文件 => 创建面板 -->
      <script src="devtools.js"></script>
  </body>
  </html>
  1. devtools.js 文件 => 创建 DevTools 面板 panels面板
js 复制代码
chrome.devtools.panels.create(
  "XHR拦截器",
  null,
  "panel.html",
  (panel) => {
    if (panel) console.log("[XHR Interceptor] DevTools panel created.");
  }
);
  1. panel.html 文件 => 定义插件的面板页面
html 复制代码
  <!DOCTYPE html>
  <html lang="zh-CN">
  <head>
      <meta charset="UTF-8" />
      <title>XHR Interceptor</title>
      <!-- 样式文件引入 -->
      <link rel="stylesheet" href="panel.css" />
  </head>
  <body>
      <!-- 插件面板样式构建 -->
      <!-- 引入 panel.js 文件 => 定义面板交互逻辑 -->
      <script src="panel.js" type="module"></script>
  </body>
  </html>

2. 注入js脚本到页面

  1. background.js 中 注入 page-inject.js 脚本到页面
js 复制代码
const PAGE_SCRIPT = chrome.runtime.getURL("scripts/page-inject.js");

function injectPageScript() {
  const script = document.createElement("script");
  script.src = PAGE_SCRIPT;
  script.type = "text/javascript";
  script.addEventListener("load", () => script.remove());
  script.addEventListener("error", () => script.remove());
  document.documentElement.appendChild(script);
}
  1. background.js 中 监听 storage 变化
js 复制代码
const MESSAGE_SOURCE = "xhr-interceptor-extension";
chrome.storage.onChanged.addListener((changes, namespace) => {
  // 当 storage 变化时,发送配置到页面
  postConfigToPage({});
});

function postConfigToPage(config) {
  // 直接使用 window.postMessage:content script 与页面共享同一个 window 消息通道,
  // 且不会触发页面的 CSP(避免内联脚本被阻止)。
  window.postMessage(
    {
      source: MESSAGE_SOURCE,
      type: "config:update",
      payload: config || buildConfig(),
    },
    "*"
  );
}

3. 重写 XHR、fetch 方法

js 复制代码
// 保存原始方法
const ORIGINAL = {
    open: XMLHttpRequest.prototype.open,
    send: XMLHttpRequest.prototype.send,
    fetch: window.fetch ? window.fetch.bind(window) : null,

};

function hookXMLHttpRequest() {
    XMLHttpRequest.prototype.open = function patchedOpen(method, url) {
        // 做一些标记、额外操作
        return ORIGINAL.open.apply(this, arguments);
    };

    XMLHttpRequest.prototype.send = function patchedSend(body) {
        // 检查 开关是否打开、规则是否匹配
        // 如果匹配,构建响应数据并返回
        // 如果不匹配,继续发送请求
        return ORIGINAL.send.apply(this, arguments);
    };
}

  function hookFetch() {
    if (!ORIGINAL.fetch) return;

    window.fetch = function patchedFetch(input, init) {
      // 解析 method + url,然后复用 pickMatchingRule 进行匹配
      const meta = extractFetchMeta(input, init);
      const rule = pickMatchingRule(meta.method, meta.url);
      if (!enabled || !rule) return ORIGINAL.fetch(input, init);

      const res = buildMockFetchResponse(rule);
      return Promise.resolve(res);
    };
  }

4. 规则列表匹配

js 复制代码
let rules = []; // 保存规则列表
function pickMatchingRule(method, url) {
    if (!url) return null;
    const upperMethod = (method || "GET").toUpperCase();
    for (const rule of rules) {
      if (!rule.regex) continue;
      if (rule.method !== "ANY" && rule.method !== upperMethod) continue;
      rule.regex.lastIndex = 0;
      if (rule.regex.test(url)) {
        return rule;
      }
    }
    return null;
  }

5. 监听消息 & 同步面板数据、规则到页面

接收插件的规则、自定义返回内容(从 background.js 发送的消息)

js 复制代码
const script = document.currentScript;
const messageSource =
    (script && script.dataset && script.dataset.source) ||
    "xhr-interceptor-extension";

// 监听消息通道
function setupMessageChannel() {
    window.addEventListener("message", (event) => {
        if (event.source !== window) return;
        if (!event.data || event.data.source !== messageSource) return;
        if (event.data.type === "config:update") {
            // 判断为background.js发送的消息
            // 更新插件面板的数据(开关的状态、规则的数据)
        }
    });
}

6. 面板存储规则到storage & 处理ui交互

chrome.storage

  1. staorage 的读取、设置、移除
js 复制代码
// 读取
 const stored = await chrome.storage.local.get({ rules: [] })

// 设置
await chrome.storage.local.set({ rules });

// 移除 可以传 字符串 | 数组
await chrome.storage.local.remove("rules");

// 清空
await chrome.storage.local.clear();
  1. 面板的ui交互 正常的ui交互,正常的原生开发就行

源码

xiaoyi1255

结语

如果本文对你有收获,麻烦动动发财的小手,点点关注、点点赞!!!👻👻👻

因为收藏===会了

如果有不对、更好的方式实现、可以优化的地方欢迎在评论区指出,谢谢👾👾👾

相关推荐
EnCi Zheng8 分钟前
M5-markconv自定义CSS样式指南 [特殊字符]
前端·css·python
kyriewen12 分钟前
你的网页慢,用户不说直接走——前端性能监控教你“读心术”
前端·性能优化·监控
广州华水科技13 分钟前
北斗GNSS变形监测在大坝安全监测中的应用与优势分析
前端
前端老石人24 分钟前
前端开发中的 URL 完全指南
开发语言·前端·javascript·css·html
CAE虚拟与现实24 分钟前
五一假期闲来无事,来个前段、后端的说明吧
前端·后端·vtk·three.js·前后端
Sarvartha35 分钟前
三目运算符
linux·服务器·前端
晓晨的博客42 分钟前
ROS1录制的bag包转换为ROS2格式
前端·chrome
Wect1 小时前
LeetCode 72. 编辑距离:动态规划经典题解
前端·算法·typescript
donecoding1 小时前
别再让 pnpm 跟着 nvm 跑了!独立安装终极指南
前端·node.js·前端工程化
不可能的是1 小时前
从 /simplify 指令深挖 Claude Code 多 Agent 协同机制
javascript