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.
@@ -0,0 +1,448 @@
1
+ /**
2
+ * NotificationManager - Manages notifications and real-time updates
3
+ * Part of the modular backend architecture for Phase 3
4
+ */
5
+ const chalk = require('chalk');
6
+
7
+ class NotificationManager {
8
+ constructor(webSocketServer) {
9
+ this.webSocketServer = webSocketServer;
10
+ this.notificationHistory = [];
11
+ this.maxHistorySize = 1000;
12
+ this.subscribers = new Map();
13
+ this.throttleMap = new Map();
14
+ this.defaultThrottleTime = 1000; // 1 second
15
+ }
16
+
17
+ /**
18
+ * Initialize the notification manager
19
+ */
20
+ async initialize() {
21
+ console.log(chalk.blue('📢 Initializing Notification Manager...'));
22
+
23
+ // Setup WebSocket event listeners
24
+ if (this.webSocketServer) {
25
+ this.webSocketServer.on('refresh_requested', (data) => {
26
+ this.handleRefreshRequest(data);
27
+ });
28
+ }
29
+
30
+ console.log(chalk.green('✅ Notification Manager initialized'));
31
+ }
32
+
33
+ /**
34
+ * Send conversation state change notification
35
+ * @param {string} conversationId - Conversation ID
36
+ * @param {string} oldState - Previous state
37
+ * @param {string} newState - New state
38
+ * @param {Object} metadata - Additional metadata
39
+ */
40
+ notifyConversationStateChange(conversationId, oldState, newState, metadata = {}) {
41
+ const notification = {
42
+ type: 'conversation_state_change',
43
+ conversationId,
44
+ oldState,
45
+ newState,
46
+ metadata,
47
+ timestamp: new Date().toISOString(),
48
+ id: this.generateNotificationId()
49
+ };
50
+
51
+ // Throttle rapid state changes for the same conversation
52
+ const throttleKey = `state_${conversationId}`;
53
+ if (this.isThrottled(throttleKey)) {
54
+ console.log(chalk.yellow(`⏱️ Throttling state change for conversation ${conversationId}`));
55
+ return;
56
+ }
57
+
58
+ this.addToHistory(notification);
59
+
60
+ // Send via WebSocket
61
+ if (this.webSocketServer) {
62
+ this.webSocketServer.notifyConversationStateChange(conversationId, newState, {
63
+ oldState,
64
+ ...metadata
65
+ });
66
+ }
67
+
68
+ // Send to local subscribers
69
+ this.notifySubscribers('conversation_state_change', notification);
70
+
71
+ console.log(chalk.green(`🔄 State change: ${conversationId} ${oldState} → ${newState}`));
72
+ }
73
+
74
+ /**
75
+ * Send data refresh notification
76
+ * @param {Object} data - Refreshed data
77
+ * @param {string} source - Source of the refresh
78
+ */
79
+ notifyDataRefresh(data, source = 'system') {
80
+ const notification = {
81
+ type: 'data_refresh',
82
+ data,
83
+ source,
84
+ timestamp: new Date().toISOString(),
85
+ id: this.generateNotificationId()
86
+ };
87
+
88
+ // Throttle data refresh notifications
89
+ if (this.isThrottled('data_refresh')) {
90
+ console.log(chalk.yellow('⏱️ Throttling data refresh notification'));
91
+ return;
92
+ }
93
+
94
+ this.addToHistory(notification);
95
+
96
+ // Send via WebSocket
97
+ if (this.webSocketServer) {
98
+ this.webSocketServer.notifyDataRefresh(data);
99
+ }
100
+
101
+ // Send to local subscribers
102
+ this.notifySubscribers('data_refresh', notification);
103
+
104
+ console.log(chalk.green(`📊 Data refreshed (source: ${source})`));
105
+ }
106
+
107
+ /**
108
+ * Send system status notification
109
+ * @param {Object} status - System status
110
+ * @param {string} level - Notification level (info, warning, error)
111
+ */
112
+ notifySystemStatus(status, level = 'info') {
113
+ const notification = {
114
+ type: 'system_status',
115
+ status,
116
+ level,
117
+ timestamp: new Date().toISOString(),
118
+ id: this.generateNotificationId()
119
+ };
120
+
121
+ this.addToHistory(notification);
122
+
123
+ // Send via WebSocket
124
+ if (this.webSocketServer) {
125
+ this.webSocketServer.notifySystemStatus({
126
+ ...status,
127
+ level
128
+ });
129
+ }
130
+
131
+ // Send to local subscribers
132
+ this.notifySubscribers('system_status', notification);
133
+
134
+ const emoji = level === 'error' ? '❌' : level === 'warning' ? '⚠️' : 'ℹ️';
135
+ console.log(chalk[level === 'error' ? 'red' : level === 'warning' ? 'yellow' : 'blue'](`${emoji} System status: ${status.message || JSON.stringify(status)}`));
136
+ }
137
+
138
+ /**
139
+ * Send file change notification
140
+ * @param {string} filePath - Path of changed file
141
+ * @param {string} changeType - Type of change (created, modified, deleted)
142
+ */
143
+ notifyFileChange(filePath, changeType) {
144
+ const notification = {
145
+ type: 'file_change',
146
+ filePath,
147
+ changeType,
148
+ timestamp: new Date().toISOString(),
149
+ id: this.generateNotificationId()
150
+ };
151
+
152
+ // Throttle file change notifications for the same file
153
+ const throttleKey = `file_${filePath}`;
154
+ if (this.isThrottled(throttleKey, 2000)) { // 2 second throttle for files
155
+ return;
156
+ }
157
+
158
+ this.addToHistory(notification);
159
+
160
+ // Send via WebSocket
161
+ if (this.webSocketServer) {
162
+ this.webSocketServer.broadcast({
163
+ type: 'file_change',
164
+ data: {
165
+ filePath,
166
+ changeType
167
+ }
168
+ }, 'file_updates');
169
+ }
170
+
171
+ // Send to local subscribers
172
+ this.notifySubscribers('file_change', notification);
173
+
174
+ console.log(chalk.cyan(`📁 File ${changeType}: ${filePath}`));
175
+ }
176
+
177
+ /**
178
+ * Send process change notification
179
+ * @param {Array} processes - Current processes
180
+ * @param {Array} changedProcesses - Processes that changed
181
+ */
182
+ notifyProcessChange(processes, changedProcesses) {
183
+ const notification = {
184
+ type: 'process_change',
185
+ processes,
186
+ changedProcesses,
187
+ timestamp: new Date().toISOString(),
188
+ id: this.generateNotificationId()
189
+ };
190
+
191
+ // Throttle process change notifications
192
+ if (this.isThrottled('process_change', 5000)) { // 5 second throttle for processes
193
+ return;
194
+ }
195
+
196
+ this.addToHistory(notification);
197
+
198
+ // Send via WebSocket
199
+ if (this.webSocketServer) {
200
+ this.webSocketServer.broadcast({
201
+ type: 'process_change',
202
+ data: {
203
+ processes,
204
+ changedProcesses
205
+ }
206
+ }, 'process_updates');
207
+ }
208
+
209
+ // Send to local subscribers
210
+ this.notifySubscribers('process_change', notification);
211
+
212
+ if (changedProcesses.length > 0) {
213
+ console.log(chalk.blue(`⚡ Process changes detected: ${changedProcesses.length} processes`));
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Handle refresh request from WebSocket client
219
+ * @param {Object} data - Request data
220
+ */
221
+ handleRefreshRequest(data) {
222
+ console.log(chalk.blue(`🔄 Refresh requested by client: ${data.clientId}`));
223
+
224
+ // Emit refresh event that analytics server can listen to
225
+ this.notifySubscribers('refresh_requested', {
226
+ clientId: data.clientId,
227
+ timestamp: new Date().toISOString()
228
+ });
229
+ }
230
+
231
+ /**
232
+ * Subscribe to notifications
233
+ * @param {string} type - Notification type
234
+ * @param {Function} callback - Callback function
235
+ * @returns {Function} Unsubscribe function
236
+ */
237
+ subscribe(type, callback) {
238
+ if (!this.subscribers.has(type)) {
239
+ this.subscribers.set(type, new Set());
240
+ }
241
+
242
+ this.subscribers.get(type).add(callback);
243
+
244
+ // Return unsubscribe function
245
+ return () => {
246
+ const typeSubscribers = this.subscribers.get(type);
247
+ if (typeSubscribers) {
248
+ typeSubscribers.delete(callback);
249
+ if (typeSubscribers.size === 0) {
250
+ this.subscribers.delete(type);
251
+ }
252
+ }
253
+ };
254
+ }
255
+
256
+ /**
257
+ * Notify all subscribers of a specific type
258
+ * @param {string} type - Notification type
259
+ * @param {Object} notification - Notification data
260
+ */
261
+ notifySubscribers(type, notification) {
262
+ const typeSubscribers = this.subscribers.get(type);
263
+ if (!typeSubscribers) return;
264
+
265
+ typeSubscribers.forEach(callback => {
266
+ try {
267
+ callback(notification);
268
+ } catch (error) {
269
+ console.error(chalk.red(`Error in notification subscriber for ${type}:`), error);
270
+ }
271
+ });
272
+ }
273
+
274
+ /**
275
+ * Check if a notification type is throttled
276
+ * @param {string} key - Throttle key
277
+ * @param {number} throttleTime - Throttle time in milliseconds
278
+ * @returns {boolean} Is throttled
279
+ */
280
+ isThrottled(key, throttleTime = this.defaultThrottleTime) {
281
+ const now = Date.now();
282
+ const lastTime = this.throttleMap.get(key);
283
+
284
+ if (lastTime && (now - lastTime) < throttleTime) {
285
+ return true;
286
+ }
287
+
288
+ this.throttleMap.set(key, now);
289
+ return false;
290
+ }
291
+
292
+ /**
293
+ * Add notification to history
294
+ * @param {Object} notification - Notification to add
295
+ */
296
+ addToHistory(notification) {
297
+ this.notificationHistory.push(notification);
298
+
299
+ // Keep history size manageable
300
+ if (this.notificationHistory.length > this.maxHistorySize) {
301
+ this.notificationHistory.shift();
302
+ }
303
+ }
304
+
305
+ /**
306
+ * Get notification history
307
+ * @param {string} type - Filter by type (optional)
308
+ * @param {number} limit - Limit number of results
309
+ * @returns {Array} Notification history
310
+ */
311
+ getHistory(type = null, limit = 100) {
312
+ let history = this.notificationHistory;
313
+
314
+ if (type) {
315
+ history = history.filter(notification => notification.type === type);
316
+ }
317
+
318
+ return history.slice(-limit);
319
+ }
320
+
321
+ /**
322
+ * Clear notification history
323
+ * @param {string} type - Clear specific type only (optional)
324
+ */
325
+ clearHistory(type = null) {
326
+ if (type) {
327
+ this.notificationHistory = this.notificationHistory.filter(
328
+ notification => notification.type !== type
329
+ );
330
+ } else {
331
+ this.notificationHistory = [];
332
+ }
333
+
334
+ console.log(chalk.yellow(`🗑️ Cleared notification history${type ? ` for type: ${type}` : ''}`));
335
+ }
336
+
337
+ /**
338
+ * Generate unique notification ID
339
+ * @returns {string} Notification ID
340
+ */
341
+ generateNotificationId() {
342
+ return `notif_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
343
+ }
344
+
345
+ /**
346
+ * Get notification statistics
347
+ * @returns {Object} Notification statistics
348
+ */
349
+ getStats() {
350
+ const typeCount = {};
351
+ this.notificationHistory.forEach(notification => {
352
+ typeCount[notification.type] = (typeCount[notification.type] || 0) + 1;
353
+ });
354
+
355
+ return {
356
+ historySize: this.notificationHistory.length,
357
+ maxHistorySize: this.maxHistorySize,
358
+ subscriberCount: this.subscribers.size,
359
+ typeCount,
360
+ throttleMapSize: this.throttleMap.size,
361
+ webSocketConnected: this.webSocketServer ? this.webSocketServer.isRunning : false,
362
+ webSocketClients: this.webSocketServer ? this.webSocketServer.getStats().clientCount : 0
363
+ };
364
+ }
365
+
366
+ /**
367
+ * Create a batch notification for multiple changes
368
+ * @param {Array} notifications - Array of notifications
369
+ * @param {string} batchType - Type of batch
370
+ */
371
+ createBatch(notifications, batchType = 'batch') {
372
+ if (notifications.length === 0) return;
373
+
374
+ const batchNotification = {
375
+ type: batchType,
376
+ notifications,
377
+ count: notifications.length,
378
+ timestamp: new Date().toISOString(),
379
+ id: this.generateNotificationId()
380
+ };
381
+
382
+ this.addToHistory(batchNotification);
383
+
384
+ // Send via WebSocket
385
+ if (this.webSocketServer) {
386
+ this.webSocketServer.broadcast({
387
+ type: batchType,
388
+ data: {
389
+ notifications,
390
+ count: notifications.length
391
+ }
392
+ });
393
+ }
394
+
395
+ // Send to local subscribers
396
+ this.notifySubscribers(batchType, batchNotification);
397
+
398
+ console.log(chalk.green(`📦 Batch notification sent: ${notifications.length} items`));
399
+ }
400
+
401
+ /**
402
+ * Cleanup throttle map periodically
403
+ */
404
+ cleanupThrottleMap() {
405
+ const now = Date.now();
406
+ const maxAge = this.defaultThrottleTime * 10; // 10x throttle time
407
+
408
+ this.throttleMap.forEach((timestamp, key) => {
409
+ if (now - timestamp > maxAge) {
410
+ this.throttleMap.delete(key);
411
+ }
412
+ });
413
+ }
414
+
415
+ /**
416
+ * Start periodic cleanup
417
+ */
418
+ startPeriodicCleanup() {
419
+ this.cleanupInterval = setInterval(() => {
420
+ this.cleanupThrottleMap();
421
+ }, 60000); // Clean up every minute
422
+ }
423
+
424
+ /**
425
+ * Stop periodic cleanup
426
+ */
427
+ stopPeriodicCleanup() {
428
+ if (this.cleanupInterval) {
429
+ clearInterval(this.cleanupInterval);
430
+ this.cleanupInterval = null;
431
+ }
432
+ }
433
+
434
+ /**
435
+ * Shutdown the notification manager
436
+ */
437
+ async shutdown() {
438
+ console.log(chalk.yellow('📢 Shutting down Notification Manager...'));
439
+
440
+ this.stopPeriodicCleanup();
441
+ this.subscribers.clear();
442
+ this.throttleMap.clear();
443
+
444
+ console.log(chalk.green('✅ Notification Manager shut down'));
445
+ }
446
+ }
447
+
448
+ module.exports = NotificationManager;