@hile/message-ws 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 +123 -0
- package/SKILL.md +131 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +55 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# @hile/message-ws
|
|
2
|
+
|
|
3
|
+
基于 `@hile/message-modem` 和 `ws` 模块的 WebSocket 通信抽象实现。让客户端与服务端之间的请求/响应通信像调用函数一样简单。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @hile/message-ws
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 核心特性
|
|
12
|
+
|
|
13
|
+
- **双端支持** — 客户端和服务端各自包装 `WebSocket` 实例即可
|
|
14
|
+
- **继承模式** — 继承 `MessageWs` 并实现 `exec` 方法
|
|
15
|
+
- **JSON 传输** — 消息自动 JSON 序列化/反序列化
|
|
16
|
+
- **请求/响应** — 继承 `MessageModem` 全部能力
|
|
17
|
+
- **超时控制** — 默认 30 秒,可按请求自定义
|
|
18
|
+
- **主动中止** — `abort()` 取消等待并通知对端
|
|
19
|
+
- **错误传播** — `Exception` 带 status 透传,普通 Error 映射为 500
|
|
20
|
+
- **连接状态检查** — 发送前检查 `readyState`
|
|
21
|
+
- **资源清理** — `dispose()` 移除监听
|
|
22
|
+
|
|
23
|
+
## 快速开始
|
|
24
|
+
|
|
25
|
+
### 第一步:定义子类
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { MessageWs } from '@hile/message-ws';
|
|
29
|
+
import { Exception } from '@hile/message-modem';
|
|
30
|
+
|
|
31
|
+
class AppWs extends MessageWs {
|
|
32
|
+
protected async exec(data: any): Promise<any> {
|
|
33
|
+
switch (data?.action) {
|
|
34
|
+
case 'getUser':
|
|
35
|
+
return { id: data.id, name: 'Alice' };
|
|
36
|
+
case 'restricted':
|
|
37
|
+
throw new Exception(403, 'not allowed');
|
|
38
|
+
default:
|
|
39
|
+
return data;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 第二步:服务端
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { WebSocketServer } from 'ws';
|
|
49
|
+
|
|
50
|
+
const wss = new WebSocketServer({ port: 8080 });
|
|
51
|
+
wss.on('connection', (ws) => {
|
|
52
|
+
const modem = new AppWs(ws);
|
|
53
|
+
// 自动处理客户端请求
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 第三步:客户端
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import WebSocket from 'ws';
|
|
61
|
+
|
|
62
|
+
const ws = new WebSocket('ws://localhost:8080');
|
|
63
|
+
ws.on('open', () => {
|
|
64
|
+
const modem = new AppWs(ws);
|
|
65
|
+
|
|
66
|
+
const user = await modem.request({ action: 'getUser', id: 1 }).response();
|
|
67
|
+
console.log(user); // { id: 1, name: 'Alice' }
|
|
68
|
+
|
|
69
|
+
modem.dispose();
|
|
70
|
+
ws.close();
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## API
|
|
75
|
+
|
|
76
|
+
### `MessageWs`(抽象类)
|
|
77
|
+
|
|
78
|
+
| 方法 | 签名 | 说明 |
|
|
79
|
+
|------|------|------|
|
|
80
|
+
| `constructor` | `new SubClass(ws: WebSocket)` | 传入已连接的 WebSocket 实例 |
|
|
81
|
+
| `exec` | `protected abstract exec(data: any): Promise<any>` | 子类实现:处理对端请求 |
|
|
82
|
+
| `request` | `request<T>(data: T, timeout?: number)` | 向对端发送请求,返回 `{ abort, response }` |
|
|
83
|
+
| `dispose` | `dispose(): void` | 移除消息监听,释放资源 |
|
|
84
|
+
|
|
85
|
+
### `request` 返回值
|
|
86
|
+
|
|
87
|
+
| 属性 | 类型 | 说明 |
|
|
88
|
+
|------|------|------|
|
|
89
|
+
| `abort` | `() => void` | 中止请求 |
|
|
90
|
+
| `response` | `<U>() => Promise<U>` | 等待对端响应 |
|
|
91
|
+
|
|
92
|
+
## 超时与中止
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { AbortException, Exception } from '@hile/message-modem';
|
|
96
|
+
|
|
97
|
+
// 5 秒超时
|
|
98
|
+
const result = await modem.request(data, 5000).response();
|
|
99
|
+
|
|
100
|
+
// 主动中止
|
|
101
|
+
const req = modem.request(data);
|
|
102
|
+
setTimeout(() => req.abort(), 3000);
|
|
103
|
+
try {
|
|
104
|
+
await req.response();
|
|
105
|
+
} catch (e) {
|
|
106
|
+
if (e instanceof AbortException) {
|
|
107
|
+
console.log('请求被中止或超时');
|
|
108
|
+
} else if (e instanceof Exception) {
|
|
109
|
+
console.log(`远端错误 [${e.status}]: ${e.message}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## 注意事项
|
|
115
|
+
|
|
116
|
+
- `MessageWs` 是 **抽象类**,不能直接实例化
|
|
117
|
+
- 必须在 WebSocket 连接建立(`open` 事件)后再创建实例或发送请求
|
|
118
|
+
- 消息通过 JSON 序列化传输,不支持 Buffer/Map/Set 等非 JSON 类型
|
|
119
|
+
- 使用完毕后调用 `dispose()` + `ws.close()`
|
|
120
|
+
|
|
121
|
+
## License
|
|
122
|
+
|
|
123
|
+
MIT
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: message-ws
|
|
3
|
+
description: Code generation and contribution rules for @hile/message-ws. Use when editing this package or when the user asks about @hile/message-ws patterns or API.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @hile/message-ws
|
|
7
|
+
|
|
8
|
+
本文档是面向 AI 编码模型和人类开发者的 **代码生成规范**,阅读后应能正确地使用本库编写符合架构规则的代码。
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1. 架构总览
|
|
13
|
+
|
|
14
|
+
`@hile/message-ws` 是 `@hile/message-modem` 的 **WebSocket(ws 模块)抽象实现**,用于客户端与服务端之间的请求/响应通信。
|
|
15
|
+
|
|
16
|
+
`MessageWs` 本身是 **抽象类**,实现了 `post`(通过 `ws.send` 发送 JSON),`exec` 方法留给子类实现。
|
|
17
|
+
|
|
18
|
+
继承链:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
MessageModem (abstract) ← post + exec 均抽象
|
|
22
|
+
└── MessageWs (abstract) ← 实现 post(JSON + ws.send),exec 仍抽象
|
|
23
|
+
└── 用户子类 ← 实现 exec
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
消息通过 JSON 序列化/反序列化传输。
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 2. 类型签名
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { MessageModem, type MessageTransferFormat } from '@hile/message-modem';
|
|
34
|
+
import type WebSocket from 'ws';
|
|
35
|
+
|
|
36
|
+
abstract class MessageWs extends MessageModem {
|
|
37
|
+
constructor(ws: WebSocket);
|
|
38
|
+
|
|
39
|
+
protected abstract exec(data: any): Promise<any>;
|
|
40
|
+
|
|
41
|
+
public request<T = any>(data: T, timeout?: number): {
|
|
42
|
+
abort: () => void;
|
|
43
|
+
response: <U = any>() => Promise<U>;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
public dispose(): void;
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 3. 代码生成模板与规则
|
|
53
|
+
|
|
54
|
+
### 3.1 基本子类模板
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { MessageWs } from '@hile/message-ws';
|
|
58
|
+
|
|
59
|
+
class MyWs extends MessageWs {
|
|
60
|
+
protected async exec(data: any): Promise<any> {
|
|
61
|
+
switch (data?.action) {
|
|
62
|
+
case 'ping': return 'pong';
|
|
63
|
+
default: return data;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 3.2 客户端模板
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import WebSocket from 'ws';
|
|
73
|
+
|
|
74
|
+
const ws = new WebSocket('ws://localhost:8080');
|
|
75
|
+
ws.on('open', () => {
|
|
76
|
+
const modem = new MyWs(ws);
|
|
77
|
+
|
|
78
|
+
modem.request({ action: 'getUser', id: 1 })
|
|
79
|
+
.response<User>()
|
|
80
|
+
.then(console.log);
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 3.3 服务端模板
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import { WebSocketServer } from 'ws';
|
|
88
|
+
|
|
89
|
+
const wss = new WebSocketServer({ port: 8080 });
|
|
90
|
+
wss.on('connection', (ws) => {
|
|
91
|
+
const modem = new MyWs(ws);
|
|
92
|
+
// modem 自动监听 ws 消息并处理请求
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### 3.4 强制规则
|
|
97
|
+
|
|
98
|
+
| 规则 | 说明 |
|
|
99
|
+
|------|------|
|
|
100
|
+
| **必须继承并实现 `exec`** | `MessageWs` 是抽象类 |
|
|
101
|
+
| **必须传入已连接的 `WebSocket`** | 或在 `open` 事件后创建 |
|
|
102
|
+
| **消息通过 JSON 传输** | `post` 内部 `JSON.stringify`,收到消息 `JSON.parse` |
|
|
103
|
+
| **`readyState !== OPEN` 时 `post` 会抛错** | 确保连接已建立 |
|
|
104
|
+
| **用完必须 `dispose()`** | 避免内存泄漏 |
|
|
105
|
+
|
|
106
|
+
### 3.5 反模式
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// ❌ 连接未建立就创建 modem 并发送
|
|
110
|
+
const ws = new WebSocket('ws://...');
|
|
111
|
+
const modem = new MyWs(ws);
|
|
112
|
+
modem.request('hi'); // readyState 不是 OPEN → 抛错
|
|
113
|
+
|
|
114
|
+
// ✅ 等待 open 事件
|
|
115
|
+
ws.on('open', () => {
|
|
116
|
+
const modem = new MyWs(ws);
|
|
117
|
+
modem.request('hi');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// ❌ 不能直接实例化
|
|
121
|
+
const modem = new MessageWs(ws); // abstract
|
|
122
|
+
|
|
123
|
+
// ✅ 继承并实现 exec
|
|
124
|
+
class MyWs extends MessageWs { ... }
|
|
125
|
+
|
|
126
|
+
// ❌ 传输非 JSON 安全的数据
|
|
127
|
+
modem.request(new Map()); // Map 序列化会丢失
|
|
128
|
+
|
|
129
|
+
// ✅ 只传 JSON 安全数据
|
|
130
|
+
modem.request({ key: 'value' });
|
|
131
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { MessageModem, type MessageTransferFormat } from '@hile/message-modem';
|
|
2
|
+
import type WebSocket from 'ws';
|
|
3
|
+
/**
|
|
4
|
+
* 基于 `ws` 模块的 WebSocket 通信层。
|
|
5
|
+
* exec 方法由子类实现,本类不做实现。
|
|
6
|
+
*
|
|
7
|
+
* 构造时传入已连接的 WebSocket 实例,自动绑定 message 事件。
|
|
8
|
+
* 消息通过 JSON 序列化/反序列化传输。
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* class MyWs extends MessageWs {
|
|
12
|
+
* protected exec(data: any): Promise<any> {
|
|
13
|
+
* return Promise.resolve(data);
|
|
14
|
+
* }
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* const ws = new WebSocket('ws://localhost:8080');
|
|
18
|
+
* ws.on('open', () => {
|
|
19
|
+
* const modem = new MyWs(ws);
|
|
20
|
+
* modem.request('hello').response().then(console.log);
|
|
21
|
+
* });
|
|
22
|
+
*/
|
|
23
|
+
export declare abstract class MessageWs extends MessageModem {
|
|
24
|
+
private readonly ws;
|
|
25
|
+
private readonly listener;
|
|
26
|
+
constructor(ws: WebSocket);
|
|
27
|
+
protected post<T = any>(data: MessageTransferFormat<T>): void;
|
|
28
|
+
/**
|
|
29
|
+
* 向对端发送请求
|
|
30
|
+
*/
|
|
31
|
+
request<T = any>(data: T, timeout?: number): {
|
|
32
|
+
abort: () => void;
|
|
33
|
+
response: <U = any>() => Promise<U>;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* 移除消息监听,释放资源
|
|
37
|
+
*/
|
|
38
|
+
dispose(): void;
|
|
39
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { MessageModem } from '@hile/message-modem';
|
|
2
|
+
/**
|
|
3
|
+
* 基于 `ws` 模块的 WebSocket 通信层。
|
|
4
|
+
* exec 方法由子类实现,本类不做实现。
|
|
5
|
+
*
|
|
6
|
+
* 构造时传入已连接的 WebSocket 实例,自动绑定 message 事件。
|
|
7
|
+
* 消息通过 JSON 序列化/反序列化传输。
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* class MyWs extends MessageWs {
|
|
11
|
+
* protected exec(data: any): Promise<any> {
|
|
12
|
+
* return Promise.resolve(data);
|
|
13
|
+
* }
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* const ws = new WebSocket('ws://localhost:8080');
|
|
17
|
+
* ws.on('open', () => {
|
|
18
|
+
* const modem = new MyWs(ws);
|
|
19
|
+
* modem.request('hello').response().then(console.log);
|
|
20
|
+
* });
|
|
21
|
+
*/
|
|
22
|
+
export class MessageWs extends MessageModem {
|
|
23
|
+
ws;
|
|
24
|
+
listener;
|
|
25
|
+
constructor(ws) {
|
|
26
|
+
super();
|
|
27
|
+
this.ws = ws;
|
|
28
|
+
this.listener = (raw) => {
|
|
29
|
+
try {
|
|
30
|
+
const msg = JSON.parse(raw.toString());
|
|
31
|
+
this.receive(msg);
|
|
32
|
+
}
|
|
33
|
+
catch { }
|
|
34
|
+
};
|
|
35
|
+
this.ws.on('message', this.listener);
|
|
36
|
+
}
|
|
37
|
+
post(data) {
|
|
38
|
+
if (this.ws.readyState !== this.ws.OPEN) {
|
|
39
|
+
throw new Error('WebSocket is not open. Current readyState: ' + this.ws.readyState);
|
|
40
|
+
}
|
|
41
|
+
this.ws.send(JSON.stringify(data));
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 向对端发送请求
|
|
45
|
+
*/
|
|
46
|
+
request(data, timeout) {
|
|
47
|
+
return this.send(data, timeout);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 移除消息监听,释放资源
|
|
51
|
+
*/
|
|
52
|
+
dispose() {
|
|
53
|
+
this.ws.removeListener('message', this.listener);
|
|
54
|
+
}
|
|
55
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hile/message-ws",
|
|
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
|
+
"@types/ws": "^8.18.1",
|
|
22
|
+
"fix-esm-import-path": "^1.10.3",
|
|
23
|
+
"vitest": "^4.0.18"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@hile/message-modem": "^1.0.1",
|
|
27
|
+
"ws": "^8.19.0"
|
|
28
|
+
},
|
|
29
|
+
"gitHead": "8f802f24910712571463865ddbe856d2cfba0786"
|
|
30
|
+
}
|