claude-code-templates 1.8.0 → 1.8.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/README.md +246 -0
- package/package.json +26 -12
- package/src/analytics/core/ConversationAnalyzer.js +754 -0
- package/src/analytics/core/FileWatcher.js +285 -0
- package/src/analytics/core/ProcessDetector.js +242 -0
- package/src/analytics/core/SessionAnalyzer.js +597 -0
- package/src/analytics/core/StateCalculator.js +190 -0
- package/src/analytics/data/DataCache.js +550 -0
- package/src/analytics/notifications/NotificationManager.js +448 -0
- package/src/analytics/notifications/WebSocketServer.js +526 -0
- package/src/analytics/utils/PerformanceMonitor.js +455 -0
- package/src/analytics-web/assets/js/main.js +312 -0
- package/src/analytics-web/components/Charts.js +114 -0
- package/src/analytics-web/components/ConversationTable.js +437 -0
- package/src/analytics-web/components/Dashboard.js +573 -0
- package/src/analytics-web/components/SessionTimer.js +596 -0
- package/src/analytics-web/index.html +882 -49
- package/src/analytics-web/index.html.original +1939 -0
- package/src/analytics-web/services/DataService.js +357 -0
- package/src/analytics-web/services/StateService.js +276 -0
- package/src/analytics-web/services/WebSocketService.js +523 -0
- package/src/analytics.js +626 -2317
- package/src/analytics.log +0 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocketServer - Handles real-time communication between server and clients
|
|
3
|
+
* Part of the modular backend architecture for Phase 3
|
|
4
|
+
*/
|
|
5
|
+
const WebSocket = require('ws');
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
|
|
8
|
+
class WebSocketServer {
|
|
9
|
+
constructor(httpServer, options = {}, performanceMonitor = null) {
|
|
10
|
+
this.httpServer = httpServer;
|
|
11
|
+
this.performanceMonitor = performanceMonitor;
|
|
12
|
+
this.options = {
|
|
13
|
+
port: options.port || 3334,
|
|
14
|
+
path: options.path || '/ws',
|
|
15
|
+
heartbeatInterval: options.heartbeatInterval || 30000,
|
|
16
|
+
...options
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
this.wss = null;
|
|
20
|
+
this.clients = new Map();
|
|
21
|
+
this.heartbeatInterval = null;
|
|
22
|
+
this.isRunning = false;
|
|
23
|
+
this.messageQueue = [];
|
|
24
|
+
this.maxQueueSize = 100;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Initialize and start the WebSocket server
|
|
29
|
+
*/
|
|
30
|
+
async initialize() {
|
|
31
|
+
try {
|
|
32
|
+
console.log(chalk.blue('🔌 Initializing WebSocket server...'));
|
|
33
|
+
|
|
34
|
+
// Create WebSocket server
|
|
35
|
+
this.wss = new WebSocket.Server({
|
|
36
|
+
server: this.httpServer,
|
|
37
|
+
path: this.options.path,
|
|
38
|
+
clientTracking: true
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
this.setupEventHandlers();
|
|
42
|
+
this.startHeartbeat();
|
|
43
|
+
this.isRunning = true;
|
|
44
|
+
|
|
45
|
+
console.log(chalk.green(`✅ WebSocket server initialized on ${this.options.path}`));
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error(chalk.red('❌ Failed to initialize WebSocket server:'), error);
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Setup WebSocket event handlers
|
|
54
|
+
*/
|
|
55
|
+
setupEventHandlers() {
|
|
56
|
+
this.wss.on('connection', (ws, request) => {
|
|
57
|
+
this.handleConnection(ws, request);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
this.wss.on('error', (error) => {
|
|
61
|
+
console.error(chalk.red('WebSocket server error:'), error);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
this.wss.on('close', () => {
|
|
65
|
+
console.log(chalk.yellow('🔌 WebSocket server closed'));
|
|
66
|
+
this.isRunning = false;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Handle new WebSocket connection
|
|
72
|
+
* @param {WebSocket} ws - WebSocket connection
|
|
73
|
+
* @param {Object} request - HTTP request object
|
|
74
|
+
*/
|
|
75
|
+
handleConnection(ws, request) {
|
|
76
|
+
const clientId = this.generateClientId();
|
|
77
|
+
const clientInfo = {
|
|
78
|
+
id: clientId,
|
|
79
|
+
ws: ws,
|
|
80
|
+
ip: request.socket.remoteAddress,
|
|
81
|
+
userAgent: request.headers['user-agent'],
|
|
82
|
+
connectedAt: new Date(),
|
|
83
|
+
isAlive: true,
|
|
84
|
+
subscriptions: new Set()
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
this.clients.set(clientId, clientInfo);
|
|
88
|
+
console.log(chalk.green(`🔗 WebSocket client connected: ${clientId} (${this.clients.size} total)`));
|
|
89
|
+
|
|
90
|
+
// Track WebSocket connection in performance monitor
|
|
91
|
+
if (this.performanceMonitor) {
|
|
92
|
+
this.performanceMonitor.recordWebSocket('connection', {
|
|
93
|
+
clientId,
|
|
94
|
+
totalClients: this.clients.size,
|
|
95
|
+
ip: request.socket.remoteAddress
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Send welcome message
|
|
100
|
+
this.sendToClient(clientId, {
|
|
101
|
+
type: 'connection',
|
|
102
|
+
data: {
|
|
103
|
+
clientId: clientId,
|
|
104
|
+
serverTime: new Date().toISOString(),
|
|
105
|
+
message: 'Connected to Claude Code Analytics WebSocket'
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Send any queued messages
|
|
110
|
+
this.sendQueuedMessages(clientId);
|
|
111
|
+
|
|
112
|
+
// Setup client event handlers
|
|
113
|
+
ws.on('message', (message) => {
|
|
114
|
+
this.handleClientMessage(clientId, message);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
ws.on('close', (code, reason) => {
|
|
118
|
+
this.handleClientDisconnect(clientId, code, reason);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
ws.on('error', (error) => {
|
|
122
|
+
console.error(chalk.red(`WebSocket client error (${clientId}):`), error);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
ws.on('pong', () => {
|
|
126
|
+
this.handleClientPong(clientId);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Handle message from client
|
|
132
|
+
* @param {string} clientId - Client ID
|
|
133
|
+
* @param {Buffer} message - Message buffer
|
|
134
|
+
*/
|
|
135
|
+
handleClientMessage(clientId, message) {
|
|
136
|
+
try {
|
|
137
|
+
const data = JSON.parse(message.toString());
|
|
138
|
+
const client = this.clients.get(clientId);
|
|
139
|
+
|
|
140
|
+
if (!client) return;
|
|
141
|
+
|
|
142
|
+
console.log(chalk.cyan(`📨 Message from ${clientId}:`), data.type);
|
|
143
|
+
|
|
144
|
+
// Track message in performance monitor
|
|
145
|
+
if (this.performanceMonitor) {
|
|
146
|
+
this.performanceMonitor.recordWebSocket('message_received', {
|
|
147
|
+
clientId,
|
|
148
|
+
messageType: data.type,
|
|
149
|
+
messageSize: message.length
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
switch (data.type) {
|
|
154
|
+
case 'subscribe':
|
|
155
|
+
this.handleSubscription(clientId, data.channel);
|
|
156
|
+
break;
|
|
157
|
+
case 'unsubscribe':
|
|
158
|
+
this.handleUnsubscription(clientId, data.channel);
|
|
159
|
+
break;
|
|
160
|
+
case 'ping':
|
|
161
|
+
this.sendToClient(clientId, { type: 'pong', timestamp: Date.now() });
|
|
162
|
+
break;
|
|
163
|
+
case 'refresh_request':
|
|
164
|
+
this.handleRefreshRequest(clientId);
|
|
165
|
+
break;
|
|
166
|
+
default:
|
|
167
|
+
console.warn(chalk.yellow(`Unknown message type from ${clientId}: ${data.type}`));
|
|
168
|
+
}
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error(chalk.red(`Error parsing message from ${clientId}:`), error);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Handle client subscription to a channel
|
|
176
|
+
* @param {string} clientId - Client ID
|
|
177
|
+
* @param {string} channel - Channel name
|
|
178
|
+
*/
|
|
179
|
+
handleSubscription(clientId, channel) {
|
|
180
|
+
const client = this.clients.get(clientId);
|
|
181
|
+
if (!client) return;
|
|
182
|
+
|
|
183
|
+
client.subscriptions.add(channel);
|
|
184
|
+
console.log(chalk.green(`📡 Client ${clientId} subscribed to ${channel}`));
|
|
185
|
+
|
|
186
|
+
this.sendToClient(clientId, {
|
|
187
|
+
type: 'subscription_confirmed',
|
|
188
|
+
data: { channel, subscriptions: Array.from(client.subscriptions) }
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Handle client unsubscription from a channel
|
|
194
|
+
* @param {string} clientId - Client ID
|
|
195
|
+
* @param {string} channel - Channel name
|
|
196
|
+
*/
|
|
197
|
+
handleUnsubscription(clientId, channel) {
|
|
198
|
+
const client = this.clients.get(clientId);
|
|
199
|
+
if (!client) return;
|
|
200
|
+
|
|
201
|
+
client.subscriptions.delete(channel);
|
|
202
|
+
console.log(chalk.yellow(`📡 Client ${clientId} unsubscribed from ${channel}`));
|
|
203
|
+
|
|
204
|
+
this.sendToClient(clientId, {
|
|
205
|
+
type: 'unsubscription_confirmed',
|
|
206
|
+
data: { channel, subscriptions: Array.from(client.subscriptions) }
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Handle refresh request from client
|
|
212
|
+
* @param {string} clientId - Client ID
|
|
213
|
+
*/
|
|
214
|
+
handleRefreshRequest(clientId) {
|
|
215
|
+
console.log(chalk.blue(`🔄 Refresh requested by ${clientId}`));
|
|
216
|
+
// Emit refresh event that the main analytics server can listen to
|
|
217
|
+
this.emit('refresh_requested', { clientId });
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Handle client disconnection
|
|
222
|
+
* @param {string} clientId - Client ID
|
|
223
|
+
* @param {number} code - Close code
|
|
224
|
+
* @param {Buffer} reason - Close reason
|
|
225
|
+
*/
|
|
226
|
+
handleClientDisconnect(clientId, code, reason) {
|
|
227
|
+
this.clients.delete(clientId);
|
|
228
|
+
console.log(chalk.yellow(`🔗 WebSocket client disconnected: ${clientId} (${this.clients.size} remaining)`));
|
|
229
|
+
console.log(chalk.gray(` Close code: ${code}, Reason: ${reason || 'No reason provided'}`));
|
|
230
|
+
|
|
231
|
+
// Track disconnection in performance monitor
|
|
232
|
+
if (this.performanceMonitor) {
|
|
233
|
+
this.performanceMonitor.recordWebSocket('disconnection', {
|
|
234
|
+
clientId,
|
|
235
|
+
closeCode: code,
|
|
236
|
+
totalClients: this.clients.size,
|
|
237
|
+
reason: reason?.toString() || 'No reason provided'
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Handle client pong response
|
|
244
|
+
* @param {string} clientId - Client ID
|
|
245
|
+
*/
|
|
246
|
+
handleClientPong(clientId) {
|
|
247
|
+
const client = this.clients.get(clientId);
|
|
248
|
+
if (client) {
|
|
249
|
+
client.isAlive = true;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Broadcast message to all connected clients
|
|
255
|
+
* @param {Object} message - Message to broadcast
|
|
256
|
+
* @param {string} channel - Optional channel filter
|
|
257
|
+
*/
|
|
258
|
+
broadcast(message, channel = null) {
|
|
259
|
+
const messageStr = JSON.stringify({
|
|
260
|
+
...message,
|
|
261
|
+
timestamp: Date.now(),
|
|
262
|
+
server: 'Claude Code Analytics'
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
let sentCount = 0;
|
|
266
|
+
this.clients.forEach((client, clientId) => {
|
|
267
|
+
// Filter by channel subscription if specified
|
|
268
|
+
if (channel && !client.subscriptions.has(channel)) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (client.ws.readyState === WebSocket.OPEN) {
|
|
273
|
+
try {
|
|
274
|
+
client.ws.send(messageStr);
|
|
275
|
+
sentCount++;
|
|
276
|
+
} catch (error) {
|
|
277
|
+
console.error(chalk.red(`Error sending to client ${clientId}:`), error);
|
|
278
|
+
this.clients.delete(clientId);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
if (sentCount > 0) {
|
|
284
|
+
console.log(chalk.green(`📢 Broadcasted ${message.type} to ${sentCount} clients${channel ? ` on channel ${channel}` : ''}`));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Queue message if no clients connected
|
|
288
|
+
if (sentCount === 0 && this.clients.size === 0) {
|
|
289
|
+
this.queueMessage(message);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Send message to specific client
|
|
295
|
+
* @param {string} clientId - Client ID
|
|
296
|
+
* @param {Object} message - Message to send
|
|
297
|
+
*/
|
|
298
|
+
sendToClient(clientId, message) {
|
|
299
|
+
const client = this.clients.get(clientId);
|
|
300
|
+
if (!client || client.ws.readyState !== WebSocket.OPEN) {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
const messageStr = JSON.stringify({
|
|
306
|
+
...message,
|
|
307
|
+
timestamp: Date.now(),
|
|
308
|
+
server: 'Claude Code Analytics'
|
|
309
|
+
});
|
|
310
|
+
client.ws.send(messageStr);
|
|
311
|
+
return true;
|
|
312
|
+
} catch (error) {
|
|
313
|
+
console.error(chalk.red(`Error sending to client ${clientId}:`), error);
|
|
314
|
+
this.clients.delete(clientId);
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Queue message for future delivery
|
|
321
|
+
* @param {Object} message - Message to queue
|
|
322
|
+
*/
|
|
323
|
+
queueMessage(message) {
|
|
324
|
+
this.messageQueue.push({
|
|
325
|
+
...message,
|
|
326
|
+
queuedAt: Date.now()
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// Keep queue size manageable
|
|
330
|
+
if (this.messageQueue.length > this.maxQueueSize) {
|
|
331
|
+
this.messageQueue.shift();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Send queued messages to newly connected client
|
|
337
|
+
* @param {string} clientId - Client ID
|
|
338
|
+
*/
|
|
339
|
+
sendQueuedMessages(clientId) {
|
|
340
|
+
if (this.messageQueue.length === 0) return;
|
|
341
|
+
|
|
342
|
+
console.log(chalk.blue(`📦 Sending ${this.messageQueue.length} queued messages to ${clientId}`));
|
|
343
|
+
|
|
344
|
+
this.messageQueue.forEach(message => {
|
|
345
|
+
this.sendToClient(clientId, {
|
|
346
|
+
...message,
|
|
347
|
+
type: 'queued_' + message.type,
|
|
348
|
+
wasQueued: true
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Notify clients of conversation state change
|
|
355
|
+
* @param {string} conversationId - Conversation ID
|
|
356
|
+
* @param {string} newState - New state
|
|
357
|
+
* @param {Object} metadata - Additional metadata
|
|
358
|
+
*/
|
|
359
|
+
notifyConversationStateChange(conversationId, newState, metadata = {}) {
|
|
360
|
+
this.broadcast({
|
|
361
|
+
type: 'conversation_state_change',
|
|
362
|
+
data: {
|
|
363
|
+
conversationId,
|
|
364
|
+
newState,
|
|
365
|
+
...metadata
|
|
366
|
+
}
|
|
367
|
+
}, 'conversation_updates');
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Notify clients of data refresh
|
|
372
|
+
* @param {Object} data - Updated data
|
|
373
|
+
*/
|
|
374
|
+
notifyDataRefresh(data) {
|
|
375
|
+
this.broadcast({
|
|
376
|
+
type: 'data_refresh',
|
|
377
|
+
data
|
|
378
|
+
}, 'data_updates');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Notify clients of system status change
|
|
383
|
+
* @param {Object} status - System status
|
|
384
|
+
*/
|
|
385
|
+
notifySystemStatus(status) {
|
|
386
|
+
this.broadcast({
|
|
387
|
+
type: 'system_status',
|
|
388
|
+
data: status
|
|
389
|
+
}, 'system_updates');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Start heartbeat mechanism
|
|
394
|
+
*/
|
|
395
|
+
startHeartbeat() {
|
|
396
|
+
this.heartbeatInterval = setInterval(() => {
|
|
397
|
+
this.clients.forEach((client, clientId) => {
|
|
398
|
+
if (!client.isAlive) {
|
|
399
|
+
console.log(chalk.yellow(`💔 Terminating unresponsive client: ${clientId}`));
|
|
400
|
+
client.ws.terminate();
|
|
401
|
+
this.clients.delete(clientId);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
client.isAlive = false;
|
|
406
|
+
if (client.ws.readyState === WebSocket.OPEN) {
|
|
407
|
+
client.ws.ping();
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
}, this.options.heartbeatInterval);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Stop heartbeat mechanism
|
|
415
|
+
*/
|
|
416
|
+
stopHeartbeat() {
|
|
417
|
+
if (this.heartbeatInterval) {
|
|
418
|
+
clearInterval(this.heartbeatInterval);
|
|
419
|
+
this.heartbeatInterval = null;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Generate unique client ID
|
|
425
|
+
* @returns {string} Client ID
|
|
426
|
+
*/
|
|
427
|
+
generateClientId() {
|
|
428
|
+
return `client_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Get server statistics
|
|
433
|
+
* @returns {Object} Server statistics
|
|
434
|
+
*/
|
|
435
|
+
getStats() {
|
|
436
|
+
const clientStats = Array.from(this.clients.values()).map(client => ({
|
|
437
|
+
id: client.id,
|
|
438
|
+
ip: client.ip,
|
|
439
|
+
connectedAt: client.connectedAt,
|
|
440
|
+
subscriptions: Array.from(client.subscriptions),
|
|
441
|
+
isAlive: client.isAlive
|
|
442
|
+
}));
|
|
443
|
+
|
|
444
|
+
return {
|
|
445
|
+
isRunning: this.isRunning,
|
|
446
|
+
clientCount: this.clients.size,
|
|
447
|
+
queuedMessages: this.messageQueue.length,
|
|
448
|
+
clients: clientStats,
|
|
449
|
+
uptime: this.isRunning ? Date.now() - this.startTime : 0
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Gracefully close all connections and stop server
|
|
455
|
+
*/
|
|
456
|
+
async close() {
|
|
457
|
+
console.log(chalk.yellow('🔌 Closing WebSocket server...'));
|
|
458
|
+
|
|
459
|
+
this.stopHeartbeat();
|
|
460
|
+
|
|
461
|
+
// Close all client connections
|
|
462
|
+
this.clients.forEach((client, clientId) => {
|
|
463
|
+
if (client.ws.readyState === WebSocket.OPEN) {
|
|
464
|
+
client.ws.close(1000, 'Server shutting down');
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
this.clients.clear();
|
|
469
|
+
|
|
470
|
+
if (this.wss) {
|
|
471
|
+
await new Promise((resolve) => {
|
|
472
|
+
this.wss.close(resolve);
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
this.isRunning = false;
|
|
477
|
+
console.log(chalk.green('✅ WebSocket server closed'));
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Event emitter functionality
|
|
482
|
+
*/
|
|
483
|
+
emit(event, data) {
|
|
484
|
+
// Simple event emitter implementation
|
|
485
|
+
if (this.listeners && this.listeners[event]) {
|
|
486
|
+
this.listeners[event].forEach(callback => {
|
|
487
|
+
try {
|
|
488
|
+
callback(data);
|
|
489
|
+
} catch (error) {
|
|
490
|
+
console.error(`Error in WebSocket event listener for ${event}:`, error);
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Add event listener
|
|
498
|
+
* @param {string} event - Event name
|
|
499
|
+
* @param {Function} callback - Callback function
|
|
500
|
+
*/
|
|
501
|
+
on(event, callback) {
|
|
502
|
+
if (!this.listeners) {
|
|
503
|
+
this.listeners = {};
|
|
504
|
+
}
|
|
505
|
+
if (!this.listeners[event]) {
|
|
506
|
+
this.listeners[event] = [];
|
|
507
|
+
}
|
|
508
|
+
this.listeners[event].push(callback);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Remove event listener
|
|
513
|
+
* @param {string} event - Event name
|
|
514
|
+
* @param {Function} callback - Callback function
|
|
515
|
+
*/
|
|
516
|
+
off(event, callback) {
|
|
517
|
+
if (this.listeners && this.listeners[event]) {
|
|
518
|
+
const index = this.listeners[event].indexOf(callback);
|
|
519
|
+
if (index !== -1) {
|
|
520
|
+
this.listeners[event].splice(index, 1);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
module.exports = WebSocketServer;
|