import React, { useEffect, useRef, useState } from 'react'; import { X, Download, AlertTriangle, Paperclip } from '../icons'; import api from '../utils/api'; import { useEscapeKey } from '../hooks/useEscapeKey'; import { useFocusTrap } from '../hooks/useFocusTrap'; interface MailDrawerProps { decky: string; storedAs: string; fields: Record; onClose: () => void; } interface AttachmentManifest { filename?: string | null; content_type?: string | null; size?: number | null; sha256?: string | null; } function parseAttachments(fields: Record): AttachmentManifest[] { const raw = fields.attachments_json; if (typeof raw !== 'string' || !raw) return []; try { const parsed = JSON.parse(raw); return Array.isArray(parsed) ? parsed : []; } catch (err) { console.error('mail: failed to parse attachments_json', err); return []; } } const Row: React.FC<{ label: string; value: React.ReactNode }> = ({ label, value }) => (
{label}
{value ?? }
); const MailDrawer: React.FC = ({ decky, storedAs, fields, onClose }) => { const panelRef = useRef(null); useEscapeKey(onClose, true); useFocusTrap(panelRef, true); useEffect(() => { const prev = document.body.style.overflow; document.body.style.overflow = 'hidden'; return () => { document.body.style.overflow = prev; }; }, []); const [downloading, setDownloading] = useState(false); const [error, setError] = useState(null); const attachments = parseAttachments(fields); const handleDownload = async () => { setDownloading(true); setError(null); try { const res = await api.get( `/artifacts/${encodeURIComponent(decky)}/${encodeURIComponent(storedAs)}?service=smtp`, { responseType: 'blob' }, ); const blobUrl = URL.createObjectURL(res.data); const a = document.createElement('a'); a.href = blobUrl; a.download = storedAs; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(blobUrl); } catch (err: any) { const status = err?.response?.status; setError( status === 403 ? 'Admin role required to download mail.' : status === 404 ? 'Message not found on disk (may have been purged).' : status === 400 ? 'Server rejected the request (invalid parameters).' : 'Download failed — see console.' ); console.error('mail download failed', err); } finally { setDownloading(false); } }; const recipients = fields.to_hdr || (Array.isArray(fields.rcpts) ? fields.rcpts.join(', ') : null) || (typeof fields.rcpts === 'string' ? fields.rcpts : null); return (
e.stopPropagation()} style={{ width: 'min(620px, 100%)', height: '100%', backgroundColor: 'var(--bg-color, #0d1117)', borderLeft: '1px solid var(--border-color, #30363d)', padding: '24px', overflowY: 'auto', color: 'var(--text-color)', }} >
STORED MESSAGE · {decky}
{fields.subject || storedAs}
Attacker-controlled content. Phishing kits / malware likely.
{error && (
{error}
)}

HEADERS

BODY

{attachments.length > 0 && (

ATTACHMENTS ({attachments.length})

{attachments.map((att, idx) => (
{att.filename || '(unnamed)'}
{att.content_type ?? '?'} · {att.size != null ? `${att.size} B` : '? B'}
{att.sha256 && (
{att.sha256}
)}
))}
)}
); }; export default MailDrawer;