# 事件、RPC 与契约

三种具体通信机制：通知事实、请求响应以及使用有版本的服务。

<span id="elige-por-el-tipo-de-interacción"></span>

<span class="section-anchor" id="section-1"></span>

## 按交互类型选择

| 需求 | API | 结果 |
| --- | --- | --- |
| 通知事实，无需响应 | `emit` / `on` | 带身份和数据的事件 |
| 向提供方请求响应 | `request` / `handle` | 返回值或错误的 Promise |
| 调用本地服务的方法 | `provide` / `consume` | 带版本契约的代理 |

这三种机制都位于浏览器的 JavaScript 进程中。此处的 RPC 不意味着 HTTP 或服务器间传输。实现内部可以调用 fetch，但该副作用及其授权由实现负责。

使用 `wu` 的片段假定已在 shell 客户端模块中执行 `import { wu } from 'wu-framework'`，并通过 `await wu.init(...)` 初始化。使用 `appWu` 的片段采用在 `mount(container, context)` 中交给应用的 `context.wu`。需要可复制的完整示例，请参阅[应用间求和](/zh-cn/docs/guides/rpc-between-apps/)：包括客户端、提供方、manifest、grants 和 Astro 页面，并展示可观察的响应以及可恢复的错误。

<span id="eventos-y-envelope"></span>

<span class="section-anchor" id="section-2"></span>

## 事件与 envelope

```js
// Código del shell, con autoridad host.
const stop = wu.on('catalog:*', (event) => {
  console.log(event.name, event.data, event.appName, event.verified);
});

const accepted = wu.emit('catalog:selected', { productId: 'p-42' });
if (!accepted) console.info('El bus rechazó el evento');
stop();
```

回调接收包含 `name`、`data`、`timestamp`、`appName`、`meta` 和 `verified` 的对象。数据跨越总线边界时会被分离并冻结。`once` 在首次交付后移除订阅；`off` 移除指定订阅。

在应用中使用 `context.wu.on` 和 `context.wu.emit`。facade 会附加应用身份并验证 grants。严格模式下，身份无效、缺少能力或向保留命名空间发射时返回 `false`。被拒绝的订阅和 RPC 可能抛出错误：检查 `emit` 的布尔返回值，并处理会抛出异常的 API。

总线默认最多保存 100 个事件。`eventBus.replay(pattern, callback)` 将已有记录再次交给指定回调；它不会重新执行整个系统，也不保证恢复外部副作用。`history: false` 可将一次发射排除在历史记录之外。

<span id="rpc-con-timeout-y-versión"></span>

<span class="section-anchor" id="section-3"></span>

## 具有超时和版本的 RPC

```js
const release = wu.eventBus.handle('catalog:lookup', ({ productId }) => {
  return { productId, title: 'Producto de demostración' };
}, { version: '1.0.0', allow: ['search'] });

// Dentro del lifecycle de la app search, con rpc.call:catalog:lookup:
async function lookup(appWu, productId, signal) {
  return appWu.request('catalog:lookup', { productId }, {
    version: '^1.0.0',
    timeout: 2500,
    signal,
  });
}

// Cuando el proveedor del shell ya no deba atender:
release();
```

提供方应用需要针对通道的 `rpc.provide`，使用方需要 `rpc.call`。此外，`allow` 会过滤调用者身份。严格模式下，具有应用作用域的提供方默认 allow 仅允许自身应用；应用间通信需要明确声明使用方。

`wu.handle(channel, handler, options)` 保留 `version`、`allow` 和 `replace`。在生命周期中，`context.wu.handle` 还加入应用身份和 grants；微应用 handlers 应使用该 facade。

`request` 的默认超时为 **0**，即不限时。UI 交互应设置有限超时。handler 的第二个参数包含关联信息、身份和一个 `signal`。handler 应配合该 signal 取消自身工作；拒绝等待不会撤销已经执行的操作。

一个通道只有一个活动提供方。在严格条件下替换同一提供方需要 `{ replace: true }`；其他身份不能占用已有所有者的通道。释放、替换和撤销提供方都会中止关联请求。总线会发射带有关联信息的 `<canal>:request`、`<canal>:response` 和 `<canal>:error` 跟踪事件。

<span id="contratos-de-métodos-locales"></span>

<span class="section-anchor" id="section-4"></span>

## 本地方法契约

```js
const handle = wu.provide('formatters', {
  price(value) {
    return new Intl.NumberFormat('es-MX', {
      style: 'currency', currency: 'MXN',
    }).format(value);
  },
}, {
  version: '1.0.0',
  shape: { price: 'function' },
});

const formatters = wu.consume('formatters', '^1.0.0');
console.log(formatters.price(120));
handle.revoke();
```

`consume` 返回动态代理。不使用 `{ wait: true }` 时，创建代理不要求提供方已经存在：首次访问成员才会验证存在性和版本。使用 `{ wait: true, timeout: 2500 }` 时，会等待兼容的提供方出现。

`provide` 的默认版本为 `0.0.0`，而 RPC `handle` 的默认版本为 `1.0.0`。`shape` 验证成员存在性或 `typeof`，不能替代完整的参数与结果 schema。

应用 facade 对契约同样要求 `rpc.provide`/`rpc.call`，并自动将提供方关联到应用。使用方得到的代理不是 Promise；无法通过该代理访问名为 `then` 的方法。契约没有实现与总线 RPC 相同的允许列表和载荷分离机制；选择契约时应考虑这一限制。

<span id="errores-que-conviene-manejar"></span>

<span class="section-anchor" id="section-5"></span>

## 应当处理的错误

| RPC 错误码 | 原因 |
| --- | --- |
| `WU_RPC_NO_HANDLER` | 通道没有提供方 |
| `WU_RPC_VERSION_MISMATCH` | 版本不满足范围 |
| `WU_RPC_CALLER_DENIED` | 使用方不在 `allow` 中 |
| `WU_RPC_CALL_DENIED` / `WU_RPC_PROVIDE_DENIED` | 缺少有效 grant |
| `WU_RPC_CHANNEL_OWNED` | 其他所有者已注册此通道 |
| `WU_RPC_REPLACE_REQUIRED` | 未明确声明替换意图 |
| `WU_RPC_PROVIDER_RELEASED` / `WU_RPC_PROVIDER_REPLACED` | 请求过程中提供方发生变化 |

<span id="referencia-de-implementación"></span>

<span class="section-anchor" id="section-6"></span>

## 实现参考

事件：`src/core/wu-event-bus.js`；RPC：`src/core/wu-event-bus.js` 和 `src/core/wu-event-bus.js`；单例 handle 快捷方法：`src/index.js`；replay：`src/core/wu-event-bus.js`；契约：`src/core/wu-contracts.js` 和 `src/core/wu-contracts.js`；facade：`src/core/wu-core.js`。测试：`tests/unit/event-bus.test.js`、`tests/unit/contracts.test.js`、`tests/unit/capability-security-v3.test.js`、`tests/unit/scoped-events-teardown.test.js`。

首个 provider 拥有合同名称。在所有者撤销合同或卸载之前，其他应用或尝试替换应用合同的 host 会收到 `WU_CAPABILITY_OWNED`。同一所有者可替换其实现。当存在 `shape` 时，远程 ports 仅暴露其中声明的方法。