/*eslint-env browser*/ /** * This is a very minimal example for a websocket client * You could use this as a starting point to creating your own interfaces */ // Data that the user needs to provide depending on the Ontime URL const isSecure = window.location.protocol === 'https:'; const userProvidedSocketUrl = `${isSecure ? 'wss' : 'ws'}://${window.location.host}${getStageHash()}/ws`; let websocket = null connectSocket(); let reconnectTimeout; const reconnectInterval = 1000; let reconnectAttempts = 0; /** * Connects to the websocket server * @param {string} socketUrl */ function connectSocket(socketUrl = userProvidedSocketUrl) { websocket = new WebSocket(socketUrl); websocket.onopen = () => { clearTimeout(reconnectTimeout); reconnectAttempts = 0; console.warn('WebSocket connected'); }; websocket.onclose = () => { console.warn('WebSocket disconnected'); reconnectTimeout = setTimeout(() => { console.warn(`WebSocket: attempting reconnect ${reconnectAttempts}`); if (websocket && websocket.readyState === WebSocket.CLOSED) { reconnectAttempts += 1; connectSocket(); } }, reconnectInterval); }; websocket.onerror = (error) => { console.error('WebSocket error:', error); }; websocket.onmessage = (event) => { // all objects from ontime are structured with tag and payload const { tag, payload } = JSON.parse(event.data); /** * runtime-data is sent * - on connect with the full state * - and then on every update with a patch */ if (tag === 'runtime-data') { handleOntimePayload(payload); } }; } let localData = {}; let localVars = {}; /** * Handles the ontime payload updates * @param {object} payload - The payload object containing the updates */ function handleOntimePayload(payload) { // 1. apply the patch into your local copy of the data localData = { ...localData, ...payload }; if ('eventNow' in payload) { let imgelement = document.getElementById("image"); let timerelement = document.getElementById("timer"); localVars.nowid = payload.eventNow.id; localVars.layoutimg = payload.eventNow.custom["layoutimage"] ?? null; let newready = payload.eventNow.custom["ready"] == "true"; if (localVars.ready != newready) { localVars.ready = newready; updateReady(); } newready = payload.eventNow.custom["stageready"] == "true"; if (localVars.stageready != newready) { localVars.stageready = newready; updateStageButton(); } imgelement.src = localVars.layoutimg; if (localVars.layoutimg) { imgelement.classList.remove("hidden"); timerelement.classList.add("hidden"); }else { imgelement.classList.add("hidden"); timerelement.classList.remove("hidden"); } } } async function blink(id) { document.getElementById(id).classList.add("blink"); setTimeout(() => { document.getElementById(id).classList.remove("blink"); }, 3000); } function onReadyClick() { const message = { tag: "change", payload: { [localVars.nowid]: { "custom:stageready": !localVars.stageready } } }; websocket.send(JSON.stringify(message)); } function updateStageButton() { let stagereadybutton = document.getElementById("stageready"); if (localVars.stageready) { stagereadybutton.classList.remove("redoutline"); stagereadybutton.classList.add("greenoutline"); }else { stagereadybutton.classList.add("redoutline"); stagereadybutton.classList.remove("greenoutline"); } } function updateReady() { let fohreadybutton = document.getElementById("fohready"); let container = document.getElementById("container"); if (localVars.ready) { container.classList.remove("redoutline"); container.classList.add("greenoutline"); blink("container") fohreadybutton.classList.remove("redoutline"); fohreadybutton.classList.add("greenoutline"); }else { container.classList.add("redoutline"); container.classList.remove("greenoutline"); fohreadybutton.classList.add("redoutline"); fohreadybutton.classList.remove("greenoutline"); } } /** * Updates the DOM with a given payload * @param {string} field - The runtime data field * @param {object} payload - The patch object for the field */ function updateDOM(field, payload) { const domElement = document.getElementById(field); if (domElement) { domElement.innerText = payload; } } /** * Stringifies an object into a pretty string * @param {object} data - The data object to format * @returns {string} The formatted data string */ function formatObject(data) { return JSON.stringify(data, null, 2); } /** * Utility to handle a demo deployed in an ontime stage * You can likely ignore this in your app * * an url looks like * https://cloud.getontime.no/stage-hash/external/demo/ -> /stage-hash * @returns {string} - The stage hash if the app is running in an ontime stage */ function getStageHash() { const href = window.location.href; if (!href.includes('getontime.no')) { return ''; } const hash = href.split('/'); const stageHash = hash.at(3); return stageHash ? `/${stageHash}` : ''; }