const path = require('path') const { ipcRenderer } = require('electron') const { v4: uuidv4 } = require('uuid'); const utf8 = require('utf8'); const blobUtil = require('blob-util') // 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') const config = require(configPath); // set date format const dateFormat = new Intl.DateTimeFormat('en-GB', { timeStyle: 'long', dateStyle: 'full' }); // set date format information const dateFormatShort = new Intl.DateTimeFormat('en-GB', { year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric', hour12: false, }); const dateFormatHours = new Intl.DateTimeFormat('en-GB', { 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 chatDB = path.join(configFolder, 'chatDB') // ---- 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')); var db = new PouchDB(chatDB); /* // 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('/chatDB', require('express-pouchdb')(PouchDB)); //app.listen(5984); 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(); db.createIndex({ index: { fields: ['timestamp', 'uuid', 'dxcallsign', 'dxgrid', 'msg', 'checksum', 'type', 'command', 'status', 'percent', 'bytesperminute', '_attachments'] } }).then(function(result) { // handle result console.log(result) }).catch(function(err) { console.log(err); }); db.find({ selector: { timestamp: { $exists: true } }, sort: [{ 'timestamp': 'asc' }] }).then(function(result) { // handle result if (typeof(result) !== 'undefined') { result.docs.forEach(function(item) { //console.log(item) // another query with attachments db.get(item._id, { attachments: true }).then(function(item_with_attachments){ update_chat(item_with_attachments); }); }); } }).catch(function(err) { console.log(err); }); // WINDOW LISTENER window.addEventListener('DOMContentLoaded', () => { // theme selector if(config.theme != 'default'){ var theme_path = "../node_modules/bootswatch/dist/"+ config.theme +"/bootstrap.min.css"; document.getElementById("bootstrap_theme").href = theme_path; } else { var theme_path = "../node_modules/bootstrap/dist/css/bootstrap.min.css"; document.getElementById("bootstrap_theme").href = theme_path; } document.querySelector('emoji-picker').addEventListener("emoji-click", (event) => { document.getElementById('chatModuleMessage').setRangeText(event.detail.emoji.unicode) console.log(event.detail); }) 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) { return location.reload(); }).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("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) { // 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 = 130 + (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) }) 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 = 130 + (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); }); // 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; // reset textarea size var message_container_height_offset = 150; 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 file_checksum = crc32(file).toString(16).toUpperCase(); console.log(file_checksum) var data_with_attachment = timestamp + split_char + chatmessage + split_char + filename + split_char + filetype + split_char + file; document.getElementById('selectFilesButton').innerHTML = ``; var uuid = uuidv4(); console.log(data_with_attachment) let Data = { command: "send_message", dxcallsign: dxcallsign, mode: 255, frames: 1, 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: "transmit", status: 'transmit', uuid: uuid, _attachments: { [filename]: { content_type: filetype, data: btoa(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('action-update-transmission-status', (event, arg) => { var data = arg["data"][0] console.log(data.status); db.get(data.uuid, { attachments: true }).then(function(doc) { return db.put({ _id: data.uuid, _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) }); }); ipcRenderer.on('action-new-msg-received', (event, arg) => { console.log(arg.data) var new_msg = arg.data; new_msg.forEach(function(item) { console.log(item.status) let obj = new Object(); //handle ping 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.snr = item.snr; obj.type = 'ping'; obj.filename = 'null'; obj.filetype = 'null'; obj.file = 'null'; 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'; add_obj_to_database(obj); update_chat_obj_by_uuid(obj.uuid); // handle ARQ transmission } else if (item.arq == 'transmission' && item.status == 'received') { var encoded_data = atob(item.data); var splitted_data = encoded_data.split(split_char); console.log(splitted_data) obj.timestamp = parseInt(splitted_data[4]); obj.dxcallsign = item.dxcallsign; obj.dxgrid = item.dxgrid; obj.command = splitted_data[1]; obj.checksum = splitted_data[2]; // convert message to unicode from utf8 because of emojis obj.uuid = utf8.decode(splitted_data[3]); obj.msg = utf8.decode(splitted_data[5]); obj.status = 'null'; obj.snr = 'null'; obj.type = 'received'; obj.filename = utf8.decode(splitted_data[6]); obj.filetype = utf8.decode(splitted_data[7]); obj.file = btoa(splitted_data[8]); 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; // 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"; } // check if image, then display it if(filetype == 'image/png' || filetype =="png"){ var fileheader = `
snr: ${obj.snr} - ${timestamp}
snr: ${obj.snr} - ${timestamp}
new chat opened - ${timestamp}
${message_html}
${timestamp}
${message_html}
${timestamp} - ${get_icon_for_state(obj.status)}
${obj.percent} % - ${obj.bytesperminute} Bpm