'use server'; import { getImapConnection, getEmails, getEmailContent } from './email-service'; import { cacheEmailList, cacheEmailContent, cacheImapSession, getCachedEmailList, getRedisClient, warmupRedisCache } from '@/lib/redis'; // Keep track of ongoing prefetch operations to prevent duplicates const prefetchInProgress = new Map(); const lastPrefetchTime = new Map(); const PREFETCH_COOLDOWN_MS = 30000; // 30 seconds between prefetch operations /** * Check if we should prefetch for a user based on cooldown */ function shouldPrefetch(userId: string, key: string = 'general'): boolean { const prefetchKey = `${userId}:${key}`; // Check if prefetch is already in progress if (prefetchInProgress.get(prefetchKey)) { console.log(`Prefetch already in progress for ${prefetchKey}`); return false; } // Check cooldown const lastTime = lastPrefetchTime.get(prefetchKey) || 0; const now = Date.now(); if (now - lastTime < PREFETCH_COOLDOWN_MS) { console.log(`Prefetch cooldown active for ${prefetchKey}, last was ${Math.round((now - lastTime)/1000)}s ago`); return false; } // Mark as in progress and update last time prefetchInProgress.set(prefetchKey, true); lastPrefetchTime.set(prefetchKey, now); return true; } /** * Mark prefetch as completed */ function markPrefetchCompleted(userId: string, key: string = 'general'): void { const prefetchKey = `${userId}:${key}`; prefetchInProgress.set(prefetchKey, false); } /** * Get cached emails with timeout to ensure fast UI response * If cache access takes longer than timeout, return null to use regular IMAP fetch */ export async function getCachedEmailsWithTimeout( userId: string, folder: string, page: number, perPage: number, timeoutMs: number = 100, accountId?: string ): Promise { // Skip cache if accountId is 'loading-account' if (accountId === 'loading-account') { console.log(`Skipping cache for loading account`); return null; } return new Promise((resolve) => { const timeoutId = setTimeout(() => { console.log(`Cache access timeout for ${userId}:${folder}:${page}:${perPage}${accountId ? ` for account ${accountId}` : ''}`); resolve(null); }, timeoutMs); getCachedEmailList(userId, accountId || 'default', folder, page, perPage) .then(result => { clearTimeout(timeoutId); if (result) { console.log(`Using cached data for ${userId}:${folder}:${page}:${perPage}${accountId ? ` for account ${accountId}` : ''}`); resolve(result); } else { resolve(null); } }) .catch(err => { clearTimeout(timeoutId); console.error('Error accessing cache:', err); resolve(null); }); }); } /** * Refresh emails in background without blocking UI * This allows the UI to show cached data immediately while refreshing in background */ export async function refreshEmailsInBackground( userId: string, folder: string = 'INBOX', page: number = 1, perPage: number = 20 ): Promise { const prefetchKey = `refresh:${folder}:${page}`; // Skip if already in progress or in cooldown if (!shouldPrefetch(userId, prefetchKey)) { return; } // Use setTimeout to ensure this runs after current execution context setTimeout(async () => { try { console.log(`Background refresh for ${userId}:${folder}:${page}:${perPage}`); const freshData = await getEmails(userId, folder, page, perPage); console.log(`Background refresh completed for ${userId}:${folder}`); } catch (error) { console.error('Background refresh error:', error); } finally { markPrefetchCompleted(userId, prefetchKey); } }, 100); } /** * Prefetch basic email data for faster initial loading * This function should be called when a user logs in */ export async function prefetchUserEmailData(userId: string): Promise { // Skip if already in progress or in cooldown if (!shouldPrefetch(userId)) { return; } console.log(`Starting email prefetch for user ${userId}`); const startTime = Date.now(); try { // Connect to IMAP server const client = await getImapConnection(userId); // 1. Prefetch mailbox list const mailboxes = await client.list(); const mailboxPaths = mailboxes.map(mailbox => mailbox.path); // Cache mailbox list in session data await cacheImapSession(userId, { lastActive: Date.now(), mailboxes: mailboxPaths }); console.log(`Prefetched ${mailboxPaths.length} folders for user ${userId}`); // 2. Prefetch email lists for important folders const importantFolders = [ 'INBOX', mailboxPaths.find(path => path.toLowerCase().includes('sent')) || 'Sent', mailboxPaths.find(path => path.toLowerCase().includes('draft')) || 'Drafts' ].filter(Boolean); // Fetch first page of each important folder for (const folder of importantFolders) { try { console.log(`Prefetching emails for ${folder}`); const emailList = await getEmails(userId, folder, 1, 20); console.log(`Prefetched ${emailList.emails.length} emails for ${folder}`); } catch (error) { console.error(`Error prefetching emails for folder ${folder}:`, error); // Continue with other folders even if one fails } } // 3. Prefetch content of recent unread emails in INBOX try { // Get the list again (it's already cached so this will be fast) const inboxList = await getEmails(userId, 'INBOX', 1, 20); // Prefetch content for up to 5 recent unread emails const unreadEmails = inboxList.emails .filter(email => !email.flags.seen) .slice(0, 5); if (unreadEmails.length > 0) { console.log(`Prefetching content for ${unreadEmails.length} unread emails`); // Fetch content in parallel for speed await Promise.allSettled( unreadEmails.map(email => getEmailContent(userId, email.id, 'INBOX') .catch(err => console.error(`Error prefetching email ${email.id}:`, err)) ) ); console.log(`Completed prefetching content for unread emails`); } } catch (error) { console.error('Error prefetching unread email content:', error); } const duration = (Date.now() - startTime) / 1000; console.log(`Email prefetch completed for user ${userId} in ${duration.toFixed(2)}s`); } catch (error) { console.error('Error during email prefetch:', error); } finally { markPrefetchCompleted(userId); } } /** * Prefetch a specific folder's emails * This can be used when the user navigates to a folder to preload more pages */ export async function prefetchFolderEmails( userId: string, folder: string, pages: number = 3, startPage: number = 1, accountId?: string ): Promise { const prefetchKey = `folder:${folder}:${startPage}${accountId ? `:${accountId}` : ''}`; // Skip if already in progress or in cooldown if (!shouldPrefetch(userId, prefetchKey)) { return; } try { console.log(`Prefetching ${pages} pages of emails for folder ${folder} starting from page ${startPage}${accountId ? ` for account ${accountId}` : ''}`); // Calculate the range of pages to prefetch const pagesToFetch = Array.from( { length: pages }, (_, i) => startPage + i ); console.log(`Will prefetch pages: ${pagesToFetch.join(', ')}`); // Fetch multiple pages in parallel await Promise.allSettled( pagesToFetch.map(page => getEmails(userId, folder, page, 20) .then(result => { console.log(`Successfully prefetched and cached page ${page} of ${folder} with ${result.emails.length} emails`); return result; }) .catch(err => { console.error(`Error prefetching page ${page} of ${folder}:`, err); return null; }) ) ); console.log(`Completed prefetching ${pages} pages of ${folder}`); } catch (error) { console.error(`Error prefetching folder ${folder}:`, error); } finally { markPrefetchCompleted(userId, prefetchKey); } }