diff --git a/hooks/use-email-state.ts b/hooks/use-email-state.ts index 5176b6c1..40b5fe53 100644 --- a/hooks/use-email-state.ts +++ b/hooks/use-email-state.ts @@ -15,8 +15,6 @@ 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 @@ -27,45 +25,6 @@ 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(); @@ -663,16 +622,75 @@ export const useEmailState = () => { // Don't fetch if we're already fetching if (state.isLoadingUnreadCounts) return; - // Implement exponential backoff with higher thresholds - // Current implementation does this but needs tuning + // Skip fetching if an email was viewed recently (within last 5 seconds) const now = Date.now(); const lastViewedTimestamp = (window as any).__lastViewedEmailTimestamp || 0; - if (lastViewedTimestamp && now - lastViewedTimestamp < 5000) { // Increase from 2000ms + if (lastViewedTimestamp && now - lastViewedTimestamp < 5000) { // Increased from 2000ms for better performance return; } - // Cache unread counts with longer TTL (30-60 minutes) - // Implement batch fetching for all folders at once + // 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 }); + } }, [dispatch, session?.user, state.isLoadingUnreadCounts, logEmailOp]); // Calculate and update unread counts