summaryrefslogblamecommitdiffstats
path: root/js/messaging.js
blob: 86d90e7d6149d3412b19b9cfaf7e185462230399 (plain) (tree)
1
2
3
4
5
6
7

                                                


                                                                                          
                                                  
                     





                    
 












                                                            




                                                                                      

 

                                               
 
                                    
 




                                 
 
















                                                                                                       
                                                                                                                          













                                                                        
                                                                    




                                                                                                                                                  


                                                   
 




                                     
 
                                                                                                      
                                                                                   
     
 

                         


                                 






                                                
 
                                                         
                                                                    












                                                         
 
                                             
 
                                                                                                   






                                                
                                                                             



                                 
 


                                                          
                                                                              

                                          
                                                        
                                                      


                                                                                   





                                              
                                                                              

                                      

                
                                     



                              
 
                            
 









                                                         
 







                                             
                                          






                                                      
                                                                                                             

                                             
                 
                                  
              
 
                          
                                                                                                            



                                  

       

 





                                                         
                                                             



                                             






                                        
                                          






                                                      
                                                                                                            
                        
                                                                      
                 
                                  
              
 
                          
                                                                                                           



                                  

       
 
                                   

                                                                   
                                                 










                                                                                                                                                   











                                                                                                
                                                                                                                           
                                                                                 

                 
                               





                                                                    
                                                                      
            
                                                                               
     
 
 
                               













                                               



                                                                                                              
                                   
                      


                                                              
                             
                                                                                  


                                                             
                                                               
                                   
                                                                                             




                                                                                    

                                                                                     















                                                                                             
                                                                                                                                                                                                                                                                                                               



                              
             
       












                                                                            

 
                                                             









                                                         
















                                       

                                                                     


                                  
                                                                                             


                                  

       
 
                               
                             


                                                   
                                                  


                                                 
                                                        
         














                                                                      
 

                                                                           























                                                                                                                          
             






                                              













                                                             


                                                                

                                                                                  
                                                                         


                                                                                                          
                                                                                                  





                                                                                                                                                             




                                                                                                                     
                                                                                                                                       




                                                                                                                              
                                                                                                                                                           
                                                                                                                 



                                                                                
                                                                                                                                     

                          




                                           



                                                                                          

                                                        
                                               



                                               
                                 
                                                                                                          

                             






                                                                   
 
 
                                       





                                                     









































                                                                                                                         

                                
                         



                                                              
   
const API_ENDPOINT = "https://gimb.tk/test.php";
const DIRECTORY_URL = "/directory.json";

const ENCRYPTED_MESSAGE_REGEX = /<!-- beziapp-e2eemsg-(\d{4}) -->(\S+?)<!-- end-msg -->/g;

// "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
function loadDirectory() {
    $.ajax({
        url: DIRECTORY_URL,
        crossDomain: true,

        dataType: "json",
        cache: false,
        type: "GET",

        success: (data) => {
            // If we were able to retrieve it, update the saved directory
            localforage.setItem("directory", data);
            directory = data;
            // 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
    });

    if (window.location.hash.length > 1 && !window.location.hash.substring(1).startsWith("beziapp")) {
    	$("#full-name").val(decodeURIComponent(window.location.hash.substring(1)));
    }

    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.replace(/_/g, "|")
            },
            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='beziapp-msg-e2ee-form-${randomencdivid}'>
            ${D("thisMessageWasEncrypted")}
            <input type="password" autocomplete="new-password" id="beziapp-msg-e2ee-password-${randomencdivid}" placeholder="${S("password")} ...">
            <button
                type="button"
                value="Decrypt"
                class="btn waves-effect waves-light"
                onclick="
										try {
	                    $('#beziapp-msg-e2ee-content-${randomencdivid}').html(
	                        filterXSS(
	                            sjcl.decrypt(
	                                $('#beziapp-msg-e2ee-password-${randomencdivid}').val(),
	                                $('#beziapp-msg-e2ee-content-${randomencdivid}').html()
	                            )
	                        )
	                    );
      	              $('#beziapp-msg-e2ee-content-${randomencdivid}').show();
    	                $('#beziapp-msg-e2ee-form-${randomencdivid}').hide();
										} catch (err) {
											alert('${D("incorrectPassword")}');
										}
                "
            >
                ${S("decrypt")}
            </button>
        </div>
        <div id="beziapp-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].forEach(element => {
        if (!element["zadeva"].startsWith("beziapp-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">
                            <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();
                                $('#navigation-main').scrollIntoView();
                                "
                            >
                                <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 {
        return (messages[messageType].length ?? 0);
    }
}

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="beziapp-msg-e2ee-form-${randomencdivid}">
                        This message was encrypted by BežiApp.
                        <input type="password" autocomplete="new-password" id="beziapp-msg-e2ee-password-${randomencdivid}" placeholder="Enter password ...">
                        <input type="button" value="Decrypt" onclick="
														try {
	                            console.log($('beziapp-msg-e2ee-content-${randomencdivid}').text());
	                            $('#beziapp-msg-e2ee-content-${randomencdivid}').html(
	                                sjcl.decrypt(
	                                    $('#beziapp-msg-e2ee-password-${randomencdivid}').val(),
										$('#beziapp-msg-e2ee-content-${randomencdivid}').text()
	                                )
	                            );
	                            $('#beziapp-msg-e2ee-content-${randomencdivid}').show();
	                            $('#beziapp-msg-e2ee-form-${randomencdivid}').hide();
														} catch(err) {
															alert('${D("incorrectPassword")}');
														}
                            "
                        >
                    </div>
                    <div id="beziapp-msg-e2ee-content-${randomencdivid}" hidden>
                        <!-- beziapp-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();
    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;
                }
            }
        }
    };
    var instance = 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");
    var instances = M.FloatingActionButton.init(fab_elem, fab_options);

    // Setup modals
    const modal_elems = document.querySelectorAll('.modal');
    const modal_options = {
        onOpenStart: () => { $("#fab-new").hide() },
        onCloseEnd: () => { $("#fab-new").show() },
        dismissible: false
    };
    var instances = M.Modal.init(modal_elems, modal_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 });
});