opc-agent 2.0.0 → 2.0.1
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/dist/channels/email.d.ts +32 -26
- package/dist/channels/email.js +239 -62
- package/dist/channels/feishu.d.ts +21 -6
- package/dist/channels/feishu.js +225 -126
- package/dist/channels/websocket.d.ts +46 -3
- package/dist/channels/websocket.js +306 -37
- package/dist/channels/wechat.d.ts +33 -13
- package/dist/channels/wechat.js +229 -42
- package/dist/cli.js +712 -11
- package/dist/core/a2a.d.ts +17 -0
- package/dist/core/a2a.js +43 -1
- package/dist/core/agent.d.ts +16 -0
- package/dist/core/agent.js +108 -0
- package/dist/core/runtime.d.ts +6 -0
- package/dist/core/runtime.js +161 -2
- package/dist/core/sandbox.d.ts +26 -0
- package/dist/core/sandbox.js +117 -0
- package/dist/core/workflow-graph.d.ts +93 -0
- package/dist/core/workflow-graph.js +247 -0
- package/dist/doctor.d.ts +15 -0
- package/dist/doctor.js +183 -0
- package/dist/eval/index.d.ts +65 -0
- package/dist/eval/index.js +191 -0
- package/dist/index.d.ts +30 -6
- package/dist/index.js +60 -4
- package/dist/plugins/content-filter.d.ts +7 -0
- package/dist/plugins/content-filter.js +25 -0
- package/dist/plugins/index.d.ts +42 -0
- package/dist/plugins/index.js +108 -2
- package/dist/plugins/logger.d.ts +6 -0
- package/dist/plugins/logger.js +20 -0
- package/dist/plugins/rate-limiter.d.ts +7 -0
- package/dist/plugins/rate-limiter.js +35 -0
- package/dist/protocols/a2a/client.d.ts +25 -0
- package/dist/protocols/a2a/client.js +115 -0
- package/dist/protocols/a2a/index.d.ts +6 -0
- package/dist/protocols/a2a/index.js +12 -0
- package/dist/protocols/a2a/server.d.ts +41 -0
- package/dist/protocols/a2a/server.js +295 -0
- package/dist/protocols/a2a/types.d.ts +91 -0
- package/dist/protocols/a2a/types.js +15 -0
- package/dist/protocols/a2a/utils.d.ts +6 -0
- package/dist/protocols/a2a/utils.js +47 -0
- package/dist/protocols/agui/client.d.ts +10 -0
- package/dist/protocols/agui/client.js +75 -0
- package/dist/protocols/agui/index.d.ts +4 -0
- package/dist/protocols/agui/index.js +25 -0
- package/dist/protocols/agui/server.d.ts +37 -0
- package/dist/protocols/agui/server.js +191 -0
- package/dist/protocols/agui/types.d.ts +107 -0
- package/dist/protocols/agui/types.js +17 -0
- package/dist/protocols/index.d.ts +2 -0
- package/dist/protocols/index.js +19 -0
- package/dist/protocols/mcp/agent-tools.d.ts +11 -0
- package/dist/protocols/mcp/agent-tools.js +129 -0
- package/dist/protocols/mcp/index.d.ts +5 -0
- package/dist/protocols/mcp/index.js +11 -0
- package/dist/protocols/mcp/server.d.ts +31 -0
- package/dist/protocols/mcp/server.js +248 -0
- package/dist/protocols/mcp/types.d.ts +92 -0
- package/dist/protocols/mcp/types.js +17 -0
- package/dist/publish/index.d.ts +45 -0
- package/dist/publish/index.js +350 -0
- package/dist/schema/oad.d.ts +682 -65
- package/dist/schema/oad.js +36 -3
- package/dist/security/approval.d.ts +36 -0
- package/dist/security/approval.js +113 -0
- package/dist/security/index.d.ts +4 -0
- package/dist/security/index.js +8 -0
- package/dist/security/keys.d.ts +16 -0
- package/dist/security/keys.js +117 -0
- package/dist/studio/server.d.ts +63 -0
- package/dist/studio/server.js +625 -0
- package/dist/studio-ui/index.html +662 -0
- package/dist/telemetry/index.d.ts +93 -0
- package/dist/telemetry/index.js +285 -0
- package/package.json +5 -3
- package/scripts/install.ps1 +31 -0
- package/scripts/install.sh +40 -0
- package/src/channels/email.ts +351 -177
- package/src/channels/feishu.ts +349 -236
- package/src/channels/websocket.ts +399 -87
- package/src/channels/wechat.ts +329 -149
- package/src/cli.ts +783 -12
- package/src/core/a2a.ts +60 -0
- package/src/core/agent.ts +125 -0
- package/src/core/runtime.ts +127 -0
- package/src/core/sandbox.ts +143 -0
- package/src/core/workflow-graph.ts +365 -0
- package/src/doctor.ts +156 -0
- package/src/eval/index.ts +211 -0
- package/src/eval/suites/basic.json +16 -0
- package/src/eval/suites/memory.json +12 -0
- package/src/eval/suites/safety.json +14 -0
- package/src/index.ts +54 -6
- package/src/plugins/content-filter.ts +23 -0
- package/src/plugins/index.ts +133 -2
- package/src/plugins/logger.ts +18 -0
- package/src/plugins/rate-limiter.ts +38 -0
- package/src/protocols/a2a/client.ts +132 -0
- package/src/protocols/a2a/index.ts +8 -0
- package/src/protocols/a2a/server.ts +333 -0
- package/src/protocols/a2a/types.ts +88 -0
- package/src/protocols/a2a/utils.ts +50 -0
- package/src/protocols/agui/client.ts +83 -0
- package/src/protocols/agui/index.ts +4 -0
- package/src/protocols/agui/server.ts +218 -0
- package/src/protocols/agui/types.ts +153 -0
- package/src/protocols/index.ts +2 -0
- package/src/protocols/mcp/agent-tools.ts +134 -0
- package/src/protocols/mcp/index.ts +8 -0
- package/src/protocols/mcp/server.ts +262 -0
- package/src/protocols/mcp/types.ts +69 -0
- package/src/publish/index.ts +376 -0
- package/src/schema/oad.ts +39 -2
- package/src/security/approval.ts +131 -0
- package/src/security/index.ts +3 -0
- package/src/security/keys.ts +87 -0
- package/src/studio/server.ts +629 -0
- package/src/studio-ui/index.html +662 -0
- package/src/telemetry/index.ts +324 -0
- package/src/types/agent-workstation.d.ts +2 -0
- package/tests/a2a-protocol.test.ts +285 -0
- package/tests/agui-protocol.test.ts +246 -0
- package/tests/channels/discord.test.ts +79 -0
- package/tests/channels/email.test.ts +148 -0
- package/tests/channels/feishu.test.ts +123 -0
- package/tests/channels/telegram.test.ts +129 -0
- package/tests/channels/websocket.test.ts +53 -0
- package/tests/channels/wechat.test.ts +170 -0
- package/tests/chat-cli.test.ts +160 -0
- package/tests/daemon.test.ts +135 -0
- package/tests/deepbrain-wire.test.ts +234 -0
- package/tests/doctor.test.ts +38 -0
- package/tests/eval.test.ts +173 -0
- package/tests/init-role.test.ts +124 -0
- package/tests/mcp-client.test.ts +92 -0
- package/tests/mcp-server.test.ts +178 -0
- package/tests/plugin-a2a-enhanced.test.ts +230 -0
- package/tests/publish.test.ts +231 -0
- package/tests/scheduler.test.ts +200 -0
- package/tests/security-enhanced.test.ts +233 -0
- package/tests/skill-learner.test.ts +161 -0
- package/tests/studio.test.ts +229 -0
- package/tests/subagent.test.ts +63 -0
- package/tests/telemetry.test.ts +186 -0
- package/tests/tools/builtin-extended.test.ts +138 -0
- package/tests/workflow-graph.test.ts +279 -0
- package/tutorial/customer-service-agent/README.md +612 -0
- package/tutorial/customer-service-agent/SOUL.md +26 -0
- package/tutorial/customer-service-agent/agent.yaml +63 -0
- package/tutorial/customer-service-agent/package.json +19 -0
- package/tutorial/customer-service-agent/src/index.ts +69 -0
- package/tutorial/customer-service-agent/src/skills/faq.ts +27 -0
- package/tutorial/customer-service-agent/src/skills/ticket.ts +22 -0
- package/tutorial/customer-service-agent/tsconfig.json +14 -0
|
@@ -3,79 +3,348 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.WebSocketChannel = void 0;
|
|
4
4
|
const index_1 = require("./index");
|
|
5
5
|
const ws_1 = require("ws");
|
|
6
|
-
/**
|
|
7
|
-
* WebSocket channel — real-time bidirectional communication.
|
|
8
|
-
*/
|
|
9
6
|
class WebSocketChannel extends index_1.BaseChannel {
|
|
10
7
|
type = 'websocket';
|
|
11
8
|
wss = null;
|
|
12
|
-
|
|
13
|
-
clients = new
|
|
14
|
-
|
|
9
|
+
config;
|
|
10
|
+
clients = new Map(); // sessionId -> ClientInfo
|
|
11
|
+
rooms = new Map(); // roomId -> Set<sessionId>
|
|
12
|
+
heartbeatTimer = null;
|
|
13
|
+
constructor(configOrPort = 3002) {
|
|
15
14
|
super();
|
|
16
|
-
|
|
15
|
+
if (typeof configOrPort === 'number') {
|
|
16
|
+
this.config = { port: configOrPort, heartbeatInterval: 30000, authTokens: [], maxClientsPerRoom: 100 };
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
this.config = {
|
|
20
|
+
port: configOrPort.port ?? 3002,
|
|
21
|
+
heartbeatInterval: configOrPort.heartbeatInterval ?? 30000,
|
|
22
|
+
authTokens: configOrPort.authTokens ?? [],
|
|
23
|
+
maxClientsPerRoom: configOrPort.maxClientsPerRoom ?? 100,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
17
26
|
}
|
|
18
27
|
async start() {
|
|
19
28
|
return new Promise((resolve) => {
|
|
20
|
-
this.wss = new ws_1.WebSocketServer({ port: this.port });
|
|
21
|
-
this.wss.on('connection', (ws) => {
|
|
22
|
-
this.
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
this.wss = new ws_1.WebSocketServer({ port: this.config.port });
|
|
30
|
+
this.wss.on('connection', (ws, req) => {
|
|
31
|
+
const url = new URL(req.url ?? '/', `http://localhost:${this.config.port}`);
|
|
32
|
+
const token = url.searchParams.get('token');
|
|
33
|
+
const sessionId = url.searchParams.get('sessionId') ?? `ws_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
34
|
+
// Authentication check
|
|
35
|
+
if (this.config.authTokens.length > 0) {
|
|
36
|
+
if (!token || !this.config.authTokens.includes(token)) {
|
|
37
|
+
ws.close(4001, 'Unauthorized');
|
|
25
38
|
return;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Handle reconnection: if sessionId already exists, replace the connection
|
|
42
|
+
const existing = this.clients.get(sessionId);
|
|
43
|
+
if (existing) {
|
|
44
|
+
try {
|
|
45
|
+
existing.ws.close(4000, 'Replaced by new connection');
|
|
46
|
+
}
|
|
47
|
+
catch { }
|
|
48
|
+
}
|
|
49
|
+
const clientInfo = {
|
|
50
|
+
ws,
|
|
51
|
+
sessionId,
|
|
52
|
+
rooms: existing?.rooms ?? new Set(),
|
|
53
|
+
isAlive: true,
|
|
54
|
+
authenticated: true,
|
|
55
|
+
connectedAt: Date.now(),
|
|
56
|
+
lastMessageAt: Date.now(),
|
|
57
|
+
};
|
|
58
|
+
this.clients.set(sessionId, clientInfo);
|
|
59
|
+
// Re-register in rooms after reconnect
|
|
60
|
+
for (const roomId of clientInfo.rooms) {
|
|
61
|
+
const room = this.rooms.get(roomId);
|
|
62
|
+
if (room)
|
|
63
|
+
room.add(sessionId);
|
|
64
|
+
}
|
|
65
|
+
ws.on('pong', () => {
|
|
66
|
+
clientInfo.isAlive = true;
|
|
67
|
+
});
|
|
68
|
+
ws.on('message', async (data, isBinary) => {
|
|
69
|
+
clientInfo.lastMessageAt = Date.now();
|
|
70
|
+
clientInfo.isAlive = true;
|
|
71
|
+
if (isBinary) {
|
|
72
|
+
await this.handleBinaryMessage(clientInfo, data);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
26
75
|
try {
|
|
27
76
|
const parsed = JSON.parse(data.toString());
|
|
28
|
-
|
|
29
|
-
id: parsed.id ?? `ws_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
30
|
-
role: 'user',
|
|
31
|
-
content: parsed.content ?? parsed.message ?? data.toString(),
|
|
32
|
-
timestamp: Date.now(),
|
|
33
|
-
metadata: {
|
|
34
|
-
sessionId: parsed.sessionId ?? 'ws-default',
|
|
35
|
-
platform: 'websocket',
|
|
36
|
-
},
|
|
37
|
-
};
|
|
38
|
-
const response = await this.handler(msg);
|
|
39
|
-
ws.send(JSON.stringify({
|
|
40
|
-
id: response.id,
|
|
41
|
-
content: response.content,
|
|
42
|
-
timestamp: response.timestamp,
|
|
43
|
-
}));
|
|
77
|
+
await this.handleTextMessage(clientInfo, parsed);
|
|
44
78
|
}
|
|
45
|
-
catch
|
|
79
|
+
catch {
|
|
46
80
|
ws.send(JSON.stringify({ error: 'Invalid message format' }));
|
|
47
81
|
}
|
|
48
82
|
});
|
|
49
83
|
ws.on('close', () => {
|
|
50
|
-
|
|
84
|
+
// Don't immediately remove - allow reconnection window
|
|
85
|
+
const info = this.clients.get(sessionId);
|
|
86
|
+
if (info && info.ws === ws) {
|
|
87
|
+
// Mark as disconnected but keep for potential reconnection
|
|
88
|
+
setTimeout(() => {
|
|
89
|
+
const current = this.clients.get(sessionId);
|
|
90
|
+
if (current && current.ws === ws) {
|
|
91
|
+
this.removeClient(sessionId);
|
|
92
|
+
}
|
|
93
|
+
}, 60000); // 60s reconnection window
|
|
94
|
+
}
|
|
51
95
|
});
|
|
52
|
-
ws.send(JSON.stringify({
|
|
96
|
+
ws.send(JSON.stringify({
|
|
97
|
+
type: 'connected',
|
|
98
|
+
sessionId,
|
|
99
|
+
timestamp: Date.now(),
|
|
100
|
+
}));
|
|
53
101
|
});
|
|
102
|
+
// Start heartbeat
|
|
103
|
+
this.heartbeatTimer = setInterval(() => {
|
|
104
|
+
for (const [sessionId, info] of this.clients) {
|
|
105
|
+
if (!info.isAlive) {
|
|
106
|
+
try {
|
|
107
|
+
info.ws.terminate();
|
|
108
|
+
}
|
|
109
|
+
catch { }
|
|
110
|
+
this.removeClient(sessionId);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
info.isAlive = false;
|
|
114
|
+
try {
|
|
115
|
+
info.ws.ping();
|
|
116
|
+
}
|
|
117
|
+
catch { }
|
|
118
|
+
}
|
|
119
|
+
}, this.config.heartbeatInterval);
|
|
54
120
|
this.wss.on('listening', () => {
|
|
55
|
-
console.log(`[WebSocketChannel] Listening on port ${this.port}`);
|
|
121
|
+
console.log(`[WebSocketChannel] Listening on port ${this.config.port}`);
|
|
56
122
|
resolve();
|
|
57
123
|
});
|
|
58
124
|
});
|
|
59
125
|
}
|
|
60
126
|
async stop() {
|
|
61
|
-
|
|
62
|
-
|
|
127
|
+
if (this.heartbeatTimer) {
|
|
128
|
+
clearInterval(this.heartbeatTimer);
|
|
129
|
+
this.heartbeatTimer = null;
|
|
130
|
+
}
|
|
131
|
+
for (const [, info] of this.clients) {
|
|
132
|
+
try {
|
|
133
|
+
info.ws.close();
|
|
134
|
+
}
|
|
135
|
+
catch { }
|
|
63
136
|
}
|
|
64
137
|
this.clients.clear();
|
|
138
|
+
this.rooms.clear();
|
|
65
139
|
return new Promise((resolve, reject) => {
|
|
66
140
|
if (!this.wss)
|
|
67
141
|
return resolve();
|
|
68
142
|
this.wss.close((err) => (err ? reject(err) : resolve()));
|
|
69
143
|
});
|
|
70
144
|
}
|
|
145
|
+
/** Handle text (JSON) messages */
|
|
146
|
+
async handleTextMessage(client, parsed) {
|
|
147
|
+
const type = parsed.type ?? 'message';
|
|
148
|
+
switch (type) {
|
|
149
|
+
case 'join': {
|
|
150
|
+
const roomId = parsed.room;
|
|
151
|
+
if (!roomId) {
|
|
152
|
+
client.ws.send(JSON.stringify({ error: 'room is required for join' }));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
this.joinRoom(client.sessionId, roomId);
|
|
156
|
+
client.ws.send(JSON.stringify({ type: 'joined', room: roomId, members: this.getRoomMembers(roomId).length }));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
case 'leave': {
|
|
160
|
+
const roomId = parsed.room;
|
|
161
|
+
if (roomId) {
|
|
162
|
+
this.leaveRoom(client.sessionId, roomId);
|
|
163
|
+
client.ws.send(JSON.stringify({ type: 'left', room: roomId }));
|
|
164
|
+
}
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
case 'room_message': {
|
|
168
|
+
const roomId = parsed.room;
|
|
169
|
+
const content = parsed.content ?? parsed.message;
|
|
170
|
+
if (roomId && content) {
|
|
171
|
+
this.broadcastToRoom(roomId, {
|
|
172
|
+
type: 'room_message',
|
|
173
|
+
room: roomId,
|
|
174
|
+
from: client.sessionId,
|
|
175
|
+
content,
|
|
176
|
+
timestamp: Date.now(),
|
|
177
|
+
}, client.sessionId);
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
case 'ping': {
|
|
182
|
+
client.ws.send(JSON.stringify({ type: 'pong', timestamp: Date.now() }));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
default: {
|
|
186
|
+
// Regular chat message
|
|
187
|
+
if (!this.handler)
|
|
188
|
+
return;
|
|
189
|
+
const msg = {
|
|
190
|
+
id: parsed.id ?? `ws_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
191
|
+
role: 'user',
|
|
192
|
+
content: parsed.content ?? parsed.message ?? JSON.stringify(parsed),
|
|
193
|
+
timestamp: Date.now(),
|
|
194
|
+
metadata: {
|
|
195
|
+
sessionId: client.sessionId,
|
|
196
|
+
platform: 'websocket',
|
|
197
|
+
room: parsed.room,
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
const response = await this.handler(msg);
|
|
201
|
+
client.ws.send(JSON.stringify({
|
|
202
|
+
id: response.id,
|
|
203
|
+
content: response.content,
|
|
204
|
+
timestamp: response.timestamp,
|
|
205
|
+
}));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/** Handle binary messages */
|
|
210
|
+
async handleBinaryMessage(client, data) {
|
|
211
|
+
// Emit binary data with metadata
|
|
212
|
+
client.ws.send(JSON.stringify({
|
|
213
|
+
type: 'binary_ack',
|
|
214
|
+
size: data.length,
|
|
215
|
+
timestamp: Date.now(),
|
|
216
|
+
}));
|
|
217
|
+
// If handler exists, pass as base64
|
|
218
|
+
if (this.handler) {
|
|
219
|
+
const msg = {
|
|
220
|
+
id: `ws_bin_${Date.now()}`,
|
|
221
|
+
role: 'user',
|
|
222
|
+
content: `[binary:${data.length} bytes]`,
|
|
223
|
+
timestamp: Date.now(),
|
|
224
|
+
metadata: {
|
|
225
|
+
sessionId: client.sessionId,
|
|
226
|
+
platform: 'websocket',
|
|
227
|
+
binary: true,
|
|
228
|
+
binaryData: data.toString('base64'),
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
const response = await this.handler(msg);
|
|
232
|
+
client.ws.send(JSON.stringify({
|
|
233
|
+
id: response.id,
|
|
234
|
+
content: response.content,
|
|
235
|
+
timestamp: response.timestamp,
|
|
236
|
+
}));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/** Join a room */
|
|
240
|
+
joinRoom(sessionId, roomId) {
|
|
241
|
+
const client = this.clients.get(sessionId);
|
|
242
|
+
if (!client)
|
|
243
|
+
return false;
|
|
244
|
+
if (!this.rooms.has(roomId)) {
|
|
245
|
+
this.rooms.set(roomId, new Set());
|
|
246
|
+
}
|
|
247
|
+
const room = this.rooms.get(roomId);
|
|
248
|
+
if (room.size >= this.config.maxClientsPerRoom) {
|
|
249
|
+
client.ws.send(JSON.stringify({ error: 'Room is full', room: roomId }));
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
room.add(sessionId);
|
|
253
|
+
client.rooms.add(roomId);
|
|
254
|
+
// Notify other room members
|
|
255
|
+
this.broadcastToRoom(roomId, {
|
|
256
|
+
type: 'member_joined',
|
|
257
|
+
room: roomId,
|
|
258
|
+
sessionId,
|
|
259
|
+
members: room.size,
|
|
260
|
+
timestamp: Date.now(),
|
|
261
|
+
}, sessionId);
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
/** Leave a room */
|
|
265
|
+
leaveRoom(sessionId, roomId) {
|
|
266
|
+
const client = this.clients.get(sessionId);
|
|
267
|
+
if (client)
|
|
268
|
+
client.rooms.delete(roomId);
|
|
269
|
+
const room = this.rooms.get(roomId);
|
|
270
|
+
if (room) {
|
|
271
|
+
room.delete(sessionId);
|
|
272
|
+
if (room.size === 0) {
|
|
273
|
+
this.rooms.delete(roomId);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
this.broadcastToRoom(roomId, {
|
|
277
|
+
type: 'member_left',
|
|
278
|
+
room: roomId,
|
|
279
|
+
sessionId,
|
|
280
|
+
members: room.size,
|
|
281
|
+
timestamp: Date.now(),
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/** Remove client completely */
|
|
287
|
+
removeClient(sessionId) {
|
|
288
|
+
const client = this.clients.get(sessionId);
|
|
289
|
+
if (!client)
|
|
290
|
+
return;
|
|
291
|
+
for (const roomId of client.rooms) {
|
|
292
|
+
this.leaveRoom(sessionId, roomId);
|
|
293
|
+
}
|
|
294
|
+
this.clients.delete(sessionId);
|
|
295
|
+
}
|
|
296
|
+
/** Get room member session IDs */
|
|
297
|
+
getRoomMembers(roomId) {
|
|
298
|
+
return [...(this.rooms.get(roomId) ?? [])];
|
|
299
|
+
}
|
|
300
|
+
/** Get all rooms */
|
|
301
|
+
getRooms() {
|
|
302
|
+
return [...this.rooms.keys()];
|
|
303
|
+
}
|
|
304
|
+
/** Broadcast to all clients */
|
|
71
305
|
broadcast(content) {
|
|
72
306
|
const msg = JSON.stringify({ type: 'broadcast', content, timestamp: Date.now() });
|
|
73
|
-
for (const
|
|
74
|
-
if (
|
|
75
|
-
|
|
307
|
+
for (const [, info] of this.clients) {
|
|
308
|
+
if (info.ws.readyState === 1) {
|
|
309
|
+
info.ws.send(msg);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
/** Broadcast to all clients in a room */
|
|
314
|
+
broadcastToRoom(roomId, data, excludeSessionId) {
|
|
315
|
+
const room = this.rooms.get(roomId);
|
|
316
|
+
if (!room)
|
|
317
|
+
return;
|
|
318
|
+
const msg = typeof data === 'string' ? data : JSON.stringify(data);
|
|
319
|
+
for (const sessionId of room) {
|
|
320
|
+
if (sessionId === excludeSessionId)
|
|
321
|
+
continue;
|
|
322
|
+
const client = this.clients.get(sessionId);
|
|
323
|
+
if (client && client.ws.readyState === 1) {
|
|
324
|
+
client.ws.send(msg);
|
|
76
325
|
}
|
|
77
326
|
}
|
|
78
327
|
}
|
|
328
|
+
/** Send to specific session */
|
|
329
|
+
sendToSession(sessionId, data) {
|
|
330
|
+
const client = this.clients.get(sessionId);
|
|
331
|
+
if (!client || client.ws.readyState !== 1)
|
|
332
|
+
return false;
|
|
333
|
+
client.ws.send(typeof data === 'string' ? data : JSON.stringify(data));
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
/** Get connection stats */
|
|
337
|
+
getStats() {
|
|
338
|
+
const roomDetails = {};
|
|
339
|
+
for (const [roomId, members] of this.rooms) {
|
|
340
|
+
roomDetails[roomId] = members.size;
|
|
341
|
+
}
|
|
342
|
+
return {
|
|
343
|
+
clients: this.clients.size,
|
|
344
|
+
rooms: this.rooms.size,
|
|
345
|
+
roomDetails,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
79
348
|
}
|
|
80
349
|
exports.WebSocketChannel = WebSocketChannel;
|
|
81
350
|
//# sourceMappingURL=websocket.js.map
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { BaseChannel } from './index';
|
|
2
2
|
/**
|
|
3
|
-
* WeChat Channel
|
|
4
|
-
*
|
|
3
|
+
* WeChat Official Account Channel — v1.0.0
|
|
4
|
+
*
|
|
5
|
+
* Handles:
|
|
6
|
+
* - GET verification (signature/timestamp/nonce/echostr)
|
|
7
|
+
* - POST XML message parsing and reply
|
|
8
|
+
* - Customer Service Message API for async replies
|
|
9
|
+
* - Access token management with caching
|
|
10
|
+
* - Template messages
|
|
11
|
+
* - Subscribe/unsubscribe events
|
|
5
12
|
*/
|
|
6
13
|
export interface WeChatChannelConfig {
|
|
7
14
|
/** WeChat Official Account AppID */
|
|
@@ -10,9 +17,9 @@ export interface WeChatChannelConfig {
|
|
|
10
17
|
appSecret: string;
|
|
11
18
|
/** Verification token for message validation */
|
|
12
19
|
token: string;
|
|
13
|
-
/** AES encoding key for encrypted messages */
|
|
20
|
+
/** AES encoding key for encrypted messages (optional) */
|
|
14
21
|
encodingAESKey?: string;
|
|
15
|
-
/** HTTP server port (default:
|
|
22
|
+
/** HTTP server port (default: 8080) */
|
|
16
23
|
port?: number;
|
|
17
24
|
}
|
|
18
25
|
export interface WeChatMessage {
|
|
@@ -39,24 +46,37 @@ export declare class WeChatChannel extends BaseChannel {
|
|
|
39
46
|
private config;
|
|
40
47
|
private accessToken;
|
|
41
48
|
private tokenExpiry;
|
|
49
|
+
private server;
|
|
42
50
|
constructor(config: WeChatChannelConfig);
|
|
43
51
|
start(): Promise<void>;
|
|
44
52
|
stop(): Promise<void>;
|
|
45
|
-
/**
|
|
46
|
-
|
|
53
|
+
/** Verify WeChat signature for GET requests */
|
|
54
|
+
private handleVerification;
|
|
55
|
+
/** Handle incoming POST messages (XML) */
|
|
56
|
+
private handleIncoming;
|
|
57
|
+
/** Verify WeChat signature */
|
|
58
|
+
verifySignature(signature: string, timestamp: string, nonce: string): boolean;
|
|
59
|
+
/** Parse WeChat XML message using regex */
|
|
60
|
+
static parseXML(xml: string): WeChatMessage | null;
|
|
61
|
+
/** Format response as WeChat XML */
|
|
62
|
+
static formatXMLResponse(toUser: string, fromUser: string, content: string): string;
|
|
47
63
|
/** Handle incoming WeChat message */
|
|
48
64
|
handleMessage(wxMsg: WeChatMessage): Promise<string>;
|
|
49
|
-
/** Handle WeChat events
|
|
65
|
+
/** Handle WeChat events */
|
|
50
66
|
private handleEvent;
|
|
51
67
|
/** Convert WeChat message to internal Message */
|
|
52
68
|
private wechatToMessage;
|
|
69
|
+
/** Get or refresh access token */
|
|
70
|
+
getAccessToken(): Promise<string>;
|
|
71
|
+
/** Send customer service message */
|
|
72
|
+
sendMessage(openId: string, text: string): Promise<void>;
|
|
53
73
|
/** Send template message */
|
|
54
74
|
sendTemplateMessage(data: TemplateMessageData): Promise<boolean>;
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
75
|
+
/** Read request body */
|
|
76
|
+
private readBody;
|
|
77
|
+
/** Simple HTTPS GET */
|
|
78
|
+
private httpsGet;
|
|
79
|
+
/** Simple HTTPS POST */
|
|
80
|
+
private httpsPost;
|
|
61
81
|
}
|
|
62
82
|
//# sourceMappingURL=wechat.d.ts.map
|