summaryrefslogblamecommitdiffstats
path: root/dist/js/messaging.js
blob: dba57efa31314b8bcc1c3e61937ceaa443e40d55 (plain) (tree)







































                                                                                      























































                                                                                                                           














































































































































































































































































































                                                                                                                                                                                                                                                                                                               











                                                                                    














































































































































































































































                                                                                                                                                        
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 blue-grey darken-1">
                        <div class="card-content white-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"])} &raquo; ${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 });
});