courrier formatting
This commit is contained in:
parent
af18853d30
commit
67ab46879c
@ -157,6 +157,7 @@ interface ImapSessionData {
|
|||||||
lastActive: number;
|
lastActive: number;
|
||||||
mailboxes?: string[];
|
mailboxes?: string[];
|
||||||
lastVisit?: number;
|
lastVisit?: number;
|
||||||
|
defaultAccountId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -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 })
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user