courrier formatting

This commit is contained in:
alma 2025-04-30 17:26:29 +02:00
parent af18853d30
commit 67ab46879c
2 changed files with 69 additions and 48 deletions

View File

@ -157,6 +157,7 @@ interface ImapSessionData {
lastActive: number; lastActive: number;
mailboxes?: string[]; mailboxes?: string[];
lastVisit?: number; lastVisit?: number;
defaultAccountId?: string;
} }
/** /**

View File

@ -36,8 +36,16 @@ const connectionPool: Record<string, {
lastUsed: number; lastUsed: number;
isConnecting: boolean; isConnecting: boolean;
connectionPromise?: Promise<ImapFlow>; connectionPromise?: Promise<ImapFlow>;
connectionAttempts?: number;
}> = {}; }> = {};
// Track overall connection metrics
let totalConnectionRequests = 0;
let totalNewConnections = 0;
let totalReuseConnections = 0;
let totalConnectionErrors = 0;
let lastMetricsReset = Date.now();
const CONNECTION_TIMEOUT = 15 * 60 * 1000; // Increased to 15 minutes for long-lived connections const CONNECTION_TIMEOUT = 15 * 60 * 1000; // Increased to 15 minutes for long-lived connections
const MAX_POOL_SIZE = 20; // Maximum number of connections to keep in the pool const MAX_POOL_SIZE = 20; // Maximum number of connections to keep in the pool
const CONNECTION_CHECK_INTERVAL = 60 * 1000; // Check every minute const CONNECTION_CHECK_INTERVAL = 60 * 1000; // Check every minute
@ -47,6 +55,16 @@ setInterval(() => {
const now = Date.now(); const now = Date.now();
const connectionKeys = Object.keys(connectionPool); const connectionKeys = Object.keys(connectionPool);
// If we've been collecting metrics for more than an hour, log and reset
if (now - lastMetricsReset > 60 * 60 * 1000) {
console.log(`[IMAP METRICS] Total requests: ${totalConnectionRequests}, New connections: ${totalNewConnections}, Reused: ${totalReuseConnections}, Errors: ${totalConnectionErrors}, Success rate: ${((totalReuseConnections + totalNewConnections) / totalConnectionRequests * 100).toFixed(2)}%`);
totalConnectionRequests = 0;
totalNewConnections = 0;
totalReuseConnections = 0;
totalConnectionErrors = 0;
lastMetricsReset = now;
}
// If we're over the pool size limit, sort by last used and remove oldest // If we're over the pool size limit, sort by last used and remove oldest
if (connectionKeys.length > MAX_POOL_SIZE) { if (connectionKeys.length > MAX_POOL_SIZE) {
const sortedConnections = connectionKeys const sortedConnections = connectionKeys
@ -94,6 +112,9 @@ setInterval(() => {
} }
} }
}); });
// Log connection pool status
console.log(`[IMAP POOL] Current size: ${connectionKeys.length}, Max: ${MAX_POOL_SIZE}`);
}, CONNECTION_CHECK_INTERVAL); }, CONNECTION_CHECK_INTERVAL);
/** /**
@ -104,6 +125,9 @@ export async function getImapConnection(
userId: string, userId: string,
accountId?: string accountId?: string
): Promise<ImapFlow> { ): Promise<ImapFlow> {
const startTime = Date.now();
totalConnectionRequests++;
console.log(`Getting IMAP connection for user ${userId}${accountId ? ` account ${accountId}` : ''}`); console.log(`Getting IMAP connection for user ${userId}${accountId ? ` account ${accountId}` : ''}`);
// Special handling for 'default' accountId - find the first available account // Special handling for 'default' accountId - find the first available account
@ -142,6 +166,7 @@ export async function getImapConnection(
}); });
} }
} else { } else {
totalConnectionErrors++;
throw new Error('No email accounts configured for this user'); throw new Error('No email accounts configured for this user');
} }
} }
@ -160,6 +185,8 @@ export async function getImapConnection(
try { try {
const client = await connection.connectionPromise; const client = await connection.connectionPromise;
connection.lastUsed = Date.now(); connection.lastUsed = Date.now();
totalReuseConnections++;
console.log(`[IMAP] Reused pending connection for ${connectionKey} in ${Date.now() - startTime}ms`);
return client; return client;
} catch (error) { } catch (error) {
console.error(`Error waiting for connection for ${connectionKey}:`, error); console.error(`Error waiting for connection for ${connectionKey}:`, error);
@ -177,6 +204,8 @@ export async function getImapConnection(
// Update session data in Redis // Update session data in Redis
await updateSessionData(userId, accountId); await updateSessionData(userId, accountId);
totalReuseConnections++;
console.log(`[IMAP] Successfully reused connection for ${connectionKey} in ${Date.now() - startTime}ms`);
return connection.client; return connection.client;
} else { } else {
console.log(`Existing connection for ${connectionKey} not usable, recreating`); console.log(`Existing connection for ${connectionKey} not usable, recreating`);
@ -198,60 +227,54 @@ export async function getImapConnection(
if (!credentials) { if (!credentials) {
console.log(`Credentials not found in cache for ${userId}${accountId ? ` account ${accountId}` : ''}, attempting database lookup`); console.log(`Credentials not found in cache for ${userId}${accountId ? ` account ${accountId}` : ''}, attempting database lookup`);
credentials = await getUserEmailCredentials(userId, accountId); credentials = await getUserEmailCredentials(userId, accountId);
if (!credentials) { if (!credentials) {
throw new Error('No email credentials found'); console.error(`No credentials found for user ${userId}${accountId ? ` account ${accountId}` : ''}`);
totalConnectionErrors++;
throw new Error('Email account credentials not found');
} }
// Cache credentials for future use // Cache the credentials for future use
await cacheEmailCredentials(userId, accountId, credentials); await cacheEmailCredentials(userId, accountId, credentials);
} }
// Validate credentials // Initialize connection tracking
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');
}
// Create connection record with connecting state
connectionPool[connectionKey] = { connectionPool[connectionKey] = {
client: null as any, // Will be set once connected client: null as any,
lastUsed: Date.now(), lastUsed: Date.now(),
isConnecting: true isConnecting: true,
connectionAttempts: (connectionPool[connectionKey]?.connectionAttempts || 0) + 1
}; };
// Create the connection promise // Create connection promise
const connectionPromise = createImapConnection(credentials, connectionKey); const connectionPromise = createImapConnection(credentials, connectionKey)
.then(client => {
// Update connection pool entry
connectionPool[connectionKey].client = client;
connectionPool[connectionKey].isConnecting = false;
connectionPool[connectionKey].lastUsed = Date.now();
// Update session data
updateSessionData(userId, accountId).catch(err => {
console.error(`Failed to update session data: ${err.message}`);
});
totalNewConnections++;
console.log(`[IMAP] Created new connection for ${connectionKey} in ${Date.now() - startTime}ms (attempt #${connectionPool[connectionKey].connectionAttempts})`);
return client;
})
.catch(error => {
// Handle connection error
console.error(`Failed to create IMAP connection for ${connectionKey}:`, error);
delete connectionPool[connectionKey];
totalConnectionErrors++;
throw error;
});
// Save the promise to allow other requests to wait for this connection
connectionPool[connectionKey].connectionPromise = connectionPromise; connectionPool[connectionKey].connectionPromise = connectionPromise;
try { return connectionPromise;
const client = await connectionPromise;
console.log(`Successfully connected to IMAP server for ${connectionKey}`);
// Update connection record
connectionPool[connectionKey] = {
client,
lastUsed: Date.now(),
isConnecting: false
};
// Update session data in Redis
await updateSessionData(userId, accountId);
return client;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
console.error(`IMAP connection error for ${connectionKey}:`, errorMessage);
// Clean up failed connection
delete connectionPool[connectionKey];
throw new Error(`Failed to connect to IMAP server: ${errorMessage}`);
}
} }
/** /**
@ -272,10 +295,7 @@ async function createImapConnection(credentials: EmailCredentials, connectionKey
rejectUnauthorized: false rejectUnauthorized: false
}, },
// Connection timeout settings // Connection timeout settings
disableAutoIdle: false, // Keep idle to auto-refresh connection disableAutoIdle: false // Keep idle to auto-refresh connection
idleTimeout: 60000, // 1 minute
idleRefreshTimeout: 30000, // 30 seconds
idleRefreshIntervalMs: 30 * 1000, // 30 seconds
}); });
await client.connect(); await client.connect();
@ -302,12 +322,12 @@ async function updateSessionData(userId: string, accountId?: string): Promise<vo
await cacheImapSession(userId, { await cacheImapSession(userId, {
...sessionData, ...sessionData,
lastActive: Date.now(), lastActive: Date.now(),
...(accountId && { lastAccountId: accountId }) ...(accountId && { defaultAccountId: accountId })
}); });
} else { } else {
await cacheImapSession(userId, { await cacheImapSession(userId, {
lastActive: Date.now(), lastActive: Date.now(),
...(accountId && { lastAccountId: accountId }) ...(accountId && { defaultAccountId: accountId })
}); });
} }
} }