You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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通过消息通信调用请求逻辑:

  1. 创建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 });
    }
  }
});
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 07:50:28