UNPKG

@assistant-ui/react

Version:

Open-source TypeScript/React library for building production-grade AI chat experiences

120 lines (107 loc) • 3.22 kB
"use client"; import type { Unsubscribe } from "@assistant-ui/core"; import { useEffect, type RefObject } from "react"; import { AssistantFrameHost } from "@assistant-ui/core"; type UseAssistantFrameHostOptions = { iframeRef: Readonly<RefObject<HTMLIFrameElement | null | undefined>>; targetOrigin?: string; register: (frameHost: AssistantFrameHost) => Unsubscribe; }; /** * React hook that manages the lifecycle of an AssistantFrameHost and its binding to the current AssistantRuntime. * * Usage example: * ```typescript * function MyComponent() { * const iframeRef = useRef<HTMLIFrameElement>(null); * * useAssistantFrameHost({ * iframeRef, * targetOrigin: "https://trusted-domain.com", * }); * * return <iframe ref={iframeRef} src="..." />; * } * ``` */ export const useAssistantFrameHost = ({ iframeRef, targetOrigin, register, }: UseAssistantFrameHostOptions): void => { useEffect(() => { let active: | { iframe: HTMLIFrameElement; window: Window; dispose: () => void; } | undefined; const syncFrame = () => { const iframe = iframeRef.current; const iframeWindow = iframe?.isConnected === false ? null : iframe?.contentWindow; if (active && active.iframe === iframe && active.window === iframeWindow) return; const previous = active; active = undefined; let disposalFailed = false; let disposalError: unknown; try { previous?.dispose(); } catch (error) { disposalFailed = true; disposalError = error; } if (iframe && iframeWindow) { const frameHost = new AssistantFrameHost(iframeWindow, targetOrigin); let unsubscribe: Unsubscribe; try { unsubscribe = register(frameHost); } catch (error) { frameHost.dispose(); throw error; } const onLoad = () => frameHost.frameLoaded(); iframe.addEventListener("load", onLoad); active = { iframe, window: iframeWindow, dispose: () => { iframe.removeEventListener("load", onLoad); let cleanupFailed = false; let cleanupError: unknown; try { frameHost.dispose(); } catch (error) { cleanupFailed = true; cleanupError = error; } try { unsubscribe(); } catch (error) { if (cleanupFailed) { console.error( "[assistant-ui] AssistantFrameHost unregistration failed.", error, ); } else { cleanupFailed = true; cleanupError = error; } } if (cleanupFailed) throw cleanupError; }, }; } if (disposalFailed) throw disposalError; }; syncFrame(); const observer = new MutationObserver(syncFrame); observer.observe(document, { childList: true, subtree: true }); return () => { observer.disconnect(); active?.dispose(); }; }, [iframeRef, targetOrigin, register]); };