头图

使用 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.

在这里插入图片描述

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 浏览器版本发行说明,但是后面有事就耽搁了,现在觉得还是得提起来,这样能有效跟进版本迭代和一些坑,不至于出现问题手忙脚乱。

引用


月恒
40 声望4 粉丝

前端