import { createNanoEvents } from "nanoevents"; import { useCallback, useEffect, useRef } from "react"; import { batchUpdate } from "./raf-queue"; export const createPubsub = () => { type Action = PublishMap[Type] extends undefined ? { type: Type; payload?: undefined } : { type: Type; payload: PublishMap[Type] }; // eslint-disable-next-line @typescript-eslint/no-explicit-any const emitter = createNanoEvents>(); if (typeof window === "object") { window.addEventListener( "message", (event: MessageEvent) => { // @todo this has no type safety built in, could be anything from any source. // we could potentially maintain a list of valid event types at runtime // at the very least we could add a brand property or something to our events if (typeof event.data?.type === "string") { // Execute all updates within a single batch to improve performance batchUpdate(() => emitter.emit(event.data.type, event.data.payload)); } }, false ); } return { /** * To publish a postMessage event on the current window and parent window from the iframe. */ publish(action: Action) { window.parent.postMessage(action, "*"); window.postMessage(action, "*"); }, /** * To publish a postMessage event on the iframe and parent window from the parent window. */ usePublish() { const iframeRef = useRef(null); const publishCallback = useCallback( (action: Action) => { const element = iframeRef.current; if (element?.contentWindow == null) { return; } element.contentWindow.postMessage(action, "*"); window.postMessage(action, "*"); }, [iframeRef] ); return [publishCallback, iframeRef] as const; }, /** * To subscribe a message event on the current window. */ useSubscribe( type: Type, onAction: (payload: PublishMap[Type]) => void ) { useEffect(() => { return emitter.on(type, onAction); }, [type, onAction]); }, subscribe( type: Type, onAction: (payload: PublishMap[Type]) => void ) { return emitter.on(type, onAction); }, }; };