diff options
Diffstat (limited to 'assets/js/messaging.js')
-rw-r--r-- | assets/js/messaging.js | 650 |
1 files changed, 650 insertions, 0 deletions
diff --git a/assets/js/messaging.js b/assets/js/messaging.js new file mode 100644 index 0000000..890da18 --- /dev/null +++ b/assets/js/messaging.js @@ -0,0 +1,650 @@ +const API_ENDPOINT = "https://gimb.tk/test.php"; +const DIRECTORY_URL = "/directory.json"; + +const ENCRYPTED_MESSAGE_REGEX = /<!-- ba-e2eemsg-(\d{4}) -->(\S+?)<!-- end-msg -->/; + +// "Global" object for name directory and messages +var directory = null; +var messages = { + "0": [], + "1": [], + "2": [] +} +var current_tab = 0; + +async function checkLogin() { + localforage.getItem("logged_in").then(function (value) { + // This code runs once the value has been loaded + // from the offline store. + if (value !== true) { + window.location.replace("/index.html"); + } + }).catch(function (err) { + // This code runs if there were any errors + console.log(err); + }); +} + +// -----------HTML HELPERS----------- +function htmlEncode(value) { + // Create a in-memory element, set its inner text (which is automatically encoded) + // Then grab the encoded contents back out. The element never exists on the DOM. + return $("<textarea/>").text(value).html(); +} + +function htmlDecode(value) { + return $("<textarea/>").html(value).text(); +} +// --------------------------------- + +// Try to fetch name:id directory +async function loadDirectory() { + $.ajax({ + url: DIRECTORY_URL, + crossDomain: true, + dataType: "json", + cache: false, + type: "GET", + success: async function (data) { + // If we were able to retrieve it, update the saved directory + let promises_to_run = [ + localforage.getItem("username").then((value) => { + username = value; + }), + localforage.getItem("password").then((value) => { + password = value; + }), + localforage.getItem("grades").then((value) => { + grades = value; + }) + ]; + await Promise.all(promises_to_run); + var gsecdata; + try { + let gsecInstance = new gsec(); + await gsecInstance.login(username, password); + gsecdata = await gsecInstance.fetchTeachersDirectory().catch( (err) => { + gsecErrorHandlerUI(err); + setLoading(false); + }); + } catch (err) { + gsecErrorHandlerUI(err); + setLoading(false); + } + directory = {...gsecdata, ...data }; + console.log(directory); + localforage.setItem("directory", directory); + // Populate autocomplete + populateAutocomplete(); + }, + error: () => { + // Otherwise, try to retrieve stored directory + localforage.getItem("directory").then((stored_directory) => { + if (stored_directory === null) { + // If unable, set directory to null (so other functions know that we don't have it) + UIAlert( D("nameDirectoryNotSet"), "loadDirectory(): stored_directory === null" ); + directory = null; + // Disable send button + document.getElementById("msg-send").disabled = true; + } else { + directory = stored_directory; + // Populate autocomplete + populateAutocomplete(); + } + }); + } + }); +} + +function populateAutocomplete() { + let elems = document.querySelectorAll(".autocomplete-fullname"); + // če se uporablja globalna var directory, ki je shranjena kot objekt (vedno shranjen kot reference), bo pri let x=y x le pointer na object y + // in se bo spremenil z spremembo "originala". spodnja stvar itak ni preveč efficent, loop čez vseh 7000 ljudi bi lahko delal težave... + // kakšen Object.keys bi bila boljša varianta ampak raje napišem tale komentar... idk, to se mi je zdelo uporabno ampak sedaj obžalujem + // samo guglal sem "copying an object js" + let autocomplete_entries = Object.assign({}, directory); + for (let variableKey in autocomplete_entries) { + autocomplete_entries[variableKey] = null; + } + + M.Autocomplete.init(elems, { + data: autocomplete_entries, + onAutocomplete: validateName, + minLength: 0 + }); + + $(document).ready(function () { + if (window.location.hash.length > 1 && !window.location.hash.substring(1).startsWith("beziapp")) { + var hashValue = decodeURIComponent(window.location.hash.substring(1)); + $("#full-name").val(hashValue); + if (hashValue in directory) { + $("#msg-send").removeAttr("disabled"); + } + $("#beziapp-new-message").modal(); + $("#beziapp-new-message").modal("open"); + } + }); + + M.updateTextFields(); + validateName(); +} + +// Function to toggle loading bar +function setLoading(state) { + if (state) { + $("#loading-bar").removeClass("hidden"); + } else { + $("#loading-bar").addClass("hidden"); + } +} + +// Function, responsible for fetching and displaying data +async function loadMessages(force_refresh = true, messageType = 0) { + setLoading(true); + // Load required data + let promises_to_run = [ + localforage.getItem("username").then((value) => { + username = value; + }), + localforage.getItem("password").then((value) => { + password = value; + }), + localforage.getItem("messages").then((value) => { + messages = value; + }) + ]; + + Promise.all(promises_to_run).then(() => { + + if (messages[messageType] == null || messages[messageType].length === 0 || force_refresh) { + $.ajax({ + url: API_ENDPOINT, + crossDomain: true, + data: { + "u": username, + "p": password, + "m": "fetchsporocilaseznam", + "a": messageType // Message type, see API doc for details + }, + dataType: "json", + cache: false, + type: "GET", + + success: (data) => { + // If data is null, the request failed + if (data === null) { + UIAlert( D("requestFailed") ); + setLoading(false); + } else { + // Save messages & populate view + // console.log(data); // debug + messages[messageType.toString()] = data; + localforage.setItem("messages", messages).then((value) => { + displayData(messageType); + setLoading(false); + }); + } + }, + + error: () => { + UIAlert( D("errorFetchingMessages") ); + setLoading(false); + } + }) + } else { + displayData(messageType); + setLoading(false); + } + }); +} + +async function loadMsg(id) { + + setLoading(true); + // Load required data + let promises_to_run = [ + localforage.getItem("username").then((value) => { + username = value; + }), + localforage.getItem("password").then((value) => { + password = value; + }), + ]; + + Promise.all(promises_to_run).then(() => { + $.ajax({ + url: API_ENDPOINT, + crossDomain: true, + data: { + "u": username, + "p": password, + "m": "fetchsporocilo", + "a": id.replace(/_/g, "|") + }, + dataType: "json", + cache: false, + type: "GET", + success: (data) => { + // If data is null, the request failed + if (data === null) { + UIAlert( `${D("unableToReceiveTheMessage")} ${D("requestFailed")}` ); + } else { + displayMessage(id, data); + } + setLoading(false); + }, + + error: () => { + UIAlert( `${D("unableToReceiveTheMessage")} ${D("noInternetConnection")}` ); + setLoading(false); + } + + }) + }); +} + + +async function deleteMsg(id) { + setLoading(true); + // Load required data + let promises_to_run = [ + localforage.getItem("username").then((value) => { + username = value; + }), localforage.getItem("password").then((value) => { + password = value; + }), + ]; + Promise.all(promises_to_run).then(() => { + $.ajax({ + url: API_ENDPOINT, + crossDomain: true, + data: { + "u": username, + "p": password, + "m": "izbrisisporocilo", + "a": id + }, + dataType: "json", + cache: false, + type: "GET", + success: (data) => { + // If data is null, the request failed + if (data === null) { + UIAlert( `${D("unableToDeleteTheMessage")} ${D("requestFailed")}` ); + } else { + document.getElementById("msg_box-" + id).remove(); + } + setLoading(false); + }, + + error: () => { + UIAlert( `${D("unableToDeleteTheMessage")} ${D("noInternetConnection")}` ); + setLoading(false); + } + + }) + }); +} + +function displayMessage(id, data) { + let regex_results = ENCRYPTED_MESSAGE_REGEX.exec(data["telo"]); + if (regex_results != null) { + var datatodecrypt = regex_results[2]; + var randomencdivid = Math.floor(Math.random() * 9999).toString().padStart(4, "0"); + + var msgcontent = ` + <div id='ba-msg-e2ee-form-${randomencdivid}'> + ${D("thisMessageWasEncrypted")} + <input type="password" autocomplete="new-password" id="ba-msg-e2ee-password-${randomencdivid}" placeholder="${S("password")} ..."> + <button + type="button" + value="Decrypt" + class="btn waves-effect waves-light" + onclick=" + try { + $('#ba-msg-e2ee-content-${randomencdivid}').html( + filterXSS( + sjcl.decrypt( + $('#ba-msg-e2ee-password-${randomencdivid}').val(), + $('#ba-msg-e2ee-content-${randomencdivid}').html() + ) + ) + ); + $('#ba-msg-e2ee-content-${randomencdivid}').show(); + $('#ba-msg-e2ee-form-${randomencdivid}').hide(); + } catch (err) { + alert('${D("incorrectPassword")}'); + } + " + > + ${S("decrypt")} + </button> + </div> + <div id="ba-msg-e2ee-content-${randomencdivid}" hidden> + ${datatodecrypt} + </div> + ` + $(`#msg_body-${id.replace(/\|/g, "_")}`).html(msgcontent); + } else { + $(`#msg_body-${id.replace(/\|/g, "_")}`).html(filterXSS(data["telo"])); + } +} + +// Function for displaying data +function displayData(messageType) { + let div_selector = ""; + switch (messageType) { + case 0: + div_selector = "#beziapp-received"; + break; + case 1: + div_selector = "#beziapp-sent"; + break; + case 2: + div_selector = "#beziapp-deleted"; + break; + } + + // $("#storage-bar").show(); + $("#storage-progressbar").width(Number(Number(getNumMessages(messageType) / 120) * 100).toFixed(2) + "%"); + $("#storage-stats").text(`${getNumMessages(messageType)}/120`); + + let msg_list = $(div_selector); + msg_list.html(""); + messages[messageType.toString()].forEach(element => { + if (!element["zadeva"].startsWith("ba-ctlmsg")) { + + msg_list.append(` + <div class="col s12 m12" id="msg_box-${filterXSS(element["id"])}"> + <div class="card color-card"> + <div class="card-content general-text"> + <span class="card-title"> + ${filterXSS(element["zadeva"])} + </span> + <p id="msg_body-${filterXSS(element["id"]).replace(/\|/g, "_")}"> + <button + class="btn waves-effect waves-light" + onclick="loadMsg('${filterXSS(element["id"])}')" + type="submit" + > + Load message + <i class="material-icons right">move_to_inbox</i> + </button> + <p> + </div> + <div class="card-action general-text"> + <a onclick="deleteMsg('${filterXSS(element["id"])}')"> + <i class="material-icons">delete</i> + </a> + <a onclick=" + $('#full-name').val('${filterXSS(element["posiljatelj"])}'); + $('#msg-subject').val('Re: ${filterXSS(element["zadeva"])}'); + M.updateTextFields(); + " + data-target="beziapp-new-message" + class="modal-trigger" + > + <i class="material-icons">reply</i> + </a> + ${filterXSS(element["posiljatelj"])} » ${filterXSS(element["datum"]["dan"])}. ${filterXSS(element["datum"]["mesec"])}. ${filterXSS(element["datum"]["leto"])} at ${filterXSS(element["cas"]["ura"]).padStart(2, "0")}:${filterXSS(element["cas"]["minuta"]).padStart(2, "0")} + </div> + </div> + </div> + `); + } + }); +} + +// -1 = cumulative +function getNumMessages(messageType = -1) { + if (messageType === -1) { + let sum = 0; + for (const [messageType, messageList] of Object.entries(messages)) { + sum += messageList.length; + } + return sum; + } else { + var what_to_return = messages[messageType].length; + if(what_to_return == null) + return 0 + return what_to_return; + } +} + +async function sendMessage(recipient_number, subject, body) { + setLoading(true); + let promises_to_run = [ + localforage.getItem("username").then((value) => { + username = value; + }), + localforage.getItem("password").then((value) => { + password = value; + }), + ]; + Promise.all(promises_to_run).then(() => { + $.ajax({ + url: API_ENDPOINT, + crossDomain: true, + data: { + "u": username, + "p": password, + "m": "posljisporocilo", + "a": recipient_number, + "b": subject, + "c": body + }, + + dataType: "json", + cache: false, + + type: "POST", + success: () => { + // we CAN'T know wether the message was delievered + UIAlert(D("messageWasProbablySent")); + setLoading(false); + }, + error: () => { + UIAlert(D("errorSendingMessage"), D("noInternetConnection")); + setLoading(false); + } + }) + }); +} + +async function validateName() { + if (directory !== null) { + if ($("#full-name").val() in directory) { + $("#full-name").addClass("valid"); + $("#full-name").removeClass("invalid"); + $("#msg-send").removeAttr("disabled"); + } else { + $("#full-name").addClass("invalid"); + $("#full-name").removeClass("valid"); + $("#msg-send").attr("disabled", "disabled"); + } + } +} + +// Setup event listeners for buttons +function setupEventListeners() { + // Button to add a photo + $("#msg-add-photo").click(() => { + let input = document.createElement("input"); + input.type = "file"; + input.onchange = (e) => { + // getting a hold of the file reference + let file = e.target.files[0]; + // setting up the reader + let reader = new FileReader(); + reader.readAsDataURL(file); // this is reading as data url + + // here we tell the reader what to do when it's done reading... + reader.onload = readerEvent => { + additionalstufftoaddtomessage += `<br><img src="${readerEvent.target.result}" />`; // this is the content! + if ($("#msg-added-image").html().length > 1) { + $("#msg-added-image").append(`<img style="width: 20mm" src="${readerEvent.target.result}" />`); + } else { + $("#msg-added-image").html(` + <input + type="button" + value="${S("removeImages")}" + class="btn waves-effect waves-light" + onclick=" + additionalstufftoaddtomessage = ''; + $('#msg-added-image').html(''); + " + /> + <br> + ${D("largeImagesNote")} + <br> + ${S("attachedImages")}: + <br> + <img style="width:20mm" src="${readerEvent.target.result}" /> + `); + // ravno obratni narekovaji + } + UIAlert(D("imageAddedAsAnAttachment")); + } + } + input.click(); + }); + + // Verify recipient when input loses focus + $("#full-name").on("blur", validateName); + + // Setup refresh icon + $("#refresh-icon").click(() => { + loadMessages(true, current_tab); + }); + + // Setup checkbox handler + $("#encrypt-checkbox").change(function() { + if (this.checked) { + $("#encryption-key-input").prop("hidden", false); + } else { + $("#encryption-key-input").prop("hidden", true); + } + }); + + // Button to send message + $("#msg-send").click(() => { + localforage.getItem("directory").then(function (value) { + var msgcontent = $("#msg-body").val() + additionalstufftoaddtomessage; + var msgsubject = $("#msg-subject").val(); + if ($("#encryption-key-input").prop("hidden") !== true) { + var randomencdivid = Math.floor(Math.random() * 9999).toString().padStart(4, "0"); + var addrparts = window.location.href.split("/"); // engleski + + var encrypted_message = sjcl.encrypt($("#msg-e2ee-pass-input").val(), msgcontent); + msgcontent = ` + <script src="${addrparts[0]}//${addrparts[2]}/js/lib/sjcl.js"></script> + <div id="ba-msg-e2ee-form-${randomencdivid}"> + This message was encrypted by BežiApp. + <input type="password" autocomplete="new-password" id="ba-msg-e2ee-password-${randomencdivid}" placeholder="Enter password ..."> + <input type="button" value="Decrypt" onclick=" + try { + console.log($('ba-msg-e2ee-content-${randomencdivid}').text()); + $('#ba-msg-e2ee-content-${randomencdivid}').html( + sjcl.decrypt( + $('#ba-msg-e2ee-password-${randomencdivid}').val(), + $('#ba-msg-e2ee-content-${randomencdivid}').text() + ) + ); + $('#ba-msg-e2ee-content-${randomencdivid}').show(); + $('#ba-msg-e2ee-form-${randomencdivid}').hide(); + } catch(err) { + alert('${D("incorrectPassword")}'); + } + "> + </div> + <div id="ba-msg-e2ee-content-${randomencdivid}" hidden> + <!-- ba-e2eemsg-${msgcontent.length.toString().padStart(4, "0")} -->${encrypted_message}<!-- end-msg --> + </div> + ` + } + + console.log(msgcontent); + console.log(encrypted_message); + + sendMessage(value[$("#full-name").val()], msgsubject, htmlEncode(msgcontent)); + $("#msg-body").val(""); + $("#full-name").val(""); + $("#msg-subject").val(""); + // $("#msg-send").prop("disabled", true); + $("#msg-send").attr("disabled", "disabled"); + additionalstufftoaddtomessage = ""; + + $("#msg-added-image").html(""); + $("#msg-e2ee-pass").hide(); + + }).catch(function (err) { + UIAlert( `${D("unableToReadDirectory")} ${D("messageCouldNotBeSend")}`, "45245" ); + console.log(err); + }); + }); +} + +function getUrlParameter(sParam) { + const url_params = new URLSearchParams(window.location.search); + const found_param = url_params.get(sParam); + return found_param +} + +var additionalstufftoaddtomessage = ""; +document.addEventListener("DOMContentLoaded", () => { + + checkLogin(); + + // Setup modals + const modal_elems = document.querySelectorAll('.modal'); + const modal_options = { + onOpenStart: () => { $("#fab-new").hide() }, + onCloseEnd: () => { $("#fab-new").show() }, + dismissible: false + }; + M.Modal.init(modal_elems, modal_options); + + loadDirectory(); + setupEventListeners(); + + // Setup tabs + const tabs = document.querySelectorAll(".tabs"); + const tab_options = { + // swipeable: true, // TODO: figure out how to fix height when it's enabled (it's good for UX to have it enabled) + onShow: (tab) => { + if ($(tab).hasClass("active")) { + switch (tab.id) { + case "beziapp-received": + current_tab = 0; + loadMessages(false, 0); + break; + case "beziapp-sent": + current_tab = 1; + loadMessages(false, 1); + break; + case "beziapp-deleted": + current_tab = 2; + loadMessages(false, 2); + break; + } + } + } + }; + M.Tabs.init(tabs, tab_options); + + // Setup floating action button + const fab_options = { + hoverEnabled: false, + toolbarEnabled: false + } + const fab_elem = document.querySelectorAll(".fixed-action-btn"); + M.FloatingActionButton.init(fab_elem, fab_options); + + var receivedmessages = null; + loadMessages(true, 0); + M.updateTextFields(); + + // Setup side menu + const menus = document.querySelectorAll(".side-menu"); + M.Sidenav.init(menus, { edge: "right", draggable: true }); +}); |