"use client";
import { useEffect, useEffectEvent, useRef } from "react";
const EMBED_ORIGIN = "https://embed.withheadlight.com";
const VERSION = "0.1.0";
type HeadlightChartProps = {
token: string;
data: unknown;
embedOrigin?: string;
version?: string;
height?: number;
onNodeClick?: (nodeId: string, nodeType: string) => void;
onError?: (error: { code: string; message: string }) => void;
};
export function HeadlightChart({
token,
data,
embedOrigin = EMBED_ORIGIN,
version = VERSION,
height = 720,
onNodeClick,
onError,
}: HeadlightChartProps) {
const iframeRef = useRef<HTMLIFrameElement>(null);
const readyRef = useRef(false);
const src = `${embedOrigin.replace(/\/$/, "")}/chart/v${version}/index.html`;
const chartOrigin = new URL(src).origin;
const postRender = useEffectEvent(() => {
iframeRef.current?.contentWindow?.postMessage(
{ type: "render", v: 1, token, payload: data },
chartOrigin
);
});
const onMessage = useEffectEvent((event: MessageEvent) => {
const iframe = iframeRef.current;
if (event.origin !== chartOrigin || event.source !== iframe?.contentWindow) {
return;
}
if (typeof event.data !== "object" || event.data == null) {
return;
}
if (event.data.type === "ready") {
readyRef.current = true;
postRender();
return;
}
if (event.data.type === "nodeClick") {
onNodeClick?.(event.data.nodeId, event.data.nodeType);
return;
}
if (event.data.type === "error") {
onError?.({ code: event.data.code, message: event.data.message });
}
});
useEffect(() => {
const iframe = iframeRef.current;
if (iframe == null) {
return;
}
const handleMessage = (event: MessageEvent) => {
onMessage(event);
};
window.addEventListener("message", handleMessage);
iframe.src = src;
return () => {
readyRef.current = false;
window.removeEventListener("message", handleMessage);
iframe.removeAttribute("src");
};
}, [chartOrigin, src]);
useEffect(() => {
if (!readyRef.current) {
return;
}
postRender();
}, [token, data]);
return (
<iframe
ref={iframeRef}
title="Headlight household chart"
sandbox="allow-scripts allow-same-origin"
style={{ width: "100%", height, border: 0 }}
/>
);
}