'use client'; import { createContext, use, useEffect, useRef, useState } from 'react'; import { newMessagePortRpcSession, type RpcStub } from 'capnweb'; type EmbedBlockData = | { type: 'text'; content: string } | { type: 'embed'; url: string; height?: number; aspectRatio?: string; }; // Define the interface the *host* exposes (methods you can call) export type HostMethods = { open(url: string): void; replaceWith(block: EmbedBlockData): void; addBelow(block: EmbedBlockData): void; }; export type HostSession = RpcStub; const RpcSessionContext = createContext(null); function connectToHost(): Promise { return new Promise((resolve) => { // Listen for the host to send us a MessagePort function handleMessage(event: MessageEvent) { if (event.data?.type !== 'parts.page.channel') return; const port = event.ports[0]; if (!port) return; // Start the RPC session over the transferred port const session = newMessagePortRpcSession(port, null) as HostSession; resolve(session); // Remove listener once connected window.removeEventListener('message', handleMessage); } window.addEventListener('message', handleMessage); // Signal to the host that we want to connect window.parent.postMessage({ type: 'parts.page.connect' }, '*'); }); } export function RpcSessionProvider({ children, }: { children: React.ReactNode; }) { const [session, setSession] = useState(null); const connectingRef = useRef(false); useEffect(() => { // De-dupe: only connect once even in StrictMode double-mount if (connectingRef.current) return; connectingRef.current = true; connectToHost().then((session) => setSession(() => session)); return () => { // Cleanup on unmount session?.[Symbol.dispose](); }; }, [session]); return {children}; } export function useRpcSession() { return use(RpcSessionContext); }