271 lines
7.5 KiB
TypeScript
271 lines
7.5 KiB
TypeScript
import * as plugins from '../../plugins.js';
|
|
import type { OpsServer } from '../classes.opsserver.js';
|
|
import * as interfaces from '../../../ts_interfaces/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 All Emails Handler
|
|
this.typedrouter.addTypedHandler(
|
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetAllEmails>(
|
|
'getAllEmails',
|
|
async (dataArg) => {
|
|
const emails = this.getAllQueueEmails();
|
|
return { emails };
|
|
}
|
|
)
|
|
);
|
|
|
|
// Get Email Detail Handler
|
|
this.typedrouter.addTypedHandler(
|
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetEmailDetail>(
|
|
'getEmailDetail',
|
|
async (dataArg) => {
|
|
const email = this.getEmailDetail(dataArg.emailId);
|
|
return { email };
|
|
}
|
|
)
|
|
);
|
|
|
|
// Resend Failed Email Handler
|
|
this.typedrouter.addTypedHandler(
|
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ResendEmail>(
|
|
'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 {
|
|
const newQueueId = await queue.enqueue(
|
|
item.processingResult,
|
|
item.processingMode,
|
|
item.route
|
|
);
|
|
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 all queue items mapped to catalog IEmail format
|
|
*/
|
|
private getAllQueueEmails(): interfaces.requests.IEmail[] {
|
|
const emailServer = this.opsServerRef.dcRouterRef.emailServer;
|
|
if (!emailServer?.deliveryQueue) {
|
|
return [];
|
|
}
|
|
|
|
const queue = emailServer.deliveryQueue;
|
|
const queueMap = (queue as any).queue as Map<string, any>;
|
|
|
|
if (!queueMap) {
|
|
return [];
|
|
}
|
|
|
|
const emails: interfaces.requests.IEmail[] = [];
|
|
|
|
for (const [id, item] of queueMap.entries()) {
|
|
emails.push(this.mapQueueItemToEmail(item));
|
|
}
|
|
|
|
// Sort by createdAt descending (newest first)
|
|
emails.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
|
|
return emails;
|
|
}
|
|
|
|
/**
|
|
* Get a single email detail by ID
|
|
*/
|
|
private getEmailDetail(emailId: string): interfaces.requests.IEmailDetail | null {
|
|
const emailServer = this.opsServerRef.dcRouterRef.emailServer;
|
|
if (!emailServer?.deliveryQueue) {
|
|
return null;
|
|
}
|
|
|
|
const queue = emailServer.deliveryQueue;
|
|
const item = queue.getItem(emailId);
|
|
|
|
if (!item) {
|
|
return null;
|
|
}
|
|
|
|
return this.mapQueueItemToEmailDetail(item);
|
|
}
|
|
|
|
/**
|
|
* Map a queue item to catalog IEmail format
|
|
*/
|
|
private mapQueueItemToEmail(item: any): interfaces.requests.IEmail {
|
|
const processingResult = item.processingResult;
|
|
let from = '';
|
|
let to = '';
|
|
let subject = '';
|
|
let messageId = '';
|
|
let size = '0 B';
|
|
|
|
if (processingResult) {
|
|
if (processingResult.email) {
|
|
from = processingResult.email.from || '';
|
|
to = (processingResult.email.to || [])[0] || '';
|
|
subject = processingResult.email.subject || '';
|
|
} else if (processingResult.from) {
|
|
from = processingResult.from;
|
|
to = (processingResult.to || [])[0] || '';
|
|
subject = processingResult.subject || '';
|
|
}
|
|
|
|
// Try to get messageId
|
|
if (typeof processingResult.getMessageId === 'function') {
|
|
try {
|
|
messageId = processingResult.getMessageId() || '';
|
|
} catch {
|
|
messageId = '';
|
|
}
|
|
}
|
|
|
|
// Compute approximate size
|
|
const textLen = processingResult.text?.length || 0;
|
|
const htmlLen = processingResult.html?.length || 0;
|
|
let attachSize = 0;
|
|
if (typeof processingResult.getAttachmentsSize === 'function') {
|
|
try {
|
|
attachSize = processingResult.getAttachmentsSize() || 0;
|
|
} catch {
|
|
attachSize = 0;
|
|
}
|
|
}
|
|
size = this.formatSize(textLen + htmlLen + attachSize);
|
|
}
|
|
|
|
// Map queue status to catalog TEmailStatus
|
|
const status = this.mapStatus(item.status);
|
|
|
|
const createdAt = item.createdAt instanceof Date ? item.createdAt.getTime() : item.createdAt;
|
|
|
|
return {
|
|
id: item.id,
|
|
direction: 'outbound' as interfaces.requests.TEmailDirection,
|
|
status,
|
|
from,
|
|
to,
|
|
subject,
|
|
timestamp: new Date(createdAt).toISOString(),
|
|
messageId,
|
|
size,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Map a queue item to catalog IEmailDetail format
|
|
*/
|
|
private mapQueueItemToEmailDetail(item: any): interfaces.requests.IEmailDetail {
|
|
const base = this.mapQueueItemToEmail(item);
|
|
const processingResult = item.processingResult;
|
|
|
|
let toList: string[] = [];
|
|
let cc: string[] = [];
|
|
let headers: Record<string, string> = {};
|
|
let body = '';
|
|
|
|
if (processingResult) {
|
|
if (processingResult.email) {
|
|
toList = processingResult.email.to || [];
|
|
cc = processingResult.email.cc || [];
|
|
} else {
|
|
toList = processingResult.to || [];
|
|
cc = processingResult.cc || [];
|
|
}
|
|
|
|
headers = processingResult.headers || {};
|
|
body = processingResult.html || processingResult.text || '';
|
|
}
|
|
|
|
return {
|
|
...base,
|
|
toList,
|
|
cc,
|
|
smtpLog: [],
|
|
connectionInfo: {
|
|
sourceIp: '',
|
|
sourceHostname: '',
|
|
destinationIp: '',
|
|
destinationPort: 0,
|
|
tlsVersion: '',
|
|
tlsCipher: '',
|
|
authenticated: false,
|
|
authMethod: '',
|
|
authUser: '',
|
|
},
|
|
authenticationResults: {
|
|
spf: 'none',
|
|
spfDomain: '',
|
|
dkim: 'none',
|
|
dkimDomain: '',
|
|
dmarc: 'none',
|
|
dmarcPolicy: '',
|
|
},
|
|
rejectionReason: item.status === 'failed' ? item.lastError : undefined,
|
|
bounceMessage: item.status === 'failed' ? item.lastError : undefined,
|
|
headers,
|
|
body,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Map queue status to catalog TEmailStatus
|
|
*/
|
|
private mapStatus(queueStatus: string): interfaces.requests.TEmailStatus {
|
|
switch (queueStatus) {
|
|
case 'pending':
|
|
case 'processing':
|
|
return 'pending';
|
|
case 'delivered':
|
|
return 'delivered';
|
|
case 'failed':
|
|
return 'bounced';
|
|
case 'deferred':
|
|
return 'deferred';
|
|
default:
|
|
return 'pending';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Format byte size to human-readable string
|
|
*/
|
|
private formatSize(bytes: number): string {
|
|
if (bytes < 1024) return `${bytes} B`;
|
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
}
|
|
}
|