Files
BizGaze_Remote/server/webhooks.js
T
Sravan bda63b6f0a Merge origin/master (TURN/coturn + BizGaze-only login) into feature tree
Resolved conflicts in routes.js and share.html: kept the dev tree's superset
(ALLOW_LOCAL_LOGIN dev escape, avatar sync, richer login errors) which already
includes the incoming production BizGaze-only behavior; took the more descriptive
incoming comments. Restored 5 untracked modules (chat, calls, directory,
reminders, webhooks) that were missing from disk — required by routes/signaling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 16:27:59 +05:30

55 lines
2.3 KiB
JavaScript

// Outbound webhook delivery. emit(event, tenantId, payload) fans the event out to every
// active per-tenant subscription registered for that event, plus the legacy global
// BIZGAZE_WEBHOOK_URL (back-compat). Each delivery is HMAC-signed and retried on failure.
//
// NOTE (roadmap): retries are in-memory/best-effort. For guaranteed delivery this should
// move to a persistent queue when the app scales to multiple instances (see ARCHITECTURE.md).
const R = require('./repos');
const crypto = require('crypto');
const EVENTS = ['session.started', 'session.ended'];
function sign(secret, body) {
return crypto.createHmac('sha256', secret || '').update(body).digest('base64url');
}
const RETRY_DELAYS = [2000, 10000, 30000]; // after the first attempt
function deliver(url, secret, body, onDone) {
let attempt = 0;
const go = async () => {
attempt++;
let ok = false, status = 0, err = null;
try {
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-BizGaze-Signature': sign(secret, body), 'X-BizGaze-Event': (() => { try { return JSON.parse(body).event; } catch { return ''; } })() },
body,
signal: AbortSignal.timeout(10000),
});
status = res.status; ok = res.ok;
} catch (e) { err = (e && e.message) || 'delivery failed'; }
if (ok || attempt > RETRY_DELAYS.length) { if (onDone) onDone({ ok, status, err }); return; }
setTimeout(go, RETRY_DELAYS[attempt - 1]);
};
go();
}
function emit(event, tenantId, payload) {
const body = JSON.stringify({ event, ...payload });
// Per-tenant subscriptions
try {
for (const h of R.webhooks.activeForTenant(tenantId)) {
const subs = String(h.events || '').split(',').map((s) => s.trim());
if (subs.includes('*') || subs.includes(event)) {
deliver(h.url, h.secret, body, (r) => { try { R.webhooks.setStatus(h.id, r.ok ? 1 : 0, r.err || ('HTTP ' + r.status)); } catch (_) {} });
}
}
} catch (_) {}
// Legacy global webhook (back-compat): session.ended → BIZGAZE_WEBHOOK_URL, signed with SSO_SECRET.
if (event === 'session.ended' && process.env.BIZGAZE_WEBHOOK_URL) {
deliver(process.env.BIZGAZE_WEBHOOK_URL, process.env.SSO_SECRET || '', body);
}
}
module.exports = { emit, sign, EVENTS };