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