import * as plugins from '../../plugins.js'; import type { OpsServer } from '../classes.opsserver.js'; import * as interfaces from '../../../ts_interfaces/index.js'; import { SecurityLogger } from '../../security/index.js'; export class EmailOpsHandler { public typedrouter = new plugins.typedrequest.TypedRouter(); constructor(private opsServerRef: OpsServer) { // Add this handler's router to the parent this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter); this.registerHandlers(); } private registerHandlers(): void { // Get Queued Emails Handler this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getQueuedEmails', async (dataArg) => { const emailServer = this.opsServerRef.dcRouterRef.emailServer; if (!emailServer?.deliveryQueue) { return { items: [], total: 0 }; } const queue = emailServer.deliveryQueue; const stats = queue.getStats(); // Get all queue items and filter by status if provided const items = this.getQueueItems( dataArg.status, dataArg.limit || 50, dataArg.offset || 0 ); return { items, total: stats.queueSize, }; } ) ); // Get Sent Emails Handler this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getSentEmails', async (dataArg) => { const items = this.getQueueItems( 'delivered', dataArg.limit || 50, dataArg.offset || 0 ); return { items, total: items.length, // Note: total would ideally come from a counter }; } ) ); // Get Failed Emails Handler this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getFailedEmails', async (dataArg) => { const items = this.getQueueItems( 'failed', dataArg.limit || 50, dataArg.offset || 0 ); return { items, total: items.length, }; } ) ); // Resend Failed Email Handler this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'resendEmail', async (dataArg) => { const emailServer = this.opsServerRef.dcRouterRef.emailServer; if (!emailServer?.deliveryQueue) { return { success: false, error: 'Email server not available' }; } const queue = emailServer.deliveryQueue; const item = queue.getItem(dataArg.emailId); if (!item) { return { success: false, error: 'Email not found in queue' }; } if (item.status !== 'failed') { return { success: false, error: `Email is not in failed state (current: ${item.status})` }; } try { // Re-enqueue the failed email by creating a new queue entry // with the same data but reset attempt count const newQueueId = await queue.enqueue( item.processingResult, item.processingMode, item.route ); // Optionally remove the old failed entry await queue.removeItem(dataArg.emailId); return { success: true, newQueueId }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Failed to resend email' }; } } ) ); // Get Security Incidents Handler this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getSecurityIncidents', async (dataArg) => { const securityLogger = SecurityLogger.getInstance(); const filter: { level?: any; type?: any; } = {}; if (dataArg.level) { filter.level = dataArg.level; } if (dataArg.type) { filter.type = dataArg.type; } const incidents = securityLogger.getRecentEvents( dataArg.limit || 100, Object.keys(filter).length > 0 ? filter : undefined ); return { incidents: incidents.map(event => ({ timestamp: event.timestamp, level: event.level as interfaces.requests.TSecurityLogLevel, type: event.type as interfaces.requests.TSecurityEventType, message: event.message, details: event.details, ipAddress: event.ipAddress, userId: event.userId, sessionId: event.sessionId, emailId: event.emailId, domain: event.domain, action: event.action, result: event.result, success: event.success, })), total: incidents.length, }; } ) ); // Get Bounce Records Handler this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getBounceRecords', async (dataArg) => { const emailServer = this.opsServerRef.dcRouterRef.emailServer; // Get bounce manager from email server via reflection // BounceManager is private but we need to access it const bounceManager = (emailServer as any)?.bounceManager; if (!bounceManager) { return { records: [], suppressionList: [], total: 0 }; } // Get suppression list const suppressionList = bounceManager.getSuppressionList(); // Get hard bounced addresses and convert to records const hardBouncedAddresses = bounceManager.getHardBouncedAddresses(); // Create bounce records from the available data const records: interfaces.requests.IBounceRecord[] = []; for (const email of hardBouncedAddresses) { const bounceInfo = bounceManager.getBounceInfo(email); if (bounceInfo) { records.push({ id: `bounce-${email}`, recipient: email, sender: '', domain: email.split('@')[1] || '', bounceType: bounceInfo.type as interfaces.requests.TBounceType, bounceCategory: bounceInfo.category as interfaces.requests.TBounceCategory, timestamp: bounceInfo.lastBounce, processed: true, }); } } // Apply limit and offset const limit = dataArg.limit || 50; const offset = dataArg.offset || 0; const paginatedRecords = records.slice(offset, offset + limit); return { records: paginatedRecords, suppressionList, total: records.length, }; } ) ); // Remove from Suppression List Handler this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'removeFromSuppressionList', async (dataArg) => { const emailServer = this.opsServerRef.dcRouterRef.emailServer; const bounceManager = (emailServer as any)?.bounceManager; if (!bounceManager) { return { success: false, error: 'Bounce manager not available' }; } try { bounceManager.removeFromSuppressionList(dataArg.email); return { success: true }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Failed to remove from suppression list' }; } } ) ); } /** * Helper method to get queue items with filtering and pagination */ private getQueueItems( status?: interfaces.requests.TEmailQueueStatus, limit: number = 50, offset: number = 0 ): interfaces.requests.IEmailQueueItem[] { const emailServer = this.opsServerRef.dcRouterRef.emailServer; if (!emailServer?.deliveryQueue) { return []; } const queue = emailServer.deliveryQueue; const items: interfaces.requests.IEmailQueueItem[] = []; // Access the internal queue map via reflection // This is necessary because the queue doesn't expose iteration methods const queueMap = (queue as any).queue as Map; if (!queueMap) { return []; } // Filter and convert items for (const [id, item] of queueMap.entries()) { // Apply status filter if provided if (status && item.status !== status) { continue; } // Extract email details from processingResult if available const processingResult = item.processingResult; let from = ''; let to: string[] = []; let subject = ''; if (processingResult) { // Check if it's an Email object or raw email data if (processingResult.email) { from = processingResult.email.from || ''; to = processingResult.email.to || []; subject = processingResult.email.subject || ''; } else if (processingResult.from) { from = processingResult.from; to = processingResult.to || []; subject = processingResult.subject || ''; } } items.push({ id: item.id, processingMode: item.processingMode, status: item.status, attempts: item.attempts, nextAttempt: item.nextAttempt instanceof Date ? item.nextAttempt.getTime() : item.nextAttempt, lastError: item.lastError, createdAt: item.createdAt instanceof Date ? item.createdAt.getTime() : item.createdAt, updatedAt: item.updatedAt instanceof Date ? item.updatedAt.getTime() : item.updatedAt, deliveredAt: item.deliveredAt instanceof Date ? item.deliveredAt.getTime() : item.deliveredAt, from, to, subject, }); } // Sort by createdAt descending (newest first) items.sort((a, b) => b.createdAt - a.createdAt); // Apply pagination return items.slice(offset, offset + limit); } }