import express from 'express'; import axios from 'axios'; import { WhatsAppMessage } from './types'; import { getHandler, Handler } from './chatHandlers'; import dotenv from 'dotenv'; dotenv.config(); if (process.env.NODE_ENV === 'development') { process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; } if ( process.env.NODE_ENV !== 'development' && process.env.NODE_TLS_REJECT_UNAUTHORIZED === '0' ) { throw new Error('NODE_TLS_REJECT_UNAUTHORIZED está activado en producción. Abortando.'); } const app = express(); const port = Number(process.env.PORT) || 3001; const agentUrl = process.env.LLM_AGENT_URL as string | undefined; const openWaUrl = process.env.OPEN_WA_URL as string | undefined; const config = { API_URL: openWaUrl || '', MAX_ATTEMPTS: parseInt(process.env.MAX_ATTEMPTS || '100', 10), RETRY_MS: parseInt(process.env.RETRY_MS || '2000', 10) }; function log(level: keyof Console | 'info' | 'warn' | 'error' | 'debug', ...args: unknown[]) { const logger = (console as any)[level] as ((...args: unknown[]) => void) | undefined; if (logger) logger(...args); else console.log(...args); } async function waitForGateway() { for (let i = 1; i <= config.MAX_ATTEMPTS; i++) { try { await axios.get(`${config.API_URL}/api-docs/`); log('info', '🟢 nucleo-whatsapp ready'); return; } catch(e) { log('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'); } async function clearWebhooks() { try { const { data } = await axios.post(`${config.API_URL}/listWebhooks`); const hooks = data?.response || []; if (!hooks.length) { log('info', 'No existing webhooks to remove'); return; } log('info', `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.value?.data?.response === true) { log('debug', `✔️ Removed webhook ${id}`); } else { log('warn', `⚠️ Failed to remove webhook ${id}`); } }); const ok = results.filter((r: PromiseSettledResult) => r.status === 'fulfilled' && (r as PromiseFulfilledResult).value?.data?.response === true).length; log('info', `Cleanup OK (${ok}/${hooks.length} removed)`); } catch (e: any) { log('error', 'Failed cleaning webhooks:', e.response?.data || e.message); } } async function registerWebhook() { const url = process.env.WEBHOOK_URL || `http://whatsapp-router:${port}/webhook`; const eventConfig = { onAck: false, onAddedToGroup: true, onAnyMessage: false, 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, onMessage: true, onMessageDeleted: true, onNewProduct: true, onOrder: true, onPlugged: false, onPollVote: true, onReaction: true, onRemovedFromGroup: false, onStateChanged: false, onStory: false, }; const events = Object.entries(eventConfig) .filter(([_, enabled]) => enabled) .map(([event]) => event); const { data } = await axios.post(`${config.API_URL}/registerWebhook`, { args: { url, events } }); log('info', '✔️ Webhook registered:', data); } app.use(express.json()); app.post('/webhook', async (req: express.Request, res: express.Response) => { let message: WhatsAppMessage | undefined; let text: string | undefined; let from: string | undefined; try { if (req.body && req.body.data) { // New webhook format from nucleo-whatsapp message = req.body.data as WhatsAppMessage; text = (req.body.data.body as string) || req.body.data.text; from = req.body.data.from; } else { throw new Error('Invalid webhook format'); } }catch{} if (message) { const tipo = typeof message === 'string' ? 'texto' : 'objeto'; const origen = from || (message?.chatId ?? 'desconocido'); log('info', `📩 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 && message.chatId) || from; 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, {message}); reply = agentRes.data.reply || agentRes.data; } else { reply = await handler(message); } await axios.post(`${openWaUrl}/sendText`, { args: { to: from, content: reply } }); } catch (err: any) { console.error('Error processing message', err.message); } res.sendStatus(200); }); app.listen(port, async () => { console.log(`WhatsApp router listening on ${port}`); try { await waitForGateway(); await clearWebhooks(); await registerWebhook(); } catch (err: any) { log('error', 'Webhook setup failed:', err.message); } });