141 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			141 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /**
 | |
|  * SMTP Client Error Handler
 | |
|  * Error classification and recovery strategies
 | |
|  */
 | |
| 
 | |
| import { SmtpErrorType } from './constants.ts';
 | |
| import type { ISmtpResponse, ISmtpErrorContext, ISmtpClientOptions } from './interfaces.ts';
 | |
| import { logDebug } from './utils/logging.ts';
 | |
| 
 | |
| export class SmtpErrorHandler {
 | |
|   private options: ISmtpClientOptions;
 | |
|   
 | |
|   constructor(options: ISmtpClientOptions) {
 | |
|     this.options = options;
 | |
|   }
 | |
|   
 | |
|   /**
 | |
|    * Classify error type based on response or error
 | |
|    */
 | |
|   public classifyError(error: Error | ISmtpResponse, context?: ISmtpErrorContext): SmtpErrorType {
 | |
|     logDebug('Classifying error', this.options, { errorMessage: error instanceof Error ? error.message : String(error), context });
 | |
|     
 | |
|     // Handle Error objects
 | |
|     if (error instanceof Error) {
 | |
|       return this.classifyErrorByMessage(error);
 | |
|     }
 | |
|     
 | |
|     // Handle SMTP response codes
 | |
|     if (typeof error === 'object' && 'code' in error) {
 | |
|       return this.classifyErrorByCode(error.code);
 | |
|     }
 | |
|     
 | |
|     return SmtpErrorType.UNKNOWN_ERROR;
 | |
|   }
 | |
|   
 | |
|   /**
 | |
|    * Determine if error is retryable
 | |
|    */
 | |
|   public isRetryable(errorType: SmtpErrorType, response?: ISmtpResponse): boolean {
 | |
|     switch (errorType) {
 | |
|       case SmtpErrorType.CONNECTION_ERROR:
 | |
|       case SmtpErrorType.TIMEOUT_ERROR:
 | |
|         return true;
 | |
|       
 | |
|       case SmtpErrorType.PROTOCOL_ERROR:
 | |
|         // Only retry on temporary failures (4xx codes)
 | |
|         return response ? response.code >= 400 && response.code < 500 : false;
 | |
|       
 | |
|       case SmtpErrorType.AUTHENTICATION_ERROR:
 | |
|       case SmtpErrorType.TLS_ERROR:
 | |
|       case SmtpErrorType.SYNTAX_ERROR:
 | |
|       case SmtpErrorType.MAILBOX_ERROR:
 | |
|       case SmtpErrorType.QUOTA_ERROR:
 | |
|         return false;
 | |
|       
 | |
|       default:
 | |
|         return false;
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   /**
 | |
|    * Get retry delay for error type
 | |
|    */
 | |
|   public getRetryDelay(attempt: number, errorType: SmtpErrorType): number {
 | |
|     const baseDelay = 1000; // 1 second
 | |
|     const maxDelay = 30000;  // 30 seconds
 | |
|     
 | |
|     // Exponential backoff with jitter
 | |
|     const delay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
 | |
|     const jitter = Math.random() * 0.1 * delay; // 10% jitter
 | |
|     
 | |
|     return Math.floor(delay + jitter);
 | |
|   }
 | |
|   
 | |
|   /**
 | |
|    * Create enhanced error with context
 | |
|    */
 | |
|   public createError(
 | |
|     message: string,
 | |
|     errorType: SmtpErrorType,
 | |
|     context?: ISmtpErrorContext,
 | |
|     originalError?: Error
 | |
|   ): Error {
 | |
|     const error = new Error(message);
 | |
|     (error as any).type = errorType;
 | |
|     (error as any).context = context;
 | |
|     (error as any).originalError = originalError;
 | |
|     
 | |
|     return error;
 | |
|   }
 | |
|   
 | |
|   private classifyErrorByMessage(error: Error): SmtpErrorType {
 | |
|     const message = error.message.toLowerCase();
 | |
|     
 | |
|     if (message.includes('timeout') || message.includes('etimedout')) {
 | |
|       return SmtpErrorType.TIMEOUT_ERROR;
 | |
|     }
 | |
|     
 | |
|     if (message.includes('connect') || message.includes('econnrefused') || 
 | |
|         message.includes('enotfound') || message.includes('enetunreach')) {
 | |
|       return SmtpErrorType.CONNECTION_ERROR;
 | |
|     }
 | |
|     
 | |
|     if (message.includes('tls') || message.includes('ssl') || 
 | |
|         message.includes('certificate') || message.includes('handshake')) {
 | |
|       return SmtpErrorType.TLS_ERROR;
 | |
|     }
 | |
|     
 | |
|     if (message.includes('auth')) {
 | |
|       return SmtpErrorType.AUTHENTICATION_ERROR;
 | |
|     }
 | |
|     
 | |
|     return SmtpErrorType.UNKNOWN_ERROR;
 | |
|   }
 | |
|   
 | |
|   private classifyErrorByCode(code: number): SmtpErrorType {
 | |
|     if (code >= 500) {
 | |
|       // Permanent failures
 | |
|       if (code === 550 || code === 551 || code === 553) {
 | |
|         return SmtpErrorType.MAILBOX_ERROR;
 | |
|       }
 | |
|       if (code === 552) {
 | |
|         return SmtpErrorType.QUOTA_ERROR;
 | |
|       }
 | |
|       if (code === 500 || code === 501 || code === 502 || code === 504) {
 | |
|         return SmtpErrorType.SYNTAX_ERROR;
 | |
|       }
 | |
|       return SmtpErrorType.PROTOCOL_ERROR;
 | |
|     }
 | |
|     
 | |
|     if (code >= 400) {
 | |
|       // Temporary failures
 | |
|       if (code === 450 || code === 451 || code === 452) {
 | |
|         return SmtpErrorType.QUOTA_ERROR;
 | |
|       }
 | |
|       return SmtpErrorType.PROTOCOL_ERROR;
 | |
|     }
 | |
|     
 | |
|     return SmtpErrorType.UNKNOWN_ERROR;
 | |
|   }
 | |
| } |