Chrome扩展Manifest V3迁移:XMLHttpRequest转Fetch封装方案求助
Manifest V3下解决XMLHttpRequest兼容问题的方案
一、为Service Worker全局注入XMLHttpRequest Polyfill
直接在Service Worker入口文件顶部添加轻量XHR模拟实现,将XMLHttpRequest调用映射到你已封装的Fetch客户端,无需修改第三方库代码。
示例实现(适配你的HttpClient):
import HttpClient from './path-to-your-http-client'; globalThis.XMLHttpRequest = class { constructor() { this._method = ''; this._url = ''; this._headers = {}; this._body = null; this._response = null; this._status = 0; this.onload = null; this.onerror = null; } open(method, url) { this._method = method; this._url = url; } setRequestHeader(key, value) { this._headers[key] = value; } send(body) { this._body = body; const config = { headers: this._headers }; let requestPromise; if (this._method.toUpperCase() === 'GET') { requestPromise = HttpClient.get(this._url, config); } else if (this._method.toUpperCase() === 'POST') { requestPromise = HttpClient.post(this._url, JSON.parse(body || '{}'), config); } // 可扩展PUT、DELETE等其他方法 requestPromise .then(({ data }) => { this._response = JSON.stringify(data); this._status = 200; this.onload?.(); }) .catch(({ response }) => { this._response = JSON.stringify(response.data); this._status = response.status || 500; this.onerror?.(); }); } get responseText() { return this._response; } get status() { return this._status; } };
二、配置第三方库使用自定义HTTP客户端
多数成熟库(SignalR、Twilio系列)支持替换默认HTTP客户端,直接传入你封装的HttpClient即可,这是最优雅的方案。
1. ASP.NET SignalR 适配
import { HubConnectionBuilder } from "@microsoft/signalr"; import HttpClient from './path-to-your-http-client'; const connection = new HubConnectionBuilder() .withUrl("/your-hub-url", { httpClient: { get: (url, options) => HttpClient.get(url, { headers: options.headers }), post: (url, options) => HttpClient.post(url, options.body, { headers: options.headers }) } }) .build();
2. Twilio Voice/Conversations 适配
import { Client as ConversationsClient } from '@twilio/conversations'; import HttpClient from './path-to-your-http-client'; const twilioClient = new ConversationsClient(yourAuthToken, { transport: { request: async (options) => { const method = options.method.toLowerCase(); const config = { headers: options.headers }; if (method === 'get') { return HttpClient.get(options.url, config); } else if (['post', 'put'].includes(method)) { return HttpClient.post(options.url, options.data, config); } // 按需处理DELETE等其他请求方法 } } });
三、借助Offscreen Document中转请求(应急方案)
如果上述方法都无法适配,可利用Manifest V3的Offscreen Document特性,在支持XHR的普通页面环境中运行第三方库,Service Worker通过消息通信调用请求逻辑:
- 创建
offscreen.html及配套脚本:
<!-- offscreen.html --> <script src="./offscreen-script.js"></script>
// offscreen-script.js import { Client as TwilioClient } from '@twilio/conversations'; chrome.runtime.onMessage.addListener(async (msg, sender, sendResponse) => { if (msg.type === 'TWILIO_REQUEST') { try { const client = new TwilioClient(msg.token); // 执行具体Twilio操作,比如创建会话、发起呼叫 const result = await client.doSomething(msg.payload); sendResponse({ success: true, data: result }); } catch (err) { sendResponse({ success: false, error: err.message }); } } });
- 在Service Worker中调用:
async function callTwilioService(token, payload) { // 先创建Offscreen文档 await chrome.offscreen.createDocument({ url: 'offscreen.html', reasons: [chrome.offscreen.Reason.OTHER], justification: 'Run Twilio libraries requiring XMLHttpRequest' }); return new Promise((resolve) => { chrome.runtime.sendMessage( { type: 'TWILIO_REQUEST', token, payload }, (response) => resolve(response) ); }); }
优化你的自定义HttpClient
补充缺失的HTTP方法(PUT、DELETE),并完善非JSON响应处理:
const HttpClient = { get: async (endpoint, customConfig = {}) => { const token = await getItem('token'); const headers = { 'content-type': 'application/json' } if (token) { headers.Authorization = `Bearer ${token.accessToken}` } const config = { method: 'GET', ...customConfig, headers: { ...headers, ...customConfig.headers, } } return this._handleResponse(endpoint, config); }, post: async (endpoint, body, customConfig = {}) => { const token = await getItem('token'); const headers = { 'content-type': 'application/json' } if (token) { headers.Authorization = `Bearer ${token.accessToken}` } const config = { method: 'POST', ...customConfig, headers: { ...headers, ...customConfig.headers, } } if (body) { config.body = JSON.stringify(body) } return this._handleResponse(endpoint, config); }, put: async (endpoint, body, customConfig = {}) => { const token = await getItem('token'); const headers = { 'content-type': 'application/json' }; if (token) headers.Authorization = `Bearer ${token.accessToken}`; const config = { method: 'PUT', ...customConfig, headers: { ...headers, ...customConfig.headers } }; if (body) config.body = JSON.stringify(body); return this._handleResponse(endpoint, config); }, delete: async (endpoint, customConfig = {}) => { const token = await getItem('token'); const headers = { 'content-type': 'application/json' }; if (token) headers.Authorization = `Bearer ${token.accessToken}`; const config = { method: 'DELETE', ...customConfig, headers: { ...headers, ...customConfig.headers } }; return this._handleResponse(endpoint, config); }, _handleResponse: async (endpoint, config) => { const response = await global.fetch(endpoint, config); if (response.status === 401) { sendUnAuthorizedEvent(); return; } let data; try { data = await response.json(); } catch { data = await response.text(); // 处理非JSON响应 } if (response.ok) { return { data }; } else { return Promise.reject({ response: { data, status: response.status } }); } } }; export default HttpClient;
内容的提问来源于stack exchange,提问作者Ghazanfar Khan
相关产品推荐
相关产品推荐

