irpas技术客

【Chrome扩展程序】利用 background 实现跨域 fetch 访问_NXY666_chrome 扩展 跨域

irpas 5451

一、问题起因

????????虽然谷歌在官方文档中声称 content_script 允许无限制的跨域访问,但实际上经过版本更新该特性已经被禁用,因此我们只能通过间接的方式实现跨域访问。

content_script 的跨域问题https://blog.csdn.net/NXY666/article/details/124309832 二、源代码

./manifest.json(插件清单v3)

{ "manifest_version": 3, "name": "Expample", "version": "0.0.1", "description": "示例", "permissions": [], "host_permissions": [ "https://·/" ], "icons": { "256": "icon.png" }, "author": "NXY666", "background": { "service_worker": "background.js", "type": "module" }, "content_scripts": [ { "matches": ["https://·/*"], "js": [ "./script.js" ], "all_frames": true, "run_at": "document_idle" } ] }

./js/http.js(基于fetch的http工具)

export const http = { request: function (options) { // Post请求选项并入默认选项 let requestOptions = { method: null, url: null, param: {}, data: {}, headers: {} }; this.mergeOptions(requestOptions, options); // 格式化参数 requestOptions.param = this.formatParams(requestOptions.param); let _url = requestOptions.url + (requestOptions.param ? ('?' + requestOptions.param) : ''); let _data = requestOptions.data; if (typeof _data == "string") { requestOptions.headers["Content-type"] = "text/plain;charset=utf-8"; _data = requestOptions.data; } else if (requestOptions.data instanceof FormData) { _data = requestOptions.data; } else if (typeof requestOptions.data == "object") { let formData = new FormData(); if (Object.keys(requestOptions.data).some(key => { formData.append(key, requestOptions.data[key]); return requestOptions.data.hasOwnProperty(key) && requestOptions.data[key] instanceof File; })) { _data = formData; } else { requestOptions.headers["Content-type"] = "application/json;charset=utf-8"; _data = JSON.stringify(requestOptions.data); } } // 监听状态 let fetchOptions = { method: requestOptions.method, // *GET, POST, PUT, DELETE, etc. // mode: 'no-cors', // no-cors, *cors, same-origin cache: 'default', // *default, no-cache, reload, force-cache, only-if-cached credentials: 'include', // include, *same-origin, omit headers: requestOptions.headers, redirect: 'follow', // manual, *follow, error referrerPolicy: 'no-referrer-when-downgrade' // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url }; if (requestOptions.method.toUpperCase() !== "GET" && requestOptions.method.toUpperCase() !== "HEAD") { fetchOptions.body = _data; } return fetch(_url, fetchOptions); }, get: function (options) { options.method = "GET"; return this.request(options); }, post: function (options) { options.method = "POST"; return this.request(options); }, formatParams: function (data) { const arr = []; for (let name in data) { arr.push(encodeURIComponent(name) + "=" + encodeURIComponent(data[name])); } return arr.join("&"); }, // 原则:如果有默认值,则使用默认值,否则使用传入的值。 mergeOptions: function (targetOption, newOption) { if (!newOption) { return targetOption; } Object.keys(targetOption).forEach(function (key) { if (newOption[key] === undefined) { return; } targetOption[key] = newOption[key]; }); return targetOption; } };

./background.js(后台脚本)

import {http} from './js/http.js'; console.log('background.js'); function packMsgRep(state, data, message) { return { state, uuid: message.uuid, data, timestamp: Date.now() }; } async function parseHttpResponse(response) { if (response == null) { return { status: -2, statusText: null, body: null }; } else if (response instanceof Error) { return { status: -1, statusText: `${response.name}: ${response.message}`, body: response.stack }; } else { return { status: response.status, statusText: response.statusText, body: await response.text() }; } } chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { new Promise(async (resolve, reject) => { if (typeof message != 'object' || !message.type) { console.error("消息格式不符合规范:", message); reject(`消息 ${JSON.stringify(message)} 格式不符合规范。`); return; } switch (message.type) { case 'FetchRequest': { http.request(message.data).then(response => { resolve(parseHttpResponse(response)); }).catch(error => { reject(parseHttpResponse(error)); }); break; } case 'FetchGet': { http.get(message.data).then(response => { resolve(parseHttpResponse(response)); }).catch(error => { reject(parseHttpResponse(error)); }); break; } case 'FetchPost': { http.post(message.data).then(response => { resolve(parseHttpResponse(response)); }).catch(error => { reject(parseHttpResponse(error)); }); break; } default: { console.error("消息类型非法:", message); reject(`消息 ${message} 类型非法。`); break; } } }).then((response) => { sendResponse(packMsgRep(true, response, message)); console.log(`消息 ${JSON.stringify(message)} 处理完成。`); }).catch(e => { sendResponse(packMsgRep(false, e, message)); console.error(`消息 ${JSON.stringify(message)} 处理失败:`, e); }); return true; });

?./script.js(内容脚本)

function packMsgReq(type, data) { return { uuid: function () { return 'generate-uuid-4you-seem-professional'.replace( /[genratuidyosmpfl]/g, function (c) { const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); }(), type: type, data: data, timestamp: Date.now() }; } const http = { request: function (options) { return new Promise((resolve, reject) => { chrome.runtime.sendMessage(packMsgReq('FetchRequest', options), (response) => { if (response.state) { resolve(response.data); } else { reject(response.data); } }); }); }, get: function (options) { return new Promise((resolve, reject) => { chrome.runtime.sendMessage(packMsgReq('FetchGet', options), (response) => { if (response.state) { resolve(response.data); } else { reject(response.data); } }); }); }, post: function (options) { return new Promise((resolve, reject) => { chrome.runtime.sendMessage(packMsgReq('FetchPost', options), (response) => { if (response.state) { resolve(response.data); } else { reject(response.data); } }); }); } }; // 发送get请求 http.get({url: "https://·/"}); // 发送Delete请求 http.request({ method: "DELETE", url: "https://·/", headers: { cookie: "test=true;" } }) 三、操作流程

1. 在 manifest.json?的 host_permissions 中添加须解除限制的网站。

2. 由内容脚本调用,通过?chrome.runtime.sendMessage() 向后台脚本发送请求消息。

3. 后台脚本接收到消息,发送指定请求。然后将处理后的请求结果(消息不支持复杂对象传输)以回调形式发送回内容脚本。


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #Chrome #扩展 #跨域 #通过后台脚本间接完成 #fetch #请求