UNPKG

nitropage

Version:

A free and open source, extensible visual page builder based on SolidStart.

141 lines (118 loc) 3.37 kB
import { Accessor, createSignal, onCleanup, onMount, startTransition, } from "solid-js"; import { produce, SetStoreFunction } from "solid-js/store"; import { isServer } from "solid-js/web"; import { makeEventListener } from "./eventListener"; const UNREADY_TYPE = "$unready"; const READY_TYPE = "$ready"; const READY_CONFIRMED_TYPE = "$ready_ok"; export type Message = { type: | typeof UNREADY_TYPE | typeof READY_TYPE | typeof READY_CONFIRMED_TYPE | (string & {}); data?: any; rawData?: any; }; export type PostMessage = (message: Message) => void; export const createIframeSync = (options: { id: string; iframeRef?: Accessor<HTMLIFrameElement | undefined>; onMessage?: (message: Message) => void; }) => { const { id, iframeRef } = options; const [connected, setConnected] = createSignal(false); const remoteWindow = () => iframeRef?.()?.contentWindow ?? parent; const getQueues = (w: Window): Record<string, Record<number, any>> => ((w as any)._iframeSyncQueues ??= {}); const getQueue = (w?: Window) => { if (isServer) return []; const queues = getQueues(w ?? window); queues[id] ??= {}; return queues[id]; }; const queue = getQueue(); const remoteQueue = () => getQueue(remoteWindow()); let dataId = 0; const postMessage: PostMessage = (message) => { if (message.rawData != null) { const id = dataId++; remoteQueue()[id] = message.rawData; message.rawData = id; } remoteWindow().postMessage(message); }; makeEventListener( () => window, "message", (event: MessageEvent) => { const message = event.data as Message; if (message.type === UNREADY_TYPE) { setConnected(false); return; } if (message.type === READY_TYPE) { postMessage({ type: READY_CONFIRMED_TYPE }); return; } if (message.type === READY_CONFIRMED_TYPE) { if (!connected()) { postMessage({ type: READY_CONFIRMED_TYPE }); } setConnected(true); return; } if (message.rawData != null) { const id = message.rawData; message.rawData = queue[id]; delete queue[id]; } options.onMessage?.(message); }, ); const onDisconnect = () => { postMessage({ type: UNREADY_TYPE }); }; onMount(() => { postMessage({ type: READY_TYPE }); onCleanup(onDisconnect); }); makeEventListener(() => window, "pagehide", onDisconnect); return [connected, postMessage] as const; }; const SET_STATE_TYPE = "$setStore"; export const makeSyncedSetStore = <T>( id: string, setState: SetStoreFunction<T>, postMessage: (message: Message) => void, ) => { let promise: Promise<any> | undefined; const setStateWithTransition = async (fn: any) => { await promise; promise = startTransition(() => { try { setState(produce(fn)); } catch (err) { console.error(err); } }); }; const onMessage = (message: Message) => { if (message.type !== SET_STATE_TYPE || message.data !== id) { return; } setStateWithTransition(message.rawData); return true; }; const setter = (fn: (state: T) => void) => { setState(produce(fn)); postMessage({ type: SET_STATE_TYPE, data: id, rawData: fn }); }; return [setter, onMessage] as const; };