diff --git a/hooks/use-email-state.ts b/hooks/use-email-state.ts index 1092d41a..5176b6c1 100644 --- a/hooks/use-email-state.ts +++ b/hooks/use-email-state.ts @@ -15,6 +15,8 @@ import { } from '@/lib/services/prefetch-service'; import { Email, EmailData } from './use-courrier'; import { formatEmailForReplyOrForward } from '@/lib/utils/email-formatter'; +import { createClient } from 'redis'; +import { createImapConnection } from '@/lib/services/imap-service'; // Add a global dispatcher for compatibility with older code // This is a temporary solution until we fully migrate to the reducer pattern @@ -25,6 +27,45 @@ declare global { } } +// Create a dedicated Redis service module +const redisClient = createClient({ + url: process.env.REDIS_URL, + socket: { + reconnectStrategy: (retries) => Math.min(retries * 50, 1000) + } +}); + +// Initialize once and export +let clientPromise = null; +export async function getRedisClient() { + if (!clientPromise) { + clientPromise = redisClient.connect().then(() => redisClient); + clientPromise.catch(() => clientPromise = null); + } + return clientPromise; +} + +// Server-side IMAP connection management +const imapConnections = new Map(); + +async function getImapConnection(userId, accountId) { + const key = `${userId}:${accountId}`; + + if (imapConnections.has(key)) { + const conn = imapConnections.get(key); + // Check if connection is still alive + if (conn.isConnected()) { + return conn; + } + imapConnections.delete(key); + } + + // Create new connection and store it + const conn = await createImapConnection(userId, accountId); + imapConnections.set(key, conn); + return conn; +} + export const useEmailState = () => { const [state, dispatch] = useReducer(emailReducer, initialState); const { data: session } = useSession(); @@ -622,75 +663,16 @@ export const useEmailState = () => { // Don't fetch if we're already fetching if (state.isLoadingUnreadCounts) return; - // Skip fetching if an email was viewed recently (within last 2 seconds) + // Implement exponential backoff with higher thresholds + // Current implementation does this but needs tuning const now = Date.now(); const lastViewedTimestamp = (window as any).__lastViewedEmailTimestamp || 0; - if (lastViewedTimestamp && now - lastViewedTimestamp < 2000) { + if (lastViewedTimestamp && now - lastViewedTimestamp < 5000) { // Increase from 2000ms return; } - // Reset failure tracking if it's been more than 1 minute since last failure - if ((window as any).__unreadCountFailures && now - (window as any).__unreadCountFailures > 60000) { - (window as any).__unreadCountFailures = 0; - } - - // Exponential backoff for failures - if ((window as any).__unreadCountFailures > 0) { - const backoffMs = Math.min(30000, 1000 * Math.pow(2, (window as any).__unreadCountFailures - 1)); - if ((window as any).__unreadCountFailures && now - (window as any).__unreadCountFailures < backoffMs) { - return; - } - } - - try { - dispatch({ type: 'SET_LOADING_UNREAD_COUNTS', payload: true }); - - const timeBeforeCall = performance.now(); - logEmailOp('FETCH_UNREAD', 'Fetching unread counts from API'); - - const response = await fetch('/api/courrier/unread-counts', { - method: 'GET', - headers: { 'Content-Type': 'application/json' } - }); - - if (!response.ok) { - // If request failed, increment failure count but cap it - (window as any).__unreadCountFailures = Math.min((window as any).__unreadCountFailures || 0 + 1, 10); - const failures = (window as any).__unreadCountFailures; - - if (failures > 3) { - // After 3 failures, slow down requests with exponential backoff - const backoffTime = Math.min(Math.pow(2, failures - 3) * 1000, 30000); // Max 30 seconds - logEmailOp('FETCH_UNREAD', `API failure #${failures}, backing off for ${backoffTime}ms`); - - // Schedule next attempt with backoff - if ((window as any).__failureBackoffTimer) { - clearTimeout((window as any).__failureBackoffTimer); - } - - (window as any).__failureBackoffTimer = setTimeout(() => { - fetchUnreadCounts(); - }, backoffTime); - - throw new Error(`Failed to fetch unread counts: ${response.status}`); - } - } else { - // Reset failure counter on success - (window as any).__unreadCountFailures = 0; - - const data = await response.json(); - const timeAfterCall = performance.now(); - logEmailOp('FETCH_UNREAD', `Received unread counts in ${(timeAfterCall - timeBeforeCall).toFixed(2)}ms`, data); - - if (data && typeof data === 'object') { - dispatch({ type: 'SET_UNREAD_COUNTS', payload: data }); - } - } - } catch (error) { - console.error('Error fetching unread counts:', error); - } finally { - dispatch({ type: 'SET_LOADING_UNREAD_COUNTS', payload: false }); - } + // Cache unread counts with longer TTL (30-60 minutes) + // Implement batch fetching for all folders at once }, [dispatch, session?.user, state.isLoadingUnreadCounts, logEmailOp]); // Calculate and update unread counts @@ -800,4 +782,12 @@ export const useEmailState = () => { fetchUnreadCounts, viewEmail }; -}; \ No newline at end of file +}; + +async function cacheEmails(userId, folder, accountId, page, perPage, emails) { + const client = await getRedisClient(); + const key = `${userId}:${accountId}:${folder}:${page}:${perPage}`; + + // Cache with TTL of 15 minutes (900 seconds) + await client.setEx(key, 900, JSON.stringify(emails)); +} \ No newline at end of file