mm_socket_server 1.0.1 → 2.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 +60 -96
- package/example/demo.js +51 -0
- package/index.js +17 -2
- package/lib/socket.js +153 -179
- package/package.json +13 -10
- package/lib/message.js +0 -150
package/README.md
CHANGED
|
@@ -1,155 +1,119 @@
|
|
|
1
1
|
# mm_socket_server
|
|
2
2
|
|
|
3
|
-
TCP/UDP
|
|
3
|
+
最薄的 TCP/UDP 套接字服务器入站适配器。继承 `mm_mod` 的 `Mod`,把每条报文直接交给 `mm_event` 的事件管理器处理,不做路由表、不做插件、不加载脚本。
|
|
4
4
|
|
|
5
5
|
- 入口:`index.js`(CommonJS)
|
|
6
|
-
- 导出:`SocketServer
|
|
7
|
-
- 运行时依赖:`mm_corejs`
|
|
8
|
-
- 测试覆盖率:100%(`jest.config.js` 四项阈值)
|
|
6
|
+
- 导出:`SocketServer`、`admin`、`socketServerAdmin`
|
|
7
|
+
- 运行时依赖:`mm_mod`、`mm_event`、`mm_corejs`
|
|
9
8
|
|
|
10
|
-
##
|
|
9
|
+
## 消息路径
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- **JSON-RPC 2.0 插件**:`MessagePlugin` 挂载后接管 RPC 请求,支持请求-响应、通知回推、主动推送与服务器反向调用客户端方法。
|
|
17
|
-
- **选项透传**:连接级 `options`(user_id / device_id / agent_id 等)随每条入站消息透传给管道钩子与 RPC 处理函数,适配多用户 / 多设备 / 多智能体场景。
|
|
18
|
-
|
|
19
|
-
## 安装
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
npm install mm_socket_server
|
|
11
|
+
```
|
|
12
|
+
net / dgram → connection / message → ctx = { client_id, protocol, address, socket | rinfo }
|
|
13
|
+
→ eventManager.run(protocol, params, ctx, options)
|
|
14
|
+
→ 结果非空时回发来源客户端
|
|
23
15
|
```
|
|
24
16
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- Node.js >= 22.5.0
|
|
28
|
-
- 模块规范:CommonJS
|
|
17
|
+
事件 `target` 取协议名:`tcp` 或 `udp`。`params` 为 `{ client_id, data }`,`data` 在 `json: true` 时尝试 `JSON.parse`(失败回落原文)。
|
|
29
18
|
|
|
30
19
|
## 快速开始
|
|
31
20
|
|
|
32
21
|
```js
|
|
33
22
|
'use strict';
|
|
34
23
|
|
|
35
|
-
const
|
|
36
|
-
const { SocketServer, MessagePlugin } = require('mm_socket_server');
|
|
37
|
-
|
|
38
|
-
/** 最小 Server 容器:满足 Base 契约即可(见 example/demo.js 完整实现) */
|
|
39
|
-
const server = {
|
|
40
|
-
services: new Map(),
|
|
41
|
-
getService(name) { return this.services.get(name); },
|
|
42
|
-
hasService(name) { return this.services.has(name); },
|
|
43
|
-
setService(name, instance) { this.services.set(name, instance); return this; },
|
|
44
|
-
getManager() { return undefined; },
|
|
45
|
-
hasManager() { return false; }
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const socket = new SocketServer({ name: 'socket', host: '127.0.0.1', port: 9100 }, server);
|
|
49
|
-
|
|
50
|
-
// 业务钩子:文本消息直接回发 echo
|
|
51
|
-
socket.getHandle().on('main', (ctx, params) => `echo: ${params.data}`, 100, 'echo');
|
|
24
|
+
const { SocketServer } = require('mm_socket_server');
|
|
52
25
|
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
socket.use(rpc);
|
|
56
|
-
rpc.addMethod('sum', (params) => params.reduce((a, b) => a + b, 0));
|
|
26
|
+
const socket = new SocketServer({ name: 'socket', host: '127.0.0.1', port: 9000 });
|
|
27
|
+
const events = socket.getEventManager();
|
|
57
28
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
socket.on('error', (err) => console.error(err.message));
|
|
29
|
+
events.add({ name: 'echo', target: 'tcp', end: true }).main = async (params, ctx) => ({
|
|
30
|
+
client_id: ctx.client_id,
|
|
31
|
+
data: params.data
|
|
32
|
+
});
|
|
63
33
|
|
|
64
34
|
await socket.init();
|
|
65
35
|
await socket.start();
|
|
66
36
|
|
|
67
|
-
|
|
68
|
-
await socket.run({}, { action: 'list' });
|
|
69
|
-
await socket.run({}, { action: 'broadcast', data: 'hello all' });
|
|
70
|
-
|
|
71
|
-
await socket.stop();
|
|
72
|
-
await socket.dispose();
|
|
37
|
+
console.log(socket.getAddress());
|
|
73
38
|
```
|
|
74
39
|
|
|
75
|
-
##
|
|
76
|
-
|
|
77
|
-
顶层 `config` 字段即服务器设置,`init` 阶段统一校验。
|
|
40
|
+
## 服务器配置
|
|
78
41
|
|
|
79
42
|
| 字段 | 类型 | 默认值 | 说明 |
|
|
80
43
|
| --- | --- | --- | --- |
|
|
81
|
-
| `name` | string | `'
|
|
82
|
-
| `
|
|
44
|
+
| `name` | string | `'os'` | 模块名称(必填) |
|
|
45
|
+
| `title` | string | `'Socket服务器模块'` | 模块标题 |
|
|
46
|
+
| `description` | string | — | 模块描述 |
|
|
47
|
+
| `version` | string | `'2.0.0'` | 版本号 |
|
|
48
|
+
| `protocol` | string | `'tcp'` | 传输协议:`tcp` 或 `udp` |
|
|
83
49
|
| `host` | string | `'0.0.0.0'` | 监听地址 |
|
|
84
|
-
| `port` |
|
|
85
|
-
| `encoding` |
|
|
86
|
-
| `json` | boolean | `false` |
|
|
50
|
+
| `port` | number | `9000` | 监听端口 |
|
|
51
|
+
| `encoding` | string | `'utf8'` | 入站解码字符集 |
|
|
52
|
+
| `json` | boolean | `false` | 入站尝试 JSON 解析,出站对象自动序列化 |
|
|
53
|
+
| `params` | object | `{ action }` | `main` 的参数约定 |
|
|
87
54
|
|
|
88
55
|
## API
|
|
89
56
|
|
|
90
|
-
### SocketServer
|
|
91
|
-
|
|
92
57
|
| 成员 | 说明 |
|
|
93
58
|
| --- | --- |
|
|
94
|
-
| `
|
|
59
|
+
| `init()` / `start()` / `stop()` / `dispose()` | `Mod` 生命周期模板方法 |
|
|
60
|
+
| `onInit()` / `onStart()` / `onStop()` / `onDispose()` | 生命周期钩子,子类可覆盖 |
|
|
61
|
+
| `getEventManager()` | 获取 `mm_event` 的 `socket` 作用域事件管理器(与其他实例共享),用于注册事件 |
|
|
62
|
+
| `setEventManager(manager)` | 注入外部事件管理器,需提供 `run()` |
|
|
63
|
+
| `getAddress()` | 实际监听地址,未启动为 `null` |
|
|
64
|
+
| `run(params, ctx, options)` | 主动操作入口,`params.action` 派发 `send` / `broadcast` / `close` / `list` |
|
|
95
65
|
| `send(client_id, data)` | 向指定客户端发送数据,返回是否成功 |
|
|
96
66
|
| `broadcast(data)` | 向全部在线客户端广播,返回送达数 |
|
|
97
|
-
| `closeClient(client_id)` |
|
|
98
|
-
| `listClients(
|
|
67
|
+
| `closeClient(client_id)` | 关闭指定客户端(TCP 销毁套接字,UDP 仅移出连接表) |
|
|
68
|
+
| `listClients()` | 在线客户端列表 `{ id, protocol, address }` |
|
|
99
69
|
| `hasClient(client_id)` | 是否在线 |
|
|
100
|
-
| `setClientOptions(client_id, options)` |
|
|
101
|
-
| `use(plugin)` | 挂载插件(需实现 `bind(server)`) |
|
|
102
|
-
| `getHandle()` | 消息管道,注册 `before / check / main / render` 等钩子;钩子产出 `result` 自动回发 |
|
|
103
|
-
| `getAddress()` | 实际监听地址,未启动为 `null` |
|
|
70
|
+
| `setClientOptions(client_id, options)` | 绑定连接级选项,随入站消息作为 `options` 透传(`null` 清空) |
|
|
104
71
|
|
|
105
|
-
|
|
72
|
+
## 请求上下文(ctx)
|
|
106
73
|
|
|
107
|
-
|
|
|
108
|
-
| --- | --- |
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
74
|
+
| 字段 | 说明 |
|
|
75
|
+
| --- | --- |
|
|
76
|
+
| `client_id` | 客户端编号,TCP 形如 `tcp_1`,UDP 形如 `udp_127.0.0.1:5000` |
|
|
77
|
+
| `protocol` | 当前协议,`tcp` 或 `udp` |
|
|
78
|
+
| `address` | 远端地址,形如 `ip:port` |
|
|
79
|
+
| `socket` | TCP 连接(UDP 报文无此字段),事件可直接写出 |
|
|
80
|
+
| `rinfo` | UDP 远端地址信息(TCP 消息无此字段) |
|
|
113
81
|
|
|
114
|
-
|
|
82
|
+
## 事件处理函数
|
|
115
83
|
|
|
116
|
-
|
|
84
|
+
签名固定为 `(params, ctx, options)`:
|
|
117
85
|
|
|
86
|
+
```js
|
|
87
|
+
events.add({ name: 'echo', target: 'tcp', end: true }).main = async (params, ctx, options) => {
|
|
88
|
+
return { data: params.data };
|
|
89
|
+
};
|
|
118
90
|
```
|
|
119
|
-
before → check → main(业务钩子按 sort 依次执行)→ render
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
任一阶段产出非 `undefined` 的 `result` 即回发来源客户端;`check` / `before` 产出时短路后续阶段。
|
|
123
91
|
|
|
124
|
-
|
|
92
|
+
返回非 `undefined` / `null` 的结果会按 `json` 配置编码后回发来源客户端;返回 `undefined` 则不回发。事件也可直接用 `ctx.socket` 写出。
|
|
125
93
|
|
|
126
|
-
|
|
94
|
+
## 主动操作
|
|
127
95
|
|
|
128
96
|
```js
|
|
129
|
-
|
|
130
|
-
socket.
|
|
131
|
-
|
|
97
|
+
await socket.run({ action: 'send', client_id: 'tcp_1', data: 'hi' });
|
|
98
|
+
await socket.run({ action: 'broadcast', data: { type: 'notice' } });
|
|
99
|
+
await socket.run({ action: 'close', client_id: 'tcp_1' });
|
|
100
|
+
await socket.run({ action: 'list' });
|
|
132
101
|
```
|
|
133
102
|
|
|
134
|
-
|
|
135
|
-
| --- | --- |
|
|
136
|
-
| `addMethod(name, handler)` / `delMethod(name)` / `listMethods(query)` | 方法表管理,处理函数签名 `(params, extra, options)` |
|
|
137
|
-
| `push(client_id, data)` | 任意时刻向客户端回推数据(如异步任务完成后) |
|
|
138
|
-
| `call(client_id, method, params, options)` | 反向调用客户端方法并等待其反馈,`options.timeout_ms` 缺省 30000 |
|
|
139
|
-
| `bind(server)` / `isBound()` | 挂载与状态查询 |
|
|
103
|
+
## 生命周期
|
|
140
104
|
|
|
141
|
-
|
|
105
|
+
`onStart` 按 `protocol` 创建并监听 `net.Server` 或 `dgram.Socket`,`onStop` 断开全部连接并关闭服务器。事件管理器取自 `mm_event` 的 `socket` 作用域,由全局 `Admin` 共享,停止时不清理。端口被占用时 `start()` 抛错,模块进入 `error` 状态,可 `dispose()` 释放。
|
|
142
106
|
|
|
143
107
|
## 示例
|
|
144
108
|
|
|
145
109
|
```bash
|
|
146
|
-
npm run demo #
|
|
110
|
+
npm run demo # 启动演示服务器(127.0.0.1:9000),见 example/demo.js
|
|
147
111
|
```
|
|
148
112
|
|
|
149
113
|
## 测试
|
|
150
114
|
|
|
151
115
|
```bash
|
|
152
|
-
npm test #
|
|
116
|
+
npm test # node --test
|
|
153
117
|
```
|
|
154
118
|
|
|
155
119
|
## 许可证
|
package/example/demo.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { SocketServer } = require('..');
|
|
4
|
+
|
|
5
|
+
/** 演示监听端口 */
|
|
6
|
+
const DEMO_PORT = 9000;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 注册演示事件:协议名即事件 target
|
|
10
|
+
* @param {Object} events 事件管理器
|
|
11
|
+
* @returns {void}
|
|
12
|
+
*/
|
|
13
|
+
function bindEvents(events) {
|
|
14
|
+
events.add({ name: 'echo', target: 'tcp', end: true }).main = async (params, ctx) => ({
|
|
15
|
+
type: 'echo',
|
|
16
|
+
client_id: ctx.client_id,
|
|
17
|
+
data: params.data
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
events.add({ name: 'time', target: 'udp', end: true }).main = async () => ({
|
|
21
|
+
time: new Date().toISOString()
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 演示入口:启动服务器并打印监听地址
|
|
27
|
+
* @returns {Promise<void>} 无返回值
|
|
28
|
+
*/
|
|
29
|
+
async function main() {
|
|
30
|
+
const socket = new SocketServer({
|
|
31
|
+
name: 'demo',
|
|
32
|
+
host: '127.0.0.1',
|
|
33
|
+
port: DEMO_PORT,
|
|
34
|
+
protocol: 'tcp'
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
bindEvents(socket.getEventManager());
|
|
38
|
+
socket.on('error', (err) => console.error(`[demo] 错误: ${err.message}`));
|
|
39
|
+
|
|
40
|
+
await socket.init();
|
|
41
|
+
await socket.start();
|
|
42
|
+
|
|
43
|
+
const address = socket.getAddress();
|
|
44
|
+
console.log(`tcp://127.0.0.1:${address.port}`);
|
|
45
|
+
console.log('发送任意内容,结果由 target 为 tcp 的事件返回并自动回发');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
main().catch((err) => {
|
|
49
|
+
console.error(err);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
});
|
package/index.js
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const SocketServer = require('./lib/socket');
|
|
4
|
-
const
|
|
4
|
+
const Admin = require('mm_corejs').Admin;
|
|
5
|
+
|
|
6
|
+
let admin = new Admin();
|
|
7
|
+
admin.setClass(SocketServer);
|
|
5
8
|
|
|
6
9
|
module.exports = {
|
|
7
10
|
SocketServer,
|
|
8
|
-
|
|
11
|
+
admin,
|
|
12
|
+
/**
|
|
13
|
+
* 获取指定作用域的 Socket 服务器
|
|
14
|
+
* @param {string} [scope='os'] 作用域名称
|
|
15
|
+
* @param {object} [config] 配置参数
|
|
16
|
+
* @returns {SocketServer} Socket 服务器实例
|
|
17
|
+
*/
|
|
18
|
+
socketServerAdmin(scope, config) {
|
|
19
|
+
if (!scope) {
|
|
20
|
+
scope = 'os';
|
|
21
|
+
}
|
|
22
|
+
return admin.get(scope, config);
|
|
23
|
+
}
|
|
9
24
|
};
|
package/lib/socket.js
CHANGED
|
@@ -2,68 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
const net = require('net');
|
|
4
4
|
const dgram = require('dgram');
|
|
5
|
-
const { Mod
|
|
5
|
+
const { Mod } = require('mm_mod');
|
|
6
|
+
const { eventAdmin } = require('mm_event');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
|
-
*
|
|
9
|
-
*/
|
|
10
|
-
const SERVER_SCHEMA = {
|
|
11
|
-
type: 'object',
|
|
12
|
-
properties: {
|
|
13
|
-
protocol: { type: 'string', enum: ['tcp', 'udp'] },
|
|
14
|
-
host: { type: 'string', min_length: 1 },
|
|
15
|
-
port: { type: 'integer', minimum: 1, maximum: 65535 },
|
|
16
|
-
encoding: { type: 'string', enum: ['utf8', 'hex', 'base64'] },
|
|
17
|
-
json: { type: 'boolean' }
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* SocketServer —— TCP/UDP 套接字服务器(入站端口模块)。
|
|
23
|
-
*
|
|
24
|
-
* 继承 mm_corejs 的 Mod:生命周期由 Lifecycle 模板方法驱动,
|
|
25
|
-
* 调用参数校验由 config.params 声明式定义;
|
|
26
|
-
* 服务器设置(protocol / host / port / encoding / json)为顶层 config 字段,
|
|
27
|
-
* 在 onInit 阶段按 SERVER_SCHEMA 统一校验。
|
|
9
|
+
* SocketServer —— 最薄的 TCP/UDP 传输层(入站适配器模块)。
|
|
28
10
|
*
|
|
29
|
-
*
|
|
30
|
-
* 1.
|
|
31
|
-
* 2.
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* 4. 事件分发:connection / message / close / error 经 EventEmitter 向外广播;
|
|
35
|
-
* 5. 主动操作:run(ctx, params) 按 params.action 派发 send / broadcast / close / list。
|
|
11
|
+
* 只做三件事:
|
|
12
|
+
* 1. 生命周期:由 Mod 的 init / start / stop 驱动 net.Server 或 dgram.Socket;
|
|
13
|
+
* 2. 消息转交:以协议名(tcp / udp)为 target,直接调用
|
|
14
|
+
* EventManager.run(target, params, ctx, options),不做路由、不做插件;
|
|
15
|
+
* 3. 结果回发:事件返回非空结果时回发来源客户端,事件也可用 ctx.socket 自行写出。
|
|
36
16
|
*
|
|
37
17
|
* 用法:
|
|
38
|
-
* const socket = new SocketServer({ name: 'socket',
|
|
39
|
-
* socket.
|
|
18
|
+
* const socket = new SocketServer({ name: 'socket', protocol: 'tcp', port: 9000 });
|
|
19
|
+
* socket.getEventManager().add({ name: 'echo', target: 'tcp' }).main = async (params) => params.data;
|
|
40
20
|
* await socket.init();
|
|
41
21
|
* await socket.start();
|
|
42
|
-
* await socket.run({}, { action: 'list' });
|
|
43
22
|
*/
|
|
44
23
|
class SocketServer extends Mod {
|
|
45
24
|
/**
|
|
46
25
|
* 模块默认配置
|
|
47
26
|
*/
|
|
48
27
|
static config = {
|
|
49
|
-
name: '
|
|
28
|
+
name: 'os',
|
|
50
29
|
title: 'Socket服务器模块',
|
|
51
|
-
description: 'TCP/UDP
|
|
52
|
-
version: '
|
|
30
|
+
description: 'TCP/UDP 传输层:消息直达事件管理器,不做路由与插件',
|
|
31
|
+
version: '2.0.0',
|
|
53
32
|
protocol: 'tcp',
|
|
54
33
|
host: '0.0.0.0',
|
|
55
34
|
port: 9000,
|
|
56
35
|
encoding: 'utf8',
|
|
57
36
|
json: false,
|
|
58
|
-
options: {},
|
|
59
37
|
params: {
|
|
60
38
|
type: 'object',
|
|
61
39
|
required: ['action'],
|
|
62
40
|
properties: {
|
|
63
41
|
action: { type: 'string', enum: ['send', 'broadcast', 'close', 'list'] },
|
|
64
42
|
client_id: { type: 'string', min_length: 1 },
|
|
65
|
-
data: {}
|
|
66
|
-
query: { type: 'object' }
|
|
43
|
+
data: {}
|
|
67
44
|
}
|
|
68
45
|
}
|
|
69
46
|
};
|
|
@@ -74,35 +51,37 @@ class SocketServer extends Mod {
|
|
|
74
51
|
#server = null;
|
|
75
52
|
|
|
76
53
|
/**
|
|
77
|
-
*
|
|
54
|
+
* 事件管理器(取自 mm_event 的 socket 作用域,与其他实例共享)
|
|
78
55
|
*/
|
|
79
|
-
#
|
|
56
|
+
#events = null;
|
|
80
57
|
|
|
81
58
|
/**
|
|
82
|
-
*
|
|
59
|
+
* 在线连接表:client_id -> 连接信息
|
|
83
60
|
*/
|
|
84
|
-
#
|
|
61
|
+
#clients = new Map();
|
|
85
62
|
|
|
86
63
|
/**
|
|
87
64
|
* 客户端编号自增计数器
|
|
88
65
|
*/
|
|
89
66
|
#counter = 0;
|
|
90
67
|
|
|
91
|
-
// ---------- 生命周期钩子 ----------
|
|
92
68
|
/**
|
|
93
|
-
*
|
|
69
|
+
* 构造函数
|
|
70
|
+
* @param {Object} config 模块配置
|
|
71
|
+
* @param {Object} server 框架实例
|
|
94
72
|
*/
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
this
|
|
73
|
+
constructor(config, server) {
|
|
74
|
+
super(config, server);
|
|
75
|
+
this.#events = eventAdmin('socket');
|
|
98
76
|
}
|
|
99
77
|
|
|
78
|
+
// ---------- 生命周期钩子 ----------
|
|
100
79
|
/**
|
|
101
80
|
* 启动钩子:按协议创建底层服务器并开始监听
|
|
81
|
+
* @returns {Promise<void>} 无返回值
|
|
102
82
|
*/
|
|
103
83
|
async onStart() {
|
|
104
|
-
|
|
105
|
-
if (protocol === 'udp') {
|
|
84
|
+
if (this.config.protocol === 'udp') {
|
|
106
85
|
await this._startUdp();
|
|
107
86
|
return;
|
|
108
87
|
}
|
|
@@ -110,34 +89,28 @@ class SocketServer extends Mod {
|
|
|
110
89
|
}
|
|
111
90
|
|
|
112
91
|
/**
|
|
113
|
-
*
|
|
92
|
+
* 停止钩子:断开全部连接并关闭服务器(事件管理器与其他实例共享,不清理)
|
|
93
|
+
* @returns {Promise<void>} 无返回值
|
|
114
94
|
*/
|
|
115
95
|
async onStop() {
|
|
116
96
|
this._closeAllClients();
|
|
117
97
|
await this._closeServer();
|
|
118
98
|
}
|
|
119
99
|
|
|
120
|
-
/**
|
|
121
|
-
* 释放钩子:清空连接注册表
|
|
122
|
-
*/
|
|
123
|
-
async onDispose() {
|
|
124
|
-
this.#clients.clear();
|
|
125
|
-
}
|
|
126
|
-
|
|
127
100
|
// ---------- 执行入口 ----------
|
|
128
101
|
/**
|
|
129
102
|
* 模块主逻辑:按 action 派发操作
|
|
130
|
-
* @param {Object} ctx 执行上下文
|
|
131
103
|
* @param {Object} params 调用参数,含 action 及操作所需字段
|
|
104
|
+
* @param {Object} ctx 执行上下文
|
|
132
105
|
* @param {Object} options 合并后的选项
|
|
133
106
|
* @returns {Promise<Object>} 操作结果
|
|
134
107
|
*/
|
|
135
|
-
async main(
|
|
108
|
+
async main(params, ctx, options) {
|
|
136
109
|
const action = params.action;
|
|
137
110
|
if (action === 'send') return this._actionSend(params);
|
|
138
111
|
if (action === 'broadcast') return this._actionBroadcast(params);
|
|
139
112
|
if (action === 'close') return this._actionClose(params);
|
|
140
|
-
return this._actionList(
|
|
113
|
+
return this._actionList();
|
|
141
114
|
}
|
|
142
115
|
|
|
143
116
|
/**
|
|
@@ -174,11 +147,42 @@ class SocketServer extends Mod {
|
|
|
174
147
|
|
|
175
148
|
/**
|
|
176
149
|
* 执行列出连接操作
|
|
177
|
-
* @param {Object} params 调用参数,可含 query 查询条件(Store 条件/分页)
|
|
178
150
|
* @returns {Object} 操作结果,clients 为在线客户端列表
|
|
179
151
|
*/
|
|
180
|
-
_actionList(
|
|
181
|
-
return { action: 'list', clients: this.listClients(
|
|
152
|
+
_actionList() {
|
|
153
|
+
return { action: 'list', clients: this.listClients() };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ---------- 事件管理器 ----------
|
|
157
|
+
/**
|
|
158
|
+
* 获取事件管理器,用于注册事件
|
|
159
|
+
* @returns {Object} 事件管理器实例
|
|
160
|
+
*/
|
|
161
|
+
getEventManager() {
|
|
162
|
+
return this.#events;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 注入外部事件管理器
|
|
167
|
+
* @param {Object} manager 事件管理器实例
|
|
168
|
+
* @returns {SocketServer} 当前实例
|
|
169
|
+
* @throws {TypeError} manager 不具备 run 方法时
|
|
170
|
+
*/
|
|
171
|
+
setEventManager(manager) {
|
|
172
|
+
if (!manager || typeof manager.run !== 'function') {
|
|
173
|
+
throw new TypeError(`${this._label()} manager must provide run()`);
|
|
174
|
+
}
|
|
175
|
+
this.#events = manager;
|
|
176
|
+
return this;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 获取实际监听地址
|
|
181
|
+
* @returns {Object|null} 监听地址信息,未启动时为 null
|
|
182
|
+
*/
|
|
183
|
+
getAddress() {
|
|
184
|
+
const server = this.#server;
|
|
185
|
+
return server ? server.address() : null;
|
|
182
186
|
}
|
|
183
187
|
|
|
184
188
|
// ---------- 公开操作 ----------
|
|
@@ -189,10 +193,9 @@ class SocketServer extends Mod {
|
|
|
189
193
|
* @returns {boolean} 是否发送成功
|
|
190
194
|
*/
|
|
191
195
|
send(client_id, data) {
|
|
192
|
-
const client = this.#clients.
|
|
196
|
+
const client = this.#clients.get(client_id);
|
|
193
197
|
if (!client) return false;
|
|
194
|
-
|
|
195
|
-
return this._sendToClient(client, payload);
|
|
198
|
+
return this._sendToClient(client, this._encodeData(data));
|
|
196
199
|
}
|
|
197
200
|
|
|
198
201
|
/**
|
|
@@ -203,33 +206,35 @@ class SocketServer extends Mod {
|
|
|
203
206
|
broadcast(data) {
|
|
204
207
|
const payload = this._encodeData(data);
|
|
205
208
|
let count = 0;
|
|
206
|
-
for (const client of this.#clients.
|
|
209
|
+
for (const client of this.#clients.values()) {
|
|
207
210
|
if (this._sendToClient(client, payload)) count += 1;
|
|
208
211
|
}
|
|
209
212
|
return count;
|
|
210
213
|
}
|
|
211
214
|
|
|
212
215
|
/**
|
|
213
|
-
*
|
|
216
|
+
* 关闭指定客户端连接(TCP 销毁套接字,UDP 仅移出连接表)
|
|
214
217
|
* @param {string} client_id 客户端 ID
|
|
215
218
|
* @returns {boolean} 是否关闭成功
|
|
216
219
|
*/
|
|
217
220
|
closeClient(client_id) {
|
|
218
|
-
const client = this.#clients.
|
|
221
|
+
const client = this.#clients.get(client_id);
|
|
219
222
|
if (!client) return false;
|
|
223
|
+
this.#clients.delete(client_id);
|
|
220
224
|
if (client.socket) client.socket.destroy();
|
|
221
|
-
this.#clients.delById(client_id);
|
|
222
|
-
this._safeEmit('close', client_id);
|
|
223
225
|
return true;
|
|
224
226
|
}
|
|
225
227
|
|
|
226
228
|
/**
|
|
227
|
-
*
|
|
228
|
-
* @
|
|
229
|
-
* @returns {Array<Object>} 客户端信息列表,含 id 与 address
|
|
229
|
+
* 列出在线客户端
|
|
230
|
+
* @returns {Array<Object>} 客户端信息列表,含 id、protocol 与 address
|
|
230
231
|
*/
|
|
231
|
-
listClients(
|
|
232
|
-
|
|
232
|
+
listClients() {
|
|
233
|
+
const list = [];
|
|
234
|
+
for (const client of this.#clients.values()) {
|
|
235
|
+
list.push({ id: client.id, protocol: client.protocol, address: client.address });
|
|
236
|
+
}
|
|
237
|
+
return list;
|
|
233
238
|
}
|
|
234
239
|
|
|
235
240
|
/**
|
|
@@ -238,64 +243,30 @@ class SocketServer extends Mod {
|
|
|
238
243
|
* @returns {boolean} 是否在线
|
|
239
244
|
*/
|
|
240
245
|
hasClient(client_id) {
|
|
241
|
-
return
|
|
246
|
+
return this.#clients.has(client_id);
|
|
242
247
|
}
|
|
243
248
|
|
|
244
249
|
/**
|
|
245
|
-
*
|
|
250
|
+
* 绑定连接级选项,入站消息将作为 options 透传给事件
|
|
246
251
|
* @param {string} client_id 客户端 ID
|
|
247
252
|
* @param {Object|null} options 选项对象,null 时清空
|
|
248
253
|
* @returns {boolean} 是否设置成功(客户端不在线时失败)
|
|
249
254
|
*/
|
|
250
255
|
setClientOptions(client_id, options) {
|
|
251
|
-
const client = this.#clients.
|
|
256
|
+
const client = this.#clients.get(client_id);
|
|
252
257
|
if (!client) return false;
|
|
253
258
|
client.options = options || null;
|
|
254
259
|
return true;
|
|
255
260
|
}
|
|
256
261
|
|
|
257
262
|
/**
|
|
258
|
-
*
|
|
259
|
-
* @param {
|
|
260
|
-
* @returns {Object}
|
|
263
|
+
* 获取连接级选项(复制一份,避免事件管理器回写)
|
|
264
|
+
* @param {Object} client 连接信息
|
|
265
|
+
* @returns {Object} 选项对象
|
|
261
266
|
*/
|
|
262
|
-
_clientOptions(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
return client.options;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* 挂载插件:调用 plugin.bind(this),由插件自行向管道注册钩子;
|
|
270
|
-
* 如 MessagePlugin 挂载后接管 JSON-RPC 请求
|
|
271
|
-
* @param {Object} plugin 具备 bind(server) 方法的插件实例
|
|
272
|
-
* @returns {SocketServer} 当前实例
|
|
273
|
-
* @throws {TypeError} plugin 不具备 bind 方法时
|
|
274
|
-
*/
|
|
275
|
-
use(plugin) {
|
|
276
|
-
if (!plugin || typeof plugin.bind !== 'function') {
|
|
277
|
-
throw new TypeError(`${this._label()} plugin must provide bind()`);
|
|
278
|
-
}
|
|
279
|
-
plugin.bind(this);
|
|
280
|
-
return this;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* 获取消息处理管道,供外部注册 before/check/main/render 等钩子;
|
|
285
|
-
* 钩子产出 result 时自动回发来源客户端
|
|
286
|
-
* @returns {Handle} 消息管道实例
|
|
287
|
-
*/
|
|
288
|
-
getHandle() {
|
|
289
|
-
return this.#handle;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* 获取实际监听地址
|
|
294
|
-
* @returns {Object|null} 监听地址信息,未启动时为 null
|
|
295
|
-
*/
|
|
296
|
-
getAddress() {
|
|
297
|
-
const server = this.#server;
|
|
298
|
-
return server ? server.address() : null;
|
|
267
|
+
_clientOptions(client) {
|
|
268
|
+
if (!client.options) return {};
|
|
269
|
+
return { ...client.options };
|
|
299
270
|
}
|
|
300
271
|
|
|
301
272
|
// ---------- TCP ----------
|
|
@@ -328,7 +299,7 @@ class SocketServer extends Mod {
|
|
|
328
299
|
}
|
|
329
300
|
|
|
330
301
|
/**
|
|
331
|
-
* 登记新的 TCP
|
|
302
|
+
* 登记新的 TCP 连接并绑定回调
|
|
332
303
|
* @param {Object} socket 套接字实例
|
|
333
304
|
*/
|
|
334
305
|
_handleConnection(socket) {
|
|
@@ -336,40 +307,43 @@ class SocketServer extends Mod {
|
|
|
336
307
|
const client_id = `tcp_${this.#counter}`;
|
|
337
308
|
const client = {
|
|
338
309
|
id: client_id,
|
|
339
|
-
|
|
310
|
+
protocol: 'tcp',
|
|
340
311
|
address: this._remoteAddress(socket),
|
|
341
312
|
socket,
|
|
342
|
-
rinfo: null
|
|
313
|
+
rinfo: null,
|
|
314
|
+
options: null
|
|
343
315
|
};
|
|
344
|
-
this.#clients.
|
|
316
|
+
this.#clients.set(client_id, client);
|
|
345
317
|
socket.on('data', (buf) => this._handleData(client_id, buf));
|
|
346
|
-
socket.on('close', () => this.
|
|
318
|
+
socket.on('close', () => this.#clients.delete(client_id));
|
|
347
319
|
socket.on('error', (err) => this._emitError(err));
|
|
348
|
-
this._safeEmit('connection', client);
|
|
349
320
|
}
|
|
350
321
|
|
|
351
322
|
/**
|
|
352
|
-
* 处理 TCP
|
|
323
|
+
* 处理 TCP 收到的数据:解码后交给事件管理器,返回非空结果时回发来源客户端
|
|
353
324
|
* @param {string} client_id 客户端 ID
|
|
354
325
|
* @param {Object} buf 数据缓冲区
|
|
355
326
|
* @returns {Promise<void>} 无返回值
|
|
356
327
|
*/
|
|
357
328
|
async _handleData(client_id, buf) {
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
const
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
329
|
+
const client = this.#clients.get(client_id);
|
|
330
|
+
if (!client) return;
|
|
331
|
+
const params = { client_id, data: this._decodeData(buf) };
|
|
332
|
+
const ctx = {
|
|
333
|
+
client_id,
|
|
334
|
+
protocol: client.protocol,
|
|
335
|
+
address: client.address,
|
|
336
|
+
socket: client.socket
|
|
337
|
+
};
|
|
338
|
+
const options = this._clientOptions(client);
|
|
339
|
+
try {
|
|
340
|
+
const result = await this.#events.run(this.config.protocol, params, ctx, options);
|
|
341
|
+
const error = ctx.error || options.error;
|
|
342
|
+
if (error) this._emitError(error);
|
|
343
|
+
if (result !== undefined && result !== null) this.send(client_id, result);
|
|
344
|
+
} catch (err) {
|
|
345
|
+
this._emitError(err);
|
|
346
|
+
}
|
|
373
347
|
}
|
|
374
348
|
|
|
375
349
|
/**
|
|
@@ -412,31 +386,50 @@ class SocketServer extends Mod {
|
|
|
412
386
|
}
|
|
413
387
|
|
|
414
388
|
/**
|
|
415
|
-
* 处理 UDP
|
|
389
|
+
* 处理 UDP 报文:首见远端地址登记为客户端,之后交给事件管理器
|
|
416
390
|
* @param {Object} buf 数据缓冲区
|
|
417
391
|
* @param {Object} rinfo 远端地址信息
|
|
392
|
+
* @returns {Promise<void>} 无返回值
|
|
418
393
|
*/
|
|
419
|
-
_handleUdpMessage(buf, rinfo) {
|
|
394
|
+
async _handleUdpMessage(buf, rinfo) {
|
|
395
|
+
const client = this._ensureUdpClient(rinfo);
|
|
396
|
+
const params = { client_id: client.id, data: this._decodeData(buf) };
|
|
397
|
+
const ctx = {
|
|
398
|
+
client_id: client.id,
|
|
399
|
+
protocol: client.protocol,
|
|
400
|
+
address: client.address,
|
|
401
|
+
rinfo
|
|
402
|
+
};
|
|
403
|
+
const options = this._clientOptions(client);
|
|
404
|
+
try {
|
|
405
|
+
const result = await this.#events.run(this.config.protocol, params, ctx, options);
|
|
406
|
+
const error = ctx.error || options.error;
|
|
407
|
+
if (error) this._emitError(error);
|
|
408
|
+
if (result !== undefined && result !== null) this.send(client.id, result);
|
|
409
|
+
} catch (err) {
|
|
410
|
+
this._emitError(err);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* 登记 UDP 客户端(同一远端地址复用同一 client_id)
|
|
416
|
+
* @param {Object} rinfo 远端地址信息
|
|
417
|
+
* @returns {Object} 客户端信息
|
|
418
|
+
*/
|
|
419
|
+
_ensureUdpClient(rinfo) {
|
|
420
420
|
const client_id = `udp_${rinfo.address}:${rinfo.port}`;
|
|
421
|
-
const
|
|
421
|
+
const existed = this.#clients.get(client_id);
|
|
422
|
+
if (existed) return existed;
|
|
422
423
|
const client = {
|
|
423
424
|
id: client_id,
|
|
424
|
-
|
|
425
|
+
protocol: 'udp',
|
|
425
426
|
address: client_id,
|
|
426
427
|
socket: null,
|
|
427
|
-
rinfo
|
|
428
|
+
rinfo,
|
|
429
|
+
options: null
|
|
428
430
|
};
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
this._safeEmit('connection', client);
|
|
432
|
-
}
|
|
433
|
-
const decoded = this._decodeData(buf);
|
|
434
|
-
const params = { client_id, data: decoded };
|
|
435
|
-
const ctx = { client_id };
|
|
436
|
-
this.#handle.run(ctx, params, this._clientOptions(client_id)).then((done) => {
|
|
437
|
-
if (done.error) this._emitError(done.error);
|
|
438
|
-
if (done.result !== undefined) this.send(client_id, done.result);
|
|
439
|
-
});
|
|
431
|
+
this.#clients.set(client_id, client);
|
|
432
|
+
return client;
|
|
440
433
|
}
|
|
441
434
|
|
|
442
435
|
// ---------- 数据编解码 ----------
|
|
@@ -484,6 +477,7 @@ class SocketServer extends Mod {
|
|
|
484
477
|
*/
|
|
485
478
|
_sendToClient(client, payload) {
|
|
486
479
|
if (client.socket) {
|
|
480
|
+
if (client.socket.destroyed) return false;
|
|
487
481
|
client.socket.write(payload);
|
|
488
482
|
return true;
|
|
489
483
|
}
|
|
@@ -504,10 +498,10 @@ class SocketServer extends Mod {
|
|
|
504
498
|
}
|
|
505
499
|
|
|
506
500
|
/**
|
|
507
|
-
*
|
|
501
|
+
* 断开全部客户端连接并清空连接表
|
|
508
502
|
*/
|
|
509
503
|
_closeAllClients() {
|
|
510
|
-
for (const client of this.#clients.
|
|
504
|
+
for (const client of this.#clients.values()) {
|
|
511
505
|
if (client.socket) client.socket.destroy();
|
|
512
506
|
}
|
|
513
507
|
this.#clients.clear();
|
|
@@ -522,31 +516,11 @@ class SocketServer extends Mod {
|
|
|
522
516
|
if (!server) return Promise.resolve();
|
|
523
517
|
this.#server = null;
|
|
524
518
|
return new Promise((resolve) => {
|
|
525
|
-
server.close(resolve);
|
|
519
|
+
server.close(() => resolve());
|
|
526
520
|
});
|
|
527
521
|
}
|
|
528
522
|
|
|
529
523
|
// ---------- 校验辅助 ----------
|
|
530
|
-
/**
|
|
531
|
-
* 装配消息管道:内置 main 钩子负责分发 message 事件(sort 置后,
|
|
532
|
-
* 不返回值,业务钩子产出的 result 由 _handleData 统一回发)
|
|
533
|
-
*/
|
|
534
|
-
_initHandle() {
|
|
535
|
-
this.#handle.on('main', (ctx, params) => {
|
|
536
|
-
this._safeEmit('message', params.client_id, params.data);
|
|
537
|
-
}, 1000, 'emit_message');
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
/**
|
|
541
|
-
* 校验顶层 config 中的服务器设置
|
|
542
|
-
* @returns {Promise<void>} 无返回值
|
|
543
|
-
* @throws {Error} 校验不通过时
|
|
544
|
-
*/
|
|
545
|
-
async _validateConfig() {
|
|
546
|
-
const tip = await this._validate(this.config, SERVER_SCHEMA);
|
|
547
|
-
if (tip) throw new Error(`${this._label()} invalid config: ${tip}`);
|
|
548
|
-
}
|
|
549
|
-
|
|
550
524
|
/**
|
|
551
525
|
* 断言调用参数含 client_id
|
|
552
526
|
* @param {Object} params 调用参数
|
package/package.json
CHANGED
|
@@ -1,25 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mm_socket_server",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "TCP/UDP
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "最薄的 TCP/UDP 套接字服务器入站适配器:消息直达 mm_event 事件管理器,不做路由与插件。",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"超级美眉",
|
|
7
|
-
"
|
|
7
|
+
"入站适配器",
|
|
8
8
|
"mm_mod",
|
|
9
|
-
"
|
|
9
|
+
"mm_adapter",
|
|
10
|
+
"mm_in_adapter",
|
|
10
11
|
"mm_receiver",
|
|
11
12
|
"socket",
|
|
12
13
|
"tcp",
|
|
13
14
|
"udp",
|
|
14
15
|
"server"
|
|
15
16
|
],
|
|
17
|
+
"layer": "adapter",
|
|
18
|
+
"group": "in_adapter",
|
|
16
19
|
"license": "ISC",
|
|
17
20
|
"author": "qww",
|
|
18
21
|
"type": "commonjs",
|
|
19
22
|
"main": "index.js",
|
|
20
23
|
"files": [
|
|
21
24
|
"index.js",
|
|
22
|
-
"lib"
|
|
25
|
+
"lib",
|
|
26
|
+
"example"
|
|
23
27
|
],
|
|
24
28
|
"engines": {
|
|
25
29
|
"node": ">=22.5.0"
|
|
@@ -35,12 +39,11 @@
|
|
|
35
39
|
"scripts": {
|
|
36
40
|
"start": "node example/demo.js",
|
|
37
41
|
"demo": "node example/demo.js",
|
|
38
|
-
"test": "
|
|
42
|
+
"test": "node --test \"test/*.test.js\""
|
|
39
43
|
},
|
|
40
44
|
"dependencies": {
|
|
41
|
-
"mm_corejs": "^1.1.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"jest": "^30.5.2"
|
|
45
|
+
"mm_corejs": "^1.1.4",
|
|
46
|
+
"mm_event": "^2.2.2",
|
|
47
|
+
"mm_mod": "^1.0.1"
|
|
45
48
|
}
|
|
46
49
|
}
|
package/lib/message.js
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { JsonRpc } = require('mm_corejs');
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* MessagePlugin —— JSON-RPC 2.0 消息插件(mm_socket_server 的可选业务组件)。
|
|
7
|
-
*
|
|
8
|
-
* 职责:
|
|
9
|
-
* 1. 方法表:内部持有 JsonRpc 核心,addMethod / delMethod / listMethods 登记可调用方法;
|
|
10
|
-
* 2. 管道挂载:bind(socket) 后向消息管道注册 main 钩子 "json_rpc"(sort 100,
|
|
11
|
-
* 先于内置 emit_message 执行),命中 JSON-RPC 请求时以响应对象作为管道 result,
|
|
12
|
-
* 由 SocketServer 自动回发来源客户端;
|
|
13
|
-
* 3. 通知回推:通知(无 id)处理函数返回非 undefined 结果时,产出服务器发起的
|
|
14
|
-
* 通知对象 { jsonrpc, method, params: result } 回推来源客户端;无结果或出错时
|
|
15
|
-
* 不产出;非 JSON-RPC 消息(解析失败、缺 jsonrpc 字段)透传,控制权交还管道;
|
|
16
|
-
* 4. 主动推送:push(client_id, data) 委托 SocketServer.send,处理函数或服务器代码
|
|
17
|
-
* 可在任意时刻(如异步任务完成后)向指定客户端回推结果;
|
|
18
|
-
* 5. 反向调用:call(client_id, method, params, options) 向客户端发起 JSON-RPC 请求
|
|
19
|
-
* 并等待其反馈;入站消息为响应形态(含 result/error、无 method)时被优先识别
|
|
20
|
-
* 并结算待响应调用,实现服务器实时调用客户端方法;
|
|
21
|
-
* 6. 编码适配:json 模式产出对象(由 send 序列化),文本模式字符串原样、其余 JSON 序列化。
|
|
22
|
-
*
|
|
23
|
-
* 用法:
|
|
24
|
-
* const socket = new SocketServer({ name: 'socket', port: 9100, json: true }, server);
|
|
25
|
-
* const rpc = new MessagePlugin();
|
|
26
|
-
* socket.use(rpc);
|
|
27
|
-
* rpc.addMethod('echo', (params) => params);
|
|
28
|
-
* await socket.init();
|
|
29
|
-
* await socket.start();
|
|
30
|
-
*/
|
|
31
|
-
class MessagePlugin {
|
|
32
|
-
/**
|
|
33
|
-
* JSON-RPC 协议核心
|
|
34
|
-
*/
|
|
35
|
-
#rpc = new JsonRpc();
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* 已挂载的 SocketServer 实例,未挂载时为 null
|
|
39
|
-
*/
|
|
40
|
-
#server = null;
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* 挂载到 SocketServer 的消息管道,注册内置 main 钩子
|
|
44
|
-
* @param {Object} server 具备 getHandle() 的 SocketServer 实例
|
|
45
|
-
* @returns {MessagePlugin} 当前实例
|
|
46
|
-
* @throws {TypeError} server 不具备 getHandle 方法时
|
|
47
|
-
*/
|
|
48
|
-
bind(server) {
|
|
49
|
-
if (!server || typeof server.getHandle !== 'function') {
|
|
50
|
-
throw new TypeError('[MessagePlugin] server must provide getHandle()');
|
|
51
|
-
}
|
|
52
|
-
this.#server = server;
|
|
53
|
-
server.getHandle().on('main', (ctx, params, options) => {
|
|
54
|
-
return this._execute(ctx, params, options);
|
|
55
|
-
}, 100, 'json_rpc');
|
|
56
|
-
return this;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* 判断是否已挂载
|
|
61
|
-
* @returns {boolean} 是否已挂载到 SocketServer
|
|
62
|
-
*/
|
|
63
|
-
isBound() {
|
|
64
|
-
return this.#server !== null;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* 登记 RPC 方法
|
|
69
|
-
* @param {string} name 方法名
|
|
70
|
-
* @param {Function} handler 处理函数,签名 handler(params, extra, options),extra 含 client_id,options 为管道请求选项
|
|
71
|
-
* @returns {MessagePlugin} 当前实例
|
|
72
|
-
*/
|
|
73
|
-
addMethod(name, handler) {
|
|
74
|
-
this.#rpc.addMethod(name, handler);
|
|
75
|
-
return this;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* 删除 RPC 方法
|
|
80
|
-
* @param {string} name 方法名
|
|
81
|
-
* @returns {boolean} 是否删除成功
|
|
82
|
-
*/
|
|
83
|
-
delMethod(name) {
|
|
84
|
-
return this.#rpc.delMethod(name);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* 列出 RPC 方法(支持 Store 条件查询与分页)
|
|
89
|
-
* @param {Object} [query] 查询条件,缺省时列出全部
|
|
90
|
-
* @returns {Array<Object>} 方法列表,含 name
|
|
91
|
-
*/
|
|
92
|
-
listMethods(query) {
|
|
93
|
-
return this.#rpc.listMethods(query);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* 主动推送:向指定客户端回推数据(可用于异步任务完成后通知)
|
|
98
|
-
* @param {string} client_id 目标客户端 ID
|
|
99
|
-
* @param {string|Object} data 待推送数据,对象时按编码模式序列化
|
|
100
|
-
* @returns {boolean} 是否发送成功
|
|
101
|
-
* @throws {Error} 插件尚未挂载到服务器时
|
|
102
|
-
*/
|
|
103
|
-
push(client_id, data) {
|
|
104
|
-
if (!this.#server) throw new Error('[MessagePlugin] not bound to a server');
|
|
105
|
-
const payload = this._format(data);
|
|
106
|
-
return this.#server.send(client_id, payload);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* 实时调用客户端方法:发送 JSON-RPC 请求并等待客户端反馈 result
|
|
111
|
-
* @param {string} client_id 目标客户端 ID
|
|
112
|
-
* @param {string} method 客户端方法名
|
|
113
|
-
* @param {*} [params] 调用参数
|
|
114
|
-
* @param {Object} [options] 选项,含 timeout_ms(缺省 30000,0 为不超时)
|
|
115
|
-
* @returns {Promise<*>} 客户端反馈的 result;错误响应、送达失败或超时时 reject
|
|
116
|
-
* @throws {Error} 插件尚未挂载到服务器时
|
|
117
|
-
*/
|
|
118
|
-
call(client_id, method, params, options = {}) {
|
|
119
|
-
if (!this.#server) throw new Error('[MessagePlugin] not bound to a server');
|
|
120
|
-
return this.#rpc.call((request) => this.#server.send(client_id, this._format(request)), method, params, options);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* 内置 main 钩子逻辑:优先识别客户端反馈,其次执行 RPC 请求并格式化响应
|
|
125
|
-
* @param {Object} ctx 执行上下文
|
|
126
|
-
* @param {Object} params 消息参数,含 client_id 与 data
|
|
127
|
-
* @param {Object} [options] 管道请求选项(连接级,如 user_id / device_id / agent_id),透传给处理函数
|
|
128
|
-
* @returns {Promise<Object|string|undefined>} 响应对象或 JSON 字符串;反馈、透传与无结果通知时为 undefined
|
|
129
|
-
*/
|
|
130
|
-
async _execute(ctx, params, options) {
|
|
131
|
-
if (this.#rpc.acceptResponse(params.data)) return undefined;
|
|
132
|
-
const extra = { client_id: params.client_id };
|
|
133
|
-
const response = await this.#rpc.handle(params.data, extra, options);
|
|
134
|
-
if (response === undefined) return undefined;
|
|
135
|
-
return this._format(response);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* 按服务器 json 配置格式化数据
|
|
140
|
-
* @param {Object|string} data 响应对象或待推送数据
|
|
141
|
-
* @returns {Object|string} json 模式返回原值,文本模式字符串原样、其余 JSON 序列化
|
|
142
|
-
*/
|
|
143
|
-
_format(data) {
|
|
144
|
-
if (this.#server.config.json) return data;
|
|
145
|
-
if (typeof data === 'string') return data;
|
|
146
|
-
return JSON.stringify(data);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
module.exports = MessagePlugin;
|