允许 Quasar 应用与 BEX 的各个部分进行通信至关重要。Quasar 使用 bridge
弥补了这一缺陷。
¥Allowing a Quasar App to communicate with the various parts of the BEX is essential. Quasar closes this gap using a bridge
.
BEX 中有 3 个区域需要通信层:
¥There are 3 areas in a BEX which will need a communication layer:
Quasar 应用本身 - 这适用于所有类型的 BEX,例如弹出窗口、选项页面、开发工具或网页。
¥The Quasar App itself - this is true for all types of BEX i.e Popup, Options Page, Dev Tools or Web Page
背景脚本
¥Background Script
内容脚本
¥Content Script
通信规则(Communication Rules)
¥Communication Rules
你可以使用我们的 BEX 桥接器直接在后台脚本、内容脚本实例和 popup/devtools/options 页面之间进行通信。
¥You can use our BEX bridge to directly communicate between the background script, instances of the content scripts and the popup/devtools/options page.
对于 BEX 的每个部分,BEX 桥接器的使用都是可选的,但是,如果你希望能够直接在任何 bex 部分之间进行通信,则需要在后台脚本中创建它。在底层,后台脚本充当主要通信点。所有消息都会通过后台脚本中的桥接器(并重定向到正确的收件人)。
¥The use of the BEX bridge is optional for each part of the BEX, however if you want to be able to directly communicate between any bex part, then you need to create it in your background script. Under the hood, the background script acts as the main point of communication. All messages go through the bridge in the background script (and get redirected to the right recipient).
桥接器(The Bridge)
¥The Bridge
桥接器是一个基于 Promise 的事件系统,它在 BEX 的所有部分之间共享,因此你可以监听 Quasar 应用中的事件,并从其他部分触发事件,反之亦然。这就是 Quasar BEX 模式的强大之处。
¥The bridge is a promise based event system which is shared between all parts of the BEX and as such allows you to listen for events in your Quasar App, emit them from other parts or vice versa. This is what gives Quasar BEX mode it’s power.
要从 Quasar 应用 (/src) 内部访问桥接器,你可以使用 $q.bex
。在其他方面,可以通过创建桥接实例来使用桥接。
¥To access the bridge from within your Quasar App (/src) you can use $q.bex
. In other areas, the bridge is made available via creating an instance of it.
让我们看看它是如何工作的。
¥Let’s see how it works.
后台脚本(The background script)
¥The background script
警告
你可以在 manifest.json 中指定多个后台脚本,但是,只能在其中一个后台脚本中创建 BEX 桥接。不要在 BEX 的背景部分使用多个桥接实例。
¥You can have multiple background scripts specified in your manifest.json, however, create the BEX bridge ONLY in one of those background scripts. Do not use multiple bridge instances for the background part of your BEX.
/**
* Importing the file below initializes the extension background.
* * Warnings:
* 1. Do NOT remove the import statement below. It is required for the extension to work.
* If you don't need createBridge(), leave it as "import '#q-app/bex/background'".
* 2. Do NOT import this file in multiple background scripts. Only in one!
* 3. Import it in your background service worker (if available for your target browser).
*/
import { createBridge } from '#q-app/bex/background'
/**
* Call useBridge() to enable communication with the app & content scripts
* (and between the app & content scripts), otherwise skip calling
* useBridge() and use no bridge.
*/
const bridge = createBridge({ debug: false })
内容脚本(Content scripts)
¥Content scripts
/**
* Importing the file below initializes the content script.
* * Warning:
* Do not remove the import statement below. It is required for the extension to work.
* If you don't need createBridge(), leave it as "import '#q-app/bex/content'".
*/
import { createBridge } from '#q-app/bex/content'
// The use of the bridge is optional.
const bridge = createBridge({ debug: false })
/**
* bridge.portName is 'content@<path>-<number>'
* where <path> is the relative path of this content script
* filename (without extension) from /src-bex
* (eg. 'my-content-script', 'subdir/my-script')
* and <number> is a unique instance number (1-10000).
*/
// Attach initial bridge listeners...
/**
* Leave this AFTER you attach your initial listeners
* so that the bridge can properly handle them.
* * You can also disconnect from the background script
* later on by calling bridge.disconnectFromBackground().
* * To check connection status, access bridge.isConnected
*/
bridge.connectToBackground()
.then(() => {
console.log('Connected to background')
})
.catch(err => {
console.error('Failed to connect to background:', err)
})
Popup/devtools/options 页面(Popup/devtools/options page)
¥Popup/devtools/options page
<template>
<div />
</template>
<script setup>
import { useQuasar } from 'quasar'
const $q = useQuasar()
// Use $q.bex (the bridge)
// $q.bex.portName is "app"
</script>
请注意,devtools/popup/options 页面的 portName 将是 app
。
¥Please note that the devtools/popup/options page portName will be app
.
通过桥接器发送消息(Messaging through the bridge)
¥Messaging through the bridge
// Listen to a message from the client
bridge.on('test', message => {
console.log(message)
console.log(message.payload)
console.log(message.from)
})
// Send a message and split payload into chunks
// to avoid max size limit of BEX messages.
// Warning! This happens automatically when the payload is an array.
// If you actually want to send an Array, wrap it in an object.
bridge.send({
event: 'test',
to: 'app',
payload: [ 'chunk1', 'chunk2', 'chunk3', ... ]
}).then(responsePayload => { ... }).catch(err => { ... })
// Send a message and wait for a response
bridge.send({
event: 'test',
to: 'background',
payload: { banner: 'Hello from content-script' }
}).then(responsePayload => { ... }).catch(err => { ... })
// Listen to a message from the client and respond synchronously
bridge.on('test', message => {
console.log(message)
return { banner: 'Hello from a content-script!' }
})
// Listen to a message from the client and respond asynchronously
bridge.on('test', async message => {
console.log(message)
const result = await someAsyncFunction()
return result
})
bridge.on('test', message => {
console.log(message)
return new Promise(resolve => {
setTimeout(() => {
resolve({ banner: 'Hello from a content-script!' })
}, 1000)
})
})
// Broadcast a message to app & content scripts
bridge.portList.forEach(portName => {
bridge.send({ event: 'test', to: portName, payload: 'Hello from background!' })
})
// Find any connected content script and send a message to it
const contentPort = bridge.portList.find(portName => portName.startsWith('content@'))
if (contentPort) {
bridge.send({ event: 'test', to: contentPort, payload: 'Hello from background!' })
}
// Send a message to a certain content script
bridge
.send({ event: 'test', to: 'content@my-content-script-2345', payload: 'Hello from a content-script!' })
.then(responsePayload => { ... })
.catch(err => { ... })
// Listen for connection events
// (the "@quasar:ports" is an internal event name registered automatically by the bridge)
// --> ({ portList: string[], added?: string } | { portList: string[], removed?: string })
bridge.on('@quasar:ports', ({ portList, added, removed }) => {
console.log('Ports:', portList)
if (added) {
console.log('New connection:', added)
} else if (removed) {
console.log('Connection removed:', removed)
}
})
// Current bridge port name (can be 'background', 'app', or 'content@<name>-<xxxxx>')
console.log(bridge.portName)
警告!发送大量数据
所有浏览器扩展程序对可作为通信消息传递的数据量都有硬性限制(例如:50MB)。如果你的有效载荷超出了该数量,你可以发送分块(payload
参数应为数组)。
¥All browser extensions have a hard limit on the amount of data that can be passed as communication messages (example: 50MB). If you exceed that amount on your payload, you can send chunks (payload
param should be an Array).
bridge.send({
event: 'some.event',
to: 'app',
payload: [ chunk1, chunk2, ...chunkN ]
})
计算负载大小时,请记住负载被封装在 Bridge 构建的消息中,该消息还包含一些其他属性。这也会占用一些字节。因此,你的块大小应该比浏览器的阈值低几个字节。
¥When calculating the payload size, have in mind that the payload is wrapped in a message built by the Bridge that contains some other properties too. That takes a few bytes as well. So your chunks’ size should be with a few bytes below the browser’s threshold.
警告!发送数组时的性能
正如我们在上面的警告中看到的,如果 payload
是数组,则桥接器将为数组的每个元素发送一条消息。当你实际想要发送数组(而不是将有效负载拆分成块)时,这将非常低效。
¥Like we’ve seen on the warning above, if payload
is Array then the bridge will send a message for each of the Array’s elements. When you actually want to send an Array (not split the payload into chunks), this will be VERY inefficient.
解决方案是将数组封装在一个对象中(这样只会发送一条消息):
¥The solution is to wrap your Array in an Object (so only one message will be sent):
bridge.send({
event: 'some.event',
to: 'background',
payload: {
myArray: [ /*...*/ ]
}
})
桥接调试模式(Bridge debug mode)
¥Bridge debug mode
如果你在 BEX 部分之间发送消息时遇到问题,你可以为你感兴趣的桥接器启用调试模式。这样做时,通信内容也会输出到浏览器控制台:
¥If you encounter problems with sending messages between the BEX parts, you could enable the debug mode for the bridges that interest you. In doing so, the communication will also be outputted to the browser console:
// Dynamically set debug mode
bridge.setDebug(true) // boolean
// Log a message on the console (if debug is enabled)
bridge.log('Hello world!')
bridge.log('Hello', 'world!')
bridge.log('Hello world!', { some: 'data' })
bridge.log('Hello', 'world', '!', { some: 'object' })
// Log a warning on the console (regardless of the debug setting)
bridge.warn('Hello world!')
bridge.warn('Hello', 'world!')
bridge.warn('Hello world!', { some: 'data' })
bridge.warn('Hello', 'world', '!', { some: 'object' })
清理监听器(Clean up your listeners)
¥Clean up your listeners
在 BEX 的生命周期内,不要忘记删除不再需要的监听器:
¥Don’t forget to remove the listeners that are no longer needed, during the lifetime of your BEX:
bridge.off('some.event', this.someFunction)