whatsapp-crm-common
Version:
Componentes compartidos para servicios de WhatsApp CRM - Common utilities and types for WhatsApp CRM system
262 lines • 10.2 kB
JavaScript
;
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