summaryrefslogblamecommitdiffstats
path: root/assets/js/messaging.js
blob: 890da1866c7a89a8aa87e7fede97ed8c29152806 (plain) (tree)
1
2
3
4
5
6
7
8

                                                
 
                                                                                    
 
                                                  
                     
                


                

                    
 
                             









                                                                

 

                                     


                                                                                          

 
                            
                                                   
 
                                    
 
                                 























































                                                                                                                           


                                 





























                                                                                                                                                      


                                 
                            




                                                        
 
 
                                                         
                                                                    























































                                                                                                           
 
 
                            
 








































                                                                                                             
 

 
                              






































                                                                                                            
 
 
                                   








































                                                                                                                                                          
 
 
                               
                                   























                                                                                                                  

                                                                                       


































                                                                                                                                                                                                                                                                                                                                           



                                           











                                                                                    

 
                                                             




































                                                                                             
 
 
                               










                                                                    



                                    


















































































































                                                                                                                                                                                


                                  


                                                                       
 
 
                                       

                                                     





















































                                                                                                                                 
   
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"])} &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 });
});