// 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 (
);
}