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

Chrome扩展Manifest v3中如何获取JSONP数据?

问题:Manifest v3环境下如何正确获取JSONP数据

错误信息

拒绝加载脚本 'https://dorar.net/dorar_api.json?skey=انما الاعمال بالنياتpage=1&callback=jsonp_callback_255',因为它违反了以下内容安全策略指令:"script-src 'self'"。注意未显式设置'script-src-elem',因此使用'script-src'作为回退。

Chrome扩展Manifest v3不允许加载外部脚本,因此无法直接获取JSONP API——所有处理JSONP的库本质都是加载外部脚本。我已将fetchJsonp下载到本地文件夹,并在popup.html中引入:

<script src="./thirdparty/fetchJsonp.min.js"></script>

相关代码

background.js

chrome.runtime.onInstalled.addListener(() => {
  chrome.contextMenus.create({
    title: 'Check this hadith',
    id: 'check-hadith',
    contexts: ['selection'],
  });
});

chrome.contextMenus.onClicked.addListener(async (info, tab) => {
  chrome.storage.local.set({ text: info.selectionText }, async () => {
    await chrome.windows.create({
      url: chrome.runtime.getURL('popup.html'),
      type: 'popup',
    });
  });
});
import {
  getAllHadith,
  getAllHadithInfo,
} from './utils/extractHadithInfo.js';

const searchForHadithByText = async (text, page = 1) => {
  const url = `https://dorar.net/dorar_api.json?skey=${text}&page=${page}`;
  const data = await convertToJSON(url);
  return data;
};

const convertToJSON = async (url) => {
  try {
    const data = await fetchJsonp(encodeURI(url)); // 错误发生在此处!!!
    
    // 其余代码与错误无关
    const html = he.decode(data.ahadith.result);
    const allHadith = getAllHadith(html);
    const allHadithInfo = getAllHadithInfo(html);

    const result = allHadith.map((hadith, index) => {
      return {
        ...hadith,
        ...allHadithInfo[index],
      };
    });
    return result;
  } catch (err) {
    console.error(err);
  }
};

const cards = document.getElementsByClassName('cards')[0];
chrome.storage.local.get('text', async ({ text }) => {
  const allHadith = await searchForHadithByText(text);
  const allCardsDiv = allHadith.map((_hadith) => {
    const { hadith, el_rawi, el_mohdith, source, number_or_page, grade } =
      _hadith;

    return `<div class="card">
             <p class="hadith-text">${hadith}</p>
             <div class="hadith-info">
                <p class="hadith-rawi"><span>الراوي:</span> ${el_rawi}</p>
                <p class="hadith-mohdith"><span>المتحدث:</span> ${el_mohdith}</p>
                <p class="hadith-source"><span>المصدر:</span> ${source}</p>
                <p class="hadith-number"><span>رقم الحديث أو الصفحة:</span> ${number_or_page}</p>
                <p class="hadith-grade"><span>صحة الحديث:</span> ${grade}</p>
             </div>
        </div>`;
  });

  cards.innerHTML = allCardsDiv.join('');
});

manifest.json

{
  "name": "Hadith Checker",
  "description": "Checking the selected hadith, whether it is fabricated or authentic",
  "version": "0.4",
  "manifest_version": 3,
  "background": {
    "service_worker": "background.js"
  },
  "permissions": ["contextMenus", "storage", "unlimitedStorage"],
  "icons": {
    "16": "./icons/icons16.png",
    "32": "./icons/icons32.png",
    "48": "./icons/icons48.png",
    "128": "./icons/icons128.png"
  }
}

文件夹结构

- icons
+ thirdparty 
   |
   - fetchJsonp.min.js
   - he.min.js
+ utils
   |
   - extractHadithInfo.js
- background.js
- manifest.json
- popup.css
- popup.js
- README.md

解决方案

在Manifest v3中,由于CSP限制无法直接执行外部JSONP脚本,正确做法是通过后台Service Worker发起请求并手动处理JSONP响应,具体步骤如下:

1. 更新manifest.json添加权限

添加主机权限允许访问目标API:

{
  "name": "Hadith Checker",
  "description": "Checking the selected hadith, whether it is fabricated or authentic",
  "version": "0.4",
  "manifest_version": 3,
  "background": {
    "service_worker": "background.js"
  },
  "permissions": ["contextMenus", "storage", "unlimitedStorage"],
  "host_permissions": ["https://dorar.net/*"], // 新增主机权限
  "icons": {
    "16": "./icons/icons16.png",
    "32": "./icons/icons32.png",
    "48": "./icons/icons48.png",
    "128": "./icons/icons128.png"
  }
}

2. 在background.js中添加JSONP处理逻辑

后台Service Worker不受popup的CSP限制,可以直接获取响应文本并提取JSON:

chrome.runtime.onInstalled.addListener(() => {
  chrome.contextMenus.create({
    title: 'Check this hadith',
    id: 'check-hadith',
    contexts: ['selection'],
  });
});

chrome.contextMenus.onClicked.addListener(async (info, tab) => {
  chrome.storage.local.set({ text: info.selectionText }, async () => {
    await chrome.windows.create({
      url: chrome.runtime.getURL('popup.html'),
      type: 'popup',
    });
  });
});

// 新增消息监听,处理popup的请求
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'fetchJsonp') {
    fetchJsonpFromBackground(request.url)
      .then(data => sendResponse({ success: true, data }))
      .catch(err => sendResponse({ success: false, error: err.message }));
    return true; // 保持端口开放以支持异步响应
  }
});

// 手动解析JSONP响应
async function fetchJsonpFromBackground(url) {
  const jsonpUrl = `${url}&callback=jsonpCallback`;
  const response = await fetch(jsonpUrl);
  const text = await response.text();
  
  // 去除JSONP的函数包裹,提取纯JSON字符串
  const jsonStr = text.replace(/^jsonpCallback\(/, '').replace(/\)$/, '');
  return JSON.parse(jsonStr);
}

3. 修改popup.js,改为向后台请求数据

不再直接使用fetchJsonp,通过消息机制让后台处理请求:

import {
  getAllHadith,
  getAllHadithInfo,
} from './utils/extractHadithInfo.js';

const searchForHadithByText = async (text, page = 1) => {
  const url = `https://dorar.net/dorar_api.json?skey=${encodeURIComponent(text)}&page=${page}`;
  const response = await chrome.runtime.sendMessage({
    action: 'fetchJsonp',
    url
  });
  if (response.success) {
    return convertToJSON(response.data);
  } else {
    throw new Error(response.error);
  }
};

// 调整函数,直接处理后台返回的JSON数据
const convertToJSON = (data) => {
  try {
    const html = he.decode(data.ahadith.result);
    const allHadith = getAllHadith(html);
    const allHadithInfo = getAllHadithInfo(html);

    const result = allHadith.map((hadith, index) => {
      return {
        ...hadith,
        ...allHadithInfo[index],
      };
    });
    return result;
  } catch (err) {
    console.error(err);
    throw err;
  }
};

const cards = document.getElementsByClassName('cards')[0];
chrome.storage.local.get('text', async ({ text }) => {
  try {
    const allHadith = await searchForHadithByText(text);
    const allCardsDiv = allHadith.map((_hadith) => {
      const { hadith, el_rawi, el_mohdith, source, number_or_page, grade } =
        _hadith;

      return `<div class="card">
               <p class="hadith-text">${hadith}</p>
               <div class="hadith-info">
                  <p class="hadith-rawi"><span>الراوي:</span> ${el_rawi}</p>
                  <p class="hadith-mohdith"><span>المتحدث:</span> ${el_mohdith}</p>
                  <p class="hadith-source"><span>المصدر:</span> ${source}</p>
                  <p class="hadith-number"><span>رقم الحديث أو الصفحة:</span> ${number_or_page}</p>
                  <p class="hadith-grade"><span>صحة الحديث:</span> ${grade}</p>
               </div>
          </div>`;
    });

    cards.innerHTML = allCardsDiv.join('');
  } catch (err) {
    cards.innerHTML = `<p>获取数据失败:${err.message}</p>`;
  }
});

4. 移除popup.html中的fetchJsonp引入(可选)

由于不再在popup中直接使用fetchJsonp,可以删除以下代码:

<!-- 移除该行 -->
<script src="./thirdparty/fetchJsonp.min.js"></script>

内容的提问来源于stack exchange,提问作者Ahmed El-Tabarani

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 07:10:12