diff --git a/index.js b/index.js index e7ca89a..80c1e8f 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,7 @@ const express = require("express"); const fs = require("fs"); const bodyParser = require("body-parser"); +const ws = require('ws'); const helper = require("./helpers.js"); console.log("Preparing server..."); @@ -56,6 +57,32 @@ currentState.textColors = currentState.colorSegments +const wsServer = new ws.Server({ noServer: true }); +wsServer.on('connection', socket => { + socket.on('message', function incoming(data) { + if(data.toString() == "new client"){ + updatedData() + } + }); +}); + +wsServer.broadcast = function broadcast(data) { + wsServer.clients.forEach(function each(client) { + // The data is coming in correctly + // console.log(data); + client.send(data); + }); +}; + +let updatey = undefined; + +function updatedData() { + currentState.srvTime = new Date().getTime() + wsServer.broadcast(JSON.stringify(currentState)); + clearTimeout(updatey); + setTimeout(updatedData, 5000); +} + console.log("Preparing routes..."); app.get("/", function (req, res) { const data = fs.readFileSync("templates/newAdminPanel.html", "utf8"); @@ -80,6 +107,7 @@ app.get("/api/v1/data", function (req, res) { app.get("/api/v1/set/mode", function (req, res) { currentState.mode = req.query.mode; + updatedData() res.json({ status: "ok" }); }); @@ -92,24 +120,28 @@ app.get("/api/v1/set/layout/showMillis", function (req, res) { } res.json({ status: "ok" }); } + updatedData() }); app.get("/api/v1/set/timerGoal", function (req, res) { currentState.countdownGoal = new Date(parseInt(req.query.time)).getTime(); // ToDO error handling res.json({ status: "ok" }); + updatedData() }); app.get("/api/v1/set/addMillisToTimer", function (req, res) { currentState.timeAmountInital = req.query.time; currentState.countdownGoal = new Date().getTime() + parseInt(req.query.time) res.json({ status: "ok" }); + updatedData() }); app.get("/api/v1/ctrl/timer/pause", function (req, res) { currentState.timerRunState = false; currentState.pauseMoment = new Date().getTime(); res.json({ status: "ok" }); + updatedData() }); app.get("/api/v1/ctrl/timer/play", function (req, res) { @@ -119,11 +151,13 @@ app.get("/api/v1/ctrl/timer/play", function (req, res) { currentState.countdownGoal += new Date().getTime() - currentState.pauseMoment; } res.json({ status: "ok" }); + updatedData() }); app.get("/api/v1/ctrl/timer/restart", function (req, res) { currentState.countdownGoal = new Date().getTime() + parseInt(currentState.timeAmountInital) res.json({ status: "ok" }); + updatedData() }); app.get("/api/v1/set/layout/showTime", function (req, res) { @@ -135,6 +169,7 @@ app.get("/api/v1/set/layout/showTime", function (req, res) { } res.json({ status: "ok" }); } + updatedData() }); app.get("/api/v1/set/progressbar/show", function (req, res) { @@ -143,6 +178,7 @@ app.get("/api/v1/set/progressbar/show", function (req, res) { dataToBeWritten.showProgressbar = currentState.showProgressbar } res.json({ status: "ok" }); + updatedData() }); app.get("/api/v1/set/progressbar/colors", function (req, res) { @@ -160,6 +196,7 @@ app.get("/api/v1/set/progressbar/colors", function (req, res) { res.json({ status: "error", message: error }); console.error(error) } + updatedData() }); app.get("/api/v1/set/text/colors", function (req, res) { @@ -178,6 +215,7 @@ app.get("/api/v1/set/text/colors", function (req, res) { res.json({ status: "error", message: error }); console.error(error) } + updatedData() }); app.get("/api/v1/set/text/enableColoring", function (req, res) { @@ -186,6 +224,7 @@ app.get("/api/v1/set/text/enableColoring", function (req, res) { dataToBeWritten.enableColoredText = currentState.enableColoredText } res.json({ status: "ok" }); + updatedData() }); app.get("/api/v1/ctrl/message/show", function (req, res) { @@ -193,16 +232,19 @@ app.get("/api/v1/ctrl/message/show", function (req, res) { currentState.showMessage = true currentState.messageAppearTime = new Date().getTime() res.json({ status: "ok" }); + updatedData() }); app.get("/api/v1/debug", function (req, res) { currentState.debug = (req.query.enable === 'true'); res.json({ status: "ok" }); + updatedData() }); app.get("/api/v1/ctrl/message/hide", function (req, res) { currentState.showMessage = false res.json({ status: "ok" }); + updatedData() }); app.get("/api/v1/storage/commit", function (req, res) { @@ -213,6 +255,7 @@ app.get("/api/v1/storage/commit", function (req, res) { } catch (error) { res.json({ status: "error", reason: error }); } + updatedData() }); @@ -228,13 +271,20 @@ app.get("/api/v1/storage/delete", function (req, res) { } else { } res.json({ status: "error", reason: "Missing delete argument" }); - + updatedData() }); console.log("Starting server..."); -const port = 8005 -app.listen(port); +const port = 3006 +const server = app.listen(port); +server.on('upgrade', (request, socket, head) => { + wsServer.handleUpgrade(request, socket, head, socket => { + wsServer.emit('connection', socket, request); + }); +}); + + console.info("Server running on port " + port); console.info("Visit localhost:" + port + "/timer for the timer page"); diff --git a/package-lock.json b/package-lock.json index 32978f9..f6a47af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,11 +15,13 @@ "bootstrap-icons": "^1.8.1", "countdown": "^2.6.0", "darkreader": "^4.9.44", + "eta": "^1.12.3", "express": "^4.17.3", "jquery": "^3.6.0", "js-cookie": "^3.0.1", "mdbootstrap": "^4.20.0", - "moment": "^2.29.1" + "moment": "^2.29.1", + "ws": "^8.5.0" } }, "node_modules/@popperjs/core": { @@ -192,6 +194,17 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, + "node_modules/eta": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/eta/-/eta-1.12.3.tgz", + "integrity": "sha512-qHixwbDLtekO/d51Yr4glcaUJCIjGVJyTzuqV4GPlgZo1YpgOKG+avQynErZIYrfM6JIJdtiG2Kox8tbb+DoGg==", + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "url": "https://github.com/eta-dev/eta?sponsor=1" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -603,6 +616,26 @@ "engines": { "node": ">= 0.8" } + }, + "node_modules/ws": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", + "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } } }, "dependencies": { @@ -735,6 +768,11 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, + "eta": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/eta/-/eta-1.12.3.tgz", + "integrity": "sha512-qHixwbDLtekO/d51Yr4glcaUJCIjGVJyTzuqV4GPlgZo1YpgOKG+avQynErZIYrfM6JIJdtiG2Kox8tbb+DoGg==" + }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -1033,6 +1071,12 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "ws": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", + "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", + "requires": {} } } } diff --git a/package.json b/package.json index 1690043..4cbf508 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "jquery": "^3.6.0", "js-cookie": "^3.0.1", "mdbootstrap": "^4.20.0", - "moment": "^2.29.1" + "moment": "^2.29.1", + "ws": "^8.5.0" } } diff --git a/static/js/reconnecting-websocket.min.js b/static/js/reconnecting-websocket.min.js new file mode 100644 index 0000000..3015099 --- /dev/null +++ b/static/js/reconnecting-websocket.min.js @@ -0,0 +1 @@ +!function(a,b){"function"==typeof define&&define.amd?define([],b):"undefined"!=typeof module&&module.exports?module.exports=b():a.ReconnectingWebSocket=b()}(this,function(){function a(b,c,d){function l(a,b){var c=document.createEvent("CustomEvent");return c.initCustomEvent(a,!1,!1,b),c}var e={debug:!1,automaticOpen:!0,reconnectInterval:1e3,maxReconnectInterval:3e4,reconnectDecay:1.5,timeoutInterval:2e3};d||(d={});for(var f in e)this[f]="undefined"!=typeof d[f]?d[f]:e[f];this.url=b,this.reconnectAttempts=0,this.readyState=WebSocket.CONNECTING,this.protocol=null;var h,g=this,i=!1,j=!1,k=document.createElement("div");k.addEventListener("open",function(a){g.onopen(a)}),k.addEventListener("close",function(a){g.onclose(a)}),k.addEventListener("connecting",function(a){g.onconnecting(a)}),k.addEventListener("message",function(a){g.onmessage(a)}),k.addEventListener("error",function(a){g.onerror(a)}),this.addEventListener=k.addEventListener.bind(k),this.removeEventListener=k.removeEventListener.bind(k),this.dispatchEvent=k.dispatchEvent.bind(k),this.open=function(b){h=new WebSocket(g.url,c||[]),b||k.dispatchEvent(l("connecting")),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","attempt-connect",g.url);var d=h,e=setTimeout(function(){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","connection-timeout",g.url),j=!0,d.close(),j=!1},g.timeoutInterval);h.onopen=function(){clearTimeout(e),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onopen",g.url),g.protocol=h.protocol,g.readyState=WebSocket.OPEN,g.reconnectAttempts=0;var d=l("open");d.isReconnect=b,b=!1,k.dispatchEvent(d)},h.onclose=function(c){if(clearTimeout(e),h=null,i)g.readyState=WebSocket.CLOSED,k.dispatchEvent(l("close"));else{g.readyState=WebSocket.CONNECTING;var d=l("connecting");d.code=c.code,d.reason=c.reason,d.wasClean=c.wasClean,k.dispatchEvent(d),b||j||((g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onclose",g.url),k.dispatchEvent(l("close")));var e=g.reconnectInterval*Math.pow(g.reconnectDecay,g.reconnectAttempts);setTimeout(function(){g.reconnectAttempts++,g.open(!0)},e>g.maxReconnectInterval?g.maxReconnectInterval:e)}},h.onmessage=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onmessage",g.url,b.data);var c=l("message");c.data=b.data,k.dispatchEvent(c)},h.onerror=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onerror",g.url,b),k.dispatchEvent(l("error"))}},1==this.automaticOpen&&this.open(!1),this.send=function(b){if(h)return(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","send",g.url,b),h.send(b);throw"INVALID_STATE_ERR : Pausing to reconnect websocket"},this.close=function(a,b){"undefined"==typeof a&&(a=1e3),i=!0,h&&h.close(a,b)},this.refresh=function(){h&&h.close()}}return a.prototype.onopen=function(){},a.prototype.onclose=function(){},a.prototype.onconnecting=function(){},a.prototype.onmessage=function(){},a.prototype.onerror=function(){},a.debugAll=!1,a.CONNECTING=WebSocket.CONNECTING,a.OPEN=WebSocket.OPEN,a.CLOSING=WebSocket.CLOSING,a.CLOSED=WebSocket.CLOSED,a}); diff --git a/static/js/script.js b/static/js/script.js index 941bc97..f4046df 100644 --- a/static/js/script.js +++ b/static/js/script.js @@ -1,6 +1,57 @@ var newDateObj = new Date(); newDateObj = new Date(newDateObj.getTime() + 1000 * 20) +backReqInt = 0; +websocketFailed = false +recoveryAttempts = 0; + +let socket = new ReconnectingWebSocket("ws://localhost:" + location.port); + + +socket.onopen = function (e) { + // alert("[open] Connection established"); + //alert("Sending to server"); + socket.send("new client"); +}; + +socket.onmessage = function (event) { + // alert(`[message] Data received from server: ${event.data}`); + dataFame = JSON.parse(event.data); + timeDiff = new Date().getTime() - dataFame.srvTime +}; + +socket.onclose = function (event) { + if (event.wasClean) { + console.log(`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`); + } else { + // e.g. server process killed or network down + // event.code is usually 1006 in this case + console.error('[close] Connection died'); + + /*websocketFailed = true + if (recoveryAttempts < 5) { + setTimeout(function handleWebsocketRecovery() { + // requestBackend() + websocketFailed = false + recoveryAttempts++; + console.warn("Trying to recover websocket connection") + socket.onopen = function (e) { + // alert("[open] Connection established"); + //alert("Sending to server"); + socket.send("new client"); + }; + }, 1000) + } else { + backReqInt = setInterval(requestBackend, 500); + } + */ + } +}; + +socket.onerror = function (error) { + alert(`[error] ${error.message}`); +}; + allowFullscreen = true let dataFame = {} let timeDiff = 0 @@ -87,11 +138,10 @@ let isSlowed = false function handleRecovery() { var img = document.body.appendChild(document.createElement("img")); - img.onload = function() - { - location.reload(); - }; - img.src = "SMPTE_Color_Bars.svg"; + img.onload = function () { + location.reload(); + }; + img.src = "SMPTE_Color_Bars.svg"; } let recoInter = -1 @@ -101,7 +151,7 @@ function handleUpdate() { document.getElementById("incomeData").innerHTML = JSON.stringify(data) document.getElementById("timediff").innerHTML = timeDiff + "
" + String(new Date().getTime() - data.srvTime); if (new Date().getTime() - data.srvTime > 1000 * 5) { - + if (!isSlowed) { console.error("Server timeout") clearInterval(updateInter) @@ -111,7 +161,7 @@ function handleUpdate() { recoInter = setInterval(handleRecovery, 10000) } } else { - if(isSlowed){ + if (isSlowed) { clearInterval(updateInter) clearInterval(recoInter) updateInter = setInterval(handleUpdate, 2) @@ -222,7 +272,7 @@ function getTime() { } -setInterval(requestBackend, 500); +// setInterval(requestBackend, 500); updateInter = setInterval(handleUpdate, 2); let temp = new URLSearchParams(window.location.search).get("smaller"); diff --git a/templates/timerPage.html b/templates/timerPage.html index 1a43e53..62e0a3c 100644 --- a/templates/timerPage.html +++ b/templates/timerPage.html @@ -45,6 +45,7 @@ +