框架
消息传递

消息传递API

See LicenseNPM InstallFollow PlasmoHQ on TwitterWatch our Live DEMO every FridayJoin our Discord for support and chat about our projects

Plasmo的消息传递API使得扩展不同部分之间的通信变得简单。将文件添加到您的messages目录中,Plasmo将处理其余的一切。Plasmo消息传递是一种声明式、类型安全、函数式、基于Promise的API,用于在您的扩展组件之间发送、转发和接收消息。

安装

1. 安装依赖

@plasmohq/messaging库保存在一个单独的仓库中。您首先需要使用包管理器安装它。

pnpm install @plasmohq/messaging

2. 创建background文件夹及文件

@plasmohq/messaging库要求后台服务工作者位于background/index.ts文件夹内,所有消息处理器都位于background/*文件夹中。

如果您已经有一个background.tsbackground.js文件,则必须创建一个background文件夹并将脚本移动到background/index.tsbackground/index.js中。

如果您还没有background文件夹,请创建一个background文件夹并创建一个新的空background/index.tsbackground/index.js文件。

现在,您将能够在background/子文件夹中创建新的处理器。例如,要创建名为pingmessages处理器,您需要创建一个background/messages/ping.ts。有关可用的不同类型的处理器以及如何配置它们的信息,请参阅其余文档。

此时,您的文件夹结构可能如下所示。

新文件夹结构
.
├── background
   ├── index.ts
   └── messages
       └── ping.ts

3. 生成静态类型

在编译时,Plasmo将为所有消息处理器生成静态类型。如果您运行着开发服务器,这将自动发生;每次构建时也会自动发生。sendToBackgroundrelayMessage函数都在其参数对象中接受一个name字段;此name字段将使用所有消息处理器的名称进行静态类型化。

⚠️

注意:初始类型错误

如果您遇到诸如"name" is never之类的类型错误,这是因为Plasmo需要编译您的处理器类型。解决方法:

  1. 运行开发服务器
  2. 在编辑器中重新启动TypeScript服务器

4. 就是这些

您现在已经成功安装了Plasmo的消息库。

TL;DR

消息传递API来源目标单次长期
消息流扩展页面/CSBGSW
中继流网站CS/BGSW
端口扩展页面/CSBGSW
端口BGSW扩展页面/CS
端口+中继BGSW网页

示例

消息流

使用消息流在扩展页面、标签页或内容脚本与后台服务工作者之间发起一次性消息。此流对于将繁重的计算卸载到后台服务工作者或绕过CORS非常有用。

后台服务工作者是一个具有REST风格API处理器的消息中心。要创建消息处理器,请在background/messages目录中创建一个ts模块。文件名应为消息名称,默认导出应为处理器函数:

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

扩展页面、内容脚本或标签页可以使用@plasmohq/messaging库向这些处理器发送消息。由于Plasmo框架在后端协调您的处理器,因此消息名称会被类型化,并在编辑器中启用IntelliSense:

popup.tsx
import { sendToBackground } from "@plasmohq/messaging"
 
...
const resp = await sendToBackground({
  name: "ping",
  body: {
    id: 123
  }
})
 
console.log(resp)

要从主世界中的内容脚本发送消息,您需要在请求中包含扩展的ID。一旦构建并将其添加到浏览器中,您可以在Chrome的扩展管理器窗口中找到您的扩展ID。

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' // 在Chrome的扩展管理器中找到这个
})
 
console.log(resp)

中继流

⚠️

注意: 中继消息传递API处于公共alpha预览阶段:预计会有bug、不完整/泄漏的抽象以及未来的API更改。请通过此链接报告您遇到的任何问题。

中继流允许目标网页与后台服务工作者之间通过称为中继的轻量级消息处理器进行通信。该中继通过在内容脚本中注册的relayMessage函数实现。

relayMessage函数抽象了window.postMessage机制,注册了一个监听器,检查相同来源的消息并将其转发给后台服务工作者。这些消息随后由注册在background/messages下的适当消息流处理器处理。

sendToBackgroundViaRelay函数通过中继发送消息并等待响应。它为每个消息生成唯一的实例ID以确保正确的处理和响应跟踪。

您可以在GitHub仓库 (opens in a new tab)中查看这些函数的实现。

此方法提供了Chrome扩展文档中描述的"externally_connectable" (opens in a new tab)方法的替代方案。

设置中继

要设置中继,请在内容脚本中使用relayMessage函数。一个内容脚本可以有多个中继。假设前面的例子中有ping消息处理器,并且网站为www.plasmo.com

contents/plasmo.ts
import type { PlasmoCSConfig } from "plasmo"
 
import { relayMessage } from "@plasmohq/messaging"
 
export const config: PlasmoCSConfig = {
  matches: ["http://www.plasmo.com/*"] // 只从中继来自此域的消息
}
 
relayMessage({
  name: "ping"
})

在目标网页(例如,plasmo.com)的代码中,您可以使用sendToBackgroundViaRelay通过注册的中继发送消息,如下所示:

pages/index.tsx
 
import { sendToBackgroundViaRelay } from "@plasmohq/messaging"
...
 
const resp = await sendToBackgroundViaRelay({
  name: "ping"
})
 
console.log(resp)

要在chrome.runtime不可用的上下文中中继消息,您可以使用relay函数:

sandbox.tsx
import { relayMessage } from "@plasmohq/messaging"
 
relayMessage(
  {
    name: "ping"
  },
  async (req) => {
    console.log("some message was relayed:", req)
    return {
      message: "Hello from sandbox"
    }
  }
)

端口

⚠️

端口消息传递API处于公共alpha预览阶段:预计会有bug、不完整/泄漏的抽象以及未来的API更改。请通过此链接报告您遇到的任何问题。

消息传递端口API是对chrome运行时port API (opens in a new tab)的高级抽象,用于与后台服务工作者建立长期连接。

当前实现的重点在于与后台服务工作者中的端口监听器建立连接:

要创建BGSW端口处理器,请在background/ports目录中创建一个ts模块。文件名将是端口名称,默认导出将是处理器函数:

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

在您的扩展页面中,使用@plasmohq/messaging/port下的getPort工具获取端口,或者使用usePort钩子,请记住,usePort目前依赖于React钩子,因此您需要在React组件中使用它。以下示例展示了在Svelte组件中使用getPort

popup.svelte
<script lang="ts">
  import { getPort } from "@plasmohq/messaging/port"
  import { onMount, onDestroy } from "svelte"
 
  let output = ""
 
  const messageListener = (msg) => {
    output = msg
  }
 
  const mailPort = getPort("mail")
 
  onMount(() => {
    mailPort.onMessage.addListener(messageListener)
  })
 
  onDestroy(() => {
    mailPort.onMessage.removeListener(messageListener)
  })
 
  function handleSubmit() {
    mailPort.postMessage({
      body: {
        hello: "world"
      }
    })
  }
</script>
 
<div>{output}</div>

以下是usePort在React中的示例,数据将始终反映端口处理器的最新响应:

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"
          })
        }}>
        发送数据
      </button>
    </div>
  )
}
 
export default DeltaTab

E2E类型安全(WIP)

端到端请求/响应体类型安全正在进行中,详见#334 (opens in a new tab)。在此期间,您可以使用提供的通用类型:

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)