Files
Sravan 3250530596 feat(db): complete async call-site conversion — Phase 3 done, validated on SQLite
The full sync→async conversion is complete and green on the SQLite backend. Every
DB call across the app now awaits the async adapter, so the identical code runs on
Postgres at cutover.

Converted (this commit finishes Phase 3):
- session.js: currentUser/apiKeyFromReq async → 63 route awaits + WS + static.
- routes.js: all ~250 R.* awaited; DTO helpers (namesFor, avatarsFor, buildMsgDTO,
  buildPollDTO, reactionsForMessage, postSystemMessage, pushGroupUpdate,
  issueRefreshToken, provisionFromBizgaze) made async; every `.map(x=>buildDTO(x))`
  restructured to `await Promise.all(...map(async...))` preserving order; `.filter`
  predicates that hit the DB moved to an `asyncFilter` helper; chained
  `R.x.y(...).length/.map/.filter` wrapped as `(await R.x.y(...)).method`; stream
  upload handlers (recording/transcript/attachment) made async.
- calls.js / signaling.js: all call/meeting fns async; leaveMeeting AWAITS
  persistCallHistory + finalizeTranscript BEFORE endCallByRoom (ordering matters —
  fire-and-forget would race the map teardown); WS handle()/cleanup() async with
  .catch guards.
- static.js: authAttachment(Raw) async (the .some carrier check became a loop),
  handleGet async; server.js dispatch catches handler rejections → 500 not a hang.
- media.js backfill, push.js, reminders.js, webhooks.js await their repo calls.

Validation on DB_BACKEND=sqlite: db-smoke 22/22; legacy e2e 80 checks pass with zero
FAILs (throws only at a PRE-EXISTING WS lobby-drift assertion, unrelated). Every
server file `node --check` clean.

Still on the branch — master untouched. Next: Phase 5 (pg backend + ~7 dialect
queries + data migration + Docker Postgres + cutover), then merge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 22:06:27 +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();
}
async function emit(event, tenantId, payload) {
const body = JSON.stringify({ event, ...payload });
// Per-tenant subscriptions
try {
for (const h of await 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, async (r) => { try { await 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 };