/** * SMTP Client Connection Manager * Connection pooling and lifecycle management */ import * as net from 'node:net'; import * as tls from 'node:tls'; import { EventEmitter } from 'node:events'; import { DEFAULTS, CONNECTION_STATES } from './constants.js'; import { logConnection, logDebug } from './utils/logging.js'; import { generateConnectionId } from './utils/helpers.js'; export class ConnectionManager extends EventEmitter { options; connections = new Map(); pendingConnections = new Set(); idleTimeout = null; constructor(options) { super(); this.options = options; this.setupIdleCleanup(); } /** * Get or create a connection */ async getConnection() { // Try to reuse an idle connection if pooling is enabled if (this.options.pool) { const idleConnection = this.findIdleConnection(); if (idleConnection) { const connectionId = this.getConnectionId(idleConnection) || 'unknown'; logDebug('Reusing idle connection', this.options, { connectionId }); return idleConnection; } // Check if we can create a new connection if (this.getActiveConnectionCount() >= (this.options.maxConnections || DEFAULTS.MAX_CONNECTIONS)) { throw new Error('Maximum number of connections reached'); } } return this.createConnection(); } /** * Create a new connection */ async createConnection() { const connectionId = generateConnectionId(); try { this.pendingConnections.add(connectionId); logConnection('connecting', this.options, { connectionId }); const socket = await this.establishSocket(); const connection = { socket, state: CONNECTION_STATES.CONNECTED, options: this.options, secure: this.options.secure || false, createdAt: new Date(), lastActivity: new Date(), messageCount: 0 }; this.setupSocketHandlers(socket, connectionId); this.connections.set(connectionId, connection); this.pendingConnections.delete(connectionId); logConnection('connected', this.options, { connectionId }); this.emit('connection', connection); return connection; } catch (error) { this.pendingConnections.delete(connectionId); logConnection('error', this.options, { connectionId, error }); throw error; } } /** * Release a connection back to the pool or close it */ releaseConnection(connection) { const connectionId = this.getConnectionId(connection); if (!connectionId || !this.connections.has(connectionId)) { return; } if (this.options.pool && this.shouldReuseConnection(connection)) { // Return to pool connection.state = CONNECTION_STATES.READY; connection.lastActivity = new Date(); logDebug('Connection returned to pool', this.options, { connectionId }); } else { // Close connection this.closeConnection(connection); } } /** * Close a specific connection */ closeConnection(connection) { const connectionId = this.getConnectionId(connection); if (connectionId) { this.connections.delete(connectionId); } connection.state = CONNECTION_STATES.CLOSING; try { if (!connection.socket.destroyed) { connection.socket.destroy(); } } catch (error) { logDebug('Error closing connection', this.options, { error }); } logConnection('disconnected', this.options, { connectionId }); this.emit('disconnect', connection); } /** * Close all connections */ closeAllConnections() { logDebug('Closing all connections', this.options); for (const connection of this.connections.values()) { this.closeConnection(connection); } this.connections.clear(); this.pendingConnections.clear(); if (this.idleTimeout) { clearInterval(this.idleTimeout); this.idleTimeout = null; } } /** * Get connection pool status */ getPoolStatus() { const total = this.connections.size; const active = Array.from(this.connections.values()) .filter(conn => conn.state === CONNECTION_STATES.BUSY).length; const idle = total - active; const pending = this.pendingConnections.size; return { total, active, idle, pending }; } /** * Update connection activity timestamp */ updateActivity(connection) { connection.lastActivity = new Date(); } async establishSocket() { return new Promise((resolve, reject) => { const timeout = this.options.connectionTimeout || DEFAULTS.CONNECTION_TIMEOUT; let socket; if (this.options.secure) { // Direct TLS connection socket = tls.connect({ host: this.options.host, port: this.options.port, ...this.options.tls }); } else { // Plain connection socket = new net.Socket(); socket.connect(this.options.port, this.options.host); } const timeoutHandler = setTimeout(() => { socket.destroy(); reject(new Error(`Connection timeout after ${timeout}ms`)); }, timeout); // For TLS connections, we need to wait for 'secureConnect' instead of 'connect' const successEvent = this.options.secure ? 'secureConnect' : 'connect'; socket.once(successEvent, () => { clearTimeout(timeoutHandler); resolve(socket); }); socket.once('error', (error) => { clearTimeout(timeoutHandler); reject(error); }); }); } setupSocketHandlers(socket, connectionId) { const socketTimeout = this.options.socketTimeout || DEFAULTS.SOCKET_TIMEOUT; socket.setTimeout(socketTimeout); socket.on('timeout', () => { logDebug('Socket timeout', this.options, { connectionId }); socket.destroy(); }); socket.on('error', (error) => { logConnection('error', this.options, { connectionId, error }); this.connections.delete(connectionId); }); socket.on('close', () => { this.connections.delete(connectionId); logDebug('Socket closed', this.options, { connectionId }); }); } findIdleConnection() { for (const connection of this.connections.values()) { if (connection.state === CONNECTION_STATES.READY) { return connection; } } return null; } shouldReuseConnection(connection) { const maxMessages = this.options.maxMessages || DEFAULTS.MAX_MESSAGES; const maxAge = 300000; // 5 minutes const age = Date.now() - connection.createdAt.getTime(); return connection.messageCount < maxMessages && age < maxAge && !connection.socket.destroyed; } getActiveConnectionCount() { return this.connections.size + this.pendingConnections.size; } getConnectionId(connection) { for (const [id, conn] of this.connections.entries()) { if (conn === connection) { return id; } } return null; } setupIdleCleanup() { if (!this.options.pool) { return; } const cleanupInterval = DEFAULTS.POOL_IDLE_TIMEOUT; this.idleTimeout = setInterval(() => { const now = Date.now(); const connectionsToClose = []; for (const connection of this.connections.values()) { const idleTime = now - connection.lastActivity.getTime(); if (connection.state === CONNECTION_STATES.READY && idleTime > cleanupInterval) { connectionsToClose.push(connection); } } for (const connection of connectionsToClose) { logDebug('Closing idle connection', this.options); this.closeConnection(connection); } }, cleanupInterval); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29ubmVjdGlvbi1tYW5hZ2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vdHMvbWFpbC9kZWxpdmVyeS9zbXRwY2xpZW50L2Nvbm5lY3Rpb24tbWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7O0dBR0c7QUFFSCxPQUFPLEtBQUssR0FBRyxNQUFNLFVBQVUsQ0FBQztBQUNoQyxPQUFPLEtBQUssR0FBRyxNQUFNLFVBQVUsQ0FBQztBQUNoQyxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQzNDLE9BQU8sRUFBRSxRQUFRLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQU83RCxPQUFPLEVBQUUsYUFBYSxFQUFFLFFBQVEsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQzdELE9BQU8sRUFBRSxvQkFBb0IsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBRTFELE1BQU0sT0FBTyxpQkFBa0IsU0FBUSxZQUFZO0lBQ3pDLE9BQU8sQ0FBcUI7SUFDNUIsV0FBVyxHQUFpQyxJQUFJLEdBQUcsRUFBRSxDQUFDO0lBQ3RELGtCQUFrQixHQUFnQixJQUFJLEdBQUcsRUFBRSxDQUFDO0lBQzVDLFdBQVcsR0FBMEIsSUFBSSxDQUFDO0lBRWxELFlBQVksT0FBMkI7UUFDckMsS0FBSyxFQUFFLENBQUM7UUFDUixJQUFJLENBQUMsT0FBTyxHQUFHLE9BQU8sQ0FBQztRQUN2QixJQUFJLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQztJQUMxQixDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsYUFBYTtRQUN4Qix3REFBd0Q7UUFDeEQsSUFBSSxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDO1lBQ3RCLE1BQU0sY0FBYyxHQUFHLElBQUksQ0FBQyxrQkFBa0IsRUFBRSxDQUFDO1lBQ2pELElBQUksY0FBYyxFQUFFLENBQUM7Z0JBQ25CLE1BQU0sWUFBWSxHQUFHLElBQUksQ0FB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