先上效果图:

功能简介:
选择被测试的 tab,
1.然后点击打开我们的 plugin,然后会有如上述截图的一个 POPUP 展示出来,
2.然后点击开始捕获请求,
3.然后在当前的页面进行页面操作,插件会开始捕获用户操作期间中的DocumentFetchXHRJavaScriptCSS等的返回值,
4.然后 service-worker 中使用正则表达式,来统一匹配,是否会有命中,然后将结果写入到storage
5.然后打开插件 POPUP 界面,点击加载检查结果,即可以看到相关敏感词结果.

下面我先把核心代码罗列一下,后面找个时间系统的介绍下 plugin dev 的方法和我的经验
1.开始捕获请求,即 attach chrome.debugger 到被测的 tab 下,这里会涉及到 popup 和 service-worker 的通信,后续文章会讲到,如果想提前了解,可以参考https://developer.chrome.com/docs/extensions/mv3/messaging/

popup.js
这里通过 chrome.runtime.sendMessage 告诉 service-worker, 参数有 action,值可以自己定义和处理,然后 tab 是哪一个

async function showBg() {
    let [tab] = await chrome.tabs.query({
        active: true,
        currentWindow: true
    });
    console.log("homepage tab id " + tab.id)

    chrome.runtime.sendMessage({"action":"start", "tab": tab}, (response) => {
        console.log("popup send message success")
        console.log("response:" + response)
    });
}

service-worker.js
通过监听事件chrome.runtime.onMessage来获取 popup 发来的消息,然后通过chrome.debugger.attachattach debugger, 并且开启Network Domain,为后续 network 请求做分析

chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
    const {action, tab} = request;
    if (action === "start") {
        if (tab.url.startsWith('http')) {
            currentPageUrl = tab.url;
            chrome.debugger.attach({tabId: tab.id}, '1.2', function () {
                chrome.debugger.sendCommand(
                    {tabId: tab.id},
                    'Network.enable',
                    {},
                    function () {
                        if (chrome.runtime.lastError) {
                            console.log(chrome.runtime.lastError);
                        }
                    }
                );
            });
        } else {
            console.log('Debugger can only be attached to HTTP/HTTPS pages.');
        }
    } 
    sendResponse("content got!")
})

然后再介绍下如何获取不同资源的响应值
service-worker.js
要点是
1.通过chrome.debugger.onEvent监听 Network 相关事件
2.通过 method 为Network.responseReceived来获取 responseReceived 通知
3.通过chrome.debugger.sendCommand发送 method 为Network.getResponseBody来获取实际响应值
4.通过正则表达式完成敏感词检测
5.将结果保存到storage中,供 popup 使用

let checkTypes = ['xhr', 'fetch', 'stylesheet', 'document', 'script'];

chrome.debugger.onEvent.addListener(async function (source, method, params) {
    if (method === 'Network.responseReceived') {
        // console.log('Network.responseReceived', source, method, params)
        if (checkTypes.includes(params.type.toLowerCase())) {
            pendingRequests[params.requestId] = { status: true, type: params.type, url: params.response.url };
        }
    } else if (
        method === 'Network.loadingFinished' &&
        pendingRequests[params.requestId] !== undefined
    ) {
        // console.log('Network.loadingFinished', source, method, params);
        getResponseBody(
            source.tabId,
            params.requestId,
            pendingRequests[params.requestId]
        );
        delete pendingRequests[params.requestId];
    }
});

function getResponseBody(tabId, requestId, requestInfo) {
    chrome.debugger.sendCommand(
        { tabId: tabId },
        'Network.getResponseBody',
        { requestId: requestId },
        async function (response) {
            // console.log(requestInfo.type + ' response', response.body);
            let tab = await getCurrentTab();
            currentPageUrl = tab.url;
            // 首先检查是否已经检查过当前的response
            let requestUrl = requestInfo.url;
            let newKeyValue = {}
            let checkResult = {};
            let isExits = undefined;
            // 感觉这个处理有点问题,这里用chrome tabAPI来获取当前tab的url
            let result = await chrome.storage.local.get(["key"]);

            isExits = getNestedValue(result.key, currentPageUrl + splitChar + requestUrl)
            console.log("isExits:", isExits)
            if (isExits !== undefined && oldSensitiveWordList === sensitiveWordList) {
                console.log("requestUrl:", requestUrl, "type:", requestInfo.type, "already has return")
                return;
            } else {
                console.log("result.key:", result.key)
                newKeyValue = result.key === undefined ? {} : result.key;
            }

            console.log("newKeyValue start:", newKeyValue)

            // 进行敏感词的校验

            let checkResponse = check(response.body, sensitiveWordList);
            if (checkResponse === undefined) {
                //  标记处理过了,并且没有敏感词
                checkResult = {
                    "hasSensitiveWordList": false,
                    "result": {}
                }
            } else {
                checkResult = {
                    "hasSensitiveWordList": true,
                    "result": checkResponse
                }
            }
            checkResult["page"] = currentPageUrl;
            checkResult["url"] = requestUrl;
            checkResult["type"] = requestInfo.type;
            // 保存到storage中
            setNestedValue(newKeyValue, currentPageUrl + splitChar + requestUrl, checkResult, splitChar);
            console.log("setNestedValue:", newKeyValue)
            await chrome.storage.local.set({"key": newKeyValue});
        }
    );
}

以上便是核心的逻辑代码,然后还有很多优化点,我还需要慢慢修改,有兴趣的小伙伴们可以一起讨论哦
我贴上我自己的 repo 地址给大家
https://github.com/MRJC92/react-crx-2023


↙↙↙阅读原文可查看相关链接,并与作者交流