# 消息通信与浏览器启动

> 使用进程级消息通道、注册 Service Worker 监听，并协调可选的浏览器启动窗口等待流程。

Source: https://console.getchrovia.com/zh/docs/sdk-messaging

**能力：**`messaging`。**API：**`chrovia.messaging`。在 Provision Service Worker 使用；页面访问还需要 `unrestrictedApi`。

## 发布与订阅

```js
const messaging = globalThis.chrovia?.messaging;
if (messaging) {
  const receive = data => console.log('Received status:', data);
  messaging.on('my-plugin.status', receive);
  messaging.emit('my-plugin.status', { ready: true });
  setTimeout(() => messaging.off('my-plugin.status', receive), 1000);
}
```

| 方法 | 行为 |
| --- | --- |
| `on(channel, callback)` | 注册接收消息数据的回调 |
| `emit(channel, data)` | 向命名通道发送兼容 JSON 的数据 |
| `off(channel, callback)` | 移除同一个回调引用 |

所有方法返回 `undefined`。`emit()` 不返回响应 Promise。需要 RPC 时，在自己的协议中定义请求 ID 和响应通道。向 `off` 传入另一个匿名函数不会移除原回调。

通道在浏览器进程内共享，不是扩展私有。应验证载荷，并为应用通道使用专属命名。每次 Worker 脚本执行都要注册。浏览器可以暂存早到消息并唤醒已订阅的 Service Worker，但消息不会跨浏览器退出持久保存，首个订阅者可能消费此前未处理的等待消息。

## 浏览器调用事件

`chrovia.browser.invoked` 表示进程冷启动或对现有实例的命令行再次调用。它不是标签页或窗口打开事件，不会仅因 Worker 唤醒或 macOS Dock 打开窗口而重发。

载荷包含 `initialInvocation`（布尔）、`commandLine`（字符串）、`argv` 和 `args`（字符串数组）、`switches`（对象）、`currentDirectory`（字符串，首次启动时为空）。命令行数据应作为输入验证，不要当成可信指令执行。

## 延迟正常启动窗口流程

需要在正常启动或打开窗口路径继续前完成插件初始化时，合并以下 EP 并按需签名：

```json
{
  "internal": {
    "browser_invocation": {
      "defer_window": {
        "enabled": true,
        "timeout_seconds": 30
      }
    }
  }
}
```

只在一个负责此流程的 Provision Worker 中注册处理器。此例在放行前验证本地应用配置，还需要 `extendedPrefs`：

```js
const sdk = globalThis.chrovia;
if (sdk?.messaging) {
  sdk.messaging.on('chrovia.browser.defer_window', data => {
    const channel = data?.callback_channel;
    if (typeof channel !== 'string'
        || !channel.startsWith('chrovia.browser.defer_window.reply.')) {
      return;
    }
    (async () => {
      let success = false;
      try {
        const config = await sdk.prefs?.get('my_plugin');
        success = config?.ready === true;
      } finally {
        sdk.messaging.emit(channel, { success });
      }
    })().catch(console.error);
  });
}
```

为此例在 EP 根节点设置未签名应用数据 `my_plugin: {"ready": true}`。实际请求包含调用事件字段和使用 snake_case 的 **`callback_channel`**。向该通道回复 `{success: true}` 继续正常启动路径，或 `{success: false}` 跳过已调度路径；false 本身不是退出进程命令。

顺序是调用事件、可选的 defer 事件及回复、正常启动路径。`enabled` 缺失或 false，或者缺少 `messaging` 时不等待。超时字段缺失或不是正整数时默认 30 秒。**超时默认放行**，按成功继续；格式不完整的回复也默认成功。这是流程协调，不是安全屏障。

不要安装多个争抢同一 defer 事件的插件。也不要把它当成网络注册就绪机制：[intercept 返回 void](https://console.getchrovia.com/zh/docs/sdk-network-intercept)，在处理器中 await 它不提供浏览器端就绪确认。

## 其他内置通道

| 通道 | 用途 |
| --- | --- |
| `chrovia.process.supervisor_exited` | 非退出模式下的监督方结束通知 |
| `chrovia.passwords.changed` | 调用 `passwords.watch()` 后的密码变更信号 |
| `chrovia.software_authenticator.credential.save` | 软件凭据注册保存 RPC |
| `chrovia.software_authenticator.counter.claim` | 外部签名计数器 RPC |

载荷和回复规则分别见[监督进程](https://console.getchrovia.com/zh/docs/sdk-supervisor-process)、[密码存储](https://console.getchrovia.com/zh/docs/sdk-passwords)、[软件认证器](https://console.getchrovia.com/zh/docs/sdk-software-authenticator)。应用代码不要伪造内核生命周期事件。
