Chrome API 在 React 功能组件中不起作用

Chrome API in React functional component not working

我正在尝试做一个 chrome 扩展并且很想使用 React,但是在 运行 整整一周陷入死胡同之后,这似乎不是理想的途径。因此,我决定在这里向你们优秀的 SO 用户征求一些建议,作为在使用 Vanilla JS 之前的最后努力。

这是我的 public/manifest.json 文件(我手动更改“js”:[] 字段以暂时匹配构建文件夹的名称):

{
  "manifest_version": 2,
  "name": "Name",
  "version": "0.1.0",

  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["/static/js/main.356978b8.chunk.js"]
    }
  ],

  "background": {
    "scripts": ["background.js"]
  },

  "permissions": ["tabs", "http://*/"],

  "browser_action": {
    "default_title": "Title"
  }
}

我的想法是,内容脚本将是所有 React 功能组件文件和使用上述内容的整体 index.js

这里是package.json:

{
  "name": "client",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-scripts": "4.0.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ],
    "env": {
      "webextensions": true
    }
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

这是我的文件夹结构:

src文件夹:

// src/App.js
function App() {
  return <div>Hello</div>;
}

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  console.log(request.tabs);
});

export default App;

// src/index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById("root")
);

public 文件夹:

// public/background.js
chrome.browserAction.onClicked.addListener((tab) => {
  chrome.tabs.create(
    {
      windowId: null,
      url: "http://localhost:3000",
      active: true,
      openerTabId: tab.id,
    },
    (newTab) => {

      // wait for tab to load, then send message with tabs
      chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
        if (changeInfo.status == "complete" && tabId == newTab.id) {
          chrome.tabs.getAllInWindow(null, (tabs) => {
            chrome.tabs.sendMessage(newTab.id, { tabs });
          });
        }
      });
    }
  );
});

// public/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />

    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />

    <title>Title</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

我的 background.js 只是在 chrome.browserAction.onClicked.addListener((tab)=>{...}) 单击扩展按钮时打开一个新选项卡 http://localhost:3000这很好用

build 文件夹添加到 Chrome Extensions 中的 Load Unpacked 会生成扩展名,但是当我单击按钮并创建新选项卡时,我得到

TypeError: Cannot read property 'addListener' of undefined

我知道 npm run eject 可以用于 automate/set /static/js/... 中的文件名,但现在我可以在每次构建更改时简单地更改它。

如有任何线索或想法,我们将不胜感激!

问题是您的扩展使用 两个 份相同的 React 应用 JS 文件 ("static/js/main.*.chunk.js") 运行 在不同的上下文中。一份 运行 作为内容脚本注入到活动页面中,这一份应该 运行 没有错误。但还有另一个副本 运行 在 CRA 的开发服务器 (localhost:3000) 中,您可以在新选项卡中打开。而这个 运行s 作为 普通 JS 文件与你的扩展没有任何关系,所以它 不能 调用 Chrome API。这就是您收到该错误的原因。

一个可能的解决方案是将该 JS 文件分成两个单独的部分:一个(与 index.html 相关的普通 JS)- 仅进行 React 渲染,另一个(内容脚本)- 监听来自扩展的消息. Communication between such two parts can be done using window.postMessage。在您的情况下,如下所示:

public/manifest.json:

{
...
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["contentScript.js"]
    }
  ],
...
}

public/contentScript.js:

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  window.postMessage({ type: "FROM_EXT", tabs: request.tabs }, "http://localhost:3000");
});

src/index.js:

...
window.addEventListener("message", event => {
  if (event.source != window)
    return;
  const {type, tabs} = event.data;
  if (type !== "FROM_EXT")
    return;
  console.log(tabs);
});

此外,我建议您使用自定义 CRA 模板 - complex-browserext (plug). It facilitates usage of Create React App with browser extensions. Also see this article 用于特定用法示例(另一个插件)。