|
| 1 | +import { useCallback, useEffect, useState } from "react"; |
| 2 | + |
| 3 | +export type MessageToHazel = |
| 4 | + | { type: "ready"; id: string } |
| 5 | + | { type: "setSyntax"; id: string; codec: string; value: string } |
| 6 | + | { type: "resize"; id: string; width: number; height: number }; |
| 7 | + |
| 8 | +export type MessageFromHazel = |
| 9 | + | { type: "init"; id: string; value: string } |
| 10 | + | { |
| 11 | + type: "constraints"; |
| 12 | + id: string; |
| 13 | + maxWidth: number; |
| 14 | + maxHeight: number; |
| 15 | + minWidth?: number; |
| 16 | + minHeight?: number; |
| 17 | + }; |
| 18 | + |
| 19 | +export function isFromHazelMessage(data: unknown): data is MessageFromHazel { |
| 20 | + return ( |
| 21 | + data !== null && |
| 22 | + typeof data === "object" && |
| 23 | + "type" in data && |
| 24 | + "id" in data && |
| 25 | + ["init", "constraints"].includes( |
| 26 | + (data as Record<string, unknown>).type as string, |
| 27 | + ) && |
| 28 | + typeof (data as Record<string, unknown>).id === "string" |
| 29 | + ); |
| 30 | +} |
| 31 | + |
| 32 | +type HazelIntegrationConfig = { |
| 33 | + id: string; |
| 34 | + codec: string; |
| 35 | + onInit: (value: string) => void; |
| 36 | +}; |
| 37 | + |
| 38 | +const sendToHazel = (message: MessageToHazel, targetOrigin: string) => { |
| 39 | + if (window.parent && window.parent !== window) { |
| 40 | + console.log("Sending message to Hazel", message); |
| 41 | + window.parent.postMessage(message, targetOrigin); |
| 42 | + } |
| 43 | +}; |
| 44 | + |
| 45 | +/** |
| 46 | + * Core Hazel integration for SolidJS - handles protocol, messaging, and setup |
| 47 | + */ |
| 48 | +export const useHazelIntegration = (config: HazelIntegrationConfig) => { |
| 49 | + const { id, codec, onInit } = config; |
| 50 | + const [hasInit, setHasInit] = useState(false); |
| 51 | + |
| 52 | + const targetOrigin = |
| 53 | + new URLSearchParams(window.location.search).get("parentOrigin") || "*"; |
| 54 | + |
| 55 | + const setSyntax = useCallback( |
| 56 | + (value: string) => { |
| 57 | + sendToHazel({ type: "setSyntax", id, codec, value }, targetOrigin); |
| 58 | + }, |
| 59 | + [id, codec, targetOrigin], |
| 60 | + ); |
| 61 | + |
| 62 | + useEffect(() => { |
| 63 | + const handleMessage = (event: MessageEvent) => { |
| 64 | + const data = event.data; |
| 65 | + |
| 66 | + if (!isFromHazelMessage(data) || data.id !== id) { |
| 67 | + return; |
| 68 | + } |
| 69 | + |
| 70 | + console.log("Received message from Hazel", data); |
| 71 | + |
| 72 | + switch (data.type) { |
| 73 | + case "init": |
| 74 | + if (onInit) { |
| 75 | + onInit(data.value); |
| 76 | + } |
| 77 | + break; |
| 78 | + } |
| 79 | + }; |
| 80 | + |
| 81 | + window.addEventListener("message", handleMessage); |
| 82 | + |
| 83 | + // Send ready message when component mounts |
| 84 | + if (!hasInit) { |
| 85 | + sendToHazel({ type: "ready", id }, targetOrigin); |
| 86 | + setHasInit(true); |
| 87 | + } |
| 88 | + |
| 89 | + return () => { |
| 90 | + window.removeEventListener("message", handleMessage); |
| 91 | + }; |
| 92 | + }); |
| 93 | + |
| 94 | + return { |
| 95 | + setSyntax, |
| 96 | + }; |
| 97 | +}; |
0 commit comments