const {logger} = require('./logger'); const {createClient} = require("redis"); const crypto = require("crypto"); let redisClient; const REDIS_URL = (process.env.REDIS_URL || "localhost:6379").replace(/((^\w+:|^)\/\/|^)/, 'redis://'); redisClient = createClient({url: REDIS_URL}); redisClient.on("error", (error) => logger.error(`Redis cache error : ${error}`)); void redisClient.connect(); function generateNodeID() { const buffer = crypto.randomBytes(8); return "node_"+buffer.readBigUInt64BE(0).toString(); } const PING_INTERVAL = parseInt(process.env.PING_INTERVAL_SECONDS) || 25; const CACHE_REFRESH_INTERVAL = parseInt(process.env.CACHE_REFRESH_INTERVAL_SECONDS) || 10; const pingInterval = PING_INTERVAL + PING_INTERVAL/2; const cacheRefreshInterval = CACHE_REFRESH_INTERVAL + CACHE_REFRESH_INTERVAL/2; const cacheRefreshIntervalMs = CACHE_REFRESH_INTERVAL * 1000; let lastCacheUpdateTime = 0; let cacheRefresher = null; const nodeID = process.env.HOSTNAME || generateNodeID(); const addSessionToCache = async function (sessionID, sessionData) { try { await redisClient.set(`active_sessions:${sessionID}`, JSON.stringify(sessionData), 'EX', pingInterval); logger.debug(`Session ${sessionID} stored in Redis`); } catch (error) { logger.error(error); } } const renewSession = async function (sessionID){ try { await redisClient.expire(`active_sessions:${sessionID}`, pingInterval); logger.debug(`Session ${sessionID} renewed in Redis`); } catch (error) { logger.error(error); } } const getSessionFromCache = async function (sessionID) { try { const sessionData = await redisClient.get(`active_sessions:${sessionID}`); if (sessionData) { logger.debug(`Session ${sessionID} retrieved from Redis`); return JSON.parse(sessionData); } return null; } catch (error) { logger.error(error); return null; } } const removeSessionFromCache = async function (sessionID) { try { await redisClient.del(`active_sessions:${sessionID}`); logger.debug(`Session ${sessionID} removed from Redis`); } catch (error) { logger.error(error); } } const setNodeSessions = async function (nodeID, sessionIDs) { try { await redisClient.set(`node:${nodeID}:sessions`, JSON.stringify(sessionIDs), 'EX', cacheRefreshInterval); logger.debug(`Node ${nodeID} sessions stored in Redis`); } catch (error) { logger.error(error); } } function startCacheRefresher(io) { if (cacheRefresher) clearInterval(cacheRefresher); cacheRefresher = setInterval(async () => { const now = Date.now(); if (now - lastCacheUpdateTime < cacheRefreshIntervalMs) { return; } logger.debug('Background refresh triggered'); try { const startTime = performance.now(); const sessionIDs = new Set(); const result = await io.fetchSockets(); result.forEach((socket) => { if (socket.handshake.query.sessId) { sessionIDs.add(socket.handshake.query.sessId); } }) await setNodeSessions(nodeID, Array.from(sessionIDs)); lastCacheUpdateTime = now; const duration = performance.now() - startTime; logger.info(`Background refresh complete: ${duration}ms, ${result.length} sockets`); } catch (error) { logger.error(`Background refresh error: ${error}`); } }, cacheRefreshIntervalMs / 2); } module.exports = { addSessionToCache, renewSession, getSessionFromCache, removeSessionFromCache, startCacheRefresher, }