Neah/lib/utils/email-adapters.ts
2025-04-30 22:25:48 +02:00

345 lines
9.9 KiB
TypeScript

import { EmailMessage, EmailContent, EmailAddress, LegacyEmailMessage } from '@/types/email';
import { sanitizeHtml } from './email-utils';
/**
* 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(/&nbsp;/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'
};
}
}
/**
* Detects the text direction (LTR or RTL) based on the content
*/
function detectTextDirection(text: string): 'ltr' | 'rtl' {
// Simple RTL detection for common RTL languages
// This is a basic implementation and can be enhanced
const rtlChars = /[\u0591-\u07FF\u200F\u202B\u202E\uFB1D-\uFDFD\uFE70-\uFEFC]/;
return rtlChars.test(text) ? 'rtl' : 'ltr';
}
/**
* Normalizes email addresses to the EmailAddress format
*/
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
}));
}