openclaw-channel-dmwork 0.2.0 → 0.2.2
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/package.json +3 -16
- package/src/socket.ts +30 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-channel-dmwork",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "DMWork channel plugin for OpenClaw via WuKongIM WebSocket",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -28,20 +28,7 @@
|
|
|
28
28
|
"typescript": "^5.9.3"
|
|
29
29
|
},
|
|
30
30
|
"openclaw": {
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
],
|
|
34
|
-
"channel": {
|
|
35
|
-
"id": "dmwork",
|
|
36
|
-
"label": "DMWork",
|
|
37
|
-
"selectionLabel": "DMWork (WuKongIM)",
|
|
38
|
-
"docsLabel": "dmwork",
|
|
39
|
-
"blurb": "WuKongIM gateway for DMWork",
|
|
40
|
-
"order": 90
|
|
41
|
-
},
|
|
42
|
-
"install": {
|
|
43
|
-
"localPath": "extensions/dmwork",
|
|
44
|
-
"defaultChoice": "local"
|
|
45
|
-
}
|
|
31
|
+
"id": "dmwork",
|
|
32
|
+
"type": "channel"
|
|
46
33
|
}
|
|
47
34
|
}
|
package/src/socket.ts
CHANGED
|
@@ -12,14 +12,20 @@ interface WKSocketOptions {
|
|
|
12
12
|
onError?: (err: Error) => void;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Module-level singleton tracking — ensures only one set of SDK listeners
|
|
17
|
+
* exists at any time, even if startAccount is called multiple times
|
|
18
|
+
* (e.g. during auto-restart).
|
|
19
|
+
*/
|
|
20
|
+
let activeSocket: WKSocket | null = null;
|
|
21
|
+
|
|
15
22
|
/**
|
|
16
23
|
* WuKongIM WebSocket client for bot connections.
|
|
17
24
|
* Thin wrapper around wukongimjssdk — the SDK handles binary encoding,
|
|
18
25
|
* DH key exchange, encryption, heartbeat, reconnect, and RECVACK.
|
|
19
26
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* mismatch errors from concurrent WebSocket sessions.
|
|
27
|
+
* Only one WKSocket can be active at a time (WKSDK is a singleton).
|
|
28
|
+
* Creating a new connection automatically cleans up the previous one.
|
|
23
29
|
*/
|
|
24
30
|
export class WKSocket extends EventEmitter {
|
|
25
31
|
private statusListener: ((status: ConnectStatus, reasonCode?: number) => void) | null = null;
|
|
@@ -32,26 +38,37 @@ export class WKSocket extends EventEmitter {
|
|
|
32
38
|
|
|
33
39
|
/** Connect to WuKongIM WebSocket */
|
|
34
40
|
connect(): void {
|
|
41
|
+
// If another WKSocket was active, fully clean it up first
|
|
42
|
+
if (activeSocket && activeSocket !== this) {
|
|
43
|
+
activeSocket.disconnect();
|
|
44
|
+
}
|
|
45
|
+
activeSocket = this;
|
|
46
|
+
|
|
35
47
|
const im = WKSDK.shared();
|
|
36
48
|
|
|
37
|
-
// Ensure clean state — disconnect any prior session
|
|
49
|
+
// Ensure clean state — disconnect any prior SDK session
|
|
38
50
|
try { im.disconnect(); } catch { /* ignore */ }
|
|
39
51
|
|
|
40
52
|
im.config.addr = this.opts.wsUrl;
|
|
41
53
|
im.config.uid = this.opts.uid;
|
|
42
54
|
im.config.token = this.opts.token;
|
|
43
|
-
im.config.deviceFlag = 0;
|
|
55
|
+
im.config.deviceFlag = 0;
|
|
44
56
|
|
|
45
|
-
// Remove
|
|
57
|
+
// Remove own stale listeners (safety — should already be null)
|
|
46
58
|
if (this.statusListener) {
|
|
47
59
|
im.connectManager.removeConnectStatusListener(this.statusListener);
|
|
60
|
+
this.statusListener = null;
|
|
48
61
|
}
|
|
49
62
|
if (this.messageListener) {
|
|
50
63
|
im.chatManager.removeMessageListener(this.messageListener);
|
|
64
|
+
this.messageListener = null;
|
|
51
65
|
}
|
|
52
66
|
|
|
53
|
-
//
|
|
67
|
+
// Register exactly one status listener
|
|
54
68
|
this.statusListener = (status: ConnectStatus, reasonCode?: number) => {
|
|
69
|
+
// Ignore events if we're no longer the active socket
|
|
70
|
+
if (activeSocket !== this) return;
|
|
71
|
+
|
|
55
72
|
switch (status) {
|
|
56
73
|
case ConnectStatus.Connected:
|
|
57
74
|
this.connected = true;
|
|
@@ -78,8 +95,10 @@ export class WKSocket extends EventEmitter {
|
|
|
78
95
|
};
|
|
79
96
|
im.connectManager.addConnectStatusListener(this.statusListener);
|
|
80
97
|
|
|
81
|
-
//
|
|
98
|
+
// Register exactly one message listener
|
|
82
99
|
this.messageListener = (message: Message) => {
|
|
100
|
+
if (activeSocket !== this) return;
|
|
101
|
+
|
|
83
102
|
const content = message.content;
|
|
84
103
|
const payload: MessagePayload = {
|
|
85
104
|
type: content?.contentType ?? 0,
|
|
@@ -113,6 +132,9 @@ export class WKSocket extends EventEmitter {
|
|
|
113
132
|
disconnect(): void {
|
|
114
133
|
const im = WKSDK.shared();
|
|
115
134
|
this.connected = false;
|
|
135
|
+
if (activeSocket === this) {
|
|
136
|
+
activeSocket = null;
|
|
137
|
+
}
|
|
116
138
|
if (this.statusListener) {
|
|
117
139
|
im.connectManager.removeConnectStatusListener(this.statusListener);
|
|
118
140
|
this.statusListener = null;
|