cache.js (3691B)
1 const { db } = require('./firebase'); 2 const { EXCLUDED_JIDS } = require('./config'); 3 const { clients } = require('./sseManager'); 4 5 // --- IN-MEMORY CACHE --- 6 let chatsCache = new Map(); // chatId -> chat data 7 let messagesCache = new Map(); // chatId -> Map(msgId -> message data) 8 let cacheReady = false; 9 let consecutiveCacheFailures = 0; 10 11 async function warmCache() { 12 try { 13 console.log("System: Warming cache from Firestore..."); 14 15 // 1. Fetch Chats 16 const chatsSnap = await db.collection('Chats').get(); 17 chatsSnap.forEach(doc => { 18 if (!EXCLUDED_JIDS.has(doc.id)) { 19 chatsCache.set(doc.id, { id: doc.id, ...doc.data() }); 20 } 21 }); 22 23 // 2. Fetch all Messages via CollectionGroup 24 const msgsSnap = await db.collectionGroup('Messages').get(); 25 msgsSnap.forEach(doc => { 26 const chatId = doc.ref.parent.parent.id; 27 if (!messagesCache.has(chatId)) messagesCache.set(chatId, new Map()); 28 messagesCache.get(chatId).set(doc.id, doc.data()); 29 }); 30 31 cacheReady = true; 32 consecutiveCacheFailures = 0; 33 console.log(`System: Cache warm. ${chatsCache.size} chats, ${msgsSnap.size} messages.`); 34 35 // 3. Start Permanent Listeners 36 startPermanentListeners(); 37 38 } catch (err) { 39 consecutiveCacheFailures++; 40 const backoff = Math.min(5000 * Math.pow(2, consecutiveCacheFailures), 300000); // Max 5 min 41 console.error(`System: Cache warm failed (attempt ${consecutiveCacheFailures}). Retrying in ${backoff / 1000}s.`, err.message); 42 setTimeout(warmCache, backoff); 43 } 44 } 45 46 function startPermanentListeners() { 47 // Shared Permanent Listener for Chats 48 db.collection('Chats').onSnapshot(snapshot => { 49 const changes = []; 50 snapshot.docChanges().forEach(change => { 51 if (EXCLUDED_JIDS.has(change.doc.id)) return; 52 const data = { id: change.doc.id, ...change.doc.data() }; 53 chatsCache.set(change.doc.id, data); 54 changes.push({ type: change.type, doc: data }); 55 }); 56 if (changes.length > 0) { 57 const payload = `event: update\ndata: ${JSON.stringify(changes)}\n\n`; 58 clients.chats.forEach(res => { try { res.write(payload); } catch (e) {} }); 59 } 60 }); 61 62 // Shared Permanent Listener for Messages 63 db.collectionGroup('Messages').onSnapshot(snapshot => { 64 snapshot.docChanges().forEach(change => { 65 const chatId = change.doc.ref.parent.parent.id; 66 if (!messagesCache.has(chatId)) messagesCache.set(chatId, new Map()); 67 68 const data = change.doc.data(); 69 messagesCache.get(chatId).set(change.doc.id, data); 70 71 const chatClients = clients.messages.get(chatId); 72 if (chatClients) { 73 const payload = `event: update\ndata: ${JSON.stringify([{ type: change.type, doc: data }])}\n\n`; 74 chatClients.forEach(res => { try { res.write(payload); } catch (e) {} }); 75 } 76 77 // Broadcast to global sync clients too, stamped with chatId since 78 // the raw Firestore doc doesn't carry it (it's derived from the doc path). 79 if (clients.sync.size > 0) { 80 const syncData = { ...data, chatId }; 81 const syncPayload = `event: update\ndata: ${JSON.stringify([{ type: change.type, doc: syncData }])}\n\n`; 82 clients.sync.forEach(res => { try { res.write(syncPayload); } catch (e) {} }); 83 } 84 }); 85 }); 86 } 87 88 module.exports = { 89 chatsCache, 90 messagesCache, 91 warmCache, 92 isCacheReady: () => cacheReady 93 };