const path = require("path"); const { ipcRenderer } = require("electron"); const { v4: uuidv4 } = require("uuid"); const imageCompression = require("browser-image-compression"); const blobUtil = require("blob-util"); const FD = require("./freedata"); const fs = require("fs"); // https://stackoverflow.com/a/26227660 var appDataFolder = process.env.APPDATA || (process.platform == "darwin" ? process.env.HOME + "/Library/Application Support" : process.env.HOME + "/.config"); var configFolder = path.join(appDataFolder, "FreeDATA"); var configPath = path.join(configFolder, "config.json"); var config = require(configPath); // set date format const dateFormat = new Intl.DateTimeFormat(navigator.language, { timeStyle: "long", dateStyle: "short", }); // set date format information const dateFormatShort = new Intl.DateTimeFormat(navigator.language, { year: "numeric", month: "numeric", day: "numeric", hour: "numeric", minute: "numeric", second: "numeric", hour12: false, }); const dateFormatHours = new Intl.DateTimeFormat(navigator.language, { hour: "numeric", minute: "numeric", hour12: false, }); // split character const split_char = "\0;\1;"; // global for our selected file we want to transmit // ----------------- some chat globals var filetype = ""; var file = ""; var filename = ""; var callsign_counter = 0; var selected_callsign = ""; var lastIsWritingBroadcast = new Date().getTime(); var defaultUserIcon = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0iY3VycmVudENvbG9yIiBjbGFzcz0iYmkgYmktcGVyc29uLWJvdW5kaW5nLWJveCIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNMS41IDFhLjUuNSAwIDAgMC0uNS41djNhLjUuNSAwIDAgMS0xIDB2LTNBMS41IDEuNSAwIDAgMSAxLjUgMGgzYS41LjUgMCAwIDEgMCAxaC0zek0xMSAuNWEuNS41IDAgMCAxIC41LS41aDNBMS41IDEuNSAwIDAgMSAxNiAxLjV2M2EuNS41IDAgMCAxLTEgMHYtM2EuNS41IDAgMCAwLS41LS41aC0zYS41LjUgMCAwIDEtLjUtLjV6TS41IDExYS41LjUgMCAwIDEgLjUuNXYzYS41LjUgMCAwIDAgLjUuNWgzYS41LjUgMCAwIDEgMCAxaC0zQTEuNSAxLjUgMCAwIDEgMCAxNC41di0zYS41LjUgMCAwIDEgLjUtLjV6bTE1IDBhLjUuNSAwIDAgMSAuNS41djNhMS41IDEuNSAwIDAgMS0xLjUgMS41aC0zYS41LjUgMCAwIDEgMC0xaDNhLjUuNSAwIDAgMCAuNS0uNXYtM2EuNS41IDAgMCAxIC41LS41eiIvPgogIDxwYXRoIGQ9Ik0zIDE0cy0xIDAtMS0xIDEtNCA2LTQgNiAzIDYgNC0xIDEtMSAxSDN6bTgtOWEzIDMgMCAxIDEtNiAwIDMgMyAwIDAgMSA2IDB6Ii8+Cjwvc3ZnPg=="; var defaultGroupIcon = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0iY3VycmVudENvbG9yIiBjbGFzcz0iYmkgYmktcGVvcGxlLWZpbGwiIHZpZXdCb3g9IjAgMCAxNiAxNiI+CiAgPHBhdGggZD0iTTcgMTRzLTEgMC0xLTEgMS00IDUtNCA1IDMgNSA0LTEgMS0xIDFIN1ptNC02YTMgMyAwIDEgMCAwLTYgMyAzIDAgMCAwIDAgNlptLTUuNzg0IDZBMi4yMzggMi4yMzggMCAwIDEgNSAxM2MwLTEuMzU1LjY4LTIuNzUgMS45MzYtMy43MkE2LjMyNSA2LjMyNSAwIDAgMCA1IDljLTQgMC01IDMtNSA0czEgMSAxIDFoNC4yMTZaTTQuNSA4YTIuNSAyLjUgMCAxIDAgMC01IDIuNSAyLjUgMCAwIDAgMCA1WiIvPgo8L3N2Zz4="; // ----------------------------------- // Initially fill sharedFolderFileList //TODO: Make this automatically ever N seconds var sharedFolderFileList = ""; ipcRenderer.send("read-files-in-folder", { folder: config.shared_folder_path, }); var chatDB = path.join(configFolder, "chatDB"); var userDB = path.join(configFolder, "userDB"); // ---- MessageDB try { var PouchDB = require("pouchdb"); } catch (err) { console.log(err); /* This is a fix for raspberryPi where we get an error when loading pouchdb because of leveldown package isnt running on ARM devices. pouchdb-browser does not depend on leveldb and seems to be working. */ console.log("using pouchdb-browser fallback"); var PouchDB = require("pouchdb-browser"); } PouchDB.plugin(require("pouchdb-find")); //PouchDB.plugin(require('pouchdb-replication')); PouchDB.plugin(require("pouchdb-upsert")); var db = new PouchDB(chatDB); var users = new PouchDB(userDB); /* -------- CREATE DATABASE INDEXES */ createChatIndex(); createUserIndex(); // REMOTE SYNC ATTEMPTS //var remoteDB = new PouchDB('http://172.20.10.4:5984/chatDB') /* // we need express packages for running pouchdb sync "express-pouchdb" var express = require('express'); var app = express(); app.use('/', require('express-pouchdb')(PouchDB)); app.listen(5984); var db = new PouchDB(chatDB); app.use('/chatDB', require('pouchdb-express-router')(PouchDB)); app.listen(5984); db.sync('http://172.20.10.4:5984/jojo', { //var sync = PouchDB.sync('chatDB', 'http://172.20.10.4:5984/chatDB', { live: true, retry: false }).on('change', function (change) { // yo, something changed! console.log(change) }).on('paused', function (err) { // replication was paused, usually because of a lost connection console.log(err) }).on('active', function (info) { // replication was resumed console.log(info) }).on('error', function (err) { // totally unhandled error (shouldn't happen) console.log(err) }).on('denied', function (err) { // a document failed to replicate (e.g. due to permissions) console.log(err) }).on('complete', function (info) { // handle complete; console.log(info) }); */ var dxcallsigns = new Set(); //Set default chat filter var chatFilter = [ { type: "newchat" }, { type: "received" }, { type: "transmit" }, { type: "ping-ack" }, { type: "broadcast_received" }, { type: "broadcast_transmit" }, //{ type: "request" }, //{ type: "response" }, ]; // WINDOW LISTENER window.addEventListener("DOMContentLoaded", () => { updateAllChat(false); // theme selector changeGuiDesign(config.theme); const userInfoFields = [ "user_info_image", "user_info_callsign", "user_info_gridsquare", "user_info_name", "user_info_age", "user_info_location", "user_info_radio", "user_info_antenna", "user_info_email", "user_info_website", "user_info_comments", ]; users .find({ selector: { user_info_callsign: config.mycall, }, }) .then(function (result) { console.log(result); if (typeof result.docs[0] !== "undefined") { // handle result userInfoFields.forEach(function (elem) { if (elem !== "user_info_image") { document.getElementById(elem).value = result.docs[0][elem]; } else { document.getElementById(elem).src = result.docs[0][elem]; } }); } else { console.log( config.mycall + " not found in user db - creating new entry", ); // add initial entry for own callsign and grid let obj = new Object(); obj.user_info_callsign = config.mycall; obj.user_info_gridsquare = config.mygrid; addUserToDatabaseIfNotExists(obj); document.getElementById("user_info_callsign").value = config.mycall; document.getElementById("user_info_gridsquare").value = config.mygrid; } }) .catch(function (err) { console.log(err); }); //save user info document.getElementById("userInfoSave").addEventListener("click", () => { let obj = new Object(); userInfoFields.forEach(function (subelem) { if (subelem !== "user_info_image") { obj[subelem] = document.getElementById(subelem).value; } else { obj[subelem] = document.getElementById(subelem).src; } }); addUserToDatabaseIfNotExists(obj); }); //Add event listener for filter apply button document.getElementById("btnFilter").addEventListener("click", () => { chatFilter.length = 0; if (document.getElementById("chkMessage").checked == true) { chatFilter = [{ type: "newchat" }]; chatFilter.push( { type: "received" }, { type: "transmit" }, { type: "broadcast_received" }, { type: "broadcast_transmit" }, ); } if (document.getElementById("chkPing").checked == true) chatFilter.push({ type: "ping" }); if (document.getElementById("chkPingAck").checked == true) chatFilter.push({ type: "ping-ack" }); if (document.getElementById("chkBeacon").checked == true) chatFilter.push({ type: "beacon" }); if (document.getElementById("chkRequest").checked == true) chatFilter.push({ type: "request" }); if (document.getElementById("chkResponse").checked == true) chatFilter.push({ type: "response" }); if (document.getElementById("chkNewMessage").checked == true) chatFilter.push({ new: 1 }); updateAllChat(true); }); document .querySelector("emoji-picker") .addEventListener("emoji-click", (event) => { var msg = document.getElementById("chatModuleMessage"); //Convert to utf-8--so we can just use utf-8 everywhere msg.setRangeText(event.detail.emoji.unicode.toString("utf-8")); //console.log(event.detail); //msg.focus(); }); document.getElementById("emojipickerbutton").addEventListener("click", () => { var element = document.getElementById("emojipickercontainer"); console.log(element.style.display); if (element.style.display === "none") { element.style.display = "block"; } else { element.style.display = "none"; } }); document .getElementById("delete_selected_chat") .addEventListener("click", () => { db.find({ selector: { dxcallsign: selected_callsign, }, }) .then(function (result) { // handle result if (typeof result !== "undefined") { result.docs.forEach(function (item) { console.log(item); db.get(item._id) .then(function (doc) { db.remove(doc) .then(function (doc) { updateAllChat(true); return true; }) .catch(function (err) { console.log(err); }); }) .catch(function (err) { console.log(err); }); }); } }) .catch(function (err) { console.log(err); }); }); document.getElementById("selectFilesButton").addEventListener("click", () => { //document.getElementById('selectFiles').click(); ipcRenderer.send("select-file", { title: "Title", }); }); document.getElementById("requestUserInfo").addEventListener("click", () => { ipcRenderer.send("run-tnc-command", { command: "requestUserInfo", dxcallsign: selected_callsign, }); pauseButton(document.getElementById("requestUserInfo"), 60000); }); document.getElementById("ping").addEventListener("click", () => { ipcRenderer.send("run-tnc-command", { command: "ping", dxcallsign: selected_callsign, }); }); document.addEventListener("keyup", function (event) { // Number 13 == Enter if ( event.keyCode === 13 && !event.shiftKey && document.activeElement.id == "chatModuleMessage" ) { // Cancel the default action, if needed event.preventDefault(); // Trigger the button element with a click document.getElementById("sendMessage").click(); } }); // ADJUST TEXTAREA SIZE document.getElementById("chatModuleMessage").addEventListener("input", () => { var textarea = document.getElementById("chatModuleMessage"); var text = textarea.value; if (document.getElementById("expand_textarea").checked) { var lines = 6; } else { var lines = text.split("\n").length; if (lines >= 6) { lines = 6; } } var message_container_height_offset = 180 + 20 * lines; var message_container_height = `calc(100% - ${message_container_height_offset}px)`; document.getElementById("message-container").style.height = message_container_height; textarea.rows = lines; console.log(textarea.value); if (lastIsWritingBroadcast < new Date().getTime() - 5 * 2000) { //console.log("Sending FECIsWriting"); console.log(config.enable_is_writing); if (config.enable_is_writing == "True") { ipcRenderer.send("tnc-fec-iswriting"); } lastIsWritingBroadcast = new Date().getTime(); } }); document.getElementById("expand_textarea").addEventListener("click", () => { var textarea = document.getElementById("chatModuleMessage"); if (document.getElementById("expand_textarea").checked) { var lines = 6; document.getElementById("expand_textarea_button").className = "bi bi-chevron-compact-down"; } else { var lines = 1; document.getElementById("expand_textarea_button").className = "bi bi-chevron-compact-up"; } var message_container_height_offset = 180 + 20 * lines; //var message_container_height_offset = 90 + (23*lines); var message_container_height = `calc(100% - ${message_container_height_offset}px)`; document.getElementById("message-container").style.height = message_container_height; textarea.rows = lines; console.log(textarea.rows); }); // NEW CHAT document .getElementById("createNewChatButton") .addEventListener("click", () => { var dxcallsign = document.getElementById("chatModuleNewDxCall").value; var uuid = uuidv4(); db.post({ _id: uuid, timestamp: Math.floor(Date.now() / 1000), dxcallsign: dxcallsign.toUpperCase(), dxgrid: "---", msg: "null", checksum: "null", type: "newchat", status: "null", uuid: uuid, }) .then(function (response) { // handle response console.log("new database entry"); console.log(response); }) .catch(function (err) { console.log(err); }); update_chat_obj_by_uuid(uuid); }); // open file selector for user image document.getElementById("userImageSelector").addEventListener("click", () => { ipcRenderer.send("select-user-image", { title: "Title", }); }); // open file selector for shared folder document .getElementById("sharedFolderButton") .addEventListener("click", () => { ipcRenderer.send("read-files-in-folder", { folder: config.shared_folder_path, }); }); document .getElementById("openSharedFilesFolder") .addEventListener("click", () => { ipcRenderer.send("open-folder", { path: config.shared_folder_path, }); }); document .getElementById("requestSharedFolderList") .addEventListener("click", () => { ipcRenderer.send("run-tnc-command", { command: "requestSharedFolderList", dxcallsign: selected_callsign, }); pauseButton(document.getElementById("requestSharedFolderList"), 60000); }); // SEND MSG document.getElementById("sendMessage").addEventListener("click", () => { document.getElementById("emojipickercontainer").style.display = "none"; var dxcallsign = selected_callsign.toUpperCase(); var textarea = document.getElementById("chatModuleMessage"); var chatmessage = textarea.value; //Remove non-printable chars from begining and end of string--should save us a byte here and there chatmessage = chatmessage.toString().trim(); // reset textarea size var message_container_height_offset = 200; var message_container_height = `calc(100% - ${message_container_height_offset}px)`; document.getElementById("message-container").style.height = message_container_height; textarea.rows = 1; document.getElementById("expand_textarea_button").className = "bi bi-chevron-compact-up"; document.getElementById("expand_textarea").checked = false; //console.log(file); //console.log(filename); //console.log(filetype); if (filetype == "") { filetype = "plain/text"; } var timestamp = Math.floor(Date.now() / 1000); var uuid = uuidv4(); let uuidlast = uuid.lastIndexOf("-"); uuidlast += 1; if (uuidlast > 0) { uuid = uuid.substring(uuidlast); } // check if broadcast if (dxcallsign.startsWith("BC-")) { //let broadcastChannelId = dxcallsign.split("BC-")[1]; //broadcastChannelIdCRC = crc32(broadcastChannelId) // .toString(16) // .toUpperCase(); //dxcallsignWithID = "BC-" + broadcastChannelIdCRC; var tnc_command = "broadcast"; var message_type = "broadcast_transmit"; // slice uuid for reducing overhead uuid = uuid.slice(-4); let Data = { command: tnc_command, broadcastChannel: dxcallsign, data: chatmessage, uuid: uuid, }; ipcRenderer.send("run-tnc-command", Data); } else { var message_type = "transmit"; var file_checksum = crc32(file).toString(16).toUpperCase(); var tnc_command = "msg"; var data_with_attachment = timestamp + split_char + chatmessage + split_char + filename + split_char + filetype + split_char + file; document.getElementById("selectFilesButton").innerHTML = ``; console.log(data_with_attachment); let Data = { command: tnc_command, dxcallsign: dxcallsign, mode: 255, frames: 5, data: data_with_attachment, checksum: file_checksum, uuid: uuid, }; ipcRenderer.send("run-tnc-command", Data); } db.post({ _id: uuid, timestamp: timestamp, dxcallsign: dxcallsign, dxgrid: "null", msg: chatmessage, checksum: file_checksum, type: message_type, status: "transmit", attempt: 1, uuid: uuid, _attachments: { [filename]: { content_type: filetype, //data: btoa(file) data: FD.btoa_FD(file), }, }, }) .then(function (response) { // handle response console.log("new database entry"); console.log(response); }) .catch(function (err) { console.log(err); }); update_chat_obj_by_uuid(uuid); // clear input document.getElementById("chatModuleMessage").value = ""; // after adding file data to our attachment variable, delete it from global filetype = ""; file = ""; filename = ""; }); // cleanup after transmission filetype = ""; file = ""; filename = ""; }); ipcRenderer.on("return-selected-files", (event, arg) => { filetype = arg.mime; console.log(filetype); file = arg.data; filename = arg.filename; document.getElementById("selectFilesButton").innerHTML = ` New file selected `; }); ipcRenderer.on("return-shared-folder-files", (event, arg) => { console.log(arg); sharedFolderFileList = arg.files; var tbl = document.getElementById("sharedFolderTable"); if (tbl == undefined) return; tbl.innerHTML = ""; let counter = 0; arg.files.forEach((file) => { //console.log(file["name"]); var row = document.createElement("tr"); let id = document.createElement("td"); let idText = document.createElement("span"); idText.innerText = counter += 1; id.appendChild(idText); row.appendChild(id); let filename = document.createElement("td"); let filenameText = document.createElement("span"); filenameText.innerText = file["name"]; filename.appendChild(filenameText); row.appendChild(filename); let filetype = document.createElement("td"); let filetypeText = document.createElement("span"); filetypeText.innerHTML = ` `; filetype.appendChild(filetypeText); row.appendChild(filetype); let filesize = document.createElement("td"); let filesizeText = document.createElement("span"); filesizeText.innerText = formatBytes(file["size"], 2); filesize.appendChild(filesizeText); row.appendChild(filesize); tbl.appendChild(row); }); }); ipcRenderer.on("return-select-user-image", (event, arg) => { let imageFiletype = arg.mime; let imageFile = arg.data; imageFile = blobUtil.base64StringToBlob(imageFile, imageFiletype); var options = { maxSizeMB: 0.01, maxWidthOrHeight: 125, useWebWorker: false, }; imageCompression(imageFile, options) .then(function (compressedFile) { console.log( "compressedFile instanceof Blob", compressedFile instanceof Blob, ); // true console.log( `compressedFile size ${compressedFile.size / 1024 / 1024} MB`, ); // smaller than maxSizeMB console.log(compressedFile.size); blobUtil .blobToBase64String(compressedFile) .then(function (base64String) { // update image document.getElementById("user_info_image").src = "data:" + imageFiletype + ";base64," + base64String; }) .catch(function (err) { document.getElementById("user_info_image").src = "img/icon.png"; }); }) .catch(function (error) { console.log(error.message); }); }); ipcRenderer.on("action-update-transmission-status", (event, arg) => { var data = arg["data"][0]; if (data.status == "opening") return; if (typeof data.uuid === undefined) return; //console.log(data.status); if (data.uuid !== "no-uuid") { db.get(data.uuid, { attachments: true, }) .then(function (doc) { return db.put({ _id: doc.uuid.toString(), _rev: doc._rev, timestamp: doc.timestamp, dxcallsign: doc.dxcallsign, dxgrid: doc.dxgrid, msg: doc.msg, checksum: doc.checksum, type: "transmit", status: data.status, percent: data.percent, bytesperminute: data.bytesperminute, uuid: doc.uuid, _attachments: doc._attachments, }); }) .then(function (response) { update_chat_obj_by_uuid(data.uuid); }) .catch(function (err) { console.log(err); console.log(data); }); } }); //Render is typing message in correct chat window ipcRenderer.on("action-show-feciswriting", (event, arg) => { //console.log("In action-show-feciswriting"); //console.log(arg); let uuid = uuidv4.toString(); let dxcallsign = arg["data"][0]["dxcallsign"]; var new_message = `

${dxcallsign} is typing....

`; var id = "chat-" + dxcallsign; let chatwin = document.getElementById(id); if (chatwin == undefined) { //console.log("Element not found!!!!! :("); return; } chatwin.insertAdjacentHTML("beforeend", new_message); scrollMessagesToBottom(); let animIcon = document.getElementById("msg-" + uuid + "-icon"); //Remove notification after about 4.5 seconds hopefully enough time before a second notification can come in setTimeout(function () { animIcon.classList = "m-1 bi bi-wifi-2"; }, 1000); setTimeout(function () { animIcon.classList = "m-1 bi bi-wifi"; }, 2000); setTimeout(function () { animIcon.classList = "m-1 bi bi-wifi-2"; }, 3000); setTimeout(function () { animIcon.classList = "m-1 bi bi-wifi-1"; }, 4000); setTimeout(() => { let feciw = document.getElementById("msg-" + uuid); feciw.remove(); }, 4500); }); ipcRenderer.on("action-new-msg-received", (event, arg) => { console.log(arg.data); var new_msg = arg.data; new_msg.forEach(function (item) { let obj = new Object(); //handle broadcast if (item.fec == "broadcast") { console.log("BROADCAST RECEIVED"); console.log(item); var transmitting_station = item.dxcallsign; var encoded_data = FD.atob_FD(item.data); var splitted_data = encoded_data.split(split_char); console.log(splitted_data); console.log(transmitting_station); // add callsign to message: var message = splitted_data[3]; console.log(message); obj.timestamp = Math.floor(Date.now() / 1000); obj.dxcallsign = splitted_data[1]; obj.dxgrid = "null"; obj.uuid = splitted_data[2]; obj.broadcast_sender = transmitting_station; obj.command = "msg"; obj.checksum = "null"; obj.msg = message; obj.status = "received"; obj.snr = item.snr; obj.type = "broadcast_received"; obj.filename = "null"; obj.filetype = "null"; obj.file = "null"; obj.new = 1; console.log(obj); add_obj_to_database(obj); update_chat_obj_by_uuid(obj.uuid); db.find({ selector: { dxcallsign: obj.dxcallsign, }, }).then(function (result) { // handle result console.log(result); }); //handle ping } else if (item.ping == "received") { obj.timestamp = parseInt(item.timestamp); obj.dxcallsign = item.dxcallsign; obj.dxgrid = item.dxgrid; obj.uuid = item.uuid; obj.command = "ping"; obj.checksum = "null"; obj.msg = "null"; obj.status = item.status; obj.hmac_signed = item.hmac_signed; obj.snr = item.snr; obj.type = "ping"; obj.filename = "null"; obj.filetype = "null"; obj.file = "null"; obj.new = 0; add_obj_to_database(obj); update_chat_obj_by_uuid(obj.uuid); // check for messages which failed and try to transmit them if (config.enable_auto_retry.toUpperCase() == "TRUE") { checkForWaitingMessages(obj.dxcallsign); } // handle ping-ack } else if (item.ping == "acknowledge") { obj.timestamp = parseInt(item.timestamp); obj.dxcallsign = item.dxcallsign; obj.dxgrid = item.dxgrid; obj.uuid = item.uuid; obj.command = "ping-ack"; obj.checksum = "null"; obj.msg = "null"; obj.status = item.status; obj.snr = item.dxsnr + "/" + item.snr; obj.type = "ping-ack"; obj.filename = "null"; obj.filetype = "null"; obj.file = "null"; obj.new = 0; add_obj_to_database(obj); update_chat_obj_by_uuid(obj.uuid); // handle beacon } else if (item.beacon == "received") { obj.timestamp = parseInt(item.timestamp); obj.dxcallsign = item.dxcallsign; obj.dxgrid = item.dxgrid; obj.uuid = item.uuid; obj.command = "beacon"; obj.checksum = "null"; obj.msg = "null"; obj.status = item.status; obj.snr = item.snr; obj.type = "beacon"; obj.filename = "null"; obj.filetype = "null"; obj.file = "null"; obj.new = 0; add_obj_to_database(obj); update_chat_obj_by_uuid(obj.uuid); // check for messages which failed and try to transmit them if (config.enable_auto_retry.toUpperCase() == "TRUE") { checkForWaitingMessages(obj.dxcallsign); } // handle ARQ transmission } else if (item.arq == "transmission" && item.status == "received") { //var encoded_data = atob(item.data); //var encoded_data = Buffer.from(item.data,'base64').toString('utf-8'); var encoded_data = FD.atob_FD(item.data); var splitted_data = encoded_data.split(split_char); console.log(splitted_data); if (splitted_data[1] == "msg") { obj.timestamp = parseInt(splitted_data[4]); obj.dxcallsign = item.dxcallsign; obj.dxgrid = item.dxgrid; obj.command = splitted_data[1]; obj.checksum = splitted_data[2]; obj.uuid = splitted_data[3]; obj.msg = splitted_data[5]; obj.status = "null"; obj.snr = "null"; obj.type = "received"; obj.filename = splitted_data[6]; obj.filetype = splitted_data[7]; //obj.file = btoa(splitted_data[8]); obj.file = FD.btoa_FD(splitted_data[8]); obj.hmac_signed = item.hmac_signed; obj.new = 1; } else if (splitted_data[1] == "req" && splitted_data[2] == "0") { obj.uuid = uuidv4().toString(); obj.timestamp = Math.floor(Date.now() / 1000); obj.dxcallsign = item.dxcallsign; obj.command = splitted_data[1]; obj.type = "request"; obj.status = "received"; obj.snr = "null"; obj.msg = "Request for station info"; obj.filename = "null"; obj.filetype = "null"; obj.file = "null"; obj.hmac_signed = item.hmac_signed; obj.new = 0; if (config.enable_request_profile == "True") { sendUserData(item.dxcallsign); } } else if (splitted_data[1] == "req" && splitted_data[2] == "1") { obj.uuid = uuidv4().toString(); obj.timestamp = Math.floor(Date.now() / 1000); obj.dxcallsign = item.dxcallsign; obj.command = splitted_data[1]; obj.type = "request"; obj.status = "received"; obj.snr = "null"; obj.msg = "Request for shared folder list"; obj.filename = "null"; obj.filetype = "null"; obj.file = "null"; obj.hmac_signed = item.hmac_signed; obj.new = 0; if (config.enable_request_shared_folder == "True") { sendSharedFolderList(item.dxcallsign); } } else if ( splitted_data[1] == "req" && splitted_data[2].substring(0, 1) == "2" ) { let name = splitted_data[2].substring(1); //console.log("In handle req for shared folder file"); obj.uuid = uuidv4().toString(); obj.timestamp = Math.floor(Date.now() / 1000); obj.dxcallsign = item.dxcallsign; obj.command = splitted_data[1]; obj.type = "request"; obj.status = "received"; obj.snr = "null"; obj.msg = "Request for shared file " + name; obj.filename = "null"; obj.filetype = "null"; obj.file = "null"; obj.hmac_signed = item.hmac_signed; obj.new = 0; if (config.enable_request_shared_folder == "True") { sendSharedFolderFile(item.dxcallsign, name); } } else if (splitted_data[1] == "res-0") { obj.uuid = uuidv4().toString(); obj.timestamp = Math.floor(Date.now() / 1000); obj.dxcallsign = item.dxcallsign; obj.command = splitted_data[1]; obj.type = "response"; obj.status = "received"; obj.snr = "null"; obj.msg = "Response for station info"; obj.filename = "null"; obj.filetype = "null"; obj.file = "null"; obj.hmac_signed = item.hmac_signed; obj.new = 0; console.log(splitted_data); let userData = new Object(); userData.user_info_image = splitted_data[2]; userData.user_info_callsign = splitted_data[3]; userData.user_info_gridsquare = splitted_data[4]; userData.user_info_name = splitted_data[5]; userData.user_info_age = splitted_data[6]; userData.user_info_location = splitted_data[7]; userData.user_info_radio = splitted_data[8]; userData.user_info_antenna = splitted_data[9]; userData.user_info_email = splitted_data[10]; userData.user_info_website = splitted_data[11]; userData.user_info_comments = splitted_data[12]; addUserToDatabaseIfNotExists(userData); getSetUserInformation(splitted_data[3]); } else if (splitted_data[1] == "res-1") { obj.uuid = uuidv4().toString(); obj.timestamp = Math.floor(Date.now() / 1000); obj.dxcallsign = item.dxcallsign; obj.command = splitted_data[1]; obj.type = "response"; obj.status = "received"; obj.snr = "null"; obj.msg = "Response for shared file list"; obj.filename = "null"; obj.filetype = "null"; obj.file = "null"; obj.hmac_signed = item.hmac_signed; obj.new = 0; console.log(splitted_data); let userData = new Object(); userData.user_info_callsign = obj.dxcallsign; let filelist = JSON.parse(splitted_data[3]); console.log(filelist); userData.user_shared_folder = filelist; addFileListToUserDatabaseIfNotExists(userData); getSetUserSharedFolder(obj.dxcallsign); //getSetUserInformation(selected_callsign); } else if (splitted_data[1] == "res-2") { console.log("In received response-2"); let sharedFileInfo = splitted_data[2].split("/", 2); obj.uuid = uuidv4().toString(); obj.timestamp = Math.floor(Date.now() / 1000); obj.dxcallsign = item.dxcallsign; obj.command = splitted_data[1]; obj.type = "received"; obj.status = "received"; obj.snr = "null"; obj.msg = "Response for shared file download"; obj.filename = sharedFileInfo[0]; obj.filetype = "application/octet-stream"; obj.file = FD.btoa_FD(sharedFileInfo[1]); obj.hmac_signed = item.hmac_signed; obj.new = 0; } else { console.log("no rule matched for handling received data!"); } add_obj_to_database(obj); update_chat_obj_by_uuid(obj.uuid); } }); //window.location = window.location; }); // Update chat list update_chat = function (obj) { var dxcallsign = obj.dxcallsign; var timestamp = dateFormat.format(obj.timestamp * 1000); //var timestampShort = dateFormatShort.format(obj.timestamp * 1000); var timestampHours = dateFormatHours.format(obj.timestamp * 1000); var dxgrid = obj.dxgrid; // check if obj.attempt exists if (typeof obj.attempt == "undefined") { db.upsert(obj._id, function (doc) { if (!doc.attempt) { doc.attempt = 1; } return doc; }); var attempt = 1; obj.attempt = attempt; } else { var attempt = obj.attempt; } // add percent and bytes per minute if not existing //console.log(obj.percent) if (typeof obj.percent == "undefined") { obj.percent = 0; obj.bytesperminute = 0; } // check if wrong status message if ( obj.status == "transmitting" && obj.type == "transmit" && obj.percent < 100 ) { var TimeDifference = new Date().getTime() / 1000 - obj.timestamp; if (TimeDifference > 21600) { //Six hours console.log( "Resetting message to failed state since in transmit status for over 6 hours:", ); console.log(obj); db.upsert(obj._id, function (doc) { doc.status = "failed"; return doc; }); obj.status = "failed"; } } // check if in transmitting status and @ 100% if ( obj.status == "transmitting" && obj.type == "transmit" && obj.percent == 100 ) { var TimeDifference = new Date().getTime() / 1000 - obj.timestamp; if (TimeDifference > 21600) { //Six hours console.log( "Resetting message to transmitted since in transmit state and at 100%:", ); console.log(obj); db.upsert(obj._id, function (doc) { doc.status = "transmitted"; return doc; }); obj.status = "transmitted"; } } if (typeof obj.new == "undefined") { obj.new = 0; } if (typeof config.max_retry_attempts == "undefined") { var max_retry_attempts = 3; } else { var max_retry_attempts = parseInt(config.max_retry_attempts); } //console.log(obj.msg); // define shortmessage if (obj.msg == "null" || obj.msg == "NULL") { var shortmsg = obj.type; } else { var shortmsg = obj.msg; var maxlength = 30; var shortmsg = shortmsg.length > maxlength ? shortmsg.substring(0, maxlength - 3) + "..." : shortmsg; } try { //console.log(Object.keys(obj._attachments)[0].length) if ( typeof obj._attachments !== "undefined" && Object.keys(obj._attachments)[0].length > 0 ) { //var filename = obj._attachments; var filename = Object.keys(obj._attachments)[0]; var filetype = filename.split(".")[1]; var filesize = obj._attachments[filename]["length"] + " Bytes"; if (filesize == "undefined Bytes") { // get filesize of new submitted data // not that nice.... // we really should avoid converting back from base64 for performance reasons... //var filesize = Math.ceil(atob(obj._attachments[filename]["data"]).length) + "Bytes"; var filesize = Math.ceil(FD.atob_FD(obj._attachments[filename]["data"]).length) + " Bytes"; } // check if image, then display it if (filetype == "image/png" || filetype == "png") { var fileheader = `

${filename} ${filesize}


`; } else { var fileheader = `

${filename} ${filesize}


`; } var controlarea_transmit = `
`; var controlarea_receive = `
`; } else { var filename = ""; var fileheader = ""; var filetype = "text/plain"; var controlarea_transmit = `
`; var controlarea_receive = `
`; } } catch (err) { console.log("error with database parsing..."); console.log(err); } // CALLSIGN LIST if (!document.getElementById("chat-" + dxcallsign + "-list")) { // increment callsign counter callsign_counter++; dxcallsigns.add(dxcallsign); if ( (callsign_counter == 1 && selected_callsign == "") || selected_callsign == dxcallsign ) { var callsign_selected = "active show"; //document.getElementById('chatModuleDxCall').value = dxcallsign; selected_callsign = dxcallsign; } if (dxcallsign.startsWith("BC-")) { var user_image = ''; } else { var user_image = ''; getSetUserInformation(dxcallsign); getSetUserSharedFolder(dxcallsign); } var new_callsign = `
${user_image}
${dxcallsign} ${dxgrid} ${timestampHours} ${shortmsg}
`; document .getElementById("list-tab-chat") .insertAdjacentHTML("beforeend", new_callsign); var message_area = `
`; document .getElementById("nav-tabContent-Chat") .insertAdjacentHTML("beforeend", message_area); // finally get and set user information to first selected item getSetUserInformation(selected_callsign); getSetUserSharedFolder(selected_callsign); // create eventlistener for listening on clicking on a callsign document .getElementById("chat-" + dxcallsign + "-list") .addEventListener("click", function () { //document.getElementById('chatModuleDxCall').value = dxcallsign; selected_callsign = dxcallsign; //Reset unread messages and new message indicator let clear = selected_callsign; clearUnreadMessages(clear); document.getElementById( `chat-${selected_callsign}-list-displaydxcall`, ).textContent = selected_callsign; document .getElementById(`chat-${selected_callsign}-list`) .classList.remove("list-group-item-warning"); setTimeout(scrollMessagesToBottom, 200); //get user information getSetUserInformation(selected_callsign); getSetUserSharedFolder(selected_callsign); if (selected_callsign.startsWith("BC-")) { document .getElementById("chatModuleMessage") .setAttribute("maxlength", 16); //console.log("Setting max message size to 16") } else { document .getElementById("chatModuleMessage") .setAttribute("maxlength", 524288); //console.log("Setting max message size to big#") } }); // if callsign entry already exists - update } else { // gridsquare - update only on receive if (obj.type !== "transmit") { document.getElementById("chat-" + dxcallsign + "-list-dxgrid").innerHTML = dxgrid; } // time document.getElementById("chat-" + dxcallsign + "-list-time").innerHTML = timestampHours; // short message document.getElementById("chat-" + dxcallsign + "-list-shortmsg").innerHTML = shortmsg; if (obj.new == 1) { document.getElementById( `chat-${obj.dxcallsign}-list-displaydxcall`, ).textContent = "*" + obj.dxcallsign; document .getElementById(`chat-${dxcallsign}-list`) .classList.add("list-group-item-warning"); } } // APPEND MESSAGES TO CALLSIGN if (!document.getElementById("msg-" + obj._id)) { if (obj.type == "ping") { //if (obj.new == 1) //{ // showOsPopUp("Ping from " + obj.dxcallsign,"You've been ping'd!"); //} var new_message = `

snr: ${obj.snr} - ${timestamp}

`; } if (obj.type == "ping-ack") { var new_message = `

Ping ack dx/mine snr: ${obj.snr} - ${timestamp}

`; } if (obj.type == "beacon") { var new_message = `

snr: ${obj.snr} - ${timestamp}

`; } if (obj.type == "request") { var new_message = `

${obj.msg} - ${timestamp}

`; } if (obj.type == "response") { var new_message = `

Response - ${timestamp}

`; } if (obj.type == "newchat") { var new_message = `

new chat opened - ${timestamp}

`; } // CHECK FOR NEW LINE AND REPLACE WITH
var message_html = obj.msg.replaceAll(/\n/g, "
"); if (obj.type == "received") { if (obj.new == 1) { showOsPopUp("Message received from " + obj.dxcallsign, obj.msg); } // check if message is signed or not for adjusting icon if(typeof obj.hmac_signed !== "undefined" && obj.hmac_signed !== "False"){ console.log(hmac_signed) var hmac_signed = ''; } else { var hmac_signed = ''; } var new_message = `
${fileheader}

${message_html}

${timestamp}

${hmac_signed}
${controlarea_receive}
`; } if (obj.type == "broadcast_received") { console.log(obj); var new_message = `

${message_html}

${timestamp}

${obj.broadcast_sender} dxcallsign
`; } if (obj.type == "broadcast_transmit") { var new_message = `

${message_html}

${timestamp} - ${get_icon_for_state( obj.status, )}

${attempt}/${max_retry_attempts} retries
`; } if (obj.type == "transmit") { //console.log(obj); //console.log('msg-' + obj._id + '-status') if (obj.status == "failed") { var progressbar_bg = "bg-danger"; var percent_value = "TRANSMISSION FAILED"; //Set to 100 so progressbar background populates obj.percent = 100; } else if (obj.status == "transmitted") { var progressbar_bg = "bg-success"; var percent_value = "TRANSMITTED"; } else { var progressbar_bg = "bg-primary"; var percent_value = obj.percent + " %"; } //Sneak in low graphics mode if so enabled for progress bars if (config.high_graphics.toString().toUpperCase() != "TRUE") { progressbar_bg += " disable-effects"; //console.log("Low graphics enabled for chat module"); } var new_message = `
${controlarea_transmit}
${fileheader}

${message_html}

${timestamp} - ${get_icon_for_state( obj.status, )}

${attempt}/${max_retry_attempts} retries

${percent_value} - ${ obj.bytesperminute } Bpm

`; } // CHECK CHECK CHECK --> This could be done better var id = "chat-" + obj.dxcallsign; document.getElementById(id).insertAdjacentHTML("beforeend", new_message); /* UPDATE EXISTING ELEMENTS */ } else if (document.getElementById("msg-" + obj._id)) { //console.log("element already exists......"); //console.log(obj); // console.log(obj.status) // console.log(obj.attempt) if ( obj.status != "broadcast_transmit" || obj.status != "broadcast_received" ) { document.getElementById("msg-" + obj._id + "-status").innerHTML = get_icon_for_state(obj.status); document .getElementById("msg-" + obj._id + "-progress") .setAttribute("aria-valuenow", obj.percent); document .getElementById("msg-" + obj._id + "-progress") .setAttribute("style", "width:" + obj.percent + "%;"); document.getElementById( "msg-" + obj._id + "-progress-information", ).innerHTML = obj.percent + "% - " + obj.bytesperminute + " Bpm"; document.getElementById("msg-" + obj._id + "-attempts").innerHTML = obj.attempt + "/" + max_retry_attempts; } if (obj.status == "transmit") { document.getElementById("msg-" + obj._id + "-status").innerHTML = get_icon_for_state(obj.status); if (typeof obj.percent !== "undefined") { document .getElementById("msg-" + obj._id + "-progress") .setAttribute("aria-valuenow", obj.percent); document .getElementById("msg-" + obj._id + "-progress") .setAttribute("style", "width:" + obj.percent + "%;"); document.getElementById( "msg-" + obj._id + "-progress-information", ).innerHTML = obj.percent + "% - " + obj.bytesperminute + " Bpm"; } else { document .getElementById("msg-" + obj._id + "-progress") .setAttribute("aria-valuenow", 0); document .getElementById("msg-" + obj._id + "-progress") .setAttribute("style", "width:0%;"); document.getElementById( "msg-" + obj._id + "-progress-information", ).innerHTML = "0% - 0 Bpm"; } document.getElementById("msg-" + obj._id + "-attempts").innerHTML = obj.attempt + "/" + max_retry_attempts; } if (obj.status == "transmitted") { //document.getElementById('msg-' + obj._id + '-progress').classList.remove("progress-bar-striped"); document .getElementById("msg-" + obj._id + "-progress") .classList.remove("progress-bar-animated"); document .getElementById("msg-" + obj._id + "-progress") .classList.remove("bg-danger"); document .getElementById("msg-" + obj._id + "-progress") .classList.add("bg-success"); document.getElementById("msg-" + obj._id + "-progress").innerHTML = ""; document.getElementById( "msg-" + obj._id + "-progress-information", ).innerHTML = "TRANSMITTED - " + obj.bytesperminute + " Bpm"; } else if ( obj.status != "broadcast_transmit" || obj.status != "broadcast_received" ) { document .getElementById("msg-" + obj._id + "-progress") .classList.add("progress-bar-striped"); document .getElementById("msg-" + obj._id + "-progress") .classList.add("progress-bar-animated"); } if (obj.status == "failed") { //document.getElementById('msg-' + obj._id + '-progress').classList.remove("progress-bar-striped"); document .getElementById("msg-" + obj._id + "-progress") .classList.remove("progress-bar-animated"); document .getElementById("msg-" + obj._id + "-progress") .classList.remove("bg-primary"); document .getElementById("msg-" + obj._id + "-progress") .classList.add("bg-danger"); console.log( document.getElementById("msg-" + obj._id + "-progress").classList, ); document.getElementById( "msg-" + obj._id + "-progress-information", ).innerHTML = "TRANSMISSION FAILED - " + obj.bytesperminute + " Bpm"; } //document.getElementById(id).className = message_class; } //Delete message event listener if ( document.getElementById("del-msg-" + obj._id) && !document .getElementById("del-msg-" + obj._id) .hasAttribute("listenerOnClick") ) { // set Attribute to determine if we already created an EventListener for this element document .getElementById("del-msg-" + obj._id) .setAttribute("listenerOnClick", "true"); document .getElementById("del-msg-" + obj._id) .addEventListener("click", () => { db.get(obj._id, { attachments: true, }) .then(function (doc) { db.remove(doc._id, doc._rev, function (err) { if (err) console.log("Error removing item " + err); }); }) .catch(function (err) { console.log(err); }); document.getElementById("msg-" + obj._id).remove(); document.getElementById("msg-" + obj._id + "-control-area").remove(); console.log("Removed message " + obj._id.toString()); // stop transmission if deleted message is still in progress if (obj.status == "transmitting") { let Data = { command: "stop_transmission", }; ipcRenderer.send("run-tnc-command", Data); } }); //scrollMessagesToBottom(); } // CREATE SAVE TO FOLDER EVENT LISTENER if ( document.getElementById("save-file-msg-" + obj._id) && !document .getElementById("save-file-msg-" + obj._id) .hasAttribute("listenerOnClick") ) { // set Attribute to determine if we already created an EventListener for this element document .getElementById("save-file-msg-" + obj._id) .setAttribute("listenerOnClick", "true"); document .getElementById("save-file-msg-" + obj._id) .addEventListener("click", () => { saveFileToFolder(obj._id); }); } // CREATE RESEND MSG EVENT LISTENER // check if element exists and if we already created NOT created an event listener if ( document.getElementById("retransmit-msg-" + obj._id) && !document .getElementById("retransmit-msg-" + obj._id) .hasAttribute("listenerOnClick") ) { // set Attribute to determine if we already created an EventListener for this element document .getElementById("retransmit-msg-" + obj._id) .setAttribute("listenerOnClick", "true"); document .getElementById("retransmit-msg-" + obj._id) .addEventListener("click", () => { // increment attempt db.upsert(obj._id, function (doc) { if (!doc.attempt) { doc.attempt = 1; } doc.attempt++; return doc; }) .then(function (res) { // success, res is {rev: '1-xxx', updated: true, id: 'myDocId'} console.log(res); update_chat_obj_by_uuid(obj.uuid); }) .catch(function (err) { // error console.log(err); }); db.get(obj._id, { attachments: true, }) .then(function (doc) { // handle doc console.log(doc); var filename = Object.keys(obj._attachments)[0]; var filetype = filename.content_type; console.log(filename); console.log(filetype); var file = obj._attachments[filename].data; console.log(file); console.log(Object.keys(obj._attachments)[0].data); //var file = atob(obj._attachments[filename]["data"]) db.getAttachment(obj._id, filename).then(function (data) { console.log(data); //Rewrote this part to use buffers to ensure encoding is corect -- n1qm var binaryString = FD.atob_FD(data); console.log(binaryString); var data_with_attachment = doc.timestamp + split_char + doc.msg + split_char + filename + split_char + filetype + split_char + binaryString; let Data = { command: "msg", dxcallsign: doc.dxcallsign, mode: 255, frames: 5, data: data_with_attachment, checksum: doc.checksum, uuid: doc.uuid, }; console.log(Data); ipcRenderer.send("run-tnc-command", Data); }); /* // convert blob data to binary string blobUtil.blobToBinaryString(data).then(function (binaryString) { console.log(binaryString) }).catch(function (err) { // error console.log(err); binaryString = blobUtil.arrayBufferToBinaryString(data); }).then(function(){ console.log(binaryString) console.log(binaryString.length) var data_with_attachment = doc.timestamp + split_char + utf8.encode(doc.msg) + split_char + filename + split_char + filetype + split_char + binaryString; let Data = { command: "msg", dxcallsign: doc.dxcallsign, mode: 255, frames: 1, data: data_with_attachment, checksum: doc.checksum, uuid: doc.uuid }; console.log(Data) ipcRenderer.send('run-tnc-command', Data); }); }); */ }) .catch(function (err) { console.log(err); }); }); } //window.location = window.location // scroll to bottom on new message scrollMessagesToBottom(); }; function saveFileToFolder(id) { db.get(id, { attachments: true, }) .then(function (obj) { console.log(obj); console.log(Object.keys(obj._attachments)[0].content_type); var filename = Object.keys(obj._attachments)[0]; var filetype = filename.content_type; var file = filename.data; console.log(file); console.log(filename.data); db.getAttachment(id, filename) .then(function (data) { // handle result console.log(data.length); //data = new Blob([data.buffer], { type: 'image/png' } /* (1) */) console.log(data); // we need to encode data because of error "an object could not be cloned" let Data = { file: data, filename: filename, filetype: filetype, }; console.log(Data); ipcRenderer.send("save-file-to-folder", Data); }) .catch(function (err) { console.log(err); return false; }); }) .catch(function (err) { console.log(err); }); } // function for setting an ICON to the corresponding state function get_icon_for_state(state) { if (state == "transmit") { var status_icon = ''; } else if (state == "transmitting") { //var status_icon = ''; var status_icon = ` `; } else if (state == "failed") { var status_icon = ''; } else if (state == "transmitted") { var status_icon = ''; } else { var status_icon = ''; } return status_icon; } update_chat_obj_by_uuid = function (uuid) { db.get(uuid, { attachments: true, }) .then(function (doc) { update_chat(doc); //return doc }) .catch(function (err) { console.log(err); }); }; add_obj_to_database = function (obj) { console.log(obj); db.put({ _id: obj.uuid, timestamp: parseInt(obj.timestamp), broadcast_sender: obj.broadcast_sender, uuid: obj.uuid, dxcallsign: obj.dxcallsign, dxgrid: obj.dxgrid, msg: obj.msg, checksum: obj.checksum, type: obj.type, command: obj.command, status: obj.status, snr: obj.snr, attempt: obj.attempt, hmac_signed: obj.hmac_signed, new: obj.new, _attachments: { [obj.filename]: { content_type: obj.filetype, data: obj.file, }, }, }) .then(function (response) { console.log("new database entry"); console.log(response); }) .catch(function (err) { console.log("already exists"); console.log(err); console.log(obj); db.upsert(obj.uuid, function (doc) { doc = obj; return doc; }) .then(function (response) { console.log("upsert"); console.log(response); }) .catch(function (err) { console.log(err); }); }); }; /* users database functions */ addUserToDatabaseIfNotExists = function (obj) { /* "user_info_callsign", "user_info_gridsquare", "user_info_name", "user_info_age", "user_info_location", "user_info_radio", "user_info_antenna", "user_info_email", "user_info_website", "user_info_comments", */ console.log(obj); users .find({ selector: { user_info_callsign: obj.user_info_callsign, }, }) .then(function (result) { // handle result console.log(result); if (result.docs.length > 0) { users .put({ _id: result.docs[0]._id, _rev: result.docs[0]._rev, user_info_callsign: obj.user_info_callsign, user_info_gridsquare: obj.user_info_gridsquare, user_info_name: obj.user_info_name, user_info_age: obj.user_info_age, user_info_location: obj.user_info_location, user_info_radio: obj.user_info_radio, user_info_antenna: obj.user_info_antenna, user_info_email: obj.user_info_email, user_info_website: obj.user_info_website, user_info_comments: obj.user_info_comments, user_info_image: obj.user_info_image, }) .then(function (response) { console.log("UPDATED USER"); console.log(response); console.log(obj); }) .catch(function (err) { console.log(err); }); } else { users .post({ user_info_callsign: obj.user_info_callsign, user_info_gridsquare: obj.user_info_gridsquare, user_info_name: obj.user_info_name, user_info_age: obj.user_info_age, user_info_location: obj.user_info_location, user_info_radio: obj.user_info_radio, user_info_antenna: obj.user_info_antenna, user_info_email: obj.user_info_email, user_info_website: obj.user_info_website, user_info_comments: obj.user_info_comments, }) .then(function (response) { console.log("NEW USER ADDED"); }) .catch(function (err) { console.log(err); }); } }) .catch(function (err) { console.log(err); }); }; addFileListToUserDatabaseIfNotExists = function (obj) { console.log(obj); users .find({ selector: { user_info_callsign: obj.user_info_callsign, }, }) .then(function (result) { // handle result if (result.docs.length > 0) { users .put({ _id: result.docs[0]._id, _rev: result.docs[0]._rev, user_shared_folder: obj.user_shared_folder, user_info_callsign: result.docs[0].user_info_callsign, user_info_gridsquare: result.docs[0].user_info_gridsquare, user_info_name: result.docs[0].user_info_name, user_info_age: result.docs[0].user_info_age, user_info_location: result.docs[0].user_info_location, user_info_radio: result.docs[0].user_info_radio, user_info_antenna: result.docs[0].user_info_antenna, user_info_email: result.docs[0].user_info_email, user_info_website: result.docs[0].user_info_website, user_info_comments: result.docs[0].user_info_comments, }) .then(function (response) { console.log("File List: UPDATED USER"); console.log(response); console.log(obj); getSetUserSharedFolder(obj.user_info_callsign); }) .catch(function (err) { console.log(err); }); } else { users .post({ user_info_callsign: obj.user_info_callsign, user_shared_folder: obj.user_shared_folder, }) .then(function (response) { console.log("File List: NEW USER ADDED"); getSetUserSharedFolder(obj.user_info_callsign); }) .catch(function (err) { console.log(err); }); } }) .catch(function (err) { console.log(err); }); }; // Scroll to bottom of message-container function scrollMessagesToBottom() { var messageBody = document.getElementById("message-container"); messageBody.scrollTop = messageBody.scrollHeight - messageBody.clientHeight; } // CRC CHECKSUMS // https://stackoverflow.com/a/50579690 // crc32 calculation //console.log(crc32('abc')); //var crc32=function(r){for(var a,o=[],c=0;c<256;c++){a=c;for(var f=0;f<8;f++)a=1&a?3988292384^a>>>1:a>>>1;o[c]=a}for(var n=-1,t=0;t>>8^o[255&(n^r.charCodeAt(t))];return(-1^n)>>>0}; //console.log(crc32('abc').toString(16).toUpperCase()); // hex var makeCRCTable = function () { var c; var crcTable = []; for (var n = 0; n < 256; n++) { c = n; for (var k = 0; k < 8; k++) { c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; } crcTable[n] = c; } return crcTable; }; var crc32 = function (str) { var crcTable = window.crcTable || (window.crcTable = makeCRCTable()); var crc = 0 ^ -1; for (var i = 0; i < str.length; i++) { crc = (crc >>> 8) ^ crcTable[(crc ^ str.charCodeAt(i)) & 0xff]; } return (crc ^ -1) >>> 0; }; function returnObjFromCallsign(database, callsign) { return new Promise((resolve, reject) => { users .find({ selector: { user_info_callsign: callsign, }, }) .then(function (result) { //return new Promise((resolve, reject) => { if (typeof result.docs[0] !== "undefined") { resolve(result.docs[0]); } else { reject("Promise rejected"); } /* if (typeof result.docs[0] !== "undefined") { return result.docs[0]; } else { return false; } */ }) .catch(function (err) { console.log(err); }); }); } function createChatIndex() { db.createIndex({ index: { fields: [ "timestamp", "uuid", "dxcallsign", "dxgrid", "msg", "checksum", "type", "command", "status", "percent", "attempt", "hmac_signed", "bytesperminute", "_attachments", "new", ], }, }) .then(function (result) { // handle result console.log(result); }) .catch(function (err) { console.log(err); }); } function createUserIndex() { users .createIndex({ index: { fields: [ "timestamp", "user_info_callsign", "user_info_gridsquare", "user_info_name", "user_info_age", "user_info_location", "user_info_radio", "user_info_antenna", "user_info_email", "user_info_website", "user_info_comments", "user_info_image", ], }, }) .then(function (result) { // handle result console.log(result); return true; }) .catch(function (err) { console.log(err); return false; }); } async function updateAllChat(clear) { if (clear == true) { filetype = ""; file = ""; filename = ""; callsign_counter = 0; //selected_callsign = ""; dxcallsigns.clear(); document.getElementById("list-tab-chat").innerHTML = ""; document.getElementById("nav-tabContent-Chat").innerHTML = ""; //document.getElementById("list-tab-chat").childNodes.remove(); //document.getElementById("nav-tab-content").childrenNodes.remove(); } //Ensure we create an index before running db.find //We can't rely on the default index existing before we get here...... :'( await db .createIndex({ index: { fields: [{ dxcallsign: "asc" }, { timestamp: "asc" }], }, }) .then(async function (result) { // handle result await db .find({ selector: { $and: [ { dxcallsign: { $exists: true } }, { timestamp: { $exists: true } }, { $or: chatFilter }, ], //$or: chatFilter }, sort: [{ dxcallsign: "asc" }, { timestamp: "asc" }], }) .then(async function (result) { console.log(result); // handle result async //document.getElementById("blurOverlay").classList.add("bg-primary"); console.log(result); if (typeof result !== "undefined") { for (const item of result.docs) { //await otherwise history will not be in chronological order await db .get(item._id, { attachments: true, }) .then(function (item_with_attachments) { update_chat(item_with_attachments); }); } } }) .catch(function (err) { console.log(err); }); }) .catch(function (err) { console.log(err); }); if (clear == true && dxcallsigns.has(selected_callsign) == false) { //Selected call sign is not visible, reset to first call sign let tmp = dxcallsigns.entries().next().value[0]; selected_callsign = tmp; document .getElementById("chat-" + tmp + "-list") .classList.add("active", "show"); document.getElementById("chat-" + tmp).classList.add("active", "show"); scrollMessagesToBottom(); } } function getSetUserSharedFolder(selected_callsign) { // TODO: This is a dirty hotfix for avoiding, this function is canceld too fast. //Should be fixed //console.log("get set user information:" + selected_callsign); if ( selected_callsign == "" || selected_callsign == null || typeof selected_callsign == "undefined" ) { console.log("return triggered"); return; } // disable button if broadcast if (selected_callsign.startsWith("BC-")) { document.getElementById("sharedFolderDXButton").disabled = true; } else { document.getElementById("sharedFolderDXButton").disabled = false; } returnObjFromCallsign(users, selected_callsign) .then(function (data) { console.log(data); if (typeof data.user_shared_folder !== "undefined") { console.log(data.user_shared_folder); // shared folder table var icons = [ "aac", "ai", "bmp", "cs", "css", "csv", "doc", "docx", "exe", "gif", "heic", "html", "java", "jpg", "js", "json", "jsx", "key", "m4p", "md", "mdx", "mov", "mp3", "mp4", "otf", "pdf", "php", "png", "ppt", "pptx", "psd", "py", "raw", "rb", "sass", "scss", "sh", "sql", "svg", "tiff", "tsx", "ttf", "txt", "wav", "woff", "xls", "xlsx", "xml", "yml", ]; var tbl = document.getElementById("sharedFolderTableDX"); tbl.innerHTML = ""; let counter = 0; data.user_shared_folder.forEach((file) => { var row = document.createElement("tr"); let dxcall = selected_callsign; let name = file["name"]; let type = file["extension"]; if (icons.indexOf(type) == -1) { type = "bi-file-earmark"; } else { type = "bi-filetype-" + type; } let id = document.createElement("td"); let idText = document.createElement("span"); counter += 1; idText.innerHTML += ' ' + counter; id.appendChild(idText); row.appendChild(id); let filename = document.createElement("td"); let filenameText = document.createElement("span"); filenameText.innerText = file["name"]; filename.appendChild(filenameText); row.appendChild(filename); let filetype = document.createElement("td"); let filetypeText = document.createElement("span"); filetypeText.innerHTML = ``; filetype.appendChild(filetypeText); row.appendChild(filetype); let filesize = document.createElement("td"); let filesizeText = document.createElement("span"); filesizeText.innerText = formatBytes(file["size"], 2); filesize.appendChild(filesizeText); row.appendChild(filesize); id.addEventListener("click", function () { //console.log(name," clicked"); sendFileReq(dxcall, name); }); tbl.appendChild(row); }); } else { document.getElementById("sharedFolderTableDX").innerHTML = "no data"; } }) .catch(function (err) { console.log(err); document.getElementById("sharedFolderTableDX").innerHTML = "no data"; }); } function getSetUserInformation(selected_callsign) { //Get user information //console.log("get set user information:" + selected_callsign); if ( selected_callsign == "" || selected_callsign == null || typeof selected_callsign == "undefined" ) { console.log("return triggered"); return; } // disable button if broadcast if (selected_callsign.startsWith("BC-")) { document.getElementById("userModalDXButton").disabled = true; document.getElementById("ping").disabled = true; } else { document.getElementById("userModalDXButton").disabled = false; document.getElementById("ping").disabled = false; } document.getElementById("dx_user_info_callsign").innerHTML = selected_callsign; returnObjFromCallsign(users, selected_callsign) .then(function (data) { console.log(data); // image if (typeof data.user_info_image !== "undefined") { try { console.log("try checking for image if base64 data"); // determine if we have a base64 encoded image console.log(data.user_info_image); console.log(data.user_info_image.split(";base64,")[1]); // split data string by "base64" for separating image type from base64 string atob(data.user_info_image.split(";base64,")[1]); if (selected_callsign.startsWith("BC-")) { data.user_info_image = defaultGroupIcon; } document.getElementById("dx_user_info_image").src = data.user_info_image; document.getElementById("user-image-" + selected_callsign).src = data.user_info_image; } catch (e) { console.log(e); console.log("corrupted image data"); if (selected_callsign.startsWith("BC-")) { var userIcon = defaultGroupIcon; } else { var userIcon = defaultUserIcon; } document.getElementById("user-image-" + selected_callsign).src = userIcon; document.getElementById("dx_user_info_image").src = userIcon; } } else { // throw error and use placeholder data // throw new Error("Data not available or corrupted"); document.getElementById("dx_user_info_image").src = defaultUserIcon; document.getElementById("user-image-" + selected_callsign).src = defaultUserIcon; } // Callsign list elements document.getElementById( "chat-" + selected_callsign + "-list-dxgrid", ).innerHTML = "" + data.user_info_gridsquare + ""; document.getElementById("user-image-" + selected_callsign).className = "p-1 rounded-circle"; document.getElementById("user-image-" + selected_callsign).style = "width: 60px"; // DX Station tab document.getElementById("dx_user_info_name").innerHTML = data.user_info_name; document.getElementById("dx_user_info_age").innerHTML = data.user_info_age; document.getElementById("dx_user_info_gridsquare").innerHTML = data.user_info_gridsquare; document.getElementById("dx_user_info_location").innerHTML = data.user_info_location; document.getElementById("dx_user_info_email").innerHTML = data.user_info_email; document.getElementById("dx_user_info_website").innerHTML = data.user_info_website; document.getElementById("dx_user_info_radio").innerHTML = data.user_info_radio; document.getElementById("dx_user_info_antenna").innerHTML = data.user_info_antenna; document.getElementById("dx_user_info_comments").innerHTML = data.user_info_comments; document.getElementById("dx_user_info_gridsquare").className = ""; document.getElementById("dx_user_info_name").className = "badge bg-secondary"; document.getElementById("dx_user_info_age").className = "badge bg-secondary"; document.getElementById("dx_user_info_gridsquare").className = ""; document.getElementById("dx_user_info_location").className = ""; document.getElementById("dx_user_info_email").className = ""; document.getElementById("dx_user_info_website").className = ""; document.getElementById("dx_user_info_radio").className = ""; document.getElementById("dx_user_info_antenna").className = ""; document.getElementById("dx_user_info_comments").className = ""; }) .catch(function (err) { console.log("writing user info to modal failed"); console.log(err); if (selected_callsign.startsWith("BC-")) { var userIcon = defaultGroupIcon; } else { var userIcon = defaultUserIcon; } // Callsign list elements document.getElementById("user-image-" + selected_callsign).src = userIcon; document.getElementById("user-image-" + selected_callsign).className = "p-1 rounded-circle w-100"; document.getElementById("user-image-" + selected_callsign).style = "height:60px"; document.getElementById( "chat-" + selected_callsign + "-list-dxgrid", ).innerHTML = "no grid"; // DX Station tab document.getElementById("dx_user_info_image").src = defaultUserIcon; document.getElementById("dx_user_info_gridsquare").className = "placeholder col-4"; document.getElementById("dx_user_info_name").className = "placeholder col-4"; document.getElementById("dx_user_info_age").className = "placeholder col-2"; document.getElementById("dx_user_info_gridsquare").className = "placeholder col-3"; document.getElementById("dx_user_info_location").className = "placeholder col-3"; document.getElementById("dx_user_info_email").className = "placeholder col-7"; document.getElementById("dx_user_info_website").className = "placeholder col-7"; document.getElementById("dx_user_info_radio").className = "placeholder col-4"; document.getElementById("dx_user_info_antenna").className = "placeholder col-4"; document.getElementById("dx_user_info_comments").className = "placeholder col-7"; }); } function sendSharedFolderList(dxcallsign) { ipcRenderer.send("read-files-in-folder", { folder: config.shared_folder_path, }); console.log(sharedFolderFileList); let fileListWithCallsign = ""; fileListWithCallsign += dxcallsign; fileListWithCallsign += split_char; fileListWithCallsign += JSON.stringify(sharedFolderFileList); console.log(fileListWithCallsign); ipcRenderer.send("run-tnc-command", { command: "responseSharedFolderList", dxcallsign: dxcallsign, folderFileList: fileListWithCallsign, }); } function sendSharedFolderFile(dxcallsign, filename) { let filePath = path.join(config.shared_folder_path, filename); console.log("In function sendSharedFolderFile ", filePath); //Make sure nothing sneaky is going on if (!filePath.startsWith(config.shared_folder_path)) { console.error("File is outside of shared folder path!"); return; } if (!fs.existsSync(filePath)) { console.warn("File doesn't seem to exist"); return; } //Read file's data let fileData = null; try { //Has to be binary let data = fs.readFileSync(filePath); fileData = data.toString("utf-8"); } catch (err) { console.log(err); return; } ipcRenderer.send("run-tnc-command", { command: "responseSharedFile", dxcallsign: dxcallsign, file: filename, filedata: fileData, }); } function sendUserData(dxcallsign) { const userInfoFields = [ "user_info_image", "user_info_callsign", "user_info_gridsquare", "user_info_name", "user_info_age", "user_info_location", "user_info_radio", "user_info_antenna", "user_info_email", "user_info_website", "user_info_comments", ]; let info = ""; userInfoFields.forEach(function (subelem) { if (subelem !== "user_info_image") { info += document.getElementById(subelem).value; info += split_char; } else { info += document.getElementById(subelem).src; info += split_char; } }); console.log(info); ipcRenderer.send("run-tnc-command", { command: "responseUserInfo", dxcallsign: dxcallsign, userinfo: info, }); } //Temporarily disable a button with timeout function pauseButton(btn, timems) { btn.disabled = true; var curText = btn.innerHTML; if (config.high_graphics.toUpperCase() == "TRUE") { btn.innerHTML = '