Skip to main content

Message API

Plasmo 提供了封装好的 Message API , 这让扩展各个模块之间通信变得简单。 在你的 Plasmo项目目录中的 background 目录,添加 Message 文件目录, Plasmo 将为你完成剩余的工作。 Plasmo 提供声明式的、类型安全的 函数结构,同时提供 Promise结构的异步函数执行结构。

安装模块

pnpm install @plasmohq/messaging

安装完成后,在你的 Plasmobackground 目录 创建 messages 模块。 如果你使用了 background.ts, 那么你需要创建 background 目录, 其中创建 index.ts

模块及通信规则

Messaging APIFromToOne-timeLong-lived
Message FlowExt-Pages/CSBGSWYesNo
Relay FlowWebsiteCS/BGSWYesNo
PortsExt-Pages/CSBGSWNoYes
PortsBGSWExt-Pages/CSNoYes
Ports + RelayBGSWWebPageYesYes

官方示例

Message Flow

使用 Message Flow 可以完成扩展组件间的一次性通信。 这是一种和普通的 API 接口非常相似的一种交互方式。

通信双方可以是 tab pagecontent script相当于我们的前端页面, background service worker 相当于我们的后端 API 接口。 这种通信规则,可以把大量的数据计算或者因为 CORS 规则无法调用的函数放到background中去完成。

background service 的工作函数是一个信息中心,每个服务都包含一个 Rest API 样式的处理器。 你可以很方便的在 background/messages 创建一个 ts 模块,文件名将会是 message 的名字,默认导出的函数必须是函数处理器。

background/messages/ping.ts
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 触发消息名称的提示感知。

popup.tsx
import { sendToBackground } from "@plasmohq/messaging"

...
const resp = await sendToBackground({
name: "ping",
body: {
id: 123
}
})

console.log(resp)

如果你需要从前端的 content script 向后端发起交互,那么你需要你的 扩展 ID。 你看的扩展 ID 可以通过, chrome://extensions 扩展管理页面获得。

contents/componentInTheMainWorld.tsx
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)
tip

这样,自然也带来了一个问题。扩展 ID 怎么获得呢?

  1. 开发阶段你可以通过,扩展管理器中获得,开发过程中保持不变。
  2. 发布后怎么获得呢? 在扩展的相关页面中,可以通过 chrome.runtime.id 获取到。

Relay Flow

warning

这个模块目前还处于 Alpha 阶段,可能有 Bug ,同时 后期的 API 也可能会变化。请谨慎使用。

可以随时通过 https://docs.plasmo.com/bug 反馈你遇到的 Bug 或者其他问题。

同 Message Flow 的区别:

  1. 调用方不同。 Message Flow 的调用来源,只能是 扩展页面或者 Content Script。 而 Relay Flow 的来源是网站页面。
  2. 运行位置不同。 Message Flow 处理器运行在 background,而 Relay Flow 跑在 Content Script 或者 Sandbox

Content Script 中的处理实例

contents/plasmo.ts
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 中的调用监听实例

sandbox.ts
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 接口。

pages/index.tsx
import { sendToBackgroundViaRelay } from "@plasmohq/messaging"
...

const resp = await sendToBackgroundViaRelay({
name: "ping"
})

console.log(resp)

Ports

warning

这个模块目前还处于 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 的名字,默认导出的函数必须是函数处理器。

background/ports/mail.ts
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 监控消息返回。

popup.svelte
<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>

使用 usePortReact API

tabs/delta.tsx
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 需要编译处理器函数。 你可以按照如下的方式解决:

  1. 重启 Dev Server
  2. 重启 你的 IDE 的 typescript Server

E2E Type Safety (WIP) 点对点类型安全

点对点 (E2E)的通信仍然在开发阶段 #334。 同时你可以使用如下的泛型来封装数据。

background/messages/ping.ts
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;
popup.tsx
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)