Appearance
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)
}
},
)