98 lines
3.6 KiB
JavaScript
98 lines
3.6 KiB
JavaScript
"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-hex>
|
|
* 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
|