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;
mailboxes?: string[];
lastVisit?: number;
defaultAccountId?: string;
}
/**

View File

@ -36,8 +36,16 @@ const connectionPool: Record<string, {
lastUsed: number;
isConnecting: boolean;
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 MAX_POOL_SIZE = 20; // Maximum number of connections to keep in the pool
const CONNECTION_CHECK_INTERVAL = 60 * 1000; // Check every minute
@ -47,6 +55,16 @@ setInterval(() => {
const now = Date.now();
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 (connectionKeys.length > MAX_POOL_SIZE) {
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);
/**
@ -104,6 +125,9 @@ export async function getImapConnection(
userId: string,
accountId?: string
): Promise<ImapFlow> {
const startTime = Date.now();
totalConnectionRequests++;
console.log(`Getting IMAP connection for user ${userId}${accountId ? ` account ${accountId}` : ''}`);
// Special handling for 'default' accountId - find the first available account
@ -142,6 +166,7 @@ export async function getImapConnection(
});
}
} else {
totalConnectionErrors++;
throw new Error('No email accounts configured for this user');
}
}
@ -160,6 +185,8 @@ export async function getImapConnection(
try {
const client = await connection.connectionPromise;
connection.lastUsed = Date.now();
totalReuseConnections++;
console.log(`[IMAP] Reused pending connection for ${connectionKey} in ${Date.now() - startTime}ms`);
return client;
} catch (error) {
console.error(`Error waiting for connection for ${connectionKey}:`, error);
@ -177,6 +204,8 @@ export async function getImapConnection(
// Update session data in Redis
await updateSessionData(userId, accountId);
totalReuseConnections++;
console.log(`[IMAP] Successfully reused connection for ${connectionKey} in ${Date.now() - startTime}ms`);
return connection.client;
} else {
console.log(`Existing connection for ${connectionKey} not usable, recreating`);
@ -198,60 +227,54 @@ export async function getImapConnection(
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');
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);
}
// 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');
}
// Create connection record with connecting state
// Initialize connection tracking
connectionPool[connectionKey] = {
client: null as any, // Will be set once connected
client: null as any,
lastUsed: Date.now(),
isConnecting: true
isConnecting: true,
connectionAttempts: (connectionPool[connectionKey]?.connectionAttempts || 0) + 1
};
// Create the connection promise
const connectionPromise = createImapConnection(credentials, connectionKey);
// Create connection promise
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;
try {
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}`);
}
return connectionPromise;
}
/**
@ -272,10 +295,7 @@ async function createImapConnection(credentials: EmailCredentials, connectionKey
rejectUnauthorized: false
},
// Connection timeout settings
disableAutoIdle: false, // Keep idle to auto-refresh connection
idleTimeout: 60000, // 1 minute
idleRefreshTimeout: 30000, // 30 seconds
idleRefreshIntervalMs: 30 * 1000, // 30 seconds
disableAutoIdle: false // Keep idle to auto-refresh connection
});
await client.connect();
@ -302,12 +322,12 @@ async function updateSessionData(userId: string, accountId?: string): Promise<vo
await cacheImapSession(userId, {
...sessionData,
lastActive: Date.now(),
...(accountId && { lastAccountId: accountId })
...(accountId && { defaultAccountId: accountId })
});
} else {
await cacheImapSession(userId, {
lastActive: Date.now(),
...(accountId && { lastAccountId: accountId })
...(accountId && { defaultAccountId: accountId })
});
}
}