Message API
Plasmo
提供了封装好的 Message API , 这让扩展各个模块之间通信变得简单。
在你的 Plasmo
项目目录中的 background
目录,添加 Message
文件目录, Plasmo
将为你完成剩余的工作。
Plasmo
提供声明式的、类型安全的 函数结构,同时提供 Promise
结构的异步函数执行结构。
安装模块
pnpm install @plasmohq/messaging
安装完成后,在你的 Plasmo
的 background
目录 创建 messages 模块。
如果你使用了 background.ts
, 那么你需要创建 background
目录, 其中创建 index.ts
。
模块及通信规则
Messaging API | From | To | One-time | Long-lived |
---|---|---|---|---|
Message Flow | Ext-Pages/CS | BGSW | Yes | No |
Relay Flow | Website | CS/BGSW | Yes | No |
Ports | Ext-Pages/CS | BGSW | No | Yes |
Ports | BGSW | Ext-Pages/CS | No | Yes |
Ports + Relay | BGSW | WebPage | Yes | Yes |
官方示例
- 官方示例 : with-messaging
Message Flow
使用 Message Flow
可以完成扩展组件间的一次性通信。 这是一种和普通的 API 接口非常相似的一种交互方式。
通信双方可以是 tab page
、content script
相当于我们的前端页面, background service worker
相当于我们的后端 API 接口。
这种通信规则,可以把大量的数据计算或者因为 CORS 规则无法调用的函数放到background
中去完成。
background service
的工作函数是一个信息中心,每个服务都包含一个 Rest API 样式的处理器。
你可以很方便的在 background/messages
创建一个 ts 模块,文件名将会是 message 的名字,默认导出的函数必须是函数处理器。
import type { PlasmoMessaging } from "@plasmohq/messaging";
const handler: PlasmoMessaging.MessageHandler = async (req, res) => {
const message = await querySomeApi(req.body.id);
res.send({
message,
});
};
export default handler;
以上是类似的后端 API 接口,接下来我们介绍 如何调用。
在 扩展页面、Content Script 或者 tab page 中我们通过 @plasmohq/messaging
的 API 发起交互。
由于 Plasmo
在后台可以解析我们的 Service 调用,你使用的 IDE 触发消息名称的提示感知。
import { sendToBackground } from "@plasmohq/messaging"
...
const resp = await sendToBackground({
name: "ping",
body: {
id: 123
}
})
console.log(resp)
如果你需要从前端的 content script
向后端发起交互,那么你需要你的 扩展 ID。
你看的扩展 ID 可以通过, chrome://extensions
扩展管理页面获得。
import { sendToBackground } from "@plasmohq/messaging"
import type { PlasmoCSConfig } from "plasmo"
export const config: PlasmoCSConfig = {
matches: ["<all_urls>"],
world: "MAIN"
}
...
const resp = await sendToBackground({
name: "ping",
body: {
id: 123
},
extensionId: 'llljfehhnoeipgngggpomjapaakbkyyy' // find this in chrome's extension manager
})
console.log(resp)
这样,自然也带来了一个问题。扩展 ID 怎么获得呢?
- 开发阶段你可以通过,扩展管理器中获得,开发过程中保持不变。
- 发布后怎么获得呢?
在扩展的相关页面中,可以通过
chrome.runtime.id
获取到。
Relay Flow
这个模块目前还处于 Alpha 阶段,可能有 Bug ,同时 后期的 API 也可能会变化。请谨慎使用。
可以随时通过 https://docs.plasmo.com/bug 反馈你遇到的 Bug 或者其他问题。
同 Message Flow 的区别:
- 调用方不同。 Message Flow 的调用来源,只能是 扩展页面或者
Content Script
。 而 Relay Flow 的来源是网站页面。 - 运行位置不同。 Message Flow 处理器运行在
background
,而Relay Flow
跑在Content Script
或者Sandbox
。
Content Script 中的处理实例
import type { PlasmoCSConfig } from "plasmo";
import { relayMessage } from "@plasmohq/messaging";
export const config: PlasmoCSConfig = {
matches: ["http://www.plasmo.com/*"], // Only relay messages from this domain
};
relayMessage({
name: "ping",
async(req)=>{
return {
message: "hello from content"
}
}
});
Sandbox 中的调用监听实例
import { relayMessage } from "@plasmohq/messaging/relay";
relayMessage(
{
name: "ping",
},
async (req) => {
console.log("some message was relayed:", req);
return {
message: "Hello from sandbox",
};
}
);
网站调用方式,同样需要使用 @plasmohq/messaging
提供的 API 接口。
import { sendToBackgroundViaRelay } from "@plasmohq/messaging"
...
const resp = await sendToBackgroundViaRelay({
name: "ping"
})
console.log(resp)
Ports
这个模块目前还处于 Alpha 阶段,可能有 Bug ,同时 后期的 API 也可能会变化。请谨慎使用。
可以随时通过 https://docs.plasmo.com/bug 反馈你遇到的 Bug 或者其他问题。
Message Ports
API 是 Plasmo
使用
chrome 的 Port API
封装完成的一种长连接。
交互双方是从 Content Script
和 扩展页面
调用 background
调用。
当前实现主要是从 background service worker
中创建监听。
创建一个 BGSW Port 处理器,只需要在 background
目录中,创建 ports
目录。
然后,创建处理器文件,文件名也是 port 的名字,默认导出的函数必须是函数处理器。
import type { PlasmoMessaging } from "@plasmohq/messaging";
const handler: PlasmoMessaging.PortHandler = async (req, res) => {
console.log(req);
res.send({
message: "Hello from port handler",
});
};
export default handler;
调用 port 处理器有两种方法。
使用 getPort 函数
在 扩展页面中,你可以通过 @plasmohq/messaging/port
的 getPort 函数获得 port 对象。
然后,通过向 port 调用 postMessage 和 onMessage 监控消息返回。
<script lang="ts">
import { getPort } from "@plasmohq/messaging/port"
import { onMount, onDestroy } from "svelte"
let output = ""
const mailPort = getPort("mail")
onMount(() => {
mailPort.onMessage.addListener((msg) => {
output = msg
})
})
onDestroy(() => {
mailPort.onMessage.removeListener((msg) => {
output = msg
})
})
function handleSubmit() {
mailPort.postMessage({
body: {
hello: "world"
}
})
}
</script>
<div>{output}</div>
使用 usePort
的 React API
import { usePort } from "@plasmohq/messaging/hook";
function DeltaTab() {
const mailPort = usePort("mail");
return (
<div>
{mailPort.data?.message}
<button
onClick={async () => {
mailPort.send({
hello: "world",
});
}}
>
Send Data
</button>
</div>
);
}
export default DeltaTab;
Initial Type Error
如果你遇到了 类似的 错误 : "name" is never
。
这是因为 Plasmo
需要编译处理器函数。
你可以按照如下的方式解决:
- 重启 Dev Server
- 重启 你的 IDE 的 typescript Server
E2E Type Safety (WIP) 点对点类型安全
点对点 (E2E)的通信仍然在开发阶段 #334。 同时你可以使用如下的泛型来封装数据。
import type { PlasmoMessaging } from "@plasmohq/messaging";
export type RequestBody = {
id: number;
};
export type ResponseBody = {
message: string;
};
const handler: PlasmoMessaging.MessageHandler<
RequestBody,
ResponseBody
> = async (req, res) => {
console.log(req.body.id);
res.send({
message: "Hello from background",
});
};
export default handler;
import { sendToBackground } from "@plasmohq/messaging"
import type { RequestBody, ResponseBody } from "~background/messages/ping"
...
const resp = await sendToBackground<RequestBody, ResponseBody>({
name: "ping",
body: {
id: 123
}
})
console.log(resp)