diff --git a/app/api/mail/login/route.ts b/app/api/mail/login/route.ts new file mode 100644 index 0000000..92f9b33 --- /dev/null +++ b/app/api/mail/login/route.ts @@ -0,0 +1,91 @@ +import { NextResponse } from 'next/server'; +import Imap from 'imap'; + +interface StoredCredentials { + user: string; + password: string; + host: string; + port: string; +} + +export let storedCredentials: StoredCredentials | null = null; + +export async function POST(request: Request) { + try { + const { email, password, host, port } = await request.json(); + + if (!email || !password || !host || !port) { + return NextResponse.json( + { error: 'Missing required fields' }, + { status: 400 } + ); + } + + // Test IMAP connection + const imap = new Imap({ + user: email, + password, + host, + port: parseInt(port), + tls: true, + tlsOptions: { + rejectUnauthorized: false, + servername: host + }, + authTimeout: 10000, + connTimeout: 10000, + debug: console.log + }); + + return new Promise((resolve, reject) => { + imap.once('ready', () => { + imap.end(); + // Store credentials + storedCredentials = { user: email, password, host, port }; + resolve(NextResponse.json({ success: true })); + }); + + imap.once('error', (err: Error) => { + imap.end(); + if (err.message.includes('Invalid login or password')) { + reject(new Error('Invalid login or password')); + } else { + reject(new Error(`IMAP connection error: ${err.message}`)); + } + }); + + imap.connect(); + }); + } catch (error) { + console.error('Error in login handler:', error); + if (error instanceof Error) { + if (error.message.includes('Invalid login or password')) { + return NextResponse.json( + { error: 'Invalid login or password', details: error.message }, + { status: 401 } + ); + } + return NextResponse.json( + { error: 'Failed to connect to email server', details: error.message }, + { status: 500 } + ); + } + return NextResponse.json( + { error: 'Unknown error occurred' }, + { status: 500 } + ); + } +} + +export async function GET() { + if (!storedCredentials) { + return NextResponse.json( + { error: 'No stored credentials found' }, + { status: 404 } + ); + } + + // Return credentials without password + const { password, ...safeCredentials } = storedCredentials; + return NextResponse.json(safeCredentials); +} \ No newline at end of file diff --git a/app/api/mail/route.ts b/app/api/mail/route.ts index 7b909e1..b2b1a85 100644 --- a/app/api/mail/route.ts +++ b/app/api/mail/route.ts @@ -1,424 +1,179 @@ import { NextResponse } from 'next/server'; import Imap from 'imap'; -import { simpleParser } from 'mailparser'; +import { parseEmailHeaders } from '@/lib/email-parser'; -// Debug logging for environment variables -console.log('Environment Variables:', { - IMAP_USER: process.env.IMAP_USER, - IMAP_HOST: process.env.IMAP_HOST, - IMAP_PORT: process.env.IMAP_PORT, - IMAP_PASSWORD: process.env.IMAP_PASSWORD ? '***' : undefined, - NODE_ENV: process.env.NODE_ENV -}); - -// Store for credentials (in memory, you might want to use a proper storage solution) -let storedCredentials: { - user: string; +interface StoredCredentials { + email: string; password: string; host: string; - port: string; -} | null = null; - -// Helper function to get stored credentials -function getStoredCredentials() { - // Server-side: use stored credentials - if (typeof window === 'undefined') { - return storedCredentials; - } - - // Client-side: use localStorage - const stored = localStorage.getItem('imapCredentials'); - return stored ? JSON.parse(stored) : null; -} - -// IMAP configuration -const getImapConfig = () => { - const credentials = getStoredCredentials(); - - if (!credentials?.user || !credentials?.password) { - throw new Error('Email credentials not found. Please log in first.'); - } - - return { - user: credentials.user, - password: credentials.password, - host: credentials.host || 'mail.infomaniak.com', - port: parseInt(credentials.port || '993', 10), - tls: true, - tlsOptions: { - rejectUnauthorized: false, - servername: credentials.host || 'mail.infomaniak.com' - }, - authTimeout: 10000, - connTimeout: 10000, - debug: console.log - }; -}; - -// Debug logging for IMAP configuration -console.log('IMAP Configuration:', { - user: getImapConfig().user, - host: getImapConfig().host, - port: getImapConfig().port, - tls: getImapConfig().tls, - hasPassword: !!getImapConfig().password, - authTimeout: getImapConfig().authTimeout, - connTimeout: getImapConfig().connTimeout -}); - -interface ImapMessage { - header: { - from?: string[]; - to?: string[]; - subject?: string[]; - date?: string[]; - [key: string]: string[] | undefined; - }; - body: string; - attributes: { - flags: string[]; - }; -} - -interface ImapError extends Error { - type?: string; - textCode?: string; - source?: string; + port: number; } interface Email { id: string; from: string; subject: string; - date: string; - body: string; + date: Date; read: boolean; starred: boolean; + body: string; } -interface EmailHeaders { - from: string; - subject: string; - date: string; -} - -function parseEmailHeaders(buffer: string): EmailHeaders { - const headers: EmailHeaders = { - from: '', - subject: '', - date: '' +interface ImapBox { + messages: { + total: number; }; +} - const lines = buffer.split('\r\n'); - for (const line of lines) { - if (line.toLowerCase().startsWith('from:')) { - headers.from = line.substring(5).trim(); - } else if (line.toLowerCase().startsWith('subject:')) { - headers.subject = line.substring(8).trim(); - } else if (line.toLowerCase().startsWith('date:')) { - headers.date = line.substring(5).trim(); +interface ImapMessage { + on: (event: string, callback: (data: any) => void) => void; + once: (event: string, callback: (data: any) => void) => void; + attributes: { + uid: number; + flags: string[]; + size: number; + }; + body: { + [key: string]: { + on: (event: string, callback: (data: any) => void) => void; + }; + }; +} + +interface ImapConfig { + user: string; + password: string; + host: string; + port: number; + tls: boolean; + authTimeout: number; + connTimeout: number; + debug?: (info: string) => void; +} + +function getStoredCredentials(): StoredCredentials | null { + if (typeof window === 'undefined') { + // Server-side + const email = process.env.IMAP_USER; + const password = process.env.IMAP_PASSWORD; + const host = process.env.IMAP_HOST; + const port = process.env.IMAP_PORT ? parseInt(process.env.IMAP_PORT) : 993; + + if (!email || !password || !host) { + return null; } + + return { email, password, host, port }; + } else { + // Client-side + const stored = localStorage.getItem('imapCredentials'); + return stored ? JSON.parse(stored) : null; } - - return headers; -} - -// Helper function to create a promise-based IMAP connection -function createImapConnection(config: any): Promise { - return new Promise((resolve, reject) => { - const imap = new Imap({ - user: config.user, - password: config.password, - host: config.host, - port: config.port, - tls: true, - tlsOptions: { - rejectUnauthorized: false, - servername: config.host - }, - authTimeout: 10000, - connTimeout: 10000, - debug: console.log, - autotls: 'always', - keepalive: true - }); - - imap.once('ready', () => { - console.log('IMAP connection established successfully'); - resolve(imap); - }); - - imap.once('error', (err: ImapError) => { - console.error('IMAP connection error:', err); - console.error('Error details:', { - type: err.type, - textCode: err.textCode, - source: err.source - }); - reject(err); - }); - - imap.once('end', () => console.log('[connection] Ended')); - imap.once('close', () => console.log('[connection] Closed')); - - try { - console.log('Attempting to connect to IMAP server...'); - console.log('Using credentials:', { - user: config.user, - passwordLength: config.password.length, - host: config.host, - port: config.port - }); - imap.connect(); - } catch (err) { - console.error('Error during IMAP connection:', err); - reject(err); - } - }); -} - -// Helper function to promisify the message fetching -function fetchMessages(imap: Imap, box: string): Promise { - return new Promise((resolve, reject) => { - imap.openBox(box, false, (err, mailbox) => { - if (err) { - reject(err); - return; - } - - // Search for all messages - imap.search(['ALL'], (err, results) => { - if (err) { - reject(err); - return; - } - - // No messages found - if (!results || !results.length) { - resolve([]); - return; - } - - const fetch = imap.fetch(results, { - bodies: ['HEADER.FIELDS (FROM TO SUBJECT DATE)', 'TEXT'], - struct: true - }); - - const messages: ImapMessage[] = []; - - fetch.on('message', (msg) => { - const message: Partial = {}; - - msg.on('body', (stream, info) => { - let buffer = ''; - stream.on('data', (chunk) => { - buffer += chunk.toString('utf8'); - }); - stream.once('end', () => { - if (info.which === 'TEXT') { - message.body = buffer; - } else { - message.header = Imap.parseHeader(buffer); - } - }); - }); - - msg.once('attributes', (attrs) => { - message.attributes = attrs; - }); - - msg.once('end', () => { - messages.push(message as ImapMessage); - }); - }); - - fetch.once('error', (err) => { - reject(err); - }); - - fetch.once('end', () => { - resolve(messages); - }); - }); - }); - }); } export async function GET() { try { - // Get stored credentials const credentials = getStoredCredentials(); if (!credentials) { return NextResponse.json( - { error: 'No IMAP credentials found. Please configure your email account.' }, + { error: 'No stored credentials found' }, { status: 401 } ); } - console.log('Starting email fetch process...'); - console.log('IMAP Configuration:', { - user: credentials.user, + const imapConfig: ImapConfig = { + user: credentials.email, + password: credentials.password, host: credentials.host, port: credentials.port, tls: true, - hasPassword: !!credentials.password - }); - - // Create IMAP connection - const imap = await createImapConnection({ - user: credentials.user, - password: credentials.password, - host: credentials.host, - port: parseInt(credentials.port), - tls: true - }); - - return new Promise((resolve, reject) => { - imap.once('ready', () => { - console.log('IMAP connection ready'); - imap.openBox('INBOX', false, (err, box) => { - if (err) { - console.error('Error opening inbox:', err); - imap.end(); - reject(new Error('Failed to open inbox')); - return; - } - - const fetch = imap.seq.fetch('1:10', { - bodies: ['HEADER.FIELDS (FROM SUBJECT DATE)', 'TEXT'], - struct: true - }); - - const messages: Email[] = []; - - fetch.on('message', (msg) => { - const email: Email = { - id: '', - from: '', - subject: '', - date: '', - body: '', - read: false, - starred: false - }; - - msg.on('body', (stream, info) => { - let buffer = ''; - stream.on('data', (chunk) => { - buffer += chunk.toString('utf8'); - }); - stream.on('end', () => { - if (info.which === 'HEADER.FIELDS (FROM SUBJECT DATE)') { - const headers = parseEmailHeaders(buffer); - email.from = headers.from; - email.subject = headers.subject; - email.date = headers.date; - } else if (info.which === 'TEXT') { - email.body = buffer; - } - }); - }); - - msg.once('attributes', (attrs) => { - email.id = attrs.uid.toString(); - email.read = !attrs.flags.includes('\\Unseen'); - email.starred = attrs.flags.includes('\\Flagged'); - }); - - msg.once('end', () => { - messages.push(email); - }); - }); - - fetch.once('error', (err) => { - console.error('Fetch error:', err); - imap.end(); - reject(new Error('Failed to fetch messages')); - }); - - fetch.once('end', () => { - imap.end(); - resolve(NextResponse.json(messages)); - }); - }); - }); - - imap.once('error', (err: ImapError) => { - console.error('IMAP connection error:', err); - console.error('Error details:', { - type: err.type, - textCode: err.textCode, - source: err.source - }); - imap.end(); - reject(new Error(err.message)); - }); - - imap.connect(); - }); - } catch (error) { - console.error('Error in GET handler:', error); - if (error instanceof Error) { - if (error.message.includes('Invalid login or password')) { - return NextResponse.json( - { error: 'Invalid login or password', details: error.message }, - { status: 401 } - ); - } - return NextResponse.json( - { error: 'Failed to fetch emails', details: error.message }, - { status: 500 } - ); - } - return NextResponse.json( - { error: 'Unknown error occurred' }, - { status: 500 } - ); - } -} - -export async function POST(request: Request) { - try { - const body = await request.json(); - const { email, password, host, port } = body; - - if (!email || !password || !host || !port) { - return NextResponse.json( - { error: 'Missing required fields' }, - { status: 400 } - ); - } - - // Store credentials - storedCredentials = { - user: email, - password, - host, - port + authTimeout: 10000, + connTimeout: 10000, + debug: (info: string) => console.log('IMAP Debug:', info) }; - // Test the connection - const imap = await createImapConnection(storedCredentials); + console.log('IMAP Config:', { + ...imapConfig, + password: '***' + }); + + const imap = new Imap(imapConfig); + + const connectPromise = new Promise((resolve, reject) => { + imap.once('ready', () => resolve(true)); + imap.once('error', (err: Error) => reject(err)); + imap.connect(); + }); + + await connectPromise; + + const openBoxPromise = new Promise((resolve, reject) => { + imap.openBox('INBOX', false, (err: Error | null, box: ImapBox) => { + if (err) reject(err); + else resolve(box); + }); + }); + + const box = await openBoxPromise; + + const fetchPromise = (imap: Imap, seqno: number): Promise => { + return new Promise((resolve, reject) => { + const f = imap.fetch(seqno, { bodies: ['HEADER', 'TEXT'] }); + f.on('message', (msg: ImapMessage) => { + resolve(msg); + }); + f.once('error', reject); + }); + }; + + const processMessage = (msg: ImapMessage): Promise => { + return new Promise((resolve, reject) => { + let body = ''; + msg.body['TEXT'].on('data', (chunk: Buffer) => { + body += chunk.toString('utf8'); + }); + msg.body['TEXT'].on('end', () => { + const headers = parseEmailHeaders(body); + resolve({ + uid: msg.attributes.uid, + flags: msg.attributes.flags, + size: msg.attributes.size, + ...headers + }); + }); + msg.body['TEXT'].on('error', reject); + }); + }; + + const messages: any[] = []; + for (let seqno = 1; seqno <= 10; seqno++) { + const msg = await fetchPromise(imap, seqno); + const processedMsg = await processMessage(msg); + messages.push(processedMsg); + } imap.end(); - return NextResponse.json({ success: true }); + const emails: Email[] = messages.map((msg) => { + return { + id: msg.uid.toString(), + from: msg.from, + subject: msg.subject, + date: msg.date, + read: !msg.flags?.includes('\\Unseen'), + starred: msg.flags?.includes('\\Flagged') || false, + body: msg.body + }; + }); + + return NextResponse.json(emails); } catch (error) { - console.error('Error in POST handler:', error); - if (error instanceof Error) { - if (error.message.includes('Invalid login or password')) { - return NextResponse.json( - { error: 'Invalid login or password', details: error.message }, - { status: 401 } - ); - } - return NextResponse.json( - { error: 'Failed to connect to email server', details: error.message }, - { status: 500 } - ); - } + console.error('Error fetching emails:', error); + const status = error instanceof Error && error.message.includes('Invalid login') + ? 401 + : 500; return NextResponse.json( - { error: 'Unknown error occurred' }, - { status: 500 } + { error: error instanceof Error ? error.message : 'Failed to fetch emails' }, + { status } ); } -} \ No newline at end of file +} \ No newline at end of file diff --git a/lib/email-parser.ts b/lib/email-parser.ts new file mode 100644 index 0000000..134db95 --- /dev/null +++ b/lib/email-parser.ts @@ -0,0 +1,23 @@ +export function parseEmailHeaders(emailBody: string): { from: string; subject: string; date: string } { + const headers: { [key: string]: string } = {}; + + // Split the email body into lines + const lines = emailBody.split('\r\n'); + + // Parse headers + for (const line of lines) { + if (line.trim() === '') break; // Stop at the first empty line (end of headers) + + const match = line.match(/^([^:]+):\s*(.+)$/); + if (match) { + const [, key, value] = match; + headers[key.toLowerCase()] = value; + } + } + + return { + from: headers['from'] || '', + subject: headers['subject'] || '', + date: headers['date'] || new Date().toISOString() + }; +} \ No newline at end of file