@deskthing/cli
Version:
An emulator for the DeskThing Server
143 lines (120 loc) • 4.62 kB
text/typescript
import { create } from 'zustand'
import { ClientToDeviceData, DeviceToClientCore, DEVICE_CLIENT, AppManifest } from '@deskthing/types'
import { ClientService } from '../services/clientService'
import { ClientLogger } from '../services/clientLogger'
import { useClientStore } from './clientStore'
interface MessageState {
messageQueue: DeviceToClientCore[]
// Actions
handleIframeMessage: (data: ClientToDeviceData, origin: string) => void
sendToIframe: (data: DeviceToClientCore) => boolean
processMessageQueue: () => void
}
export const useMessageStore = create<MessageState>()((set, get) => ({
messageQueue: [],
sendToIframe: (data) => {
const iframe = document.querySelector('#app') as HTMLIFrameElement
if (iframe?.contentWindow) {
const augmentedData = { ...data, source: "deskthing" }
ClientLogger.debug('Sending data to iframe', augmentedData)
iframe.contentWindow.postMessage(augmentedData, "*")
return true
} else {
ClientLogger.error('Iframe not found or not ready to receive messages, queuing message')
get().messageQueue.push(data)
return false
}
},
processMessageQueue: () => {
const process = () => {
const queue = get().messageQueue
if (queue.length === 0) return
ClientLogger.debug('Processing message queue', queue)
// Try sending the first message
const data = queue[0]
const success = get().sendToIframe(data)
if (success) {
// Remove the sent message and recurse
set({ messageQueue: queue.slice(1) })
process()
} else {
ClientLogger.error('Iframe not found or not ready, stopping message queue processing')
return
}
}
process()
},
handleIframeMessage: (data, origin) => {
const { appManifest, setSongData } = useClientStore.getState()
if (data.app === "client") {
handleClientMessage(data)
} else {
handleAppMessage(data, appManifest)
}
}
}))
// Helper functions
const handleClientMessage = (data: ClientToDeviceData) => {
const { sendToIframe } = useMessageStore.getState()
const { songData, settings, apps, clientManifest, requestSettings } = useClientStore.getState()
switch (data.type) {
case "get":
switch (data.request) {
case "song":
case "music":
ClientLogger.debug("Get request for music, Sending music", songData)
sendToIframe({ type: DEVICE_CLIENT.MUSIC, app: "client", payload: songData })
break
case "settings":
ClientLogger.debug("Get request for settings, Sending settings")
if (settings) {
sendToIframe({ type: DEVICE_CLIENT.SETTINGS, app: "client", payload: settings })
break
} else {
ClientLogger.warn("Settings not available, requesting from server")
}
requestSettings().then((settings) => {
sendToIframe({ type: DEVICE_CLIENT.SETTINGS, app: "client", payload: settings })
}).catch((error) => {
ClientLogger.error("Failed to get settings:", error)
})
break
case "apps":
ClientLogger.debug("Get request for apps, Sending apps", apps)
sendToIframe({ type: DEVICE_CLIENT.APPS, app: "client", payload: apps })
break
case "manifest":
ClientLogger.debug("Get request for manifest, Sending manifest")
sendToIframe({ type: DEVICE_CLIENT.MANIFEST, app: "client", payload: clientManifest })
break
}
break
case "log":
ClientLogger.clientLog(data.request as any, data.payload.message, ...(data.payload.data || []))
break
case "key":
case "action":
handleActionMessage(data)
break
}
}
const handleAppMessage = (data: ClientToDeviceData, appManifest: AppManifest | null) => {
ClientLogger.debug('Sending data to server', data)
const clientId = useClientStore.getState().clientId
const appId = data.app || appManifest?.id || "unknownId"
ClientService.sendToApp({
...data,
app: appId,
clientId: clientId
})
}
const handleActionMessage = (data: Extract<ClientToDeviceData, { type: 'action' | 'key' }>) => {
const { appManifest, clientId } = useClientStore.getState()
ClientLogger.debug('Handling action', data)
const appId = data.app || appManifest?.id || "unknownId"
ClientService.sendToApp({
...data,
app: appId,
clientId: clientId
})
}