使用 CRXJS 构建 Chrome 插件在 Chrome 浏览器升级到 130xxx 版本之后,出现 Content Security Policy 错误

843 阅读3分钟

使用 CRXJS 构建 Chrome 插件在 Chrome 浏览器升级到 130xxx 版本之后,出现 Content Security Policy 错误

一、前言

之前有个老哥找我写了插件,到现在几个月过去了,今天早上和我说 Chrome 浏览器报错运行不起来了,但是 edge 浏览器没问题。

就帮忙定位了下问题,发现是 Content Security Policy 的问题导致的报错;

老哥说最近没改动这些代码,我就要了下压缩文件,在自己的 chrome 浏览器上安装,发现没问题,可以正常运行也没有报错;

我就把我本地 chrome 浏览器版本发过去和老哥的浏览器版本对比下,发现他的浏览器版本是最新版的(自动更新到最新版了,老哥不知道),我也手动更新我的浏览器到最新版(版本 130.0.6723.59(正式版本) (arm64)),就也报错了....

二、报错内容

1. 错误信息

Refused to load the script 'chrome-extension://1b3524a5-1c44-410c-9c6b-3e806a789826/js/index.ts.js' because it violates the following Content Security Policy directive: "script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' http://localhost:* http://127.0.0.1:*". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.

image.png

2. 报错位置

3. 报错原因

  1. chrome 浏览器升级最新版
  2. 使用 crxjs 打包插件
  3. 相关 Content Security Policy 报错

三、解决方案

1. 修改 manifest.json 文件

把 web_accessible_resources 中的 use_dynamic_url 改为 false

整个 web_accessible_resources 只改动 use_dynamic_url 一个字段

"web_accessible_resources": [
    {
      "resources": ["coverage/index.html", "content/index.html", "assets/*", "js/*"],
      "matches": ["http://localhost:*/*"],
      "use_dynamic_url": false // 只改动这一行即可,把 true 改成 false
    }
  ]

2. 增加 chalk 和 gulp 包

pnpm i gulp chalk -D 

3. 在根目录增加 gulpfile.js 文件

.
├── gulpfile.js
import { createRequire } from 'module'
import fs from 'fs'
import path from 'path'
import { dirname } from 'path'
import { fileURLToPath } from 'url'

// Temp fix for CSP on Chrome 130
// Manually remove them because there is no option to disable use_dynamic_url on @crxjs/vite-plugin
function forceDisableUseDynamicUrl(done) {
  const require = createRequire(import.meta.url)
  const __filename = fileURLToPath(import.meta.url)
  const __dirname = dirname(__filename)
  const manifestPath = path.join(__dirname, 'dist', 'manifest.json')

  const manifest = require(manifestPath)

  manifest.web_accessible_resources.forEach((resource) => {
    delete resource.use_dynamic_url
  })

  if (!fs.existsSync(manifestPath)) {
    return done()
  }

  fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2))

  done()
}

export { forceDisableUseDynamicUrl }

4. 在根目录增加 vite-plugins/vite-plugin-run-command-on-demand.ts 文件

在根目录增加 vite-plugins 文件夹和 vite-plugin-run-command-on-demand.ts 文件

.
├── vite-plugins
│   └── vite-plugin-run-command-on-demand.ts
import chalk from "chalk";
import { exec } from "child_process";
import { HmrContext, Plugin } from "vite";

const pluginName = "vite-plugin-run-command-on-demand";

const log = (message: string) =>
  console.log(chalk.blue(`\n[${pluginName}]`), chalk.green(message));
const logError = (message: string) =>
  console.error(chalk.blue(`\n[${pluginName}]`), chalk.red(message));

const runCommand = (command: string): Promise<void> =>
  new Promise((resolve, reject) => {
    exec(command, (error, stdout, stderr) => {
      if (error) {
        logError(`Error executing command: ${command}\n${stderr}`);
        reject(error);
      } else {
        log(`Command executed successfully: ${command}\n${stdout}`);
        resolve();
      }
    });
  });

type CustomCommandsPluginOptions = {
  beforeServerStart?: string;
  afterServerStart?: string;
  onHotUpdate?: string;
  beforeBuild?: string;
  afterBuild?: string;
  closeBundle?: string;
};

const executeCommand = async (
  command: string | undefined,
  errorMessage: string,
) => {
  if (command) {
    try {
      await runCommand(command);
    } catch {
      logError(errorMessage);
    }
  }
};

export default function customCommandsPlugin(
  options: CustomCommandsPluginOptions = {},
): Plugin {
  return {
    name: pluginName,
    configureServer(server) {
      server.httpServer?.once("listening", async () => {
        await executeCommand(
          options.beforeServerStart,
          `Error running beforeServerStart command: ${options.beforeServerStart}`,
        );
        await executeCommand(
          options.afterServerStart,
          `Error running afterServerStart command: ${options.afterServerStart}`,
        );
      });
    },

    async handleHotUpdate(ctx: HmrContext) {
      const isPageReload = ctx.modules.some(
        (module) => !module.isSelfAccepting,
      );
      if (!isPageReload) {
        await executeCommand(
          options.onHotUpdate,
          `Error running onHotUpdate command: ${options.onHotUpdate}`,
        );
      }
      return ctx.modules;
    },

    async buildStart() {
      await executeCommand(
        options.beforeBuild,
        `Error running beforeBuild command: ${options.beforeBuild}`,
      );
    },

    async buildEnd() {
      await executeCommand(
        options.afterBuild,
        `Error running afterBuild command: ${options.afterBuild}`,
      );
    },

    async closeBundle() {
      await executeCommand(
        options.closeBundle,
        `Error running closeBundle command: ${options.closeBundle}`,
      );
    },
  };
}

5. vite.config.ts 中引入

引入上面新增的文件

import vitePluginRunCommandOnDemand from "./vite-plugins/vite-plugin-run-command-on-demand";

在 plugins 中使用

plugins: [
  vitePluginRunCommandOnDemand({
    afterServerStart: "pnpm gulp forceDisableUseDynamicUrl",
    closeBundle: "pnpm gulp forceDisableUseDynamicUrl",
  }),
]

6. 修改 tsconfig.node.json 文件

"include": ["vite.config.ts", "./vite-plugins/**/*.ts"]

7. 重新构建打包

pnpm run build

四、方案执行结果

五、总结

  • 此次报错是由于 chrome 浏览器升级之后,安全策略变更导致的;
  • 使用 CRX JS 打包 chrome 插件都会遇到这个报错,已经有老哥在 crxjs 的 github 上提交 issue 了;
  • 此次解决方案也是从这个 issue 上面找的;
  • 之前做过 chrome 浏览器版本发行说明,但是后面有事就耽搁了,现在觉得还是得提起来,这样能有效跟进版本迭代和一些坑,不至于出现问题手忙脚乱。

引用