useEmbedMessaging.ts (3037B)
1 import { useCallback, useEffect } from "react"; 2 3 import type { TrackInfo } from "./useMyMusicsPlayback"; 4 import { 5 parseThemePayload, 6 resolveEmbedTheme, 7 tokensToCssVars, 8 type EmbedThemeOverrides, 9 } from "../lib/embedTheme"; 10 11 export type EmbedPlaybackState = "playing" | "paused" | "buffering" | "error"; 12 13 const PARENT_ORIGIN = 14 typeof import.meta.env.VITE_EMBED_PARENT_ORIGIN === "string" && 15 import.meta.env.VITE_EMBED_PARENT_ORIGIN.trim() 16 ? import.meta.env.VITE_EMBED_PARENT_ORIGIN.trim() 17 : "*"; 18 19 function post(type: string, payload: Record<string, unknown> = {}) { 20 if (typeof window === "undefined" || window.parent === window) return; 21 window.parent.postMessage({ source: "mymusics", type, ...payload }, PARENT_ORIGIN); 22 } 23 24 type Options = { 25 enabled: boolean; 26 trackCount: number | null; 27 track: TrackInfo | null; 28 streamUrl: string | null; 29 playbackState: EmbedPlaybackState; 30 onNext: () => void; 31 onPlay: () => void; 32 onPause: () => void; 33 onTheme?: (patch: EmbedThemeOverrides) => void; 34 themeOverrides?: EmbedThemeOverrides; 35 }; 36 37 export function useEmbedMessaging({ 38 enabled, 39 trackCount, 40 track, 41 streamUrl, 42 playbackState, 43 onNext, 44 onPlay, 45 onPause, 46 onTheme, 47 themeOverrides, 48 }: Options) { 49 useEffect(() => { 50 if (!enabled) return; 51 post("mymusics:ready", { trackCount }); 52 }, [enabled, trackCount]); 53 54 useEffect(() => { 55 if (!enabled || !track) return; 56 post("mymusics:track", { 57 id: track.id, 58 title: track.title, 59 artist: track.artist, 60 streamUrl, 61 }); 62 }, [enabled, track, streamUrl]); 63 64 useEffect(() => { 65 if (!enabled) return; 66 post("mymusics:state", { state: playbackState }); 67 }, [enabled, playbackState]); 68 69 useEffect(() => { 70 if (!enabled) return; 71 const tokens = resolveEmbedTheme(themeOverrides ?? {}); 72 post("mymusics:theme-applied", { tokens: tokensToCssVars(tokens) }); 73 }, [enabled, themeOverrides]); 74 75 const postError = useCallback( 76 (code: string, message: string) => { 77 if (!enabled) return; 78 post("mymusics:error", { code, message }); 79 }, 80 [enabled], 81 ); 82 83 useEffect(() => { 84 if (!enabled) return; 85 const onMessage = (ev: MessageEvent) => { 86 const data = ev.data as { 87 source?: string; 88 type?: string; 89 command?: string; 90 theme?: unknown; 91 }; 92 if (data?.source !== "mymusics-host") return; 93 if (PARENT_ORIGIN !== "*" && ev.origin !== PARENT_ORIGIN) return; 94 95 if (data.type === "mymusics:theme") { 96 const patch = parseThemePayload(data.theme); 97 if (patch && onTheme) onTheme(patch); 98 return; 99 } 100 101 if (data.type !== "mymusics:command") return; 102 const cmd = data.command; 103 if (cmd === "play") onPlay(); 104 else if (cmd === "pause") onPause(); 105 else if (cmd === "next") onNext(); 106 }; 107 window.addEventListener("message", onMessage); 108 return () => window.removeEventListener("message", onMessage); 109 }, [enabled, onNext, onPlay, onPause, onTheme]); 110 111 return { postError }; 112 }