import { getDb } from '../db/client.js' import { publish } from '../plugins/valkey.js' import { sendToSessionParticipant } from '../plugins/websocket.js' const sql = getDb() // Active session timers: sessionId → { warningTimeout, expiryTimeout } const sessionTimers = new Map() export const startSessionTimer = (sessionId, expiresAt) => { const now = Date.now() const expiresMs = new Date(expiresAt).getTime() const warningMs = expiresMs - 60_000 // 1 minute before expiry // Clear any existing timers clearSessionTimer(sessionId) const timers = {} // Warning timer (1 min before expiry) if (warningMs > now) { timers.warningTimeout = setTimeout(() => { onSessionWarning(sessionId) }, warningMs - now) } // Expiry timer if (expiresMs > now) { timers.expiryTimeout = setTimeout(() => { onSessionExpired(sessionId) }, expiresMs - now) } else { // Already expired onSessionExpired(sessionId) return } sessionTimers.set(sessionId, timers) } export const clearSessionTimer = (sessionId) => { const timers = sessionTimers.get(sessionId) if (timers) { if (timers.warningTimeout) clearTimeout(timers.warningTimeout) if (timers.expiryTimeout) clearTimeout(timers.expiryTimeout) sessionTimers.delete(sessionId) } } export const extendSessionTimer = async (sessionId, additionalMinutes) => { const [session] = await sql` UPDATE chat_sessions SET expires_at = expires_at + ${additionalMinutes + ' minutes'}::interval, extended_minutes = extended_minutes + ${additionalMinutes} WHERE id = ${sessionId} RETURNING id, expires_at ` if (session) { startSessionTimer(sessionId, session.expires_at) } return session } const onSessionWarning = (sessionId) => { const data = { type: 'session_timer', remaining_seconds: 60, session_id: sessionId } sendToSessionParticipant(sessionId, 'customer', data) sendToSessionParticipant(sessionId, 'mitra', data) } const onSessionExpired = async (sessionId) => { clearSessionTimer(sessionId) // Check session is still active const [session] = await sql` SELECT id, status FROM chat_sessions WHERE id = ${sessionId} AND status = 'active' ` if (!session) return // Notify both parties const data = { type: 'session_expired', session_id: sessionId } sendToSessionParticipant(sessionId, 'customer', data) sendToSessionParticipant(sessionId, 'mitra', data) // Also publish via Valkey for any listeners await publish(`session:${sessionId}:status`, data) } // Restore timers for active sessions on server restart export const restoreActiveTimers = async () => { const activeSessions = await sql` SELECT id, expires_at FROM chat_sessions WHERE status = 'active' AND expires_at IS NOT NULL AND expires_at > NOW() ` for (const session of activeSessions) { startSessionTimer(session.id, session.expires_at) } if (activeSessions.length > 0) { console.log(`Restored ${activeSessions.length} session timer(s)`) } }