611 lines
15 KiB
TypeScript
611 lines
15 KiB
TypeScript
|
import {
|
||
|
PlatformError,
|
||
|
NetworkError,
|
||
|
AuthenticationError,
|
||
|
OperationError,
|
||
|
ConfigurationError
|
||
|
} from './base.errors.js';
|
||
|
import type { IErrorContext } from './base.errors.js';
|
||
|
|
||
|
import {
|
||
|
MTA_CONNECTION_ERROR,
|
||
|
MTA_AUTHENTICATION_ERROR,
|
||
|
MTA_DELIVERY_ERROR,
|
||
|
MTA_CONFIGURATION_ERROR,
|
||
|
MTA_DNS_ERROR,
|
||
|
MTA_TIMEOUT_ERROR,
|
||
|
MTA_PROTOCOL_ERROR
|
||
|
} from './error.codes.js';
|
||
|
|
||
|
/**
|
||
|
* Base class for MTA connection errors
|
||
|
*/
|
||
|
export class MtaConnectionError extends NetworkError {
|
||
|
/**
|
||
|
* Creates a new MTA connection error
|
||
|
*
|
||
|
* @param message Error message
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
constructor(
|
||
|
message: string,
|
||
|
context: IErrorContext = {}
|
||
|
) {
|
||
|
super(message, MTA_CONNECTION_ERROR, context);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates an instance for a DNS resolution error
|
||
|
*
|
||
|
* @param hostname Hostname that failed to resolve
|
||
|
* @param originalError Original error
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
public static dnsError(
|
||
|
hostname: string,
|
||
|
originalError?: Error,
|
||
|
context: IErrorContext = {}
|
||
|
): MtaConnectionError {
|
||
|
const errorMsg = originalError ? `: ${originalError.message}` : '';
|
||
|
return new MtaConnectionError(
|
||
|
`Failed to resolve DNS for ${hostname}${errorMsg}`,
|
||
|
{
|
||
|
...context,
|
||
|
data: {
|
||
|
...context.data,
|
||
|
hostname,
|
||
|
originalError: originalError ? {
|
||
|
message: originalError.message,
|
||
|
stack: originalError.stack
|
||
|
} : undefined
|
||
|
},
|
||
|
userMessage: `Could not connect to mail server for ${hostname}.`
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates an instance for a connection timeout
|
||
|
*
|
||
|
* @param hostname Hostname that timed out
|
||
|
* @param port Port number
|
||
|
* @param timeout Timeout in milliseconds
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
public static timeout(
|
||
|
hostname: string,
|
||
|
port: number,
|
||
|
timeout: number,
|
||
|
context: IErrorContext = {}
|
||
|
): MtaConnectionError {
|
||
|
return new MtaConnectionError(
|
||
|
`Connection to ${hostname}:${port} timed out after ${timeout}ms`,
|
||
|
{
|
||
|
...context,
|
||
|
data: {
|
||
|
...context.data,
|
||
|
hostname,
|
||
|
port,
|
||
|
timeout
|
||
|
},
|
||
|
userMessage: `Connection to mail server timed out.`
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates an instance for a connection refused error
|
||
|
*
|
||
|
* @param hostname Hostname that refused connection
|
||
|
* @param port Port number
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
public static refused(
|
||
|
hostname: string,
|
||
|
port: number,
|
||
|
context: IErrorContext = {}
|
||
|
): MtaConnectionError {
|
||
|
return new MtaConnectionError(
|
||
|
`Connection to ${hostname}:${port} refused`,
|
||
|
{
|
||
|
...context,
|
||
|
data: {
|
||
|
...context.data,
|
||
|
hostname,
|
||
|
port
|
||
|
},
|
||
|
userMessage: `Connection to mail server was refused.`
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Error class for MTA authentication errors
|
||
|
*/
|
||
|
export class MtaAuthenticationError extends AuthenticationError {
|
||
|
/**
|
||
|
* Creates a new MTA authentication error
|
||
|
*
|
||
|
* @param message Error message
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
constructor(
|
||
|
message: string,
|
||
|
context: IErrorContext = {}
|
||
|
) {
|
||
|
super(message, MTA_AUTHENTICATION_ERROR, context);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates an instance for invalid credentials
|
||
|
*
|
||
|
* @param hostname Hostname where authentication failed
|
||
|
* @param username Username that failed authentication
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
public static invalidCredentials(
|
||
|
hostname: string,
|
||
|
username: string,
|
||
|
context: IErrorContext = {}
|
||
|
): MtaAuthenticationError {
|
||
|
return new MtaAuthenticationError(
|
||
|
`Authentication failed for user ${username} at ${hostname}`,
|
||
|
{
|
||
|
...context,
|
||
|
data: {
|
||
|
...context.data,
|
||
|
hostname,
|
||
|
username
|
||
|
},
|
||
|
userMessage: `Authentication to mail server failed.`
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates an instance for unsupported authentication method
|
||
|
*
|
||
|
* @param hostname Hostname
|
||
|
* @param method Authentication method that is not supported
|
||
|
* @param supportedMethods List of supported authentication methods
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
public static unsupportedMethod(
|
||
|
hostname: string,
|
||
|
method: string,
|
||
|
supportedMethods: string[] = [],
|
||
|
context: IErrorContext = {}
|
||
|
): MtaAuthenticationError {
|
||
|
return new MtaAuthenticationError(
|
||
|
`Authentication method ${method} not supported by ${hostname}${supportedMethods.length > 0 ? `. Supported methods: ${supportedMethods.join(', ')}` : ''}`,
|
||
|
{
|
||
|
...context,
|
||
|
data: {
|
||
|
...context.data,
|
||
|
hostname,
|
||
|
method,
|
||
|
supportedMethods
|
||
|
},
|
||
|
userMessage: `The mail server doesn't support the required authentication method.`
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Error class for MTA delivery errors
|
||
|
*/
|
||
|
export class MtaDeliveryError extends OperationError {
|
||
|
/**
|
||
|
* Creates a new MTA delivery error
|
||
|
*
|
||
|
* @param message Error message
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
constructor(
|
||
|
message: string,
|
||
|
context: IErrorContext = {}
|
||
|
) {
|
||
|
super(message, MTA_DELIVERY_ERROR, context);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates an instance for a permanent delivery failure
|
||
|
*
|
||
|
* @param message Error message
|
||
|
* @param recipientAddress Recipient email address
|
||
|
* @param statusCode SMTP status code
|
||
|
* @param smtpResponse Full SMTP response
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
public static permanent(
|
||
|
message: string,
|
||
|
recipientAddress: string,
|
||
|
statusCode?: string,
|
||
|
smtpResponse?: string,
|
||
|
context: IErrorContext = {}
|
||
|
): MtaDeliveryError {
|
||
|
const statusCodeStr = statusCode ? ` (${statusCode})` : '';
|
||
|
return new MtaDeliveryError(
|
||
|
`Permanent delivery failure to ${recipientAddress}${statusCodeStr}: ${message}`,
|
||
|
{
|
||
|
...context,
|
||
|
data: {
|
||
|
...context.data,
|
||
|
recipientAddress,
|
||
|
statusCode,
|
||
|
smtpResponse,
|
||
|
permanent: true
|
||
|
},
|
||
|
userMessage: `The email could not be delivered to ${recipientAddress}.`
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates an instance for a temporary delivery failure
|
||
|
*
|
||
|
* @param message Error message
|
||
|
* @param recipientAddress Recipient email address
|
||
|
* @param statusCode SMTP status code
|
||
|
* @param smtpResponse Full SMTP response
|
||
|
* @param maxRetries Maximum number of retries
|
||
|
* @param currentRetry Current retry count
|
||
|
* @param retryDelay Delay between retries in ms
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
public static temporary(
|
||
|
message: string,
|
||
|
recipientAddress: string,
|
||
|
statusCode?: string,
|
||
|
smtpResponse?: string,
|
||
|
maxRetries: number = 3,
|
||
|
currentRetry: number = 0,
|
||
|
retryDelay: number = 60000,
|
||
|
context: IErrorContext = {}
|
||
|
): MtaDeliveryError {
|
||
|
const statusCodeStr = statusCode ? ` (${statusCode})` : '';
|
||
|
const error = new MtaDeliveryError(
|
||
|
`Temporary delivery failure to ${recipientAddress}${statusCodeStr}: ${message}`,
|
||
|
{
|
||
|
...context,
|
||
|
data: {
|
||
|
...context.data,
|
||
|
recipientAddress,
|
||
|
statusCode,
|
||
|
smtpResponse,
|
||
|
permanent: false
|
||
|
},
|
||
|
userMessage: `The email delivery to ${recipientAddress} failed temporarily. It will be retried.`
|
||
|
}
|
||
|
);
|
||
|
|
||
|
return error.withRetry(maxRetries, currentRetry, retryDelay) as MtaDeliveryError;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check if this is a permanent delivery failure
|
||
|
*/
|
||
|
public isPermanent(): boolean {
|
||
|
return !!this.context.data?.permanent;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the recipient address associated with this delivery error
|
||
|
*/
|
||
|
public getRecipientAddress(): string | undefined {
|
||
|
return this.context.data?.recipientAddress;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the SMTP status code associated with this delivery error
|
||
|
*/
|
||
|
public getStatusCode(): string | undefined {
|
||
|
return this.context.data?.statusCode;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Error class for MTA configuration errors
|
||
|
*/
|
||
|
export class MtaConfigurationError extends ConfigurationError {
|
||
|
/**
|
||
|
* Creates a new MTA configuration error
|
||
|
*
|
||
|
* @param message Error message
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
constructor(
|
||
|
message: string,
|
||
|
context: IErrorContext = {}
|
||
|
) {
|
||
|
super(message, MTA_CONFIGURATION_ERROR, context);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates an instance for a missing configuration value
|
||
|
*
|
||
|
* @param propertyPath Path to the missing property
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
public static missingConfig(
|
||
|
propertyPath: string,
|
||
|
context: IErrorContext = {}
|
||
|
): MtaConfigurationError {
|
||
|
return new MtaConfigurationError(
|
||
|
`Missing required configuration: ${propertyPath}`,
|
||
|
{
|
||
|
...context,
|
||
|
data: {
|
||
|
...context.data,
|
||
|
propertyPath
|
||
|
},
|
||
|
userMessage: `The mail server is missing required configuration.`
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates an instance for an invalid configuration value
|
||
|
*
|
||
|
* @param propertyPath Path to the invalid property
|
||
|
* @param value Current value
|
||
|
* @param expectedType Expected type or format
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
public static invalidConfig(
|
||
|
propertyPath: string,
|
||
|
value: any,
|
||
|
expectedType: string,
|
||
|
context: IErrorContext = {}
|
||
|
): MtaConfigurationError {
|
||
|
return new MtaConfigurationError(
|
||
|
`Invalid configuration value for ${propertyPath}: got ${value} (${typeof value}), expected ${expectedType}`,
|
||
|
{
|
||
|
...context,
|
||
|
data: {
|
||
|
...context.data,
|
||
|
propertyPath,
|
||
|
value,
|
||
|
expectedType
|
||
|
},
|
||
|
userMessage: `The mail server has an invalid configuration value.`
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Error class for MTA DNS errors
|
||
|
*/
|
||
|
export class MtaDnsError extends NetworkError {
|
||
|
/**
|
||
|
* Creates a new MTA DNS error
|
||
|
*
|
||
|
* @param message Error message
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
constructor(
|
||
|
message: string,
|
||
|
context: IErrorContext = {}
|
||
|
) {
|
||
|
super(message, MTA_DNS_ERROR, context);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates an instance for an MX record lookup failure
|
||
|
*
|
||
|
* @param domain Domain that failed MX lookup
|
||
|
* @param originalError Original error
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
public static mxLookupFailed(
|
||
|
domain: string,
|
||
|
originalError?: Error,
|
||
|
context: IErrorContext = {}
|
||
|
): MtaDnsError {
|
||
|
const errorMsg = originalError ? `: ${originalError.message}` : '';
|
||
|
return new MtaDnsError(
|
||
|
`Failed to lookup MX records for ${domain}${errorMsg}`,
|
||
|
{
|
||
|
...context,
|
||
|
data: {
|
||
|
...context.data,
|
||
|
domain,
|
||
|
recordType: 'MX',
|
||
|
originalError: originalError ? {
|
||
|
message: originalError.message,
|
||
|
stack: originalError.stack
|
||
|
} : undefined
|
||
|
},
|
||
|
userMessage: `Could not find mail servers for ${domain}.`
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates an instance for a TXT record lookup failure
|
||
|
*
|
||
|
* @param domain Domain that failed TXT lookup
|
||
|
* @param recordPrefix Optional record prefix (e.g., 'spf', 'dkim', 'dmarc')
|
||
|
* @param originalError Original error
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
public static txtLookupFailed(
|
||
|
domain: string,
|
||
|
recordPrefix?: string,
|
||
|
originalError?: Error,
|
||
|
context: IErrorContext = {}
|
||
|
): MtaDnsError {
|
||
|
const recordType = recordPrefix ? `${recordPrefix} TXT` : 'TXT';
|
||
|
const errorMsg = originalError ? `: ${originalError.message}` : '';
|
||
|
|
||
|
return new MtaDnsError(
|
||
|
`Failed to lookup ${recordType} records for ${domain}${errorMsg}`,
|
||
|
{
|
||
|
...context,
|
||
|
data: {
|
||
|
...context.data,
|
||
|
domain,
|
||
|
recordType,
|
||
|
recordPrefix,
|
||
|
originalError: originalError ? {
|
||
|
message: originalError.message,
|
||
|
stack: originalError.stack
|
||
|
} : undefined
|
||
|
},
|
||
|
userMessage: `Could not verify ${recordPrefix || ''} records for ${domain}.`
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Error class for MTA timeout errors
|
||
|
*/
|
||
|
export class MtaTimeoutError extends NetworkError {
|
||
|
/**
|
||
|
* Creates a new MTA timeout error
|
||
|
*
|
||
|
* @param message Error message
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
constructor(
|
||
|
message: string,
|
||
|
context: IErrorContext = {}
|
||
|
) {
|
||
|
super(message, MTA_TIMEOUT_ERROR, context);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates an instance for an SMTP command timeout
|
||
|
*
|
||
|
* @param command SMTP command that timed out
|
||
|
* @param server Server hostname
|
||
|
* @param timeout Timeout in milliseconds
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
public static commandTimeout(
|
||
|
command: string,
|
||
|
server: string,
|
||
|
timeout: number,
|
||
|
context: IErrorContext = {}
|
||
|
): MtaTimeoutError {
|
||
|
return new MtaTimeoutError(
|
||
|
`SMTP command ${command} to ${server} timed out after ${timeout}ms`,
|
||
|
{
|
||
|
...context,
|
||
|
data: {
|
||
|
...context.data,
|
||
|
command,
|
||
|
server,
|
||
|
timeout
|
||
|
},
|
||
|
userMessage: `The mail server took too long to respond.`
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates an instance for an overall transaction timeout
|
||
|
*
|
||
|
* @param server Server hostname
|
||
|
* @param timeout Timeout in milliseconds
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
public static transactionTimeout(
|
||
|
server: string,
|
||
|
timeout: number,
|
||
|
context: IErrorContext = {}
|
||
|
): MtaTimeoutError {
|
||
|
return new MtaTimeoutError(
|
||
|
`SMTP transaction with ${server} timed out after ${timeout}ms`,
|
||
|
{
|
||
|
...context,
|
||
|
data: {
|
||
|
...context.data,
|
||
|
server,
|
||
|
timeout
|
||
|
},
|
||
|
userMessage: `The mail server transaction took too long to complete.`
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Error class for MTA protocol errors
|
||
|
*/
|
||
|
export class MtaProtocolError extends OperationError {
|
||
|
/**
|
||
|
* Creates a new MTA protocol error
|
||
|
*
|
||
|
* @param message Error message
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
constructor(
|
||
|
message: string,
|
||
|
context: IErrorContext = {}
|
||
|
) {
|
||
|
super(message, MTA_PROTOCOL_ERROR, context);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates an instance for an unexpected server response
|
||
|
*
|
||
|
* @param command SMTP command that received unexpected response
|
||
|
* @param response Unexpected response
|
||
|
* @param expected Expected response pattern
|
||
|
* @param server Server hostname
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
public static unexpectedResponse(
|
||
|
command: string,
|
||
|
response: string,
|
||
|
expected: string,
|
||
|
server: string,
|
||
|
context: IErrorContext = {}
|
||
|
): MtaProtocolError {
|
||
|
return new MtaProtocolError(
|
||
|
`Unexpected SMTP response from ${server} for command ${command}: got "${response}", expected "${expected}"`,
|
||
|
{
|
||
|
...context,
|
||
|
data: {
|
||
|
...context.data,
|
||
|
command,
|
||
|
response,
|
||
|
expected,
|
||
|
server
|
||
|
},
|
||
|
userMessage: `Received an unexpected response from the mail server.`
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates an instance for a syntax error
|
||
|
*
|
||
|
* @param details Error details
|
||
|
* @param server Server hostname
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
public static syntaxError(
|
||
|
details: string,
|
||
|
server: string,
|
||
|
context: IErrorContext = {}
|
||
|
): MtaProtocolError {
|
||
|
return new MtaProtocolError(
|
||
|
`SMTP syntax error in communication with ${server}: ${details}`,
|
||
|
{
|
||
|
...context,
|
||
|
data: {
|
||
|
...context.data,
|
||
|
details,
|
||
|
server
|
||
|
},
|
||
|
userMessage: `There was a protocol error communicating with the mail server.`
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
}
|