UNPKG

skailan-conversations

Version:

Servicio de conversaciones y mensajería para Skailan

137 lines 7.18 kB
import crypto from 'crypto'; import { StartConversation } from '../../app/use-cases/StartConversation'; import { CreateMessage } from '../../app/use-cases/CreateMessage'; import { ChannelPrismaRepository } from '../../infra/database/prisma/ChannelPrismaRepository'; import { ConversationPrismaRepository } from '../../infra/database/prisma/ConversationPrismaRepository'; import { MessagePrismaRepository } from '../../infra/database/prisma/MessagePrismaRepository'; // Helper para verificar la firma del webhook de Meta const verifySignature = (req, res, next) => { const signature = req.headers['x-hub-signature-256']; if (!signature) { console.warn('Webhook received without signature.'); return res.status(400).send('Signature missing'); } const [algorithm, hash] = signature.toString().split('='); if (algorithm !== 'sha256') { console.warn('Unsupported signature algorithm.'); return res.status(400).send('Unsupported signature algorithm'); } const hmac = crypto.createHmac('sha256', process.env.WHATSAPP_APP_SECRET || ''); hmac.update(JSON.stringify(req.body)); const digest = hmac.digest('hex'); if (digest !== hash) { console.warn('Invalid signature.'); return res.status(403).send('Invalid signature'); } next(); }; export const handleWhatsappWebhook = async (req, res) => { const organizationId = req.organization?.id; if (!organizationId) { return res.status(400).json({ error: 'Organization ID not found in request.' }); } const prisma = req.tenantPrisma; if (!prisma) { return res.status(500).json({ error: 'Prisma client not initialized for tenant.' }); } const channelRepository = new ChannelPrismaRepository(prisma); const conversationRepository = new ConversationPrismaRepository(prisma); const messageRepository = new MessagePrismaRepository(prisma); // Meta envía un array de entradas for (const entry of req.body.entry) { for (const change of entry.changes) { if (change.field === 'messages') { for (const message of change.value.messages) { // Ignorar mensajes de estado o mensajes enviados por la propia aplicación if (message.type === 'status' || message.from === process.env.WHATSAPP_PHONE_NUMBER_ID) { continue; } try { // 1. Identificar o crear el contacto y la conversación const startConversationUseCase = new StartConversation(conversationRepository, channelRepository); // Asumimos que el channelId se puede obtener del config del webhook o de alguna manera // Por ahora, lo hardcodearemos o lo buscaremos por tipo const whatsappChannel = await channelRepository.findByType('whatsapp', organizationId); if (!whatsappChannel) { console.error('WhatsApp channel not found for organization', organizationId); continue; } const conversation = await startConversationUseCase.execute({ organizationId, channelId: whatsappChannel.id, contactExternalId: message.from, // Número de teléfono del remitente // Puedes intentar obtener el nombre del contacto si está disponible en el payload // contactName: message.contacts ? message.contacts[0].profile.name : undefined, }); // 2. Crear el mensaje const createMessageUseCase = new CreateMessage(messageRepository, conversationRepository); let messageContent = ''; let messageType = message.type; let mediaUrl; switch (message.type) { case 'text': messageContent = message.text.body; break; case 'image': messageContent = message.image.caption || '(Imagen)'; mediaUrl = message.image.id; // Esto es un ID de Meta, necesitará ser descargado break; case 'video': messageContent = message.video.caption || '(Video)'; mediaUrl = message.video.id; break; case 'audio': messageContent = '(Audio)'; mediaUrl = message.audio.id; break; case 'document': messageContent = message.document.filename || '(Documento)'; mediaUrl = message.document.id; break; // Añadir más tipos de mensajes según sea necesario default: messageContent = `(Mensaje de tipo no soportado: ${message.type})`; break; } await createMessageUseCase.execute({ organizationId, conversationId: conversation.id, senderType: 'contact', senderId: message.from, // O el ID del contacto si ya lo tienes type: messageType, content: messageContent, mediaUrl: mediaUrl, externalMessageId: message.id, timestamp: new Date(message.timestamp * 1000), // Convertir a milisegundos }); } catch (error) { console.error('Error processing WhatsApp message:', error.message); // Dependiendo de la política, podrías enviar una respuesta de error a Meta } } } } } res.status(200).send('EVENT_RECEIVED'); }; // Endpoint para la verificación de webhooks de Meta (GET request) export const verifyWhatsappWebhook = (req, res) => { const VERIFY_TOKEN = process.env.WHATSAPP_VERIFY_TOKEN; const mode = req.query['hub.mode']; const token = req.query['hub.verify_token']; const challenge = req.query['hub.challenge']; if (mode && token) { if (mode === 'subscribe' && token === VERIFY_TOKEN) { console.log('WEBHOOK_VERIFIED'); res.status(200).send(challenge); } else { res.status(403).send('Verification failed'); } } else { res.status(400).send('Missing parameters'); } }; //# sourceMappingURL=whatsappWebhookController.js.map