rol-websocket-channel 1.0.2 → 1.0.6
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/index.ts +307 -123
- package/message-handler.ts +23 -0
- package/openclaw.plugin.json +2 -2
- package/package.json +4 -2
- package/readme.md +6 -1
- package/src/admin/methods/index.ts +7 -1
- package/src/admin/methods/mem9.ts +341 -0
- package/src/mqtt/connection-manager.ts +262 -188
- package/src/mqtt/index.ts +6 -6
- package/src/mqtt/mqtt-client.ts +170 -119
- package/src/mqtt/mqtt.test.ts +670 -0
- package/src/mqtt/types.ts +46 -36
package/src/mqtt/mqtt-client.ts
CHANGED
|
@@ -1,119 +1,170 @@
|
|
|
1
|
-
// MQTT
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import * as ConnectionManager from "./connection-manager.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
1
|
+
// MQTT 全局客户端
|
|
2
|
+
// 封装全局 MQTT 连接逻辑(使用 MQTT.js 自带的重连功能)
|
|
3
|
+
// 全进程共享单一连接,随机生成 clientId,从 topic 解析用户名
|
|
4
|
+
|
|
5
|
+
import * as ConnectionManager from "./connection-manager.js";
|
|
6
|
+
|
|
7
|
+
// ─────────────────────────────────────────────
|
|
8
|
+
// 选项接口
|
|
9
|
+
// ─────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
export interface GlobalMqttClientOptions {
|
|
12
|
+
/** Broker 地址,如 ws://192.168.1.152:8083/mqtt */
|
|
13
|
+
mqttUrl: string;
|
|
14
|
+
/**
|
|
15
|
+
* 原始 topic,格式为 announcement/{user_name}/{agent_id}/...
|
|
16
|
+
* 用于解析用户名(作为 MQTT username)和作为消息发布目标
|
|
17
|
+
* 例:announcement/0b9a784d-e044-4c6f-9024-ef8799c131d1/ce627d3d-9e22-47f7-b1c6-a6ab69570fef/bot
|
|
18
|
+
*/
|
|
19
|
+
mqttTopic: string;
|
|
20
|
+
/** AbortSignal,触发时断开连接并结束 connect() */
|
|
21
|
+
abortSignal: AbortSignal;
|
|
22
|
+
/** 收到消息时回调 */
|
|
23
|
+
onMessage: (topic: string, payload: Buffer) => Promise<void>;
|
|
24
|
+
/** 连接成功回调 */
|
|
25
|
+
onConnect?: () => void;
|
|
26
|
+
/** 连接断开回调 */
|
|
27
|
+
onDisconnect?: () => void;
|
|
28
|
+
/** 连接错误回调 */
|
|
29
|
+
onError?: (err: Error) => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ─────────────────────────────────────────────
|
|
33
|
+
// GlobalMqttClient
|
|
34
|
+
// ─────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 全局共享 MQTT 客户端
|
|
38
|
+
*
|
|
39
|
+
* 全进程只维护一个 MQTT 连接:
|
|
40
|
+
* - clientId 每次启动随机生成
|
|
41
|
+
* - MQTT username 从 mqttTopic 中解析(announcement/{user_name}/...),默认 "default_name"
|
|
42
|
+
* - 订阅通配符 topic(announcement/{user_name}/#),不区分 agent_id
|
|
43
|
+
* - 多处调用 connect() 时复用同一连接
|
|
44
|
+
*/
|
|
45
|
+
export class GlobalMqttClient {
|
|
46
|
+
private mqttUrl: string;
|
|
47
|
+
private mqttTopic: string;
|
|
48
|
+
private abortSignal: AbortSignal;
|
|
49
|
+
private onMessageCb: (topic: string, payload: Buffer) => Promise<void>;
|
|
50
|
+
private onConnectCb?: () => void;
|
|
51
|
+
private onDisconnectCb?: () => void;
|
|
52
|
+
private onErrorCb?: (err: Error) => void;
|
|
53
|
+
|
|
54
|
+
constructor(options: GlobalMqttClientOptions) {
|
|
55
|
+
this.mqttUrl = options.mqttUrl;
|
|
56
|
+
this.mqttTopic = options.mqttTopic;
|
|
57
|
+
this.abortSignal = options.abortSignal;
|
|
58
|
+
this.onMessageCb = options.onMessage;
|
|
59
|
+
this.onConnectCb = options.onConnect;
|
|
60
|
+
this.onDisconnectCb = options.onDisconnect;
|
|
61
|
+
this.onErrorCb = options.onError;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 建立全局 MQTT 连接并等待 AbortSignal
|
|
66
|
+
*
|
|
67
|
+
* 若全局连接已存在且已连接则直接复用。
|
|
68
|
+
* 方法持续挂起直到 abortSignal 触发,触发后自动断开并 resolve。
|
|
69
|
+
*/
|
|
70
|
+
async connect(): Promise<void> {
|
|
71
|
+
const username = ConnectionManager.parseUsernameFromTopic(this.mqttTopic);
|
|
72
|
+
const subscribeTopic = ConnectionManager.getSubscribeTopic(this.mqttTopic);
|
|
73
|
+
|
|
74
|
+
console.log(
|
|
75
|
+
`[MQTT] GlobalMqttClient.connect(): url=${this.mqttUrl}, username=${username}, subscribeTopic=${subscribeTopic}`,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
if (this.abortSignal.aborted) {
|
|
79
|
+
console.log("[MQTT] GlobalMqttClient.connect(): aborted before starting");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
await ConnectionManager.createGlobalMqttConnection(
|
|
84
|
+
this.mqttUrl,
|
|
85
|
+
this.mqttTopic,
|
|
86
|
+
{
|
|
87
|
+
onConnect: () => {
|
|
88
|
+
console.log("[MQTT] GlobalMqttClient: onConnect");
|
|
89
|
+
this.onConnectCb?.();
|
|
90
|
+
},
|
|
91
|
+
onDisconnect: () => {
|
|
92
|
+
console.log("[MQTT] GlobalMqttClient: onDisconnect");
|
|
93
|
+
this.onDisconnectCb?.();
|
|
94
|
+
},
|
|
95
|
+
onError: (err) => {
|
|
96
|
+
console.error(`[MQTT] GlobalMqttClient: onError - ${err.message}`);
|
|
97
|
+
this.onErrorCb?.(err);
|
|
98
|
+
},
|
|
99
|
+
onClose: () => {
|
|
100
|
+
console.log("[MQTT] GlobalMqttClient: onClose");
|
|
101
|
+
// MQTT.js 会自动重连;仅在 abort 触发时才真正清理全局连接
|
|
102
|
+
if (this.abortSignal.aborted) {
|
|
103
|
+
ConnectionManager.closeGlobalConnection();
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
onMessage: this.onMessageCb,
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
console.log(
|
|
111
|
+
"[MQTT] GlobalMqttClient.connect(): connection created, waiting for abort signal...",
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// 挂起,直到 AbortSignal 触发
|
|
115
|
+
await new Promise<void>((resolve) => {
|
|
116
|
+
const handleAbort = () => {
|
|
117
|
+
console.log(
|
|
118
|
+
"[MQTT] GlobalMqttClient: abort signal received, disconnecting",
|
|
119
|
+
);
|
|
120
|
+
this.disconnect();
|
|
121
|
+
resolve();
|
|
122
|
+
};
|
|
123
|
+
this.abortSignal.addEventListener("abort", handleAbort, { once: true });
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
console.log("[MQTT] GlobalMqttClient.connect(): connection loop ended");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 手动断开全局连接
|
|
131
|
+
*/
|
|
132
|
+
disconnect(): void {
|
|
133
|
+
console.log(
|
|
134
|
+
"[MQTT] GlobalMqttClient.disconnect(): closing global connection",
|
|
135
|
+
);
|
|
136
|
+
ConnectionManager.closeGlobalConnection();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 向指定 topic 发布消息
|
|
141
|
+
* @returns 发布成功返回 true,未连接返回 false
|
|
142
|
+
*/
|
|
143
|
+
publish(topic: string, message: string): boolean {
|
|
144
|
+
console.log(`[MQTT] GlobalMqttClient.publish(): topic=${topic}`);
|
|
145
|
+
return ConnectionManager.publishGlobalMessage(topic, message);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 检查全局连接是否处于已连接状态
|
|
150
|
+
*/
|
|
151
|
+
isConnected(): boolean {
|
|
152
|
+
return ConnectionManager.isGlobalConnected();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* 获取当前解析的订阅 topic(通配符形式)
|
|
157
|
+
* 例:announcement/0b9a784d-e044-4c6f-9024-ef8799c131d1/#
|
|
158
|
+
*/
|
|
159
|
+
getSubscribeTopic(): string {
|
|
160
|
+
return ConnectionManager.getSubscribeTopic(this.mqttTopic);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 获取从 topic 中解析的用户名
|
|
165
|
+
* 例:announcement/myuser/agent/bot → "myuser"
|
|
166
|
+
*/
|
|
167
|
+
getUsername(): string {
|
|
168
|
+
return ConnectionManager.parseUsernameFromTopic(this.mqttTopic);
|
|
169
|
+
}
|
|
170
|
+
}
|