Skip to content

Commit

Permalink
Feature/unread msg count (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
SomajitDey authored Sep 17, 2024
1 parent bddec72 commit 93462b6
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 17 deletions.
18 changes: 12 additions & 6 deletions app/bg-worker.js
Original file line number Diff line number Diff line change
@@ -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('&');
Expand All @@ -21,18 +21,25 @@ 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]);

} 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 {
Expand All @@ -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;
}

Expand Down
52 changes: 43 additions & 9 deletions app/server.js
Original file line number Diff line number Diff line change
@@ -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() + ":<br>=========<br>" + report + "<br>=========<br><br>";
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){
Expand All @@ -20,27 +43,38 @@ 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];
}

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;
}
Expand Down Expand Up @@ -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.');
}
}

Expand All @@ -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() {
Expand Down
2 changes: 1 addition & 1 deletion app/spa.js
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ <h3>Menu</h3>
<div class="d-flex flex-column justify-content-center align-items-center mt-2 mb-3 pb-3">
<button type="button" class="btn btn-primary mt-5" onclick="spaGoTo('about');">About</button>
<button type="button" class="btn btn-primary mt-4" onclick="spaGoTo('server');">Server</button>
<button type="button" class="btn btn-primary mt-4" onclick="spaGoTo('inbox');">Inbox</button>
<button type="button" class="btn btn-primary mt-4" onclick="spaGoTo('inbox'); updateUnreadCount();">Inbox <span id ="unread" class="badge bg-danger">0</span></button>
<a href="https://github.com/somajitdey/easyform" class="btn btn-primary mt-4">Source</a>
<a href="https://buymeacoffee.com/SomajitDey" class="btn btn-primary mt-4">Donate</a>
<a href="https://github.com/SomajitDey/EasyForm/discussions/1" class="btn btn-primary mt-4">Contact</a>
Expand Down

0 comments on commit 93462b6

Please sign in to comment.