336 lines
9.5 KiB
TypeScript
336 lines
9.5 KiB
TypeScript
import { EmailMessage, EmailContent, EmailAddress, LegacyEmailMessage } from '@/types/email';
|
|
import { sanitizeHtml } from './dom-sanitizer';
|
|
import { detectTextDirection } from './text-direction';
|
|
|
|
/**
|
|
* Adapts a legacy email format to the standardized EmailMessage format
|
|
*/
|
|
export function adaptLegacyEmail(email: LegacyEmailMessage): EmailMessage {
|
|
if (!email) {
|
|
throw new Error('Cannot adapt null or undefined email');
|
|
}
|
|
|
|
// Process content
|
|
const content: EmailContent = normalizeContent(email);
|
|
|
|
// Convert email addresses to string format as required by EmailMessage interface
|
|
// Handle both array and string formats for email fields
|
|
let from: string;
|
|
let to: string;
|
|
let cc: string | undefined;
|
|
let bcc: string | undefined;
|
|
|
|
// Handle 'from' field
|
|
if (Array.isArray(email.from)) {
|
|
from = formatAddressesToString(normalizeAddresses(email.from));
|
|
} else if (typeof email.from === 'object' && 'address' in email.from) {
|
|
from = formatAddressesToString([email.from as EmailAddress]);
|
|
} else {
|
|
from = String(email.from || '');
|
|
}
|
|
|
|
// Handle 'to' field
|
|
if (Array.isArray(email.to)) {
|
|
to = formatAddressesToString(normalizeAddresses(email.to));
|
|
} else if (typeof email.to === 'object' && 'address' in email.to) {
|
|
to = formatAddressesToString([email.to as EmailAddress]);
|
|
} else {
|
|
to = String(email.to || '');
|
|
}
|
|
|
|
// Handle optional 'cc' field
|
|
if (email.cc) {
|
|
if (Array.isArray(email.cc)) {
|
|
cc = formatAddressesToString(normalizeAddresses(email.cc));
|
|
} else if (typeof email.cc === 'object' && 'address' in email.cc) {
|
|
cc = formatAddressesToString([email.cc as EmailAddress]);
|
|
} else {
|
|
cc = String(email.cc);
|
|
}
|
|
}
|
|
|
|
// Handle optional 'bcc' field
|
|
if (email.bcc) {
|
|
if (Array.isArray(email.bcc)) {
|
|
bcc = formatAddressesToString(normalizeAddresses(email.bcc));
|
|
} else if (typeof email.bcc === 'object' && 'address' in email.bcc) {
|
|
bcc = formatAddressesToString([email.bcc as EmailAddress]);
|
|
} else {
|
|
bcc = String(email.bcc);
|
|
}
|
|
}
|
|
|
|
// Convert flags if needed
|
|
const flags: string[] = normalizeFlags(email.flags);
|
|
|
|
// Create standardized email message
|
|
return {
|
|
id: email.id || '',
|
|
from,
|
|
to,
|
|
cc,
|
|
bcc,
|
|
subject: email.subject || '',
|
|
content,
|
|
date: email.date || new Date().toISOString(),
|
|
flags,
|
|
attachments: normalizeAttachments(email.attachments),
|
|
_originalFormat: email // Store original for debugging
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Detects if an email is in MIME format
|
|
*/
|
|
export function isMimeFormat(email: any): boolean {
|
|
// Simple check for MIME format indicators
|
|
if (!email) return false;
|
|
|
|
// Check for typical MIME format properties
|
|
return !!(
|
|
email.mimeContent ||
|
|
(email.headers && (email.bodyParts || email.body)) ||
|
|
(typeof email.content === 'string' && email.content.includes('MIME-Version'))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Adapts a MIME format email to the standardized EmailMessage format
|
|
* This is a placeholder - actual implementation would depend on the MIME library
|
|
*/
|
|
export function adaptMimeEmail(mimeEmail: any): EmailMessage {
|
|
// Placeholder implementation
|
|
const content: EmailContent = {
|
|
text: mimeEmail.text || mimeEmail.plainText || '',
|
|
html: mimeEmail.html || undefined,
|
|
isHtml: !!mimeEmail.html,
|
|
direction: 'ltr'
|
|
};
|
|
|
|
return {
|
|
id: mimeEmail.id || '',
|
|
from: mimeEmail.from || '',
|
|
to: mimeEmail.to || '',
|
|
cc: mimeEmail.cc,
|
|
bcc: mimeEmail.bcc,
|
|
subject: mimeEmail.subject || '',
|
|
content,
|
|
date: mimeEmail.date || new Date().toISOString(),
|
|
flags: [],
|
|
_originalFormat: mimeEmail
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Formats an array of EmailAddress objects to string format
|
|
*/
|
|
function formatAddressesToString(addresses: EmailAddress[]): string {
|
|
return addresses.map(addr => {
|
|
if (addr.name && addr.name !== addr.address) {
|
|
return `${addr.name} <${addr.address}>`;
|
|
}
|
|
return addr.address;
|
|
}).join(', ');
|
|
}
|
|
|
|
/**
|
|
* Normalizes content from various formats into the standard EmailContent format
|
|
*/
|
|
function normalizeContent(email: LegacyEmailMessage): EmailContent {
|
|
// Default content structure
|
|
const normalizedContent: EmailContent = {
|
|
html: undefined,
|
|
text: '',
|
|
isHtml: false,
|
|
direction: 'ltr'
|
|
};
|
|
|
|
try {
|
|
// Extract content based on standardized property hierarchy
|
|
let htmlContent = '';
|
|
let textContent = '';
|
|
let isHtml = false;
|
|
|
|
// Step 1: Extract content from the various possible formats
|
|
if (email.content && typeof email.content === 'object') {
|
|
isHtml = !!email.content.html;
|
|
htmlContent = email.content.html || '';
|
|
textContent = email.content.text || '';
|
|
} else if (typeof email.content === 'string') {
|
|
// Check if the string content is HTML
|
|
isHtml = email.content.trim().startsWith('<') &&
|
|
(email.content.includes('<html') ||
|
|
email.content.includes('<body') ||
|
|
email.content.includes('<div') ||
|
|
email.content.includes('<p>'));
|
|
htmlContent = isHtml ? email.content : '';
|
|
textContent = isHtml ? '' : email.content;
|
|
} else if (email.html) {
|
|
isHtml = true;
|
|
htmlContent = email.html;
|
|
textContent = email.text || email.plainText || '';
|
|
} else if (email.text || email.plainText) {
|
|
isHtml = false;
|
|
htmlContent = '';
|
|
textContent = email.text || email.plainText || '';
|
|
} else if (email.formattedContent) {
|
|
// Assume formattedContent is already HTML
|
|
isHtml = true;
|
|
htmlContent = email.formattedContent;
|
|
textContent = '';
|
|
}
|
|
|
|
// Step 2: Set the normalized content properties
|
|
normalizedContent.isHtml = isHtml;
|
|
|
|
// Always ensure we have text content
|
|
if (textContent) {
|
|
normalizedContent.text = textContent;
|
|
} else if (htmlContent) {
|
|
// Extract text from HTML if we don't have plain text
|
|
if (typeof document !== 'undefined') {
|
|
// Browser environment
|
|
const tempDiv = document.createElement('div');
|
|
tempDiv.innerHTML = htmlContent;
|
|
normalizedContent.text = tempDiv.textContent || tempDiv.innerText || '';
|
|
} else {
|
|
// Server environment - do simple strip
|
|
normalizedContent.text = htmlContent
|
|
.replace(/<[^>]*>/g, '')
|
|
.replace(/ /g, ' ')
|
|
.replace(/\s+/g, ' ')
|
|
.trim();
|
|
}
|
|
}
|
|
|
|
// If we have HTML content, sanitize it
|
|
if (isHtml && htmlContent) {
|
|
normalizedContent.html = sanitizeHtml(htmlContent);
|
|
}
|
|
|
|
// Determine text direction
|
|
normalizedContent.direction = detectTextDirection(normalizedContent.text);
|
|
|
|
return normalizedContent;
|
|
} catch (error) {
|
|
console.error('Error normalizing email content:', error);
|
|
|
|
// Return minimal valid content in case of error
|
|
return {
|
|
text: 'Error loading email content',
|
|
isHtml: false,
|
|
direction: 'ltr'
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Normalizes addresses to EmailAddress objects
|
|
*/
|
|
function normalizeAddresses(addresses: string | EmailAddress[] | undefined): EmailAddress[] {
|
|
if (!addresses) {
|
|
return [];
|
|
}
|
|
|
|
// Handle string
|
|
if (typeof addresses === 'string') {
|
|
// Check if format is "Name <email@example.com>"
|
|
const match = addresses.match(/^([^<]+)<([^>]+)>$/);
|
|
if (match) {
|
|
return [{
|
|
name: match[1].trim(),
|
|
address: match[2].trim()
|
|
}];
|
|
}
|
|
|
|
// Simple email address
|
|
return [{
|
|
name: addresses.split('@')[0] || '',
|
|
address: addresses
|
|
}];
|
|
}
|
|
|
|
// Handle array
|
|
if (Array.isArray(addresses)) {
|
|
// If already in EmailAddress format, return as is
|
|
if (addresses.length > 0 && typeof addresses[0] === 'object' && 'address' in addresses[0]) {
|
|
return addresses as EmailAddress[];
|
|
}
|
|
|
|
// Otherwise convert string elements to EmailAddress objects
|
|
return addresses.map((addr: any) => {
|
|
if (typeof addr === 'string') {
|
|
// Check if format is "Name <email@example.com>"
|
|
const match = addr.match(/^([^<]+)<([^>]+)>$/);
|
|
if (match) {
|
|
return {
|
|
name: match[1].trim(),
|
|
address: match[2].trim()
|
|
};
|
|
}
|
|
|
|
return {
|
|
name: addr.split('@')[0] || '',
|
|
address: addr
|
|
};
|
|
}
|
|
|
|
// If it's already an object with address property
|
|
if (typeof addr === 'object' && addr !== null && 'address' in addr) {
|
|
return {
|
|
name: addr.name || addr.address.split('@')[0] || '',
|
|
address: addr.address
|
|
};
|
|
}
|
|
|
|
// Fallback for unexpected formats
|
|
return {
|
|
name: '',
|
|
address: String(addr || '')
|
|
};
|
|
});
|
|
}
|
|
|
|
// Unexpected type - return empty array
|
|
console.warn(`Unexpected addresses format: ${typeof addresses}`, addresses);
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* Normalizes email flags to string array format
|
|
*/
|
|
function normalizeFlags(flags: string[] | Record<string, boolean> | undefined): string[] {
|
|
if (!flags) {
|
|
return [];
|
|
}
|
|
|
|
if (Array.isArray(flags)) {
|
|
return flags;
|
|
}
|
|
|
|
// Convert object format to array
|
|
return Object.entries(flags)
|
|
.filter(([_, value]) => value === true)
|
|
.map(([key]) => key);
|
|
}
|
|
|
|
/**
|
|
* Normalizes attachments to the expected format
|
|
*/
|
|
function normalizeAttachments(attachments: any[] | undefined): Array<{
|
|
filename: string;
|
|
contentType: string;
|
|
encoding?: string;
|
|
content?: string;
|
|
}> {
|
|
if (!attachments || !Array.isArray(attachments)) {
|
|
return [];
|
|
}
|
|
|
|
return attachments.map(att => ({
|
|
filename: att.filename || att.name || 'unknown',
|
|
contentType: att.contentType || 'application/octet-stream',
|
|
encoding: att.encoding,
|
|
content: att.content
|
|
}));
|
|
}
|