Stories

Detail Return Return

使用 CRXJS 構建 Chrome 插件在 Chrome 瀏覽器升級到 130xxx 版本之後,出現CSP錯誤

使用 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 瀏覽器版本發行説明,但是後面有事就耽擱了,現在覺得還是得提起來,這樣能有效跟進版本迭代和一些坑,不至於出現問題手忙腳亂。

引用

  • chrome 瀏覽器版本發行説明
  • crxjs issues
  • ce9242a
user avatar
0 users favorite the story!

Post Comments

Some HTML is okay.