"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildWebhookDispatcher = buildWebhookDispatcher; /** * Webhook Dispatcher — envia eventos para URLs registradas na tabela ext_webhooks. * * Fluxo: * hookBus emite ext:message.new ou ext:session.status * → dispatcher consulta ext_webhooks do tenant * → para cada webhook ativo que subscreve o evento, faz POST com envelope + assinatura HMAC * * Envelope enviado ao receptor: * { event, data, timestamp } * * Header de assinatura: * x-nw-signature: sha256= * HMAC-SHA256(secret, JSON.stringify(body)) * * Retry: sem retry automático nesta fase (Fase 5 scope mínimo). * Timeout: 10s por requisição para não bloquear o event loop. */ const crypto_1 = require("crypto"); let rootLogger; if (process.env.NODE_ENV === 'production') { rootLogger = require('../../../dist/config/logger').logger; } else { rootLogger = require('../../../backend/src/config/logger').logger; } const logger = rootLogger.child({ module: 'webhook-dispatcher' }); const DISPATCH_TIMEOUT_MS = 10000; // Eventos hookBus → nome de evento no payload enviado ao receptor const HOOK_TO_EVENT = { 'ext:message.new': 'message.new', 'ext:session.status': 'session.status', }; function sign(secret, body) { return 'sha256=' + (0, crypto_1.createHmac)('sha256', secret).update(body).digest('hex'); } async function dispatch(url, secret, event, data) { const body = JSON.stringify({ event, data, timestamp: Date.now() }); const sig = sign(secret, body); const ctrl = new AbortController(); const timer = setTimeout(() => ctrl.abort(), DISPATCH_TIMEOUT_MS); try { const res = await fetch(url, { method: 'POST', headers: { 'content-type': 'application/json', 'x-nw-signature': sig, }, body, signal: ctrl.signal, }); if (!res.ok) { logger.warn({ url, event, status: res.status }, '[webhook] Receptor retornou erro'); } } catch (err) { logger.warn({ url, event, err: err.message }, '[webhook] Falha ao entregar'); } finally { clearTimeout(timer); } } function buildWebhookDispatcher(prisma, hooks) { for (const [hookEvent, wsEvent] of Object.entries(HOOK_TO_EVENT)) { hooks.register(hookEvent, async (raw) => { const tenantId = raw.tenantId; if (!tenantId) return; // Carregar apenas webhooks ativos que subscrevem este evento let webhooks; try { webhooks = await prisma.extWebhook.findMany({ where: { tenantId, active: true, events: { has: wsEvent }, }, select: { url: true, secret: true }, }); } catch (err) { logger.error({ err, tenantId, event: wsEvent }, '[webhook] Erro ao buscar webhooks'); return; } if (webhooks.length === 0) return; // Retira tenantId do payload antes de enviar ao receptor const { tenantId: _tid, ...data } = raw; // Dispara em paralelo sem bloquear o hookBus Promise.allSettled(webhooks.map(wh => dispatch(wh.url, wh.secret, wsEvent, data))).catch(() => { }); }); } logger.info('[webhook] Dispatcher ativo para eventos: ' + Object.values(HOOK_TO_EVENT).join(', ')); } //# sourceMappingURL=webhook-dispatcher.js.map