前端轮子(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

结语

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

因为收藏===会了

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

相关推荐
前天的五花肉2 小时前
D3.js研发Biplot(代谢)图
前端·javascript·css
董世昌412 小时前
JavaScript 中 undefined 和 not defined 的区别
java·服务器·javascript
oh,huoyuyan2 小时前
【实用技巧】火语言RPA:界面『日期时间』控件,实现网页日期自动填写
前端·javascript·rpa
程序员小寒2 小时前
前端性能优化之Webpack篇
前端·webpack·性能优化
谢尔登2 小时前
React的Fiber架构
前端·react.js·架构
我是华为OD~HR~栗栗呀2 小时前
(华为od)21届-Python面经
java·前端·c++·python·华为od·华为·面试
刘一说2 小时前
ES6+核心特性全面浅析
java·前端·es6
i_am_a_div_日积月累_2 小时前
el-tree半选回显问题;el-tree获取半选节点id
javascript·vue.js·elementui
kirinlau2 小时前
Vue.observable实现vue原生轻量级状态管理详解
前端·javascript·vue.js