Initial TrueGrowth source import
This commit is contained in:
126
openspec/changes/refactor-sw-duplex-comm/design.md
Normal file
126
openspec/changes/refactor-sw-duplex-comm/design.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# Design: postmessage-duplex 通信层重构
|
||||
|
||||
## Context
|
||||
|
||||
当前系统使用原生 `postMessage` API 实现 SW 与应用层通信:
|
||||
- 应用层 → SW:直接 `controller.postMessage()`
|
||||
- SW → 应用层:`broadcastToClients()` 广播所有窗口
|
||||
|
||||
这种模式在多标签页场景下存在问题:
|
||||
1. 多个页面同时提交相同任务,SW 的去重检查存在时间窗口
|
||||
2. 广播消息所有页面都会收到,无法区分请求来源
|
||||
3. 请求-响应模式需要手动用 RxJS 实现
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
### Goals
|
||||
- 使用 postmessage-duplex 实现双工通信
|
||||
- 解决多标签页任务重复问题
|
||||
- 简化请求-响应模式的实现
|
||||
- 保持向后兼容(渐进式迁移)
|
||||
|
||||
### Non-Goals
|
||||
- 不改变现有业务逻辑
|
||||
- 不改变 IndexedDB 存储结构
|
||||
- 不改变任务执行流程
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. 通信架构
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌──────────────────────┐ ┌─────────────────┐
|
||||
│ Tab 1 (Page) │ │ Service Worker │ │ Tab 2 (Page) │
|
||||
├─────────────────┤ ├──────────────────────┤ ├─────────────────┤
|
||||
│ ServiceWorker │◄──────►│ Per-client channels │◄──────►│ ServiceWorker │
|
||||
│ Channel │ RPC │ Map<clientId,Channel>│ RPC │ Channel │
|
||||
└─────────────────┘ └──────────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
- 每个页面创建独立的 `ServiceWorkerChannel`
|
||||
- SW 端维护 `Map<clientId, channel>` 管理所有连接
|
||||
- 请求-响应使用 `call` 方法,事件通知使用 `subscribe`
|
||||
|
||||
### 2. 消息类型划分
|
||||
|
||||
| 操作 | 当前方式 | 改造后方式 |
|
||||
|------|---------|-----------|
|
||||
| 任务创建 | postMessage + 监听 | `call('task:create', params)` → `{taskId, status}` |
|
||||
| 任务取消 | postMessage | `call('task:cancel', {taskId})` → `{success}` |
|
||||
| 任务重试 | postMessage | `call('task:retry', {taskId})` → `{success}` |
|
||||
| 获取任务列表 | postMessage + timeout | `call('task:list')` → `{tasks}` |
|
||||
| 任务状态变更 | broadcast | `subscribe('task:status')` |
|
||||
| 任务完成 | broadcast | `subscribe('task:completed')` |
|
||||
| Chat 流式 | broadcast | `subscribe('chat:chunk')` |
|
||||
|
||||
### 3. 任务创建流程改造
|
||||
|
||||
**当前流程**:
|
||||
```
|
||||
1. 应用层生成 taskId
|
||||
2. 本地创建任务(乐观更新)
|
||||
3. postMessage 到 SW
|
||||
4. SW 检查重复,可能拒绝
|
||||
5. 广播 TASK_CREATED
|
||||
```
|
||||
|
||||
**改造后流程**:
|
||||
```
|
||||
1. 应用层调用 channel.call('task:create', params)
|
||||
2. SW 检查重复
|
||||
- 已存在:返回 {success: false, existingTaskId}
|
||||
- 新建:创建任务,返回 {success: true, task}
|
||||
3. 应用层根据响应更新本地状态
|
||||
4. SW 广播 task:created 给其他客户端(排除发起者)
|
||||
```
|
||||
|
||||
### 4. 多页面同步
|
||||
|
||||
- SW 维护所有通道引用
|
||||
- 当任务状态变更时,广播给所有订阅该事件的通道
|
||||
- 每个通道独立订阅,避免重复处理
|
||||
|
||||
### 5. Service Worker 端集成
|
||||
|
||||
由于 SW 不支持直接 import npm 模块,有两种方案:
|
||||
|
||||
**方案 A(推荐)**:打包时内联
|
||||
- 使用 Vite 的 `?inline` 或自定义打包将库代码内联到 SW
|
||||
|
||||
**方案 B**:复制核心代码
|
||||
- 将 postmessage-duplex 的 SW 相关代码复制到项目中
|
||||
|
||||
选择 **方案 A**,在 SW 构建配置中处理。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|------------|
|
||||
| SW 端集成复杂 | 使用打包工具处理,文档中有示例 |
|
||||
| 迁移风险 | 渐进式迁移,保留旧消息类型兼容 |
|
||||
| 库稳定性 | 库较新(v1.0.0),需关注 issues |
|
||||
|
||||
## Migration Plan
|
||||
|
||||
### Phase 1:基础设施
|
||||
1. 安装 postmessage-duplex 依赖
|
||||
2. 配置 SW 打包支持
|
||||
3. 创建新的通信服务(与现有并存)
|
||||
|
||||
### Phase 2:核心功能迁移
|
||||
1. 迁移任务创建为 call 模式
|
||||
2. 迁移任务状态为 subscribe 模式
|
||||
3. 迁移 Chat 流式为 subscribe 模式
|
||||
|
||||
### Phase 3:清理
|
||||
1. 移除旧的 RxJS 订阅逻辑
|
||||
2. 移除兼容代码
|
||||
3. 更新文档
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. SW 打包方式的具体实现细节?
|
||||
- 需要测试 Vite 对 SW 入口的打包行为
|
||||
|
||||
2. 是否需要支持消息压缩?
|
||||
- 当前任务数据量不大,暂不需要
|
||||
34
openspec/changes/refactor-sw-duplex-comm/proposal.md
Normal file
34
openspec/changes/refactor-sw-duplex-comm/proposal.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Change: 使用 postmessage-duplex 重构 Service Worker 通信层
|
||||
|
||||
## Why
|
||||
|
||||
当前 SW 与应用层通信采用原生 postMessage + 广播模式,存在以下问题:
|
||||
1. **多标签页任务重复**:同时打开多个页面时,任务可能被重复添加
|
||||
2. **无请求-响应模式**:需要手动用 RxJS filter + timeout 实现
|
||||
3. **广播无差异化**:所有客户端都收到相同消息,无法区分请求来源
|
||||
4. **无连接就绪保障**:SW 未就绪时发送的消息可能丢失
|
||||
|
||||
## What Changes
|
||||
|
||||
- **BREAKING** 重构 `SWTaskQueueClient` 使用 `postmessage-duplex` 库
|
||||
- **BREAKING** 重构 SW 端通信逻辑使用 `ServiceWorkerChannel`
|
||||
- 任务创建改用 `call` 方法(请求-响应模式),SW 返回创建结果
|
||||
- 任务状态更新使用 `subscribe` 订阅模式
|
||||
- 移除手动实现的 RxJS 超时/过滤逻辑
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected specs: 无(新增通信层规范)
|
||||
- Affected code:
|
||||
- `packages/drawnix/src/services/sw-client/client.ts`
|
||||
- `apps/web/src/sw/task-queue/queue.ts`
|
||||
- `apps/web/src/sw/task-queue/types.ts`
|
||||
- `apps/web/src/sw/index.ts`
|
||||
|
||||
## Benefits
|
||||
|
||||
1. **请求-响应模式**:任务创建等操作可等待 SW 确认结果
|
||||
2. **消息队列**:连接就绪前消息自动缓存
|
||||
3. **内置超时**:无需手动实现
|
||||
4. **多页面隔离**:每个页面有独立通道,避免重复处理
|
||||
5. **类型安全**:完整的 TypeScript 类型定义
|
||||
@@ -0,0 +1,81 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Duplex Communication Channel
|
||||
|
||||
应用层与 Service Worker 之间 SHALL 使用 postmessage-duplex 库建立双工通信通道。
|
||||
|
||||
#### Scenario: 页面初始化通道
|
||||
- **WHEN** 页面加载完成
|
||||
- **THEN** 自动创建 `ServiceWorkerChannel` 实例
|
||||
- **AND** 等待 SW 就绪后建立连接
|
||||
- **AND** 连接就绪前的消息 SHALL 自动缓存
|
||||
|
||||
#### Scenario: SW 接受新连接
|
||||
- **WHEN** SW 收到新客户端连接消息
|
||||
- **THEN** 为该客户端创建独立的通道实例
|
||||
- **AND** 将通道存入 `Map<clientId, channel>`
|
||||
|
||||
### Requirement: Request-Response Task Creation
|
||||
|
||||
任务创建操作 SHALL 使用请求-响应模式,由 SW 返回创建结果。
|
||||
|
||||
#### Scenario: 成功创建新任务
|
||||
- **GIVEN** 用户提交任务创建请求
|
||||
- **WHEN** SW 验证任务不重复
|
||||
- **THEN** SW 创建任务并返回 `{success: true, task: SWTask}`
|
||||
- **AND** 应用层根据响应更新本地状态
|
||||
- **AND** SW 广播 `task:created` 给其他客户端
|
||||
|
||||
#### Scenario: 拒绝重复任务
|
||||
- **GIVEN** 用户提交任务创建请求
|
||||
- **WHEN** SW 检测到相同参数的任务已存在
|
||||
- **THEN** SW 返回 `{success: false, existingTaskId: string, reason: 'duplicate'}`
|
||||
- **AND** 应用层 SHALL 不创建新任务
|
||||
|
||||
#### Scenario: 多页面同时提交相同任务
|
||||
- **GIVEN** Tab A 和 Tab B 几乎同时提交相同任务
|
||||
- **WHEN** SW 串行处理请求
|
||||
- **THEN** 第一个请求成功创建任务
|
||||
- **AND** 第二个请求收到重复拒绝响应
|
||||
- **AND** 两个页面最终状态一致
|
||||
|
||||
### Requirement: Event-Based Status Updates
|
||||
|
||||
任务状态变更 SHALL 使用订阅模式通知所有客户端。
|
||||
|
||||
#### Scenario: 订阅任务状态变更
|
||||
- **GIVEN** 页面订阅 `task:status` 事件
|
||||
- **WHEN** 任何任务状态发生变更
|
||||
- **THEN** 所有订阅客户端收到状态更新
|
||||
- **AND** 事件包含 `{taskId, status, progress?, phase?}`
|
||||
|
||||
#### Scenario: 任务完成通知
|
||||
- **GIVEN** 页面订阅 `task:completed` 事件
|
||||
- **WHEN** 任务执行成功完成
|
||||
- **THEN** 所有订阅客户端收到完成通知
|
||||
- **AND** 事件包含 `{taskId, result}`
|
||||
|
||||
### Requirement: Chat Streaming via Subscribe
|
||||
|
||||
Chat 流式输出 SHALL 使用订阅模式。
|
||||
|
||||
#### Scenario: 订阅 Chat 流式输出
|
||||
- **GIVEN** 页面发起 Chat 请求
|
||||
- **WHEN** SW 开始生成响应
|
||||
- **THEN** 订阅该 chatId 的客户端收到 `chat:chunk` 事件
|
||||
- **AND** 响应完成时收到 `chat:done` 事件
|
||||
|
||||
### Requirement: Multi-Tab Isolation
|
||||
|
||||
多标签页 SHALL 独立管理各自的通道连接。
|
||||
|
||||
#### Scenario: 页面刷新重建连接
|
||||
- **WHEN** 页面刷新
|
||||
- **THEN** 旧通道自动销毁
|
||||
- **AND** 新页面建立新通道
|
||||
- **AND** SW 清理旧 clientId 的通道引用
|
||||
|
||||
#### Scenario: 页面关闭清理
|
||||
- **WHEN** 页面关闭
|
||||
- **THEN** SW 检测到客户端断开
|
||||
- **AND** 清理对应的通道资源
|
||||
36
openspec/changes/refactor-sw-duplex-comm/tasks.md
Normal file
36
openspec/changes/refactor-sw-duplex-comm/tasks.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Tasks: postmessage-duplex 通信层重构
|
||||
|
||||
## 1. 基础设施
|
||||
|
||||
- [ ] 1.1 安装 postmessage-duplex 依赖到 packages/drawnix
|
||||
- [ ] 1.2 配置 SW 打包支持(内联 postmessage-duplex 到 SW bundle)
|
||||
- [ ] 1.3 创建类型定义文件 `sw-channel-types.ts`
|
||||
|
||||
## 2. 应用层改造
|
||||
|
||||
- [ ] 2.1 创建 `SWDuplexClient` 新服务(基于 ServiceWorkerChannel)
|
||||
- [ ] 2.2 实现任务创建 RPC 方法 `task:create`
|
||||
- [ ] 2.3 实现任务查询 RPC 方法 `task:list`, `task:get`
|
||||
- [ ] 2.4 实现任务操作 RPC 方法 `task:cancel`, `task:retry`, `task:delete`
|
||||
- [ ] 2.5 实现事件订阅 `task:status`, `task:completed`, `task:failed`
|
||||
- [ ] 2.6 实现 Chat 订阅 `chat:chunk`, `chat:done`, `chat:error`
|
||||
|
||||
## 3. Service Worker 改造
|
||||
|
||||
- [ ] 3.1 创建 `SWChannelManager` 管理多客户端通道
|
||||
- [ ] 3.2 注册 RPC 处理器(task:create, task:list 等)
|
||||
- [ ] 3.3 实现事件广播(排除发起者)
|
||||
- [ ] 3.4 改造任务创建逻辑(原子性检查 + 响应)
|
||||
|
||||
## 4. 集成测试
|
||||
|
||||
- [ ] 4.1 单页面任务创建测试
|
||||
- [ ] 4.2 多页面同时创建相同任务测试
|
||||
- [ ] 4.3 页面刷新后任务恢复测试
|
||||
- [ ] 4.4 Chat 流式通信测试
|
||||
|
||||
## 5. 迁移与清理
|
||||
|
||||
- [ ] 5.1 迁移 `swTaskQueueService` 使用新客户端
|
||||
- [ ] 5.2 移除旧的 RxJS 订阅逻辑
|
||||
- [ ] 5.3 更新相关文档
|
||||
Reference in New Issue
Block a user