diff --git a/gui_vue/electron/preload.ts b/gui_vue/electron/preload.ts index ff1f1286..f1c701f8 100644 --- a/gui_vue/electron/preload.ts +++ b/gui_vue/electron/preload.ts @@ -37,24 +37,24 @@ function useLoading() { @keyframes square-spin { 0% { transform: rotate(0deg); - background-image: url('../public/icon_cube_border.png'); /* Replace with the URL of your image */ + background-image: url('icon_cube_border.png'); /* Replace with the URL of your image */ background-size: cover; /* Scale the image to cover the entire container */ } 25% { transform: perspective(100px) rotateX(180deg) rotateY(0); - background-image: url('../public/icon_cube_border.png'); /* Replace with the URL of your image */ + background-image: url('icon_cube_border.png'); /* Replace with the URL of your image */ background-size: cover; /* Scale the image to cover the entire container */ } 50% { transform: perspective(100px) rotateX(180deg) rotateY(180deg); - background-image: url('../public/icon_cube_border.png'); /* Replace with the URL of your image */ + background-image: url('icon_cube_border.png'); /* Replace with the URL of your image */ background-size: cover; /* Scale the image to cover the entire container */ } 75% { transform: perspective(100px) rotateX(0) rotateY(180deg); - background-image: url('../public/icon_cube_border.png'); /* Replace with the URL of your image */ + background-image: url('icon_cube_border.png'); /* Replace with the URL of your image */ background-size: cover; /* Scale the image to cover the entire container */ } 100% { transform: perspective(100px) rotateX(0) rotateY(0); - background-image: url('../public/icon_cube_border.png'); /* Replace with the URL of your image */ + background-image: url('icon_cube_border.png'); /* Replace with the URL of your image */ background-size: cover; /* Scale the image to cover the entire container */ } } @@ -63,7 +63,7 @@ function useLoading() { width: 50px; height: 50px; background: #fff; - animation: square-spin 6s 0s cubic-bezier(0.09, 0.57, 0.49, 0.9) infinite; + animation: square-spin 3s 0s cubic-bezier(0.09, 0.57, 0.49, 0.9) infinite; } .app-loading-wrap { position: fixed; @@ -107,4 +107,4 @@ window.onmessage = ev => { ev.data.payload === 'removeLoading' && removeLoading() } -setTimeout(removeLoading, 4999) +setTimeout(removeLoading, 3000) diff --git a/gui_vue/src/js/daemon.js b/gui_vue/src/js/daemon.js new file mode 100644 index 00000000..bf1a12cc --- /dev/null +++ b/gui_vue/src/js/daemon.js @@ -0,0 +1,363 @@ +//var net = require("net"); +var net = require('node:net'); + +const path = require("path"); +const { ipcRenderer } = require("electron"); +// ----------------- init pinia stores ------------- +import { setActivePinia } from 'pinia'; +import pinia from '../store/index'; +setActivePinia(pinia); +import { useAudioStore } from '../store/audioStore.js'; +const audioStore = useAudioStore(pinia); + +import { useSettingsStore } from '../store/settingsStore.js'; +const settings = useSettingsStore(pinia); + + +var daemon = new net.Socket(); +var socketchunk = ""; // Current message, per connection. + +// global to keep track of daemon connection error emissions +var daemonShowConnectStateError = 1; + + +setTimeout(connectDAEMON, 500); + +function connectDAEMON() { + if (daemonShowConnectStateError == 1) { + console.log("connecting to daemon"); + } + + //clear message buffer after reconnecting or initial connection + socketchunk = ""; + + if (settings.tnclocation == "localhost") { + daemon.connect(3001, "127.0.0.1"); + } else { + daemon.connect(daemon_port, daemon_host); + } + + //client.setTimeout(5000); +} + +daemon.on("connect", function (err) { + console.log("daemon connection established"); + let Data = { + daemon_connection: daemon.readyState, + }; + ipcRenderer.send("request-update-daemon-connection", Data); + + daemonShowConnectStateError = 1; +}); + +daemon.on("error", function (err) { + if (daemonShowConnectStateError == 1) { + console.log("daemon connection error"); + console.log("Make sure the daemon is started."); + console.log('Run "python daemon.py" in the tnc directory.'); + + daemonShowConnectStateError = 0; + } + setTimeout(connectDAEMON, 500); + daemon.destroy(); + let Data = { + daemon_connection: daemon.readyState, + }; + ipcRenderer.send("request-update-daemon-connection", Data); +}); + +/* +client.on('close', function(data) { + console.log(' TNC connection closed'); + setTimeout(connectTNC, 2000) + let Data = { + daemon_connection: daemon.readyState, + }; + ipcRenderer.send('request-update-daemon-connection', Data); +}); +*/ + +daemon.on("end", function (data) { + daemonLog.warn("daemon connection ended"); + daemon.destroy(); + setTimeout(connectDAEMON, 500); + let Data = { + daemon_connection: daemon.readyState, + }; + ipcRenderer.send("request-update-daemon-connection", Data); +}); + +//exports.writeDaemonCommand = function(command){ +//writeDaemonCommand = function (command) { +function writeDaemonCommand(command) { + + // we use the writingCommand function to update our TCPIP state because we are calling this function a lot + // if socket opened, we are able to run commands + if (daemon.readyState == "open") { + //uiMain.setDAEMONconnection('open') + daemon.write(command + "\n"); + } + + if (daemon.readyState == "closed") { + //uiMain.setDAEMONconnection('closed') + } + + if (daemon.readyState == "opening") { + //uiMain.setDAEMONconnection('opening') + } + + let Data = { + daemon_connection: daemon.readyState, + }; + ipcRenderer.send("request-update-daemon-connection", Data); +}; + +// "https://stackoverflow.com/questions/9070700/nodejs-net-createserver-large-amount-of-data-coming-in" + +daemon.on("data", function (socketdata) { + /* + inspired by: + stackoverflow.com questions 9070700 nodejs-net-createserver-large-amount-of-data-coming-in + */ + + socketdata = socketdata.toString("utf8"); // convert data to string + socketchunk += socketdata; // append data to buffer so we can stick long data together + + // check if we received begin and end of json data + if (socketchunk.startsWith('{"') && socketchunk.endsWith('"}\n')) { + var data = ""; + + // split data into chunks if we received multiple commands + socketchunk = socketchunk.split("\n"); + data = JSON.parse(socketchunk[0]); + + // search for empty entries in socketchunk and remove them + for (var i = 0; i < socketchunk.length; i++) { + if (socketchunk[i] === "") { + socketchunk.splice(i, 1); + } + } + + //iterate through socketchunks array to execute multiple commands in row + for (i = 0; i < socketchunk.length; i++) { + //check if data is not empty + if (socketchunk[i].length > 0) { + //try to parse JSON + try { + data = JSON.parse(socketchunk[i]); + } catch (e) { + console.log(e); // "SyntaxError + daemonLog.debug(socketchunk[i]); + socketchunk = ""; + } + } + + if (data["command"] == "daemon_state") { + let Data = { + input_devices: data["input_devices"], + output_devices: data["output_devices"], + python_version: data["python_version"], + hamlib_version: data["hamlib_version"], + serial_devices: data["serial_devices"], + tnc_running_state: data["daemon_state"][0]["status"], + ram_usage: data["ram"], + cpu_usage: data["cpu"], + version: data["version"], + }; + + // update audio devices by putting them to audio store + audioStore.inputDevices = data["input_devices"]; + audioStore.outputDevices = data["output_devices"]; + + + } + + if (data["command"] == "test_hamlib") { + let Data = { + hamlib_result: data["result"], + }; + ipcRenderer.send("request-update-hamlib-test", Data); + } + } + + //finally delete message buffer + socketchunk = ""; + } +}); + +function hexToBytes(hex) { + for (var bytes = [], c = 0; c < hex.length; c += 2) + bytes.push(parseInt(hex.substr(c, 2), 16)); + return bytes; +} + +//exports.getDaemonState = function () { +function getDaemonState() { + + //function getDaemonState(){ + command = '{"type" : "get", "command" : "daemon_state"}'; + writeDaemonCommand(command); +}; + +// START TNC +// ` `== multi line string +function startTNC( +//exports.startTNC = function ( + mycall, + mygrid, + rx_audio, + tx_audio, + radiocontrol, + devicename, + deviceport, + pttprotocol, + pttport, + serialspeed, + data_bits, + stop_bits, + handshake, + rigctld_ip, + rigctld_port, + enable_fft, + enable_scatter, + low_bandwidth_mode, + tuning_range_fmin, + tuning_range_fmax, + enable_fsk, + tx_audio_level, + respond_to_cq, + rx_buffer_size, + enable_explorer, + explorer_stats, + auto_tune, + tx_delay, + tci_ip, + tci_port, + enable_mesh, +) { + var json_command = JSON.stringify({ + type: "set", + command: "start_tnc", + parameter: [ + { + mycall: mycall, + mygrid: mygrid, + rx_audio: rx_audio, + tx_audio: tx_audio, + radiocontrol: radiocontrol, + devicename: devicename, + deviceport: deviceport, + pttprotocol: pttprotocol, + pttport: pttport, + serialspeed: serialspeed, + data_bits: data_bits, + stop_bits: stop_bits, + handshake: handshake, + rigctld_port: rigctld_port, + rigctld_ip: rigctld_ip, + enable_scatter: enable_scatter, + enable_fft: enable_fft, + enable_fsk: enable_fsk, + low_bandwidth_mode: low_bandwidth_mode, + tuning_range_fmin: tuning_range_fmin, + tuning_range_fmax: tuning_range_fmax, + tx_audio_level: tx_audio_level, + respond_to_cq: respond_to_cq, + rx_buffer_size: rx_buffer_size, + enable_explorer: enable_explorer, + enable_stats: explorer_stats, + enable_auto_tune: auto_tune, + tx_delay: tx_delay, + tci_ip: tci_ip, + tci_port: tci_port, + enable_mesh: enable_mesh, + }, + ], + }); + + daemonLog.debug(json_command); + writeDaemonCommand(json_command); +}; + +// STOP TNC +//exports.stopTNC = function () { +function stopTNC() { + + command = '{"type" : "set", "command": "stop_tnc" , "parameter": "---" }'; + writeDaemonCommand(command); +}; + +// TEST HAMLIB +function testHamlib( + +//exports.testHamlib = function ( + radiocontrol, + devicename, + deviceport, + serialspeed, + pttprotocol, + pttport, + data_bits, + stop_bits, + handshake, + rigctld_ip, + rigctld_port, +) { + var json_command = JSON.stringify({ + type: "get", + command: "test_hamlib", + parameter: [ + { + radiocontrol: radiocontrol, + devicename: devicename, + deviceport: deviceport, + pttprotocol: pttprotocol, + pttport: pttport, + serialspeed: serialspeed, + data_bits: data_bits, + stop_bits: stop_bits, + handshake: handshake, + rigctld_port: rigctld_port, + rigctld_ip: rigctld_ip, + }, + ], + }); + daemonLog.debug(json_command); + writeDaemonCommand(json_command); +}; + +//Save myCall +function saveMyCall(callsign){ +//exports.saveMyCall = function (callsign) { + command = + '{"type" : "set", "command": "mycallsign" , "parameter": "' + + callsign + + '"}'; + writeDaemonCommand(command); +}; + +// Save myGrid +//exports.saveMyGrid = function (grid) { +function saveMyGrid(grid){ + + command = + '{"type" : "set", "command": "mygrid" , "parameter": "' + grid + '"}'; + writeDaemonCommand(command); +}; + +ipcRenderer.on("action-update-daemon-ip", (event, arg) => { + daemon.destroy(); + let Data = { + busy_state: "-", + arq_state: "-", + //channel_state: "-", + frequency: "-", + mode: "-", + bandwidth: "-", + dbfs_level: 0, + }; + ipcRenderer.send("request-update-tnc-state", Data); + daemon_port = arg.port; + daemon_host = arg.adress; + connectDAEMON(); +}); diff --git a/gui_vue/src/js/freedata.js b/gui_vue/src/js/freedata.js new file mode 100644 index 00000000..6b02a8d2 --- /dev/null +++ b/gui_vue/src/js/freedata.js @@ -0,0 +1,37 @@ +const fs = require("fs"); +const { ipcRenderer } = require("electron"); + +/** + * Save config and update config setting globally + * @param {string} config - config data + * @param {string} configPath + */ +exports.saveConfig = function (config, configPath) { + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); + ipcRenderer.send("set-config-global", config); +}; + +/** + * Binary to ASCII replacement + * @param {string} data in normal/usual utf-8 format + * @returns base64 encoded string + */ +exports.btoa_FD = function (data) { + return Buffer.from(data, "utf-8").toString("base64"); +}; +/** + * ASCII to Binary replacement + * @param {string} data in base64 encoding + * @returns utf-8 normal/usual string + */ +exports.atob_FD = function (data) { + return Buffer.from(data, "base64").toString("utf-8"); +}; +/** + * UTF8 to ASCII btoa + * @param {string} data in base64 encoding + * @returns base64 bota compatible data for use in browser + */ +exports.atob = function (data) { + return window.btoa(Buffer.from(data, "base64").toString("utf8")); +}; diff --git a/gui_vue/src/js/preload-chat.js b/gui_vue/src/js/preload-chat.js new file mode 100644 index 00000000..72ef9101 --- /dev/null +++ b/gui_vue/src/js/preload-chat.js @@ -0,0 +1,2976 @@ +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 + // TODO: Remove for one pager, also remove function! + //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 = + '