From 93462b6100b31040e864c70a67ab4311137e68f9 Mon Sep 17 00:00:00 2001 From: Somajit Date: Tue, 17 Sep 2024 16:59:54 +0530 Subject: [PATCH] Feature/unread msg count (#6) --- app/bg-worker.js | 18 +++++++++++------ app/server.js | 52 +++++++++++++++++++++++++++++++++++++++--------- app/spa.js | 2 +- index.html | 2 +- 4 files changed, 57 insertions(+), 17 deletions(-) diff --git a/app/bg-worker.js b/app/bg-worker.js index a65297d..61a68be 100644 --- a/app/bg-worker.js +++ b/app/bg-worker.js @@ -1,7 +1,7 @@ // Code for background worker. // Its purpose is to listen to piping-server for form data and post to Telegram when received, i.e. relaying. // Polling piping-server API is achieved using setTimeout for efficiency. One relay seeds the next before returning. -// 'main' in the following refers to server.js. +// 'main' in the following refers to "server.js". function urlEncoded2Json(str){ const arr = str.split('&'); @@ -21,10 +21,17 @@ async function pollApi(getFrom, postTo, TGchatID) { const relay = async () => { let data; // This will hold the received URL encoded form data + let errLvl = 2; // Default error level when errors are caught. May be overriden before using `throw`. // Listen to piping-server try { const response = await fetch(getFrom); // Make request + + if (! response.ok) { + errLvl = 1; // Set error to critical/fatal + throw "GET @ " + getFrom + " status: " + response.status; + } + data = urlEncoded2Json(await response.text()); // Send URL decoded form data as JSON string to main for logging. Also pass an error level. postMessage([data, 0]); @@ -32,7 +39,7 @@ async function pollApi(getFrom, postTo, TGchatID) { } catch (error) { console.error(Date() + ': Error making GET request --', error); // Send error to main for logging. Error level: 1 for high priority / fatal. - postMessage(['Failed to fetch form data.', 1]); + postMessage(['Failed to fetch form data.', errLvl]); return; } finally { @@ -48,16 +55,15 @@ async function pollApi(getFrom, postTo, TGchatID) { headers: {'Content-Type': 'application/json'}, body: JSON.stringify(payload) }) - postStatus = await response.text(); - if (! JSON.parse(postStatus).ok) { - throw "API rejected credentials" + if (! response.ok) { + throw "POST @ " + postTo + " status: " + response.status +". Is chat ID = " + TGchatID + " ok?"; } } catch (error) { console.error(Date() + ': Error making POST request --', error); // Send error to main for logging. Error level: 2 for low priority / non-fatal. - postMessage(['Failed to post form data to Telegram.', 2]); + postMessage(['Failed to post form data to Telegram.', errLvl]); return; } diff --git a/app/server.js b/app/server.js index c246b97..6eeeae0 100644 --- a/app/server.js +++ b/app/server.js @@ -1,10 +1,33 @@ +// Main entry point for the server. Deploys background worker in "bg-worker.js" for handling networking. + let myWorker = null; let getFrom, postTo, TGchatID; +let numReadMsgs = 0; +let numTotalMsgs = 0; const logs = document.getElementById("logs"); const toggleServer = document.getElementById("toggleServer"); function logThis(report) { - logs.innerHTML += Date() + ":
=========
" + report + "
=========

"; + const row = document.createElement("p"); + row.append(Date() + ": " + report); + logs.prepend(row); +} + +// Handler for updating the display of number of unread messages +function updateUnreadCount(mode){ + let numUnreadMsgs; + + if (spaCurrentPageID === "inbox") { + // Opportunity to reset everything + numTotalMsgs = 0; + numReadMsgs = 0; + numUnreadMsgs = 0; + } else if (mode === "new") { + numUnreadMsgs = ++numTotalMsgs - numReadMsgs; + } else { + numUnreadMsgs = numTotalMsgs - (++numReadMsgs); + } + document.getElementById("unread").innerText = numUnreadMsgs; } function inbox(json){ @@ -20,20 +43,26 @@ function inbox(json){ const cell = document.createElement("td"); // Create a text entry: - const entry = document.createTextNode(eval("data." + keysEnumArray[key])); + entry = eval("data." + keysEnumArray[key]); // Append entry to cell: - cell.appendChild(entry); + cell.append(entry); // Append cell to row: - row.appendChild(cell); + row.append(cell); } // Append row to table body: - document.getElementById("inboxTable").appendChild(row); + document.getElementById("inboxTable").prepend(row); + + // Update number of total messages + updateUnreadCount("new"); } function genUUID() { + // v4 UUID looks like xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx in hexadecimal. See Wikipedia. + // M stores version & N, the variant. All the x digits above are cryptographically random. + // For our uuid we simply choose the first block of hex chars from a v4 UUID. document.getElementById("uuid").value = crypto.randomUUID().split('-')[0]; } @@ -41,6 +70,11 @@ const fetchChatID = async () => { logThis("Fetching Telegram chat ID") const apiEndpoint = 'https://api.telegram.org/bot' + document.getElementById("apiKey").value + '/getUpdates'; const response = await fetch(apiEndpoint); // Make request + if (! response.ok) { + logThis("Telegram API status code:" + response.status +". Is Bot API Token ok?"); + alert("Failed to fetch chat ID. Check your Bot API Token!"); + return; + } const data = await response.json(); document.getElementById("chatID").value = data.result[0].message.chat.id; } @@ -72,13 +106,13 @@ function startWorker() { const msg = e.data[0]; if (! errLvl) { inbox(msg); - logThis('Received: ' + msg); + logThis('RECEIVED: ' + msg); } else if (errLvl === 1) { stopWorker(); - logThis('Fatal Error: ' + msg + ' See console for details.'); + logThis('FATAL ERROR: ' + msg + ' See console for details.'); alert('Server stopped due to some critical error'); } else { - logThis('Error: ' + msg + ' See console for details.'); + logThis('ERROR: ' + msg + ' See console for details.'); } } @@ -98,7 +132,7 @@ function stopWorker() { console.log("Worker terminated"); toggleServer.value = "Launch Server" logThis("Server stopped"); - document.getElementById("serverStatus").innerHTML = "Killed"; + document.getElementById("serverStatus").innerText = "Killed"; } function toggleWorker() { diff --git a/app/spa.js b/app/spa.js index 8767867..27b8702 100644 --- a/app/spa.js +++ b/app/spa.js @@ -1,6 +1,6 @@ // Utilities for turning any html into a Single Page Application (SPA). // Public API identifiers (i.e. names of exposed vars, consts and funcs) are prefixed with 'spa'. -// Blocks scopes are to hide the private elements. +// Block scopes are to hide the private elements. const spaHomePageID = document.querySelector(".spa-page").id; // Assuming first spa-page class is the home / hero page let spaCurrentPageID = spaHomePageID; diff --git a/index.html b/index.html index 8055931..4bbd433 100755 --- a/index.html +++ b/index.html @@ -26,7 +26,7 @@

Menu

- + Source Donate Contact