npm package: @i-xor/wormhole
Description: Browser-native micro-frontend protocol and runtime based on Web Components, iframe, and RxJS.
Latest version: 0.1.0

# Wormhole

[English](./README.md) | [简体中文](./README.zh-CN.md)

`@i-xor/wormhole` 是一套 browser-native 的微前端协议与运行时，构建在 Web Components、iframe、`postMessage`、`MessageChannel` 与 RxJS 之上。

Wormhole 不试图掩盖 iframe 边界，而是把主应用与子应用之间的协作显式建模为一套契约，覆盖导航、权限、事件、RPC、生命周期和故障恢复。

## 为什么用 Wormhole

大多数 iframe 集成最终会在这些地方出问题：

- 主应用和子应用对顶层路由归属没有一致约定
- 认证态和权限态靠临时拼装透传
- runtime 通道异常后的恢复能力很弱
- 跨应用通信逐渐退化成隐式的全局事件总线

Wormhole 通过下面这些机制来解决问题：

- 类型化的 bootstrap 握手
- 独立的 runtime 通道
- 主子之间明确的 capability ownership
- 显式的 `route-intent` / `route-commit` 协议
- 基于会话的消息签名
- 由宿主治理的 child-to-child broker

## 包结构

- `@i-xor/wormhole` - 顶层导出
- `@i-xor/wormhole/client` - 子应用侧 runtime client
- `@i-xor/wormhole/host` - 宿主侧 session 与 broker
- `@i-xor/wormhole/element` - 原生 `<wormhole-app>` custom element
- `@i-xor/wormhole/react` - React 包装层
- `@i-xor/wormhole/vue` - Vue 3 包装层
- `@i-xor/wormhole/protocol` - 协议类型、常量、校验和 envelope 工厂
- `@i-xor/wormhole/rx` - 底层 runtime stream 工具

## 功能概览

Wormhole v1 当前包含：

- `control:hello / control:ack / control:ready` bootstrap 生命周期
- `postMessage` bootstrap 与 `MessageChannel` runtime 通道
- 基于 `route-intent` 和 `route-commit` 的路由协商
- 结构化事件发布与 RPC 请求/响应
- context、auth、permission 和 capability snapshot
- 关键上行消息的显式 acknowledgement
- heartbeat 与 reconnect 恢复逻辑
- 用于脏状态或长事务的 before-leave 协商
- 宿主管理的 child-to-child broker 转发
- 基于原生 custom element 的 React / Vue 包装层

当前 v1 暂不处理：

- 可视化 devtools
- 建立在 granted capability 之上的 deny policy 层

## 安装

```bash
pnpm add @i-xor/wormhole rxjs
```

如果你使用 React 或 Vue 包装层，需要在消费方应用中自行安装对应 peer dependency。

## 快速开始

### 宿主侧

```ts
import { WormholeHostBroker, WormholeHostSession } from '@i-xor/wormhole/host'

const broker = new WormholeHostBroker()

const host = new WormholeHostSession({
  app: 'orders-app',
  targetOrigin: 'http://localhost:3101',
  grantedCapabilities: ['route', 'context', 'auth', 'permission', 'rpc', 'event', 'broker'],
  initialContext: {
    locale: 'en-US',
    theme: 'light'
  },
  initialAuth: {
    authenticated: true,
    transport: 'delegated',
    sessionId: 'session-1',
    subject: {
      id: 'user-1',
      tenantId: 'workspace-1',
      displayName: 'Alex'
    }
  },
  initialPermissions: {
    roles: ['workspace-admin'],
    permissions: ['APP:ORDER:READ']
  },
  broker
})
```

### 子应用侧

```ts
import { WormholeClient } from '@i-xor/wormhole/client'

const wormhole = new WormholeClient({
  app: 'orders-app',
  instanceId: 'orders-app:1',
  reconnectDelayMs: 250
})

wormhole.bootstrap(window.parent, 'http://localhost:3000', {
  routeMode: 'delegated',
  capabilities: ['route', 'context', 'auth', 'permission', 'rpc', 'event', 'broker']
})

wormhole.registerBeforeLeaveHandler(() => ({
  blocked: hasDirtyForm(),
  reason: 'form-dirty'
}))

wormhole.publishToApp('inventory-app', 'inventory.item.highlight', {
  itemId: 'item-1'
})
```

## 原生 Custom Element

```html
<wormhole-app
  app="orders-app"
  src="http://localhost:3101/"
  route-mode="delegated"
  sandbox="allow-same-origin allow-scripts allow-forms"
  referrer-policy="strict-origin-when-cross-origin"
></wormhole-app>
```

```ts
import { defineWormholeAppElement } from '@i-xor/wormhole/element'

defineWormholeAppElement()
```

`wormhole-app` 默认会尽量铺满承载容器：

- custom element 自身使用 `width: 100%` 与 `height: 100%`
- 当子应用上报 `control:resize` 时，元素与内部 iframe 会扩展到 `max(100%, contentHeight)`
- 宿主页面仍应提供稳定的高度链，保证嵌入容器可计算

## React 包装层

```tsx
import { Wormhole } from '@i-xor/wormhole/react'

<Wormhole
  app="orders-app"
  src="http://localhost:3101/"
  context={{ locale: 'en-US' }}
  auth={authSnapshot}
  permissions={permissionSnapshot}
  capabilityOwnership={{ route: 'host', auth: 'host', permission: 'host' }}
  onReady={onReady}
  onEvent={onEvent}
  onError={onError}
  onResize={onResize}
/>
```

## Vue 包装层

```vue
<template>
  <Wormhole
    app="orders-app"
    src="http://localhost:3101/"
    :context="{ locale: 'en-US' }"
    :auth="authSnapshot"
    :permissions="permissionSnapshot"
    :capability-ownership="{ route: 'host', auth: 'host', permission: 'host' }"
    @ready="onReady"
    @event="onEvent"
    @error="onError"
    @resize="onResize"
  />
</template>
```

## Runtime 模型

### Capability Ownership

Wormhole 把 capability grant 与 capability authority 分开建模。子应用可以被允许参与某个能力，但不一定拥有最终决策权。

嵌入态下的默认归属通常是：

| Capability | 常见 authority |
| --- | --- |
| `route` | `host` |
| `context` | `host` |
| `auth` | `host` |
| `permission` | `host` |
| `resize` | `child` |
| `lifecycle` | `child` |
| `rpc` | `shared` |
| `event` | `shared` |

### 导航

在 delegated routing 模式下：

1. 子应用发出 `route-intent`
2. 宿主完成校验并提交路由
3. 宿主返回 `route-commit`
4. 子应用收敛到目标路由，但不会把同一路由再次回推给宿主

### 恢复机制

- 宿主和子应用默认每 15 秒发送一次 `control:heartbeat`
- 任一侧在 45 秒内未观察到 runtime 活动时，会把当前通道标记为 stale
- 子应用会基于最近一次成功 bootstrap 的参数重建 runtime 通道

### Before Leave

当宿主需要确认是否可以离开当前页面时，可以调用 `requestBeforeLeave()`。

典型场景包括：

- 表单未保存
- 批量操作未完成
- 仍在进行中的审批或编辑流程

### Child-to-Child 协作

子应用之间不会直接互连。所有跨子应用的 publish 和 RPC 流量都经由宿主 broker 转发，这样宿主始终拥有路由、隔离与审计的控制权。

## 安全模型

- bootstrap 必须校验 origin 和预期 source
- runtime payload 必须满足 structured-clone 约束
- 不能把宿主对象引用直接传入子应用
- auth snapshot 应优先传摘要信息，而不是原始凭据
- runtime 消息在启用时可使用基于会话的 `messageAuthKey` 进行签名
- 默认 iframe sandbox 已显式声明；如有需要，宿主应按应用能力继续收窄

## 开发

```bash
pnpm install
pnpm build
pnpm test
```

## 仓库协作规范

- 贡献说明：[`CONTRIBUTING.md`](./CONTRIBUTING.md)
- 安全策略：[`SECURITY.md`](./SECURITY.md)
- 行为准则：[`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md)

## 协议参考

更完整的消息模型、生命周期规则和接入契约见 [`PROTOCOL.md`](./PROTOCOL.md)。