简化Web扩展中的消息传递:WebExt-Bridge的使用指南
文章目录
- 简化Web扩展中的消息传递:WebExt-Bridge的使用指南
- 一、简介
- 二、安装
- 三、快速示例
- 1、弹出窗口
- 2、后台服务工作者
- 四、优势
- 五、深入学习
- 六、概念与上下文
- 1、通信上下文
- 2、主要API
- sendMessage()
- onMessage()
- `allowWindowMessaging()` 和 `setNamespace()`
- `openStream()` 和 `onOpenStreamChannel()`
- 3、类型安全
- 七、类型安全协议
- 八、示例
- 1、弹出窗口 -> 后台脚本
- 2、内容脚本 -> 后台脚本
- 九、安全性
- 十、故障排除
- 1、消息传递不起作用?
- 2、无法发送消息给目标?
- 十一、总结
在开发Web扩展时,各个组件之间的通信是必不可少的,但这项任务往往充满挑战。不同的浏览器对消息传递的处理略有不同,导致我们在确定消息发送目标时可能会遇到复杂的情况。为了解决这个问题,WebExt-Bridge提供了一个简单而一致的API,可以在Web扩展的不同部分之间(如background、content-script、devtools、popup、options和window上下文)发送消息。
一、简介
WebExt-Bridge 的设计初衷是简化 Web 扩展中各部分之间的通信。无论是在后台脚本、内容脚本、开发者工具、弹出窗口、选项页还是窗口上下文之间,WebExt-Bridge 都能提供一个高效且一致的方式来发送和接收消息。该包已在生产环境中经过广泛测试,例如在 Bugflow 项目中得到了应用。
WebExt-Bridge 项目最初由 Neek Sandhu(@zikaari)于 2017 年启动,现已由 Server Side Up 接手维护。我们感谢 Neek Sandhu 的贡献,并期待在未来继续推动这一项目的发展。
官网:https://serversideup.net/open-source/webext-bridge/
GitHub仓库:https://github.com/serversideup/webext-bridge
二、安装
你可以通过NPM或Yarn轻松安装WebExt-Bridge:
# 使用NPM
npm i webext-bridge
# 使用Yarn
yarn add webext-bridge
安装完成后,你就可以在扩展的不同组件之间简单地传递消息了。
三、快速示例
接下来,让我们看一个从弹出窗口向后台脚本发送消息的快速示例:
1、弹出窗口
在你的弹出窗口中,添加以下代码:
import { sendMessage } from "webext-bridge/popup";
const response = await sendMessage("ACTION", { data: data }, "background");
2、后台服务工作者
在后台服务工作者脚本中添加以下代码:
import { onMessage } from "webext-bridge/background";
onMessage("ACTION", runAction);
async function runAction({ data }) {
// 处理数据
// 返回数据
return {};
}
就是这样!你已准备好在扩展的不同部分之间发送消息了。
四、优势
使用WebExt-Bridge的最大优势在于它简化了消息传递的过程。与传统的chrome.runtime.sendMessage
或chrome.runtime.connect
方法相比,WebExt-Bridge使得代码更加简洁易读,并允许你更具体地指定消息的发送和处理位置。它还支持跨平台的解决方案,代码可以在Firefox、Chrome、Safari和Edge上运行。
五、深入学习
对于想要深入学习WebExt-Bridge的开发者,我们推荐阅读我们的书籍《构建多平台浏览器扩展》。这本书涵盖了从基础到高级主题,如消息传递、存储和调试,并特别介绍了如何使用WebExt-Bridge来简化扩展中的消息传递。
六、概念与上下文
浏览器扩展由多个上下文组成,每个上下文可以发送和接收消息。WebExt-Bridge支持的上下文包括content-script、popup、options、background和devtools。通过在代码中导入相应的模块,你可以轻松地在这些上下文中进行通信。
1、通信上下文
WebExt-Bridge支持以下几种通信上下文:
- content-script - 注入到页面的脚本
- popup - 扩展的弹出窗口
- options - 选项页面
- background - 后台脚本
- devtools - 开发者工具
2、主要API
sendMessage()
方法签名:
sendMessage(messageId: string, data: any, destination: string)
用于向扩展的其他部分发送消息。如果目标端没有监听器,将抛出错误。监听器可以通过 Promise
获取响应。
参数说明:
messageId
(必填,string):消息的唯一标识符。data
(必填,any):需要传递的数据。destination
(必填,string):消息的目标地址,如background
、popup
、content-script@<tabId>
等。
onMessage()
方法签名:
onMessage(messageId: string, callback: function)
用于在每个上下文中注册消息监听器,当接收到指定 messageId
的消息时调用回调函数。
参数说明:
messageId
(必填,string):需要监听的消息标识符。callback
(必填,function):处理消息的回调函数,可以返回值或Promise
作为响应。
allowWindowMessaging()
和 setNamespace()
注意:这是危险操作,仅在必要时使用。
- allowWindowMessaging(namespace: string):仅在内容脚本中调用,用于解锁特定页面的消息传递通道。
- setNamespace(namespace: string):在加载的远程页面顶层框架中调用,用于设置消息传递的命名空间,确保消息属于特定的扩展。
openStream()
和 onOpenStreamChannel()
用于在调用者和目标之间打开一个流通道,适用于需要持续通信的场景。
3、类型安全
可以通过定义 Protocol 来实现类型安全:
declare module "webext-bridge" {
export interface ProtocolMap {
foo: { title: string }
bar: ProtocolWithReturn<CustomDataType, CustomReturnType>
}
}
七、类型安全协议
为了保持协议的类型一致性,WebExt-Bridge提供了类型安全的解决方案。你可以创建一个shim.d.ts
文件来定义消息协议的类型,从而在不同上下文中使用时保持一致性。
shim.d.ts
文件示例:
import type { ProtocolWithReturn } from 'webext-bridge'
declare module 'webext-bridge' {
export interface ProtocolMap {
// define message protocol types
// see https://github.com/antfu/webext-bridge#type-safe-protocols
'tab-prev': { title: string | undefined }
'get-current-tab': ProtocolWithReturn<{ tabId: number }, { title?: string }>
}
}
八、示例
以下是一些常见的使用示例:
1、弹出窗口 -> 后台脚本
从弹出窗口向后台脚本发送消息
// 弹出窗口脚本
import { sendMessage } from "webext-bridge/popup";
const sendToBackground = async () => {
await sendMessage("RECORD_NAME", {
first_name: 'John',
last_name: 'Doe'
}, "background");
}
// 后台脚本
import { onMessage } from "webext-bridge/background";
onMessage("RECORD_NAME", recordName);
async function recordName({ data }) {
// 处理数据
return {
// 返回响应
};
}
2、内容脚本 -> 后台脚本
从内容脚本向后台脚本发送消息
// 内容脚本
import { sendMessage } from "webext-bridge/content-script";
const sendToBackground = async () => {
const response = await sendMessage('RECORD_NAME', { first_name: 'John', last_name: 'Doe' }, 'background');
// 处理响应
}
// 后台脚本
import { onMessage } from "webext-bridge/background";
onMessage("RECORD_NAME", recordName);
async function recordName({ data }) {
// 处理数据
return {};
}
九、安全性
在处理 window
上下文的消息传递时,需要特别注意安全性。与 chrome.runtime.sendMessage
和 chrome.runtime.connect
不同,WebExt-Bridge 设计上没有限制与扩展通信的站点,这意味着任何网页只要使用相同的协议和命名空间,都可以发送消息。因此,为了确保安全:
- 验证消息来源:在回应之前,始终验证发送者的身份。例如,使用
isInternalEndpoint
函数来验证消息是否来自可信的内部端点。 - 使用命名空间:通过
allowWindowMessaging
和setNamespace
方法,确保只有特定的页面可以与扩展通信,避免恶意网站的干扰。
WebExt-Bridge内置了一些安全机制:
- 默认只允许扩展内部组件通信
- 与window上下文通信需要显式允许
- 提供验证sender的方法
示例
import { onMessage, isInternalEndpoint } from "webext-bridge/background";
onMessage("getUserBrowsingHistory", (message) => {
const { data, sender } = message;
// 仅响应来自内部端点的请求
if (isInternalEndpoint(sender)) {
const { range } = data;
return getHistory(range);
}
});
十、故障排除
如果消息传递不起作用,确保WebExt-Bridge在扩展的背景页面中正确加载,并检查你的背景脚本配置是否正确。
1、消息传递不起作用?
确保 WebExt-Bridge 已在扩展的后台脚本中正确加载。即使你不需要自己的后台页面,也需要添加一个简单的后台脚本来作为消息的中继:
background.js:
import "webext-bridge/background";
manifest.json:
{
"background": {
"scripts": ["path/to/transpiled/background.js"]
}
}
2、无法发送消息给目标?
在向内容脚本发送消息时,必须附加 tabId
以指定脚本所在的标签页。同时,确保在特定标签页的内容脚本中调用 allowWindowMessaging
方法,以允许与 window
上下文的消息传递。
十一、总结
WebExt-Bridge极大地简化了浏览器扩展中的消息传递,是开发扩展必备的工具之一。它提供了:
- 简单统一的API
- 类型安全
- 内置安全机制
- 跨浏览器兼容
如果你正在开发浏览器扩展,WebExt-Bridge绝对值得一试。