UNPKG

whatsapp-crm-common

Version:

Componentes compartidos para servicios de WhatsApp CRM - Common utilities and types for WhatsApp CRM system

262 lines 10.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.WhatsAppPubSubSystem = void 0; const event_types_1 = require("../../events/types/event-types"); const logger_1 = __importDefault(require("../../utils/logger")); /** * Sistema Pub/Sub para notificaciones en tiempo real * Complementa BullMQ para eventos no críticos que requieren baja latencia */ class WhatsAppPubSubSystem { constructor(redisClient, options = {}) { this.subscribers = new Map(); this.subscriberPool = []; this.activeSubscriptions = new Set(); this.publisher = redisClient.getClient(); this.options = { subscriberPoolSize: 3, enableRetry: true, retryAttempts: 3, ...options }; this.initializeSubscriberPool(); } /** * Inicializa el pool de suscriptores para distribuir la carga */ initializeSubscriberPool() { for (let i = 0; i < this.options.subscriberPoolSize; i++) { const subscriber = this.publisher.duplicate(); subscriber.on('error', (error) => { logger_1.default.error(`Subscriber ${i} error:`, error); }); this.subscriberPool.push(subscriber); } logger_1.default.info(`PubSub subscriber pool initialized with ${this.subscriberPool.length} instances`); } /** * Publica un evento para notificaciones en tiempo real */ async publishEvent(event) { try { const channel = this.getChannelForEvent(event); const subscriberCount = await this.publisher.publish(channel, JSON.stringify(event)); logger_1.default.debug(`Published event ${event.eventType} to channel ${channel}`, { subscriberCount, tenantId: event.tenantId, agentId: event.agentId }); return subscriberCount; } catch (error) { logger_1.default.error('Failed to publish PubSub event:', error); throw error; } } /** * Publica una notificación específica para la UI */ async publishNotification(event) { try { const notificationChannel = `notifications:${event.tenantId}:${event.agentId}`; const notification = { type: 'notification', event: event.eventType, data: event.data, timestamp: Date.now(), source: 'whatsapp-service' }; await this.publisher.publish(notificationChannel, JSON.stringify(notification)); logger_1.default.debug(`Published notification to ${notificationChannel}`, { event: event.eventType, tenantId: event.tenantId, agentId: event.agentId }); } catch (error) { logger_1.default.error('Failed to publish notification:', error); // No relanzar error para notificaciones } } /** * Suscribirse a un patrón de canales */ async subscribe(pattern, handler) { try { const subscriber = this.getAvailableSubscriber(); await subscriber.psubscribe(pattern); this.activeSubscriptions.add(pattern); subscriber.on('pmessage', async (pattern, channel, message) => { try { const event = JSON.parse(message); await handler(channel, event); } catch (error) { logger_1.default.error(`Error processing PubSub message from ${channel}:`, error); } }); logger_1.default.info(`Subscribed to pattern: ${pattern}`); } catch (error) { logger_1.default.error(`Failed to subscribe to pattern ${pattern}:`, error); throw error; } } /** * Suscribirse a notificaciones de un tenant/agente específico */ async subscribeToNotifications(tenantId, agentId, callback) { try { const subscriber = this.getAvailableSubscriber(); const pattern = `notifications:${tenantId}:${agentId}`; await subscriber.psubscribe(pattern); subscriber.on('pmessage', (pattern, channel, message) => { try { const notification = JSON.parse(message); callback(notification); } catch (error) { logger_1.default.error('Error parsing notification:', error); } }); const subscriptionKey = `${tenantId}_${agentId}`; this.subscribers.set(subscriptionKey, subscriber); logger_1.default.info(`Subscribed to notifications for tenant ${tenantId}, agent ${agentId}`); } catch (error) { logger_1.default.error(`Failed to subscribe to notifications for ${tenantId}:${agentId}:`, error); throw error; } } /** * Broadcast a todos los usuarios de un tenant */ async broadcastToTenant(tenantId, data) { try { const broadcastChannel = `broadcast:${tenantId}:*`; const broadcast = { type: 'broadcast', event: event_types_1.WhatsAppEventType.SYSTEM_BROADCAST, data, timestamp: Date.now(), source: 'whatsapp-service' }; await this.publisher.publish(broadcastChannel, JSON.stringify(broadcast)); logger_1.default.info(`Broadcasted to tenant ${tenantId}`, { data }); } catch (error) { logger_1.default.error(`Failed to broadcast to tenant ${tenantId}:`, error); } } /** * Obtiene estadísticas del sistema Pub/Sub */ async getStats() { try { const info = await this.publisher.info('replication'); return { activeSubscriptions: this.activeSubscriptions.size, subscriberPoolSize: this.subscriberPool.length, connectedSubscribers: this.subscribers.size, redisInfo: info }; } catch (error) { logger_1.default.error('Failed to get PubSub stats:', error); return { activeSubscriptions: this.activeSubscriptions.size, subscriberPoolSize: this.subscriberPool.length, connectedSubscribers: this.subscribers.size, error: error instanceof Error ? error.message : String(error) }; } } /** * Desconectar una suscripción específica */ async unsubscribe(tenantId, agentId) { try { const subscriptionKey = `${tenantId}_${agentId}`; const subscriber = this.subscribers.get(subscriptionKey); if (subscriber) { await subscriber.punsubscribe(); await subscriber.disconnect(); this.subscribers.delete(subscriptionKey); logger_1.default.info(`Unsubscribed ${tenantId}:${agentId}`); } } catch (error) { logger_1.default.error(`Failed to unsubscribe ${tenantId}:${agentId}:`, error); } } /** * Cerrar todas las conexiones */ async shutdown() { try { logger_1.default.info('Shutting down PubSub system...'); // Desconectar todas las suscripciones activas for (const [key, subscriber] of this.subscribers) { try { await subscriber.punsubscribe(); await subscriber.disconnect(); } catch (error) { logger_1.default.warn(`Error disconnecting subscriber ${key}:`, error); } } // Desconectar pool de suscriptores for (const subscriber of this.subscriberPool) { try { await subscriber.disconnect(); } catch (error) { logger_1.default.warn('Error disconnecting subscriber from pool:', error); } } this.subscribers.clear(); this.subscriberPool = []; this.activeSubscriptions.clear(); logger_1.default.info('PubSub system shutdown completed'); } catch (error) { logger_1.default.error('Error during PubSub shutdown:', error); throw error; } } /** * Obtiene el canal apropiado para un tipo de evento */ getChannelForEvent(event) { switch (event.eventType) { case event_types_1.WhatsAppEventType.QR_CODE_GENERATED: return `qr:${event.tenantId}:${event.agentId}`; case event_types_1.WhatsAppEventType.CONNECTION_UPDATE: return `connection:${event.tenantId}:${event.agentId}`; case event_types_1.WhatsAppEventType.PRESENCE_UPDATE: return `presence:${event.tenantId}:${event.agentId}`; case event_types_1.WhatsAppEventType.TYPING_START: case event_types_1.WhatsAppEventType.TYPING_STOP: return `typing:${event.tenantId}:${event.agentId}`; case event_types_1.WhatsAppEventType.SYSTEM_HEALTH: return `system:health`; default: return `general:${event.tenantId}:${event.agentId}`; } } /** * Obtiene un suscriptor disponible del pool */ getAvailableSubscriber() { // Implementación simple round-robin const subscriber = this.subscriberPool[0]; // Rotar el pool this.subscriberPool.push(this.subscriberPool.shift()); return subscriber; } } exports.WhatsAppPubSubSystem = WhatsAppPubSubSystem; //# sourceMappingURL=whatsapp-pubsub.js.map