/** * Client for the mail archive API on the mailpiler host (reference/mail-archive-api, * systemd `logship-mail-archive`, runtimeConfig.mailArchive). The archive journals * EVERY mailbox of the mail server, so one indexed lookup (~20 ms) replaces * scanning the folders of each mailbox over IMAP (see leadMailbox.ts) and needs * no mailbox credentials. Read-only. */ import { isValidEmail } from './leadMailbox' const cfg = () => { const c: any = (useRuntimeConfig() as any).mailArchive || {} return { baseUrl: String(c.baseUrl || '').replace(/\/+$/, ''), token: String(c.token || '') } } export const archiveAvailable = (): boolean => { const c = cfg(); return !!(c.baseUrl && c.token) } const call = async (path: string, query: Record, timeout = 60_000): Promise => { const c = cfg() if (!c.baseUrl || !c.token) throw new Error('Mail archive not configured') return await $fetch(c.baseUrl + path, { query, headers: { Authorization: 'Bearer ' + c.token }, retry: 0, timeout }) } /** Every archived mail from / to `email`, newest first (max `limit`) — same row shape as the IMAP search. */ export const searchArchive = async (email: string, limit = 500) => { if (!isValidEmail(email)) throw new Error('Invalid e-mail address') const res: any = await call('/search', { email, limit: Math.min(1000, Math.max(1, Math.round(limit) || 500)) }, 20_000) const rows = (res?.rows || []).map((r: any) => ({ account: 'archive', accountLabel: 'Archiv', folder: '', folderKind: 'archive', uid: Number(r.id), messageId: String(r.messageId || ''), date: r.date || null, subject: String(r.subject || ''), from: r.from || { name: '', address: '' }, to: r.to || [], cc: [], direction: r.direction === 'in' ? 'in' : 'out', size: Number(r.size || 0), seen: true, answered: false, hasAttachments: false // piler's counter also counts inline images — known only once opened })) return { rows, folders: 0, truncated: !!res?.truncated, remoteMs: Number(res?.ms || 0) } } export const fetchArchiveMessage = async (id: number, email: string) => { if (!isValidEmail(email)) throw new Error('Invalid e-mail address') try { const res: any = await call('/message', { id, email }) const m = res?.message if (!m) return null return { ...m, account: 'archive', accountLabel: 'Archiv', folder: '', uid: id, truncated: false, downloadable: true } } catch (err: any) { if (Number(err?.status || err?.statusCode || err?.response?.status) === 404) return null throw err } } /** Raw upstream response of one attachment (streamed through to the browser). */ export const fetchArchiveAttachment = async (id: number, index: number, email: string): Promise => { const c = cfg() if (!c.baseUrl || !c.token) throw new Error('Mail archive not configured') if (!isValidEmail(email)) throw new Error('Invalid e-mail address') const url = `${c.baseUrl}/attachment?id=${id}&index=${index}&email=${encodeURIComponent(email)}` return await fetch(url, { headers: { Authorization: 'Bearer ' + c.token }, signal: AbortSignal.timeout(120_000) }) }