rol-websocket-channel 1.4.2 → 1.4.8
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/{MQTT-API /346/226/260/345/242/236/346/226/207/344/273/266/345/212/237/350/203/275.md" → MQTT-API 5-6.md } +89 -1
- package/dist/index.js +617 -617
- package/dist/message-handler.js +515 -503
- package/dist/src/admin/cli.js +43 -43
- package/dist/src/admin/jsonrpc.js +60 -60
- package/dist/src/admin/lib/fs.js +30 -30
- package/dist/src/admin/lib/paths.js +80 -80
- package/dist/src/admin/methods/admin.js +60 -60
- package/dist/src/admin/methods/agents-extended.js +251 -251
- package/dist/src/admin/methods/artifacts.js +736 -642
- package/dist/src/admin/methods/artifacts.test.js +210 -191
- package/dist/src/admin/methods/cron.js +250 -250
- package/dist/src/admin/methods/index.js +104 -102
- package/dist/src/admin/methods/mem9.js +309 -270
- package/dist/src/admin/methods/mem9.test.js +34 -0
- package/dist/src/admin/methods/memory.js +363 -363
- package/dist/src/admin/methods/models-extended.js +190 -190
- package/dist/src/admin/methods/models.js +195 -195
- package/dist/src/admin/methods/pairing.js +268 -268
- package/dist/src/admin/methods/sessions-extended.js +215 -215
- package/dist/src/admin/methods/sessions.js +75 -75
- package/dist/src/admin/methods/skills-extended.js +157 -157
- package/dist/src/admin/methods/skills-toggle.js +183 -183
- package/dist/src/admin/methods/skills.js +528 -528
- package/dist/src/admin/methods/system.js +271 -180
- package/dist/src/admin/methods/usage.js +1170 -1170
- package/dist/src/admin/types.js +1 -1
- package/dist/src/mqtt/connection-manager.js +209 -209
- package/dist/src/mqtt/index.js +5 -5
- package/dist/src/mqtt/mqtt-client.js +110 -110
- package/dist/src/mqtt/mqtt.test.js +418 -418
- package/dist/src/mqtt/types.js +2 -2
- package/dist/src/shared/context.js +24 -24
- package/dist/src/shared/wrapper.js +23 -23
- package/message-handler.ts +15 -1
- package/openclaw.plugin.json +73 -0
- package/package.json +1 -1
- package/src/admin/methods/artifacts.test.ts +35 -0
- package/src/admin/methods/artifacts.ts +140 -2
- package/src/admin/methods/index.ts +3 -1
- package/src/admin/methods/mem9.test.ts +39 -0
- package/src/admin/methods/mem9.ts +48 -1
- package/src/admin/methods/system.ts +129 -1
package/dist/src/admin/types.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {};
|
|
1
|
+
export {};
|
|
@@ -1,209 +1,209 @@
|
|
|
1
|
-
// MQTT 全局连接管理器
|
|
2
|
-
// 全局共享单一连接(随机 clientId,从 topic 解析用户名,支持测试注入)
|
|
3
|
-
// ─────────────────────────────────────────────
|
|
4
|
-
// 全局单例连接
|
|
5
|
-
// ─────────────────────────────────────────────
|
|
6
|
-
let globalConnection = null;
|
|
7
|
-
// ─────────────────────────────────────────────
|
|
8
|
-
// 可注入的 mqtt connect 工厂(用于测试 mock)
|
|
9
|
-
// ─────────────────────────────────────────────
|
|
10
|
-
let _mqttConnectFn = null;
|
|
11
|
-
/**
|
|
12
|
-
* 注入自定义 mqtt connect 函数(测试用)
|
|
13
|
-
* 传入 null 恢复默认行为(使用真实 mqtt 库)
|
|
14
|
-
*/
|
|
15
|
-
export function _setMqttConnectFn(fn) {
|
|
16
|
-
_mqttConnectFn = fn;
|
|
17
|
-
}
|
|
18
|
-
async function getMqttConnect() {
|
|
19
|
-
if (_mqttConnectFn) {
|
|
20
|
-
return _mqttConnectFn;
|
|
21
|
-
}
|
|
22
|
-
const mqtt = await import("mqtt");
|
|
23
|
-
return mqtt.default.connect.bind(mqtt.default);
|
|
24
|
-
}
|
|
25
|
-
// ─────────────────────────────────────────────
|
|
26
|
-
// 工具函数(纯函数,便于单元测试)
|
|
27
|
-
// ─────────────────────────────────────────────
|
|
28
|
-
/**
|
|
29
|
-
* 从 MQTT topic 中解析用户名
|
|
30
|
-
*
|
|
31
|
-
* 模式:announcement/{user_name}/{agent_id}/...
|
|
32
|
-
* 例如:announcement/0b9a784d-e044-4c6f-9024-ef8799c131d1/ce627d3d-9e22-47f7-b1c6-a6ab69570fef/bot
|
|
33
|
-
* → user_name = "0b9a784d-e044-4c6f-9024-ef8799c131d1"
|
|
34
|
-
*
|
|
35
|
-
* 若无法解析则返回 "default_name"
|
|
36
|
-
*/
|
|
37
|
-
export function parseUsernameFromTopic(topic) {
|
|
38
|
-
if (!topic || typeof topic !== "string")
|
|
39
|
-
return "default_name";
|
|
40
|
-
const parts = topic.split("/");
|
|
41
|
-
if (parts[0] === "announcement" && parts[1] && parts[1].trim() !== "") {
|
|
42
|
-
return parts[1].trim();
|
|
43
|
-
}
|
|
44
|
-
return "default_name";
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* 根据 topic 生成通配符订阅路径
|
|
48
|
-
*
|
|
49
|
-
* 若能解析到 user_name:announcement/{user_name}/#(不区分 agent_id)
|
|
50
|
-
* 否则:原始 topic 末尾补 /# 若无通配符
|
|
51
|
-
*/
|
|
52
|
-
export function getSubscribeTopic(topic) {
|
|
53
|
-
const username = parseUsernameFromTopic(topic);
|
|
54
|
-
// if (username !== "default_name") {
|
|
55
|
-
// return `announcement/${username}/#`;
|
|
56
|
-
// }
|
|
57
|
-
// if (topic.endsWith("#")) return topic;
|
|
58
|
-
return topic;
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* 生成随机 MQTT 客户端 ID
|
|
62
|
-
* 格式:mqtt_client_{8位随机字符}{时间戳36进制}
|
|
63
|
-
*/
|
|
64
|
-
export function generateClientId() {
|
|
65
|
-
const rand = Math.random().toString(36).slice(2, 10).padEnd(8, "0");
|
|
66
|
-
const ts = Date.now().toString(36);
|
|
67
|
-
return `mqtt_client_${rand}${ts}`;
|
|
68
|
-
}
|
|
69
|
-
// ─────────────────────────────────────────────
|
|
70
|
-
// 全局连接 API
|
|
71
|
-
// ─────────────────────────────────────────────
|
|
72
|
-
/**
|
|
73
|
-
* 获取当前全局连接(可能为 null)
|
|
74
|
-
*/
|
|
75
|
-
export function getGlobalConnection() {
|
|
76
|
-
return globalConnection;
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* 检查全局连接是否处于已连接状态
|
|
80
|
-
*/
|
|
81
|
-
export function isGlobalConnected() {
|
|
82
|
-
return !!globalConnection?.ws?.connected;
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* 创建全局 MQTT 连接(若已连接则复用)
|
|
86
|
-
*
|
|
87
|
-
* @param mqttUrl Broker 地址,如 ws://192.168.1.152:8083/mqtt
|
|
88
|
-
* @param mqttTopic 原始 topic,用于解析用户名和作为发布目标
|
|
89
|
-
* @param callbacks 连接事件回调
|
|
90
|
-
*/
|
|
91
|
-
export async function createGlobalMqttConnection(mqttUrl, mqttTopic, callbacks) {
|
|
92
|
-
const username = parseUsernameFromTopic(mqttTopic);
|
|
93
|
-
const subscribeTopic = getSubscribeTopic(mqttTopic);
|
|
94
|
-
const clientId = generateClientId();
|
|
95
|
-
console.log(`[MQTT] createGlobalMqttConnection: url=${mqttUrl}`);
|
|
96
|
-
console.log(`[MQTT] createGlobalMqttConnection: username=${username}, clientId=${clientId}`);
|
|
97
|
-
console.log(`[MQTT] createGlobalMqttConnection: subscribeTopic=${subscribeTopic}`);
|
|
98
|
-
// 已有活跃连接,直接复用
|
|
99
|
-
if (globalConnection?.ws?.connected) {
|
|
100
|
-
console.log("[MQTT] createGlobalMqttConnection: reusing existing active connection");
|
|
101
|
-
return globalConnection;
|
|
102
|
-
}
|
|
103
|
-
// 存在断开的旧连接,先清理
|
|
104
|
-
if (globalConnection) {
|
|
105
|
-
console.log("[MQTT] createGlobalMqttConnection: closing stale connection");
|
|
106
|
-
const stale = globalConnection;
|
|
107
|
-
globalConnection = null;
|
|
108
|
-
try {
|
|
109
|
-
stale.ws.end(true);
|
|
110
|
-
}
|
|
111
|
-
catch {
|
|
112
|
-
// ignore
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
// 获取 connect 函数(真实或 mock)
|
|
116
|
-
const mqttConnect = await getMqttConnect();
|
|
117
|
-
console.log("[MQTT] createGlobalMqttConnection: calling mqtt.connect...");
|
|
118
|
-
const client = mqttConnect(mqttUrl, {
|
|
119
|
-
clientId,
|
|
120
|
-
username,
|
|
121
|
-
reconnectPeriod: 5000, // 5 秒自动重连
|
|
122
|
-
connectTimeout: 30000, // 30 秒连接超时
|
|
123
|
-
keepalive: 30, // 30 秒心跳
|
|
124
|
-
clean: true,
|
|
125
|
-
});
|
|
126
|
-
globalConnection = {
|
|
127
|
-
ws: client,
|
|
128
|
-
topic: mqttTopic,
|
|
129
|
-
subscribeTopic,
|
|
130
|
-
username,
|
|
131
|
-
};
|
|
132
|
-
setupEventHandlers(client, subscribeTopic, callbacks);
|
|
133
|
-
console.log("[MQTT] createGlobalMqttConnection: connection object created, waiting for broker...");
|
|
134
|
-
return globalConnection;
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* 关闭全局连接
|
|
138
|
-
* 先置空再调用 end(),防止 close 事件同步触发时重入
|
|
139
|
-
*/
|
|
140
|
-
export function closeGlobalConnection() {
|
|
141
|
-
if (globalConnection) {
|
|
142
|
-
console.log("[MQTT] closeGlobalConnection: force-closing...");
|
|
143
|
-
const conn = globalConnection;
|
|
144
|
-
globalConnection = null;
|
|
145
|
-
try {
|
|
146
|
-
conn.ws.end(true);
|
|
147
|
-
}
|
|
148
|
-
catch {
|
|
149
|
-
// ignore
|
|
150
|
-
}
|
|
151
|
-
console.log("[MQTT] closeGlobalConnection: done");
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
console.log("[MQTT] closeGlobalConnection: no active connection");
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* 向指定 topic 发布消息(使用全局连接)
|
|
159
|
-
* @returns 发布成功返回 true,未连接返回 false
|
|
160
|
-
*/
|
|
161
|
-
export function publishGlobalMessage(topic, message) {
|
|
162
|
-
if (!isGlobalConnected()) {
|
|
163
|
-
console.error("[MQTT] ❌ publishGlobalMessage: no connected global connection");
|
|
164
|
-
return false;
|
|
165
|
-
}
|
|
166
|
-
globalConnection.ws.publish(topic, message);
|
|
167
|
-
console.log(`[MQTT] publishGlobalMessage: published to ${topic}`);
|
|
168
|
-
return true;
|
|
169
|
-
}
|
|
170
|
-
// ─────────────────────────────────────────────
|
|
171
|
-
// 内部:事件处理
|
|
172
|
-
// ─────────────────────────────────────────────
|
|
173
|
-
function setupEventHandlers(client, subscribeTopic, callbacks) {
|
|
174
|
-
const { onConnect, onDisconnect, onError, onClose, onMessage } = callbacks;
|
|
175
|
-
client.on("connect", () => {
|
|
176
|
-
console.log("[MQTT] ✅ Global connection established");
|
|
177
|
-
client.subscribe(subscribeTopic, (err) => {
|
|
178
|
-
if (err) {
|
|
179
|
-
console.error(`[MQTT] ❌ Subscribe failed: ${err.message}`);
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
console.log(`[MQTT] ✅ Subscribed to: ${subscribeTopic}`);
|
|
183
|
-
}
|
|
184
|
-
});
|
|
185
|
-
onConnect?.();
|
|
186
|
-
});
|
|
187
|
-
client.on("message", (topic, payload) => {
|
|
188
|
-
console.log(`[MQTT] 📨 Message received on: ${topic} (${payload.length} bytes)`);
|
|
189
|
-
onMessage?.(topic, payload);
|
|
190
|
-
});
|
|
191
|
-
client.on("error", (err) => {
|
|
192
|
-
console.error(`[MQTT] ❌ Global connection error: ${err.message}`);
|
|
193
|
-
onError?.(err);
|
|
194
|
-
});
|
|
195
|
-
client.on("disconnect", () => {
|
|
196
|
-
console.log("[MQTT] 🔴 Global connection disconnected");
|
|
197
|
-
onDisconnect?.();
|
|
198
|
-
});
|
|
199
|
-
client.on("close", () => {
|
|
200
|
-
console.log("[MQTT] 🔴 Global connection closed");
|
|
201
|
-
onClose?.();
|
|
202
|
-
});
|
|
203
|
-
client.on("reconnect", () => {
|
|
204
|
-
console.log("[MQTT] 🔄 Global connection reconnecting...");
|
|
205
|
-
});
|
|
206
|
-
client.on("offline", () => {
|
|
207
|
-
console.log("[MQTT] 😴 Global connection offline");
|
|
208
|
-
});
|
|
209
|
-
}
|
|
1
|
+
// MQTT 全局连接管理器
|
|
2
|
+
// 全局共享单一连接(随机 clientId,从 topic 解析用户名,支持测试注入)
|
|
3
|
+
// ─────────────────────────────────────────────
|
|
4
|
+
// 全局单例连接
|
|
5
|
+
// ─────────────────────────────────────────────
|
|
6
|
+
let globalConnection = null;
|
|
7
|
+
// ─────────────────────────────────────────────
|
|
8
|
+
// 可注入的 mqtt connect 工厂(用于测试 mock)
|
|
9
|
+
// ─────────────────────────────────────────────
|
|
10
|
+
let _mqttConnectFn = null;
|
|
11
|
+
/**
|
|
12
|
+
* 注入自定义 mqtt connect 函数(测试用)
|
|
13
|
+
* 传入 null 恢复默认行为(使用真实 mqtt 库)
|
|
14
|
+
*/
|
|
15
|
+
export function _setMqttConnectFn(fn) {
|
|
16
|
+
_mqttConnectFn = fn;
|
|
17
|
+
}
|
|
18
|
+
async function getMqttConnect() {
|
|
19
|
+
if (_mqttConnectFn) {
|
|
20
|
+
return _mqttConnectFn;
|
|
21
|
+
}
|
|
22
|
+
const mqtt = await import("mqtt");
|
|
23
|
+
return mqtt.default.connect.bind(mqtt.default);
|
|
24
|
+
}
|
|
25
|
+
// ─────────────────────────────────────────────
|
|
26
|
+
// 工具函数(纯函数,便于单元测试)
|
|
27
|
+
// ─────────────────────────────────────────────
|
|
28
|
+
/**
|
|
29
|
+
* 从 MQTT topic 中解析用户名
|
|
30
|
+
*
|
|
31
|
+
* 模式:announcement/{user_name}/{agent_id}/...
|
|
32
|
+
* 例如:announcement/0b9a784d-e044-4c6f-9024-ef8799c131d1/ce627d3d-9e22-47f7-b1c6-a6ab69570fef/bot
|
|
33
|
+
* → user_name = "0b9a784d-e044-4c6f-9024-ef8799c131d1"
|
|
34
|
+
*
|
|
35
|
+
* 若无法解析则返回 "default_name"
|
|
36
|
+
*/
|
|
37
|
+
export function parseUsernameFromTopic(topic) {
|
|
38
|
+
if (!topic || typeof topic !== "string")
|
|
39
|
+
return "default_name";
|
|
40
|
+
const parts = topic.split("/");
|
|
41
|
+
if (parts[0] === "announcement" && parts[1] && parts[1].trim() !== "") {
|
|
42
|
+
return parts[1].trim();
|
|
43
|
+
}
|
|
44
|
+
return "default_name";
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 根据 topic 生成通配符订阅路径
|
|
48
|
+
*
|
|
49
|
+
* 若能解析到 user_name:announcement/{user_name}/#(不区分 agent_id)
|
|
50
|
+
* 否则:原始 topic 末尾补 /# 若无通配符
|
|
51
|
+
*/
|
|
52
|
+
export function getSubscribeTopic(topic) {
|
|
53
|
+
const username = parseUsernameFromTopic(topic);
|
|
54
|
+
// if (username !== "default_name") {
|
|
55
|
+
// return `announcement/${username}/#`;
|
|
56
|
+
// }
|
|
57
|
+
// if (topic.endsWith("#")) return topic;
|
|
58
|
+
return topic;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* 生成随机 MQTT 客户端 ID
|
|
62
|
+
* 格式:mqtt_client_{8位随机字符}{时间戳36进制}
|
|
63
|
+
*/
|
|
64
|
+
export function generateClientId() {
|
|
65
|
+
const rand = Math.random().toString(36).slice(2, 10).padEnd(8, "0");
|
|
66
|
+
const ts = Date.now().toString(36);
|
|
67
|
+
return `mqtt_client_${rand}${ts}`;
|
|
68
|
+
}
|
|
69
|
+
// ─────────────────────────────────────────────
|
|
70
|
+
// 全局连接 API
|
|
71
|
+
// ─────────────────────────────────────────────
|
|
72
|
+
/**
|
|
73
|
+
* 获取当前全局连接(可能为 null)
|
|
74
|
+
*/
|
|
75
|
+
export function getGlobalConnection() {
|
|
76
|
+
return globalConnection;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 检查全局连接是否处于已连接状态
|
|
80
|
+
*/
|
|
81
|
+
export function isGlobalConnected() {
|
|
82
|
+
return !!globalConnection?.ws?.connected;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 创建全局 MQTT 连接(若已连接则复用)
|
|
86
|
+
*
|
|
87
|
+
* @param mqttUrl Broker 地址,如 ws://192.168.1.152:8083/mqtt
|
|
88
|
+
* @param mqttTopic 原始 topic,用于解析用户名和作为发布目标
|
|
89
|
+
* @param callbacks 连接事件回调
|
|
90
|
+
*/
|
|
91
|
+
export async function createGlobalMqttConnection(mqttUrl, mqttTopic, callbacks) {
|
|
92
|
+
const username = parseUsernameFromTopic(mqttTopic);
|
|
93
|
+
const subscribeTopic = getSubscribeTopic(mqttTopic);
|
|
94
|
+
const clientId = generateClientId();
|
|
95
|
+
console.log(`[MQTT] createGlobalMqttConnection: url=${mqttUrl}`);
|
|
96
|
+
console.log(`[MQTT] createGlobalMqttConnection: username=${username}, clientId=${clientId}`);
|
|
97
|
+
console.log(`[MQTT] createGlobalMqttConnection: subscribeTopic=${subscribeTopic}`);
|
|
98
|
+
// 已有活跃连接,直接复用
|
|
99
|
+
if (globalConnection?.ws?.connected) {
|
|
100
|
+
console.log("[MQTT] createGlobalMqttConnection: reusing existing active connection");
|
|
101
|
+
return globalConnection;
|
|
102
|
+
}
|
|
103
|
+
// 存在断开的旧连接,先清理
|
|
104
|
+
if (globalConnection) {
|
|
105
|
+
console.log("[MQTT] createGlobalMqttConnection: closing stale connection");
|
|
106
|
+
const stale = globalConnection;
|
|
107
|
+
globalConnection = null;
|
|
108
|
+
try {
|
|
109
|
+
stale.ws.end(true);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// ignore
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// 获取 connect 函数(真实或 mock)
|
|
116
|
+
const mqttConnect = await getMqttConnect();
|
|
117
|
+
console.log("[MQTT] createGlobalMqttConnection: calling mqtt.connect...");
|
|
118
|
+
const client = mqttConnect(mqttUrl, {
|
|
119
|
+
clientId,
|
|
120
|
+
username,
|
|
121
|
+
reconnectPeriod: 5000, // 5 秒自动重连
|
|
122
|
+
connectTimeout: 30000, // 30 秒连接超时
|
|
123
|
+
keepalive: 30, // 30 秒心跳
|
|
124
|
+
clean: true,
|
|
125
|
+
});
|
|
126
|
+
globalConnection = {
|
|
127
|
+
ws: client,
|
|
128
|
+
topic: mqttTopic,
|
|
129
|
+
subscribeTopic,
|
|
130
|
+
username,
|
|
131
|
+
};
|
|
132
|
+
setupEventHandlers(client, subscribeTopic, callbacks);
|
|
133
|
+
console.log("[MQTT] createGlobalMqttConnection: connection object created, waiting for broker...");
|
|
134
|
+
return globalConnection;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 关闭全局连接
|
|
138
|
+
* 先置空再调用 end(),防止 close 事件同步触发时重入
|
|
139
|
+
*/
|
|
140
|
+
export function closeGlobalConnection() {
|
|
141
|
+
if (globalConnection) {
|
|
142
|
+
console.log("[MQTT] closeGlobalConnection: force-closing...");
|
|
143
|
+
const conn = globalConnection;
|
|
144
|
+
globalConnection = null;
|
|
145
|
+
try {
|
|
146
|
+
conn.ws.end(true);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// ignore
|
|
150
|
+
}
|
|
151
|
+
console.log("[MQTT] closeGlobalConnection: done");
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
console.log("[MQTT] closeGlobalConnection: no active connection");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* 向指定 topic 发布消息(使用全局连接)
|
|
159
|
+
* @returns 发布成功返回 true,未连接返回 false
|
|
160
|
+
*/
|
|
161
|
+
export function publishGlobalMessage(topic, message) {
|
|
162
|
+
if (!isGlobalConnected()) {
|
|
163
|
+
console.error("[MQTT] ❌ publishGlobalMessage: no connected global connection");
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
globalConnection.ws.publish(topic, message);
|
|
167
|
+
console.log(`[MQTT] publishGlobalMessage: published to ${topic}`);
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
// ─────────────────────────────────────────────
|
|
171
|
+
// 内部:事件处理
|
|
172
|
+
// ─────────────────────────────────────────────
|
|
173
|
+
function setupEventHandlers(client, subscribeTopic, callbacks) {
|
|
174
|
+
const { onConnect, onDisconnect, onError, onClose, onMessage } = callbacks;
|
|
175
|
+
client.on("connect", () => {
|
|
176
|
+
console.log("[MQTT] ✅ Global connection established");
|
|
177
|
+
client.subscribe(subscribeTopic, (err) => {
|
|
178
|
+
if (err) {
|
|
179
|
+
console.error(`[MQTT] ❌ Subscribe failed: ${err.message}`);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
console.log(`[MQTT] ✅ Subscribed to: ${subscribeTopic}`);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
onConnect?.();
|
|
186
|
+
});
|
|
187
|
+
client.on("message", (topic, payload) => {
|
|
188
|
+
console.log(`[MQTT] 📨 Message received on: ${topic} (${payload.length} bytes)`);
|
|
189
|
+
onMessage?.(topic, payload);
|
|
190
|
+
});
|
|
191
|
+
client.on("error", (err) => {
|
|
192
|
+
console.error(`[MQTT] ❌ Global connection error: ${err.message}`);
|
|
193
|
+
onError?.(err);
|
|
194
|
+
});
|
|
195
|
+
client.on("disconnect", () => {
|
|
196
|
+
console.log("[MQTT] 🔴 Global connection disconnected");
|
|
197
|
+
onDisconnect?.();
|
|
198
|
+
});
|
|
199
|
+
client.on("close", () => {
|
|
200
|
+
console.log("[MQTT] 🔴 Global connection closed");
|
|
201
|
+
onClose?.();
|
|
202
|
+
});
|
|
203
|
+
client.on("reconnect", () => {
|
|
204
|
+
console.log("[MQTT] 🔄 Global connection reconnecting...");
|
|
205
|
+
});
|
|
206
|
+
client.on("offline", () => {
|
|
207
|
+
console.log("[MQTT] 😴 Global connection offline");
|
|
208
|
+
});
|
|
209
|
+
}
|
package/dist/src/mqtt/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// MQTT 模块入口
|
|
2
|
-
// 导出所有 MQTT 相关的类型和功能
|
|
3
|
-
export * from "./types.js";
|
|
4
|
-
export * from "./connection-manager.js";
|
|
5
|
-
export * from "./mqtt-client.js";
|
|
1
|
+
// MQTT 模块入口
|
|
2
|
+
// 导出所有 MQTT 相关的类型和功能
|
|
3
|
+
export * from "./types.js";
|
|
4
|
+
export * from "./connection-manager.js";
|
|
5
|
+
export * from "./mqtt-client.js";
|
|
@@ -1,110 +1,110 @@
|
|
|
1
|
-
// MQTT 全局客户端
|
|
2
|
-
// 封装全局 MQTT 连接逻辑(使用 MQTT.js 自带的重连功能)
|
|
3
|
-
// 全进程共享单一连接,随机生成 clientId,从 topic 解析用户名
|
|
4
|
-
import * as ConnectionManager from "./connection-manager.js";
|
|
5
|
-
// ─────────────────────────────────────────────
|
|
6
|
-
// GlobalMqttClient
|
|
7
|
-
// ─────────────────────────────────────────────
|
|
8
|
-
/**
|
|
9
|
-
* 全局共享 MQTT 客户端
|
|
10
|
-
*
|
|
11
|
-
* 全进程只维护一个 MQTT 连接:
|
|
12
|
-
* - clientId 每次启动随机生成
|
|
13
|
-
* - MQTT username 从 mqttTopic 中解析(announcement/{user_name}/...),默认 "default_name"
|
|
14
|
-
* - 订阅通配符 topic(announcement/{user_name}/#),不区分 agent_id
|
|
15
|
-
* - 多处调用 connect() 时复用同一连接
|
|
16
|
-
*/
|
|
17
|
-
export class GlobalMqttClient {
|
|
18
|
-
constructor(options) {
|
|
19
|
-
this.mqttUrl = options.mqttUrl;
|
|
20
|
-
this.mqttTopic = options.mqttTopic;
|
|
21
|
-
this.abortSignal = options.abortSignal;
|
|
22
|
-
this.onMessageCb = options.onMessage;
|
|
23
|
-
this.onConnectCb = options.onConnect;
|
|
24
|
-
this.onDisconnectCb = options.onDisconnect;
|
|
25
|
-
this.onErrorCb = options.onError;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* 建立全局 MQTT 连接并等待 AbortSignal
|
|
29
|
-
*
|
|
30
|
-
* 若全局连接已存在且已连接则直接复用。
|
|
31
|
-
* 方法持续挂起直到 abortSignal 触发,触发后自动断开并 resolve。
|
|
32
|
-
*/
|
|
33
|
-
async connect() {
|
|
34
|
-
const username = ConnectionManager.parseUsernameFromTopic(this.mqttTopic);
|
|
35
|
-
const subscribeTopic = ConnectionManager.getSubscribeTopic(this.mqttTopic);
|
|
36
|
-
console.log(`[MQTT] GlobalMqttClient.connect(): url=${this.mqttUrl}, username=${username}, subscribeTopic=${subscribeTopic}`);
|
|
37
|
-
if (this.abortSignal.aborted) {
|
|
38
|
-
console.log("[MQTT] GlobalMqttClient.connect(): aborted before starting");
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
await ConnectionManager.createGlobalMqttConnection(this.mqttUrl, this.mqttTopic, {
|
|
42
|
-
onConnect: () => {
|
|
43
|
-
console.log("[MQTT] GlobalMqttClient: onConnect");
|
|
44
|
-
this.onConnectCb?.();
|
|
45
|
-
},
|
|
46
|
-
onDisconnect: () => {
|
|
47
|
-
console.log("[MQTT] GlobalMqttClient: onDisconnect");
|
|
48
|
-
this.onDisconnectCb?.();
|
|
49
|
-
},
|
|
50
|
-
onError: (err) => {
|
|
51
|
-
console.error(`[MQTT] GlobalMqttClient: onError - ${err.message}`);
|
|
52
|
-
this.onErrorCb?.(err);
|
|
53
|
-
},
|
|
54
|
-
onClose: () => {
|
|
55
|
-
console.log("[MQTT] GlobalMqttClient: onClose");
|
|
56
|
-
// MQTT.js 会自动重连;仅在 abort 触发时才真正清理全局连接
|
|
57
|
-
if (this.abortSignal.aborted) {
|
|
58
|
-
ConnectionManager.closeGlobalConnection();
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
onMessage: this.onMessageCb,
|
|
62
|
-
});
|
|
63
|
-
console.log("[MQTT] GlobalMqttClient.connect(): connection created, waiting for abort signal...");
|
|
64
|
-
// 挂起,直到 AbortSignal 触发
|
|
65
|
-
await new Promise((resolve) => {
|
|
66
|
-
const handleAbort = () => {
|
|
67
|
-
console.log("[MQTT] GlobalMqttClient: abort signal received, disconnecting");
|
|
68
|
-
this.disconnect();
|
|
69
|
-
resolve();
|
|
70
|
-
};
|
|
71
|
-
this.abortSignal.addEventListener("abort", handleAbort, { once: true });
|
|
72
|
-
});
|
|
73
|
-
console.log("[MQTT] GlobalMqttClient.connect(): connection loop ended");
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* 手动断开全局连接
|
|
77
|
-
*/
|
|
78
|
-
disconnect() {
|
|
79
|
-
console.log("[MQTT] GlobalMqttClient.disconnect(): closing global connection");
|
|
80
|
-
ConnectionManager.closeGlobalConnection();
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* 向指定 topic 发布消息
|
|
84
|
-
* @returns 发布成功返回 true,未连接返回 false
|
|
85
|
-
*/
|
|
86
|
-
publish(topic, message) {
|
|
87
|
-
console.log(`[MQTT] GlobalMqttClient.publish(): topic=${topic}`);
|
|
88
|
-
return ConnectionManager.publishGlobalMessage(topic, message);
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* 检查全局连接是否处于已连接状态
|
|
92
|
-
*/
|
|
93
|
-
isConnected() {
|
|
94
|
-
return ConnectionManager.isGlobalConnected();
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* 获取当前解析的订阅 topic(通配符形式)
|
|
98
|
-
* 例:announcement/0b9a784d-e044-4c6f-9024-ef8799c131d1/#
|
|
99
|
-
*/
|
|
100
|
-
getSubscribeTopic() {
|
|
101
|
-
return ConnectionManager.getSubscribeTopic(this.mqttTopic);
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* 获取从 topic 中解析的用户名
|
|
105
|
-
* 例:announcement/myuser/agent/bot → "myuser"
|
|
106
|
-
*/
|
|
107
|
-
getUsername() {
|
|
108
|
-
return ConnectionManager.parseUsernameFromTopic(this.mqttTopic);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
1
|
+
// MQTT 全局客户端
|
|
2
|
+
// 封装全局 MQTT 连接逻辑(使用 MQTT.js 自带的重连功能)
|
|
3
|
+
// 全进程共享单一连接,随机生成 clientId,从 topic 解析用户名
|
|
4
|
+
import * as ConnectionManager from "./connection-manager.js";
|
|
5
|
+
// ─────────────────────────────────────────────
|
|
6
|
+
// GlobalMqttClient
|
|
7
|
+
// ─────────────────────────────────────────────
|
|
8
|
+
/**
|
|
9
|
+
* 全局共享 MQTT 客户端
|
|
10
|
+
*
|
|
11
|
+
* 全进程只维护一个 MQTT 连接:
|
|
12
|
+
* - clientId 每次启动随机生成
|
|
13
|
+
* - MQTT username 从 mqttTopic 中解析(announcement/{user_name}/...),默认 "default_name"
|
|
14
|
+
* - 订阅通配符 topic(announcement/{user_name}/#),不区分 agent_id
|
|
15
|
+
* - 多处调用 connect() 时复用同一连接
|
|
16
|
+
*/
|
|
17
|
+
export class GlobalMqttClient {
|
|
18
|
+
constructor(options) {
|
|
19
|
+
this.mqttUrl = options.mqttUrl;
|
|
20
|
+
this.mqttTopic = options.mqttTopic;
|
|
21
|
+
this.abortSignal = options.abortSignal;
|
|
22
|
+
this.onMessageCb = options.onMessage;
|
|
23
|
+
this.onConnectCb = options.onConnect;
|
|
24
|
+
this.onDisconnectCb = options.onDisconnect;
|
|
25
|
+
this.onErrorCb = options.onError;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 建立全局 MQTT 连接并等待 AbortSignal
|
|
29
|
+
*
|
|
30
|
+
* 若全局连接已存在且已连接则直接复用。
|
|
31
|
+
* 方法持续挂起直到 abortSignal 触发,触发后自动断开并 resolve。
|
|
32
|
+
*/
|
|
33
|
+
async connect() {
|
|
34
|
+
const username = ConnectionManager.parseUsernameFromTopic(this.mqttTopic);
|
|
35
|
+
const subscribeTopic = ConnectionManager.getSubscribeTopic(this.mqttTopic);
|
|
36
|
+
console.log(`[MQTT] GlobalMqttClient.connect(): url=${this.mqttUrl}, username=${username}, subscribeTopic=${subscribeTopic}`);
|
|
37
|
+
if (this.abortSignal.aborted) {
|
|
38
|
+
console.log("[MQTT] GlobalMqttClient.connect(): aborted before starting");
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
await ConnectionManager.createGlobalMqttConnection(this.mqttUrl, this.mqttTopic, {
|
|
42
|
+
onConnect: () => {
|
|
43
|
+
console.log("[MQTT] GlobalMqttClient: onConnect");
|
|
44
|
+
this.onConnectCb?.();
|
|
45
|
+
},
|
|
46
|
+
onDisconnect: () => {
|
|
47
|
+
console.log("[MQTT] GlobalMqttClient: onDisconnect");
|
|
48
|
+
this.onDisconnectCb?.();
|
|
49
|
+
},
|
|
50
|
+
onError: (err) => {
|
|
51
|
+
console.error(`[MQTT] GlobalMqttClient: onError - ${err.message}`);
|
|
52
|
+
this.onErrorCb?.(err);
|
|
53
|
+
},
|
|
54
|
+
onClose: () => {
|
|
55
|
+
console.log("[MQTT] GlobalMqttClient: onClose");
|
|
56
|
+
// MQTT.js 会自动重连;仅在 abort 触发时才真正清理全局连接
|
|
57
|
+
if (this.abortSignal.aborted) {
|
|
58
|
+
ConnectionManager.closeGlobalConnection();
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
onMessage: this.onMessageCb,
|
|
62
|
+
});
|
|
63
|
+
console.log("[MQTT] GlobalMqttClient.connect(): connection created, waiting for abort signal...");
|
|
64
|
+
// 挂起,直到 AbortSignal 触发
|
|
65
|
+
await new Promise((resolve) => {
|
|
66
|
+
const handleAbort = () => {
|
|
67
|
+
console.log("[MQTT] GlobalMqttClient: abort signal received, disconnecting");
|
|
68
|
+
this.disconnect();
|
|
69
|
+
resolve();
|
|
70
|
+
};
|
|
71
|
+
this.abortSignal.addEventListener("abort", handleAbort, { once: true });
|
|
72
|
+
});
|
|
73
|
+
console.log("[MQTT] GlobalMqttClient.connect(): connection loop ended");
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* 手动断开全局连接
|
|
77
|
+
*/
|
|
78
|
+
disconnect() {
|
|
79
|
+
console.log("[MQTT] GlobalMqttClient.disconnect(): closing global connection");
|
|
80
|
+
ConnectionManager.closeGlobalConnection();
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 向指定 topic 发布消息
|
|
84
|
+
* @returns 发布成功返回 true,未连接返回 false
|
|
85
|
+
*/
|
|
86
|
+
publish(topic, message) {
|
|
87
|
+
console.log(`[MQTT] GlobalMqttClient.publish(): topic=${topic}`);
|
|
88
|
+
return ConnectionManager.publishGlobalMessage(topic, message);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 检查全局连接是否处于已连接状态
|
|
92
|
+
*/
|
|
93
|
+
isConnected() {
|
|
94
|
+
return ConnectionManager.isGlobalConnected();
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 获取当前解析的订阅 topic(通配符形式)
|
|
98
|
+
* 例:announcement/0b9a784d-e044-4c6f-9024-ef8799c131d1/#
|
|
99
|
+
*/
|
|
100
|
+
getSubscribeTopic() {
|
|
101
|
+
return ConnectionManager.getSubscribeTopic(this.mqttTopic);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 获取从 topic 中解析的用户名
|
|
105
|
+
* 例:announcement/myuser/agent/bot → "myuser"
|
|
106
|
+
*/
|
|
107
|
+
getUsername() {
|
|
108
|
+
return ConnectionManager.parseUsernameFromTopic(this.mqttTopic);
|
|
109
|
+
}
|
|
110
|
+
}
|