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('')); 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 " 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 " 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 | 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 })); }