@hile/message-modem 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +158 -0
- package/SKILL.md +170 -0
- package/package.json +25 -0
package/README.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# @hile/message-modem
|
|
2
|
+
|
|
3
|
+
传输无关的请求/响应消息通信抽象层。将底层传输机制(WebSocket、postMessage、IPC 等)与业务逻辑解耦,提供统一的 send/receive 语义。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @hile/message-modem
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 核心特性
|
|
12
|
+
|
|
13
|
+
- **传输无关** — 子类只需实现 `post`(如何发送)和 `exec`(如何处理),即可运行于任何通信通道
|
|
14
|
+
- **请求/响应配对** — 自增 ID + Promise 栈,自动配对请求与响应
|
|
15
|
+
- **超时控制** — 默认 30 秒,可按请求自定义
|
|
16
|
+
- **主动中止** — 发送方可 abort 等待,接收方可取消正在执行的任务
|
|
17
|
+
- **错误传播** — `Exception` 携带 status 码透传;普通 Error 映射为 500
|
|
18
|
+
|
|
19
|
+
## 快速开始
|
|
20
|
+
|
|
21
|
+
### 第一步:继承并实现抽象方法
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { MessageModem, type MessageTransferFormat } from '@hile/message-modem';
|
|
25
|
+
|
|
26
|
+
class WebSocketModem extends MessageModem {
|
|
27
|
+
constructor(private ws: WebSocket) {
|
|
28
|
+
super();
|
|
29
|
+
ws.addEventListener('message', (e) => {
|
|
30
|
+
this.receive(JSON.parse(e.data));
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
protected post<T>(data: MessageTransferFormat<T>): void {
|
|
35
|
+
this.ws.send(JSON.stringify(data));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
protected async exec(data: any): Promise<any> {
|
|
39
|
+
// 处理远端请求
|
|
40
|
+
return handleRequest(data);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 暴露发送方法
|
|
44
|
+
public request<T>(data: T, timeout?: number) {
|
|
45
|
+
return this.send(data, timeout);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 第二步:发送请求
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
const modem = new WebSocketModem(ws);
|
|
54
|
+
|
|
55
|
+
const { abort, response } = modem.request({ action: 'getUser', id: 1 });
|
|
56
|
+
|
|
57
|
+
// 等待响应
|
|
58
|
+
const user = await response();
|
|
59
|
+
console.log(user);
|
|
60
|
+
|
|
61
|
+
// 或中止请求
|
|
62
|
+
abort();
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 第三步:处理超时
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
try {
|
|
69
|
+
// 5 秒超时
|
|
70
|
+
const result = await modem.request(data, 5000).response();
|
|
71
|
+
} catch (e) {
|
|
72
|
+
if (e instanceof AbortException) {
|
|
73
|
+
console.log('请求超时或被中止');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## API
|
|
79
|
+
|
|
80
|
+
### `MessageModem`(抽象类)
|
|
81
|
+
|
|
82
|
+
| 方法 | 可见性 | 说明 |
|
|
83
|
+
|------|--------|------|
|
|
84
|
+
| `post(data)` | `protected abstract` | 子类实现:如何将消息发送到远端 |
|
|
85
|
+
| `exec(data)` | `protected abstract` | 子类实现:如何处理收到的请求,返回 Promise |
|
|
86
|
+
| `send(data, timeout?)` | `protected` | 发送请求,返回 `{ abort, response }` |
|
|
87
|
+
| `receive(msg)` | `public` | 接收消息入口,根据 mode 分发处理 |
|
|
88
|
+
|
|
89
|
+
### `send` 返回值
|
|
90
|
+
|
|
91
|
+
| 属性 | 类型 | 说明 |
|
|
92
|
+
|------|------|------|
|
|
93
|
+
| `abort` | `() => void` | 中止本次请求 |
|
|
94
|
+
| `response` | `<U>() => Promise<U>` | 等待远端响应 |
|
|
95
|
+
|
|
96
|
+
### 消息类型
|
|
97
|
+
|
|
98
|
+
| 枚举值 | 说明 |
|
|
99
|
+
|--------|------|
|
|
100
|
+
| `MESSAGE_MODEM_TYPE.REQUEST` | 请求消息 |
|
|
101
|
+
| `MESSAGE_MODEM_TYPE.RESPONSE` | 响应消息 |
|
|
102
|
+
| `MESSAGE_MODEM_TYPE.ABORT` | 中止消息 |
|
|
103
|
+
|
|
104
|
+
### 异常类
|
|
105
|
+
|
|
106
|
+
| 类 | status | 默认 message | 说明 |
|
|
107
|
+
|------|--------|------|------|
|
|
108
|
+
| `Exception` | 自定义 | 自定义 | 基础异常,携带 status |
|
|
109
|
+
| `TimeoutException` | `ETIMEDOUT` | `Timeout` | 超时异常 |
|
|
110
|
+
| `AbortException` | `ECONNABORTED` | `Abort` | 中止异常 |
|
|
111
|
+
|
|
112
|
+
### 消息格式
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
// 传输格式
|
|
116
|
+
interface MessageTransferFormat<T = any> {
|
|
117
|
+
id: number;
|
|
118
|
+
mode: MESSAGE_MODEM_TYPE;
|
|
119
|
+
twoway: boolean;
|
|
120
|
+
data?: T;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 响应数据格式
|
|
124
|
+
interface MessageReturnFormat<T = any> {
|
|
125
|
+
status: string | number;
|
|
126
|
+
data: T;
|
|
127
|
+
message: string;
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## 消息流转
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
发送方 接收方
|
|
135
|
+
│ │
|
|
136
|
+
│ send(data) │
|
|
137
|
+
│──── REQUEST ───────────────►│
|
|
138
|
+
│ │ exec(data)
|
|
139
|
+
│ │
|
|
140
|
+
│◄──── RESPONSE ──────────────│
|
|
141
|
+
│ resolve(data) │
|
|
142
|
+
│ │
|
|
143
|
+
│ abort() │
|
|
144
|
+
│──── ABORT ─────────────────►│
|
|
145
|
+
│ │ 取消 exec
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## 适用场景
|
|
149
|
+
|
|
150
|
+
- **iframe 通信** — 父子页面 postMessage
|
|
151
|
+
- **WebSocket** — 客户端与服务端双向通信
|
|
152
|
+
- **Web Worker** — 主线程与 Worker 通信
|
|
153
|
+
- **Electron IPC** — 主进程与渲染进程通信
|
|
154
|
+
- **Node.js child_process** — 父子进程通信
|
|
155
|
+
|
|
156
|
+
## License
|
|
157
|
+
|
|
158
|
+
MIT
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: message-modem
|
|
3
|
+
description: Code generation and contribution rules for @hile/message-modem. Use when editing this package or when the user asks about @hile/message-modem patterns or API.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @hile/message-modem
|
|
7
|
+
|
|
8
|
+
本文档是面向 AI 编码模型和人类开发者的 **代码生成规范**,阅读后应能正确地使用本库编写符合架构规则的代码。
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1. 架构总览
|
|
13
|
+
|
|
14
|
+
`@hile/message-modem` 是一个 **传输无关的请求/响应消息通信抽象层**。它将底层传输(WebSocket、postMessage、IPC 等)与业务逻辑解耦,提供统一的 send/receive 语义。
|
|
15
|
+
|
|
16
|
+
核心职责:
|
|
17
|
+
|
|
18
|
+
- 自增 ID 管理与安全重置(超过 `MAX_SAFE_INTEGER` 时归零)
|
|
19
|
+
- 请求/响应配对(通过 ID + stacks Map)
|
|
20
|
+
- 超时控制(基于 `AbortController` + `setTimeout`)
|
|
21
|
+
- 主动中止(abort):发送方可中止等待,接收方可取消正在执行的任务
|
|
22
|
+
- 错误传播:`Exception` 携带 `status`;非 `Exception` 错误映射为 500
|
|
23
|
+
|
|
24
|
+
继承关系:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
MessageModem (abstract)
|
|
28
|
+
├── post(data) — 子类实现:如何发送到远端
|
|
29
|
+
└── exec(data) — 子类实现:如何处理收到的请求
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 2. 类型签名
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// ---- 枚举 ----
|
|
38
|
+
enum MESSAGE_MODEM_TYPE {
|
|
39
|
+
REQUEST, // 请求
|
|
40
|
+
RESPONSE, // 响应
|
|
41
|
+
ABORT, // 中止
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ---- 传输格式 ----
|
|
45
|
+
interface MessageTransferFormat<T = any> {
|
|
46
|
+
id: number;
|
|
47
|
+
mode: MESSAGE_MODEM_TYPE;
|
|
48
|
+
twoway: boolean;
|
|
49
|
+
data?: T;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ---- 响应格式 ----
|
|
53
|
+
interface MessageReturnFormat<T = any> {
|
|
54
|
+
status: string | number;
|
|
55
|
+
data: T;
|
|
56
|
+
message: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ---- 异常 ----
|
|
60
|
+
class Exception extends Error {
|
|
61
|
+
readonly status: number | string;
|
|
62
|
+
constructor(status: number | string, msg: string);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
class TimeoutException extends Exception {
|
|
66
|
+
static readonly code = 'ETIMEDOUT';
|
|
67
|
+
constructor(msg?: string); // 默认 'Timeout'
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
class AbortException extends Exception {
|
|
71
|
+
static readonly code = 'ECONNABORTED';
|
|
72
|
+
constructor(msg?: string); // 默认 'Abort'
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ---- 抽象基类 ----
|
|
76
|
+
abstract class MessageModem {
|
|
77
|
+
protected abstract post<T>(data: MessageTransferFormat<T>): void;
|
|
78
|
+
protected abstract exec(data: any): Promise<any>;
|
|
79
|
+
protected send<T>(data: T, timeout?: number): {
|
|
80
|
+
abort: () => void;
|
|
81
|
+
response: <U = any>() => Promise<U>;
|
|
82
|
+
};
|
|
83
|
+
public receive(msg: MessageTransferFormat): void;
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 3. 代码生成模板与规则
|
|
90
|
+
|
|
91
|
+
### 3.1 子类实现模板
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { MessageModem, type MessageTransferFormat } from '@hile/message-modem';
|
|
95
|
+
|
|
96
|
+
class WebSocketModem extends MessageModem {
|
|
97
|
+
constructor(private ws: WebSocket) {
|
|
98
|
+
super();
|
|
99
|
+
ws.addEventListener('message', (e) => {
|
|
100
|
+
this.receive(JSON.parse(e.data));
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
protected post<T>(data: MessageTransferFormat<T>): void {
|
|
105
|
+
this.ws.send(JSON.stringify(data));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
protected async exec(data: any): Promise<any> {
|
|
109
|
+
// 处理远端请求的业务逻辑
|
|
110
|
+
return handleRequest(data);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 暴露 send 为 public
|
|
114
|
+
public request<T, U>(data: T, timeout?: number) {
|
|
115
|
+
return this.send(data, timeout);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 3.2 postMessage 场景模板
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
class IframeModem extends MessageModem {
|
|
124
|
+
constructor(private target: Window, private origin: string) {
|
|
125
|
+
super();
|
|
126
|
+
window.addEventListener('message', (e) => {
|
|
127
|
+
if (e.origin === origin) this.receive(e.data);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
protected post<T>(data: MessageTransferFormat<T>): void {
|
|
132
|
+
this.target.postMessage(data, this.origin);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
protected async exec(data: any): Promise<any> {
|
|
136
|
+
return handleIframeRequest(data);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 3.3 强制规则
|
|
142
|
+
|
|
143
|
+
| 规则 | 说明 |
|
|
144
|
+
|------|------|
|
|
145
|
+
| **必须实现 `post` 和 `exec`** | 两个 abstract 方法缺一不可 |
|
|
146
|
+
| **`send` 是 `protected`** | 子类应自行决定暴露方式和命名 |
|
|
147
|
+
| **`receive` 是 `public`** | 必须由外部消息源(事件监听器)调用 |
|
|
148
|
+
| **传输格式必须保持原样** | `post` 发送的对象结构不可修改,对端的 `receive` 依赖完整的 `MessageTransferFormat` |
|
|
149
|
+
| **`exec` 抛出 `Exception` 时 status 会透传** | 其他 Error 一律映射为 500 |
|
|
150
|
+
| **timeout 默认 30s** | 可通过 `send(data, ms)` 覆盖 |
|
|
151
|
+
| **abort 后 promise reject `AbortException`** | 不要 catch 后吞掉,保持语义清晰 |
|
|
152
|
+
|
|
153
|
+
### 3.4 反模式
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
// ❌ 不要在 post 中做异步操作
|
|
157
|
+
protected async post(data) { await fetch(...); }
|
|
158
|
+
// ✅ post 应该是同步的,异步传输应缓冲
|
|
159
|
+
|
|
160
|
+
// ❌ 不要直接修改 MessageTransferFormat 结构
|
|
161
|
+
this.post({ ...data, extra: 'field' });
|
|
162
|
+
// ✅ 业务数据放在 data 字段内
|
|
163
|
+
|
|
164
|
+
// ❌ 不要忘记连接 receive
|
|
165
|
+
// ✅ 在构造函数中绑定消息事件 → this.receive(parsed)
|
|
166
|
+
|
|
167
|
+
// ❌ 不要在 exec 中吞掉错误
|
|
168
|
+
protected async exec(data) { try { ... } catch { return null; } }
|
|
169
|
+
// ✅ 让错误冒泡,框架会处理错误响应
|
|
170
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hile/message-modem",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc -b && fix-esm-import-path --preserve-import-type ./dist",
|
|
8
|
+
"dev": "tsc -b --watch",
|
|
9
|
+
"test": "vitest run"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md",
|
|
14
|
+
"SKILL.md"
|
|
15
|
+
],
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"fix-esm-import-path": "^1.10.3",
|
|
22
|
+
"vitest": "^4.0.18"
|
|
23
|
+
},
|
|
24
|
+
"gitHead": "41b3df4d124f88453cce2a46a3529dcd6c2480ba"
|
|
25
|
+
}
|