// frontend/components/mail/MailHtmlBody.tsx — 원본 HTML 메일을 샌드박스 iframe 으로 렌더. // sandbox 에 allow-scripts 를 주지 않아 메일 속 JS 가 실행되지 않음(XSS 차단). // allow-same-origin 은 본문 높이 측정(scrollHeight)에 필요, allow-popups 는 링크 새 탭 열기용. "use client"; import { useEffect, useMemo, useRef, useState } from "react"; function wrapDocument(html: string): string { return ` ${html}`; } export function MailHtmlBody({ html }: { html: string }) { const ref = useRef(null); const [height, setHeight] = useState(160); const srcDoc = useMemo(() => wrapDocument(html), [html]); useEffect(() => { const iframe = ref.current; if (!iframe) return; let ro: ResizeObserver | null = null; const timers: ReturnType[] = []; const measure = () => { const doc = iframe.contentDocument; if (doc?.body) { const h = Math.max(doc.body.scrollHeight, doc.documentElement?.scrollHeight || 0); if (h > 0) setHeight(h + 8); } }; const onLoad = () => { measure(); const doc = iframe.contentDocument; if (doc?.body) { ro = new ResizeObserver(measure); ro.observe(doc.body); // 이미지가 늦게 로드되면 높이가 늘어나므로 로드 시 재측정. Array.from(doc.images || []).forEach((img) => { if (!img.complete) img.addEventListener("load", measure); }); } // 폰트·원격 이미지 등 후속 레이아웃 변화 대비 몇 차례 재측정. [150, 500, 1200].forEach((t) => timers.push(setTimeout(measure, t))); }; iframe.addEventListener("load", onLoad); return () => { iframe.removeEventListener("load", onLoad); ro?.disconnect(); timers.forEach(clearTimeout); }; }, [srcDoc]); return (