import express, { Application } from 'express'; import axios from 'axios'; import { getHandler } from './chatHandlers'; import { addMessageToConversation } from './store/conversation'; import { WhatsAppMessage, Conversation } from './types'; export interface WebhookConfig { API_URL: string; MAX_ATTEMPTS: number; RETRY_MS: number; } export function registerWebhookRoutes( app: Application, config: WebhookConfig, openWaUrl: string | undefined, agentUrl: string | undefined ) { app.post('/webhook', async (req: express.Request, res: express.Response) => { let message: WhatsAppMessage | undefined; let from: string | undefined; try { if (req.body && req.body.data) { message = req.body.data as WhatsAppMessage; from = req.body.data.from; } else { throw new Error('Invalid webhook format'); } } catch {} if (message) { const origen = from || message.chatId || 'desconocido'; console.log(`📩 Mensaje recibido (${message.text}) de ${origen}`); } try { if (!message) return res.sendStatus(200); if (!openWaUrl) throw new Error('Service URLs not configured'); const chatId = message.chatId || from; let conv: Conversation | undefined; if (chatId) { try { conv = await addMessageToConversation(chatId, message, openWaUrl); } catch (err: any) { console.warn('Failed updating conversation:', err.message); } } if (!conv) throw new Error('Conversation unavailable'); const handler = getHandler(chatId, agentUrl); if (!handler) throw new Error('No handler configured'); let reply: string; if (typeof handler === 'string') { const agentRes = await axios.post(handler, { conversation: conv }); reply = agentRes.data.reply || agentRes.data; } else { reply = await handler(conv); } await axios.post(`${openWaUrl}/sendText`, { args: { to: from, content: reply } }); } catch (err: any) { console.error('Error processing message', err.message); } res.sendStatus(200); }); } export async function waitForGateway(config: WebhookConfig) { for (let i = 1; i <= config.MAX_ATTEMPTS; i++) { try { await axios.get(`${config.API_URL}/api-docs/`); console.log('🟢 nucleo-whatsapp ready'); return; } catch (e) { console.warn( 'Gateway not responding', `connecting to: ${config.API_URL}/api-docs/ (attempt ${i}/${config.MAX_ATTEMPTS})…`, e ); await new Promise((r) => setTimeout(r, config.RETRY_MS)); } } throw new Error('nucleo-whatsapp did not respond in time'); } export async function clearWebhooks(config: WebhookConfig) { try { const { data } = await axios.post(`${config.API_URL}/listWebhooks`); const hooks = data?.response || []; if (!hooks.length) { console.log('No existing webhooks to remove'); return; } console.log(`Removing ${hooks.length} webhooks…`); const results = await Promise.allSettled( hooks.map((h: any) => axios.post(`${config.API_URL}/removeWebhook`, { args: { webhookId: h.id } })) ); results.forEach((r: PromiseSettledResult, i: number) => { const id = hooks[i].id; if (r.status === 'fulfilled' && (r as PromiseFulfilledResult).value?.data?.response === true) { console.log(`✔️ Removed webhook ${id}`); } else { console.warn(`⚠️ Failed to remove webhook ${id}`); } }); const ok = results.filter( (r: PromiseSettledResult) => r.status === 'fulfilled' && (r as PromiseFulfilledResult).value?.data?.response === true ).length; console.log(`Cleanup OK (${ok}/${hooks.length} removed)`); } catch (e: any) { console.error('Failed cleaning webhooks:', e.response?.data || e.message); } } export async function registerWebhook(config: WebhookConfig, port: number) { const url = process.env.WEBHOOK_URL || `http://whatsapp-router:${port}/webhook`; const eventConfig = { onAck: false, onAddedToGroup: true, // use onAnyMessage to also receive messages sent by nucleo-whatsapp itself onAnyMessage: true, onBattery: true, onBroadcast: true, onButton: true, onCallState: false, onChatDeleted: true, onChatOpened: true, onChatState: true, onContactAdded: true, onGlobalParticipantsChanged: true, onGroupApprovalRequest: true, onGroupChange: true, onIncomingCall: false, onLabel: true, onLogout: true, // disable onMessage to avoid duplicates with onAnyMessage onMessage: false, onMessageDeleted: true, onNewProduct: true, onOrder: true, onPlugged: false, onPollVote: true, onReaction: true, onRemovedFromGroup: false, onStateChanged: false, onStory: false, } as const; const events = Object.entries(eventConfig) .filter(([_, enabled]) => enabled) .map(([event]) => event); const { data } = await axios.post(`${config.API_URL}/registerWebhook`, { args: { url, events }, }); console.log('✔️ Webhook registered:', data); }