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>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
// 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 };
|
||||
Reference in New Issue
Block a user