courrier multi account restore compose
This commit is contained in:
parent
aabf043b60
commit
60cb617d7f
@ -15,6 +15,8 @@ import {
|
|||||||
} from '@/lib/services/prefetch-service';
|
} from '@/lib/services/prefetch-service';
|
||||||
import { Email, EmailData } from './use-courrier';
|
import { Email, EmailData } from './use-courrier';
|
||||||
import { formatEmailForReplyOrForward } from '@/lib/utils/email-formatter';
|
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
|
// Add a global dispatcher for compatibility with older code
|
||||||
// This is a temporary solution until we fully migrate to the reducer pattern
|
// 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 = () => {
|
export const useEmailState = () => {
|
||||||
const [state, dispatch] = useReducer(emailReducer, initialState);
|
const [state, dispatch] = useReducer(emailReducer, initialState);
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
@ -622,75 +663,16 @@ export const useEmailState = () => {
|
|||||||
// Don't fetch if we're already fetching
|
// Don't fetch if we're already fetching
|
||||||
if (state.isLoadingUnreadCounts) return;
|
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 now = Date.now();
|
||||||
const lastViewedTimestamp = (window as any).__lastViewedEmailTimestamp || 0;
|
const lastViewedTimestamp = (window as any).__lastViewedEmailTimestamp || 0;
|
||||||
if (lastViewedTimestamp && now - lastViewedTimestamp < 2000) {
|
if (lastViewedTimestamp && now - lastViewedTimestamp < 5000) { // Increase from 2000ms
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset failure tracking if it's been more than 1 minute since last failure
|
// Cache unread counts with longer TTL (30-60 minutes)
|
||||||
if ((window as any).__unreadCountFailures && now - (window as any).__unreadCountFailures > 60000) {
|
// Implement batch fetching for all folders at once
|
||||||
(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]);
|
}, [dispatch, session?.user, state.isLoadingUnreadCounts, logEmailOp]);
|
||||||
|
|
||||||
// Calculate and update unread counts
|
// Calculate and update unread counts
|
||||||
@ -801,3 +783,11 @@ export const useEmailState = () => {
|
|||||||
viewEmail
|
viewEmail
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user