From 7fc8e83330adf45012d27b154ec930c77f9701d7 Mon Sep 17 00:00:00 2001 From: alma Date: Mon, 28 Apr 2025 16:36:09 +0200 Subject: [PATCH] courrier multi account restore compose --- lib/services/email-service.ts | 120 +++++++++------------------------- 1 file changed, 30 insertions(+), 90 deletions(-) diff --git a/lib/services/email-service.ts b/lib/services/email-service.ts index a6ecb9b5..d7e916a9 100644 --- a/lib/services/email-service.ts +++ b/lib/services/email-service.ts @@ -30,94 +30,45 @@ export interface EmailListResult { mailboxes: string[]; } -// Connection pool to reuse IMAP clients +// Connection pool management const connectionPool: Record = {}; const CONNECTION_TIMEOUT = 5 * 60 * 1000; // 5 minutes // Clean up idle connections periodically setInterval(() => { const now = Date.now(); - Object.entries(connectionPool).forEach(([key, { client, lastUsed }]) => { if (now - lastUsed > CONNECTION_TIMEOUT) { console.log(`Closing idle IMAP connection for ${key}`); - client.logout().catch(err => { - console.error(`Error closing connection for ${key}:`, err); - }); + if (client && client.usable) { + client.logout().catch(err => { + console.error(`Error closing connection for ${key}:`, err); + }); + } delete connectionPool[key]; } }); }, 60 * 1000); // Check every minute /** - * Get IMAP connection for a user, reusing existing connections when possible + * Get IMAP connection for a user */ -export async function getImapConnection( - userId: string, - accountId?: string -): Promise { - console.log(`Getting IMAP connection for user ${userId}${accountId ? ` account ${accountId}` : ''}`); +export async function getImapConnection(userId: string, accountId?: string): Promise { + const key = `${userId}:${accountId || 'default'}`; - // First try to get credentials from Redis cache - let credentials = accountId - ? await getCachedEmailCredentials(userId, accountId) - : await getCachedEmailCredentials(userId, 'default'); + // Check if we have a valid connection in the pool + const existingConnection = connectionPool[key]; + if (existingConnection && existingConnection.client.usable) { + existingConnection.lastUsed = Date.now(); + return existingConnection.client; + } - // If not in cache, get from database and cache them + // Get credentials from cache or database + const credentials = await getCachedEmailCredentials(userId, accountId || 'default'); if (!credentials) { - console.log(`Credentials not found in cache for ${userId}${accountId ? ` account ${accountId}` : ''}, attempting database lookup`); - credentials = await getUserEmailCredentials(userId, accountId); - if (!credentials) { - throw new Error('No email credentials found'); - } - - // Cache credentials for future use - await cacheEmailCredentials(userId, accountId || 'default', credentials); + throw new Error('No email credentials found'); } - // Validate credentials - if (!credentials.password) { - console.error(`Missing password in credentials for user ${userId}${accountId ? ` account ${accountId}` : ''}`); - throw new Error('No password configured'); - } - - if (!credentials.email || !credentials.host) { - console.error(`Incomplete credentials for user ${userId}${accountId ? ` account ${accountId}` : ''}`); - throw new Error('Invalid email credentials configuration'); - } - - // Use accountId in connection key to ensure different accounts get different connections - const connectionKey = `${userId}:${accountId || 'default'}`; - const existingConnection = connectionPool[connectionKey]; - - // Try to get session data from Redis - const sessionData = await getCachedImapSession(userId); - - // Return existing connection if available and connected - if (existingConnection) { - try { - if (existingConnection.client.usable) { - existingConnection.lastUsed = Date.now(); - console.log(`Reusing existing IMAP connection for ${connectionKey}`); - - // Update session data in Redis - if (sessionData) { - await cacheImapSession(userId, { - ...sessionData, - lastActive: Date.now() - }); - } - - return existingConnection.client; - } - } catch (error) { - console.warn(`Existing connection for ${connectionKey} is not usable, creating new connection`); - // Will create a new connection below - } - } - - console.log(`Creating new IMAP connection for ${connectionKey}`); - // Create new connection const client = new ImapFlow({ host: credentials.host, @@ -125,30 +76,18 @@ export async function getImapConnection( secure: true, auth: { user: credentials.email, - pass: credentials.password, + pass: credentials.password }, - logger: false, - emitLogs: false, - tls: { - rejectUnauthorized: false - } + logger: false }); try { await client.connect(); - console.log(`Successfully connected to IMAP server for ${connectionKey}`); - - // Store in connection pool - connectionPool[connectionKey] = { - client, - lastUsed: Date.now() - }; - + connectionPool[key] = { client, lastUsed: Date.now() }; return client; - } catch (error: unknown) { - const errorMessage = error instanceof Error ? error.message : 'Unknown error'; - console.error(`IMAP connection error for ${connectionKey}:`, errorMessage); - throw new Error(`Failed to connect to IMAP server: ${errorMessage}`); + } catch (error) { + console.error('Error connecting to IMAP:', error); + throw error; } } @@ -281,7 +220,7 @@ export async function getEmails( // Get IMAP connection client = await getImapConnection(userId, accountId); - if (!client) { + if (!client || !client.usable) { throw new Error('Failed to establish IMAP connection'); } @@ -381,7 +320,7 @@ export async function getEmails( console.error('Error fetching emails:', error); throw error; } finally { - if (client) { + if (client && client.usable) { try { await client.mailboxClose(); } catch (error) { @@ -472,9 +411,10 @@ export async function getEmailContent( contentType: att.contentType, size: att.size || 0 })), - html: rawHtml, - text: parsedEmail.text || undefined, - content: rawHtml || parsedEmail.text || '', + content: { + text: parsedEmail.text || '', + html: rawHtml || '' + }, folder, contentFetched: true, size: size || 0