Skip to content

chrome 插件 监听websocket

安装模板

sh
npx degit antfu/vitesse-webext my-webext
cd my-webext
yarn
yarn dev

修改配置并添加页面

  • 在src下新建文件夹inspector 包含index.html
html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <base target="_blank">
  <title>inspector</title>
  <meta name="google" content="notranslate">
</head>
<body style="min-width: 100px">
  <div id="inspector"></div>
  <script type="module" src="./main.ts"></script>
</body>
</html>

包含main.ts

ts
import { createApp } from 'vue'
import App from './Inspector.vue'
import '../styles'

const app = createApp(App)
app.mount('#inspector')

包含Inspector.vue

vue
<script setup lang="ts">
// import { storageDemo } from '~/logic/storage'
</script>

<template>
  <main class="w-[300px] px-4 py-5 text-center text-gray-700">
    123
  </main>
</template>
  • 修改src/manifest.ts
ts
 browser_action: {
      default_icon: './assets/icon-512.png',
      // default_popup: './dist/popup/index.html',
    },
  • 修改vite.config.ts
ts
 rollupOptions: {
      input: {
        background: r('src/background/index.html'),
        options: r('src/options/index.html'),
        popup: r('src/popup/index.html'),
        inspector: r('src/inspector/index.html'), //新增
      },
    },
  • 修改scripts/prepare.ts
ts
async function stubIndexHtml() {
  const views = [
    'options',
    'popup',
    'background',
    'inspector',
  ]

  for (const view of views) {
    await fs.ensureDir(r(`extension/dist/${view}`))
    let data = await fs.readFile(r(`src/${view}/index.html`), 'utf-8')
    data = data
      .replace('"./main.ts"', `"http://localhost:${port}/${view}/main.ts"`)
      .replace('<div id="app"></div>', '<div id="app">Vite server did not start</div>')
      .replace('<div id="inspector"></div>', '<div id="inspector">Loading</div>')
    await fs.writeFile(r(`extension/dist/${view}/index.html`), data, 'utf-8')
    log('PRE', `stub ${view}`)
  }
}
  • src/background/main.ts 新增逻辑
ts
browser.browserAction.onClicked.addListener((tab: any) => {
  browser.windows.create({
    url: 'dist/inspector/index.html',
    type: 'popup',
    width: 400,
    height: 600,
  }).then((wnd: any) => {
    inspectors.push({ id: tab.id, popup: wnd, active: true })
  })
})

加载插件

  • chrome打开chrome://extensions/ 打开开发者模式
  • 加载已解压的扩展程序 选择对应目录-> extension
  • 右上角固定插件,点击插件弹出我们新加的页面

注意项

  • webextension-polyfill 并没有包含debugger api 所以可以直接用原生的chrome.debugger
  • 为了ts不报 chrome未声明的问题简单做了如下修改

src/my.d.ts

ts
export {}
declare global {
  const chrome: typeof any
}

完整配置

ts
import fs from 'fs-extra'
import type { Manifest } from 'webextension-polyfill'
import type PkgType from '../package.json'
import { isDev, port, r } from '../scripts/utils'

export async function getManifest() {
  const pkg = await fs.readJSON(r('package.json')) as typeof PkgType

  // update this file to update this manifest.json
  // can also be conditional based on your need
  const manifest: Manifest.WebExtensionManifest = {
    manifest_version: 2,
    name: pkg.displayName || pkg.name,
    version: pkg.version,
    description: pkg.description,
    browser_action: {
      default_icon: './assets/icon-512.png',
      // default_popup: './dist/popup/index.html',
    },
    options_ui: {
      page: './dist/options/index.html',
      open_in_tab: true,
      chrome_style: false,
    },
    background: {
      page: './dist/background/index.html',
      persistent: false,
    },
    icons: {
      16: './assets/icon-512.png',
      48: './assets/icon-512.png',
      128: './assets/icon-512.png',
    },
    permissions: [
      'cookies',
      'debugger',
      'tabs',
      'storage',
      'activeTab',
      'webNavigation',
      'http://*/',
      'https://*/',
    ],
    content_scripts: [{
      matches: ['http://*/*', 'https://*/*'],
      js: ['./dist/contentScripts/index.global.js'],
    }],
    web_accessible_resources: [
      'dist/contentScripts/style.css',
    ],
  }

  if (isDev) {
    // for content script, as browsers will cache them for each reload,
    // we use a background script to always inject the latest version
    // see src/background/contentScriptHMR.ts
    delete manifest.content_scripts
    manifest.permissions?.push('webNavigation')
    // content_security_policy: 'script-src \'self\' \'unsafe-eval\'; object-src \'self\'',

    // this is required on dev for Vite script to load
  }
  manifest.content_security_policy = `script-src \'self\'  \'unsafe-eval\' http://localhost:${port}; object-src \'self\'`

  return manifest
}

background/main.ts

ts
interface Inspector {
  id: number
  popup: any
  active: boolean
}

const inspectors: Inspector[] = []

browser.browserAction.onClicked.addListener((tab: any) => {
  const inspector = inspectors.find(({ id }) => id === tab.id)
  if (inspector && inspector.active) {
    browser.windows.update(inspector.popup.id, { focused: true })
  }
  else {
    chrome.debugger.attach({ tabId: tab.id }, '1.0', () => {
      if (browser.runtime.lastError)
        alert(browser.runtime.lastError.message)
        // 关闭调试
    })
    if (inspector) {
      inspector.active = true
      browser.runtime.sendMessage({
        message: 'reattach',
        tabId: tab.id,
      })
      browser.windows.update(inspector.popup.id, { focused: true })
    }
    else {
      // 判断当前url是不是live.douying.com
      // console.log('tab.url', tab.url)
      if (tab.url.includes('live.douyin.com') || tab.url.includes('tiktok.com')) {
        const p = tab.url.includes('live.douyin.com') ? 1 : 2
        browser.windows
          .create({
            url: `dist/inspector/index.html?id=${tab.id}&p=${p}`,
            type: 'popup',
            width: 400,
            height: 600,
          })
          .then((wnd: any) => {
            inspectors.push({ id: tab.id, popup: wnd, active: true })
          })
      }
      else {
        alert('当前页面不是直播页面')
      }
    }
  }
})

inspector/Inspector.vue 核心逻辑

ts

browser.runtime.onMessage.addListener((message) => {
  if (message.message === 'reattach' && message.tabId === tabId)
    startDebugging()
})

function startDebugging() {
  console.log('tabId', tabId)
  chrome.debugger.sendCommand({ tabId }, 'Network.enable', null, () => {
    if (browser.runtime.lastError)
      console.error(browser.runtime.lastError.message)
    else console.log('Network enabled')
  })
  browser.tabs.get(tabId).then((tab: any) => {
    if (tab.title)
      document.title = ` - ${tab.title}`
    else document.title = ''
  })
}
chrome.debugger.onEvent.addListener(
  (debuggee: any, message: any, params: any) => {
    if (debuggee.tabId !== tabId)
      return

    if (message === 'Network.webSocketFrameReceived') {
      if (params.response.payloadData)
      // 处理消息
        handleMessage(params.response.payloadData)
    }
  },
)