800 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			800 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| var apx = apx || {};
 | |
| 
 | |
| apx.privatri = {};
 | |
| apx.privatri.templates = {};
 | |
| 
 | |
| const apiUrl = "http://admin.apxtri.farm.test/api/apxtri/privatri"
 | |
| const tribe = "apxtri";
 | |
| 
 | |
| apx.privatri.loadwco = async (id, dataObj = {}, ctx = null) => {
 | |
|     // check if not authenticate, do nothing cause by default screensignin and wait authentification
 | |
|     //       if     authenticate, if url xhash then redirect if no url then change wco-link=screenmyworld
 | |
|     // if (dayjs(apx.data.headers.xdays).diff(dayjs(), "hours") >= 24) apx.apxauth.checkisauth();
 | |
|     //load main.mustache of the component
 | |
|     //when wco-xxx change it run this function
 | |
|     console.log(
 | |
|         `Load wconame:privatri apx.privatri.loadwco with id:${id} and ctx: ${JSON.stringify(
 | |
|             ctx
 | |
|         )}`
 | |
|     );
 | |
| 
 | |
|     const templatesObj = JSON.parse(localStorage.getItem("admin")).tpl;
 | |
| 
 | |
|     const template = Object.entries(templatesObj).find(([key]) => key.toLowerCase() === `privatri${id}`.toLocaleLowerCase())?.[1];
 | |
|     
 | |
|     return Mustache.render(template, dataObj);
 | |
| };
 | |
| 
 | |
| async function getOldestPrivatriids(db, storeName) {
 | |
|     const keysArray = await apx.indexedDB.getAllKeys(db, storeName);
 | |
|     
 | |
|     const oldestPerUuid = {};
 | |
| 
 | |
|     for (const key of keysArray) {
 | |
|         const [uuid, timestampStr] = key.split("_");
 | |
|         const timestamp = Number(timestampStr);
 | |
| 
 | |
|         if (!oldestPerUuid[uuid] || timestamp < oldestPerUuid[uuid].timestamp) {
 | |
|             oldestPerUuid[uuid] = { key, timestamp };
 | |
|         };
 | |
|     };
 | |
| 
 | |
|     const oldestKeysArray = Object.values(oldestPerUuid).map(obj => obj.key)
 | |
| 
 | |
|     const threadsArray = [];
 | |
| 
 | |
|     for (key of oldestKeysArray) {
 | |
|         threadsArray.push(await apx.indexedDB.get(db, storeName, key));
 | |
|     };
 | |
| 
 | |
|     return threadsArray;
 | |
| };
 | |
| 
 | |
| apx.privatri.syncronizeBackend = async (alias, lastConnection) => {
 | |
|     const threadsArray = await getOldestPrivatriids("privatri", "messages");
 | |
| 
 | |
|     for (const threadObj of threadsArray) {
 | |
|         try {
 | |
|             const response = await fetch(`${apiUrl}/${tribe}/${threadObj.thread}?since=${lastConnection}`);
 | |
|     
 | |
|             if (response.ok === false) {
 | |
|                 throw new Error("HTTP error");
 | |
|             };
 | |
|     
 | |
|             console.log(response);
 | |
| 
 | |
|             // for (const message of response) {
 | |
|             //     await apx.indexedDB.set("privatri", "messages", message);
 | |
|             // };
 | |
|         } catch (error) {
 | |
|             const displayToastAlert = async (message) => {
 | |
|                 return await apx.privatri.loadwco("toastAlert", { message });
 | |
|             };
 | |
| 
 | |
|             document.querySelector("body").insertAdjacentHTML("beforeend", await displayToastAlert("An error occurred while synchronizing messages. Please try again later."));
 | |
|         };
 | |
|     };
 | |
| };
 | |
| 
 | |
| apx.privatri.templates.scripts = {
 | |
|     createThread: async () => {
 | |
|         const bodyEl = document.querySelector("body");
 | |
|         const autoDeletionBtnElArray = document.querySelectorAll("li.autoDeletionBtn");
 | |
| 
 | |
|         const displayToastAlert = async (message) => {
 | |
|             return await apx.privatri.loadwco("toastAlert", { message });
 | |
|         };
 | |
|         
 | |
|         autoDeletionBtnElArray.forEach(btn => {
 | |
|             btn.addEventListener("click", () => {
 | |
|                 autoDeletionBtnElArray.forEach(btn => btn.classList.remove("bg-base-200"));
 | |
|                 btn.classList.add("bg-base-200");
 | |
|             });
 | |
|         });
 | |
|         
 | |
|         document.querySelector("#createThreadBtn").addEventListener("click", async () => {
 | |
|             const { publicKey, privateKey } = await apx.crypto.genKey();
 | |
| 
 | |
|             const messageObj = await (async (publicKey) => {
 | |
|                 const uuid = apx.crypto.genUUID();
 | |
|                 const timestamp = Date.now();
 | |
|                 const alias = await apx.crypto.encryptMessage(JSON.parse(localStorage.getItem("admin")).headers.xalias, publicKey);
 | |
|         
 | |
|                 return {
 | |
|                     privatriid: `${uuid}_${timestamp}`,
 | |
|                     thread: uuid,
 | |
|                     timestamp: timestamp,
 | |
|                     owner: alias,
 | |
|                     title: await apx.crypto.encryptMessage(document.querySelector("#threadNameInput").value, publicKey),
 | |
|                     sender_alias: alias,
 | |
|                     publicKey: publicKey,
 | |
|                     aliases: [
 | |
|                         alias
 | |
|                     ],
 | |
|                     message: await apx.crypto.encryptMessage(document.querySelector("#threadDescriptionInput").value, publicKey),
 | |
|                     dt_autodestruction: (() => {
 | |
|                         const selectedBtn = Array.from(autoDeletionBtnElArray).find(btn => btn.classList.contains("bg-base-200"));
 | |
|                         return parseInt(selectedBtn ? selectedBtn.getAttribute("data-auto-deletion") : 0, 10);
 | |
|                     })(),
 | |
|                     urgencydeletion: document.querySelector('input[type="checkbox"].toggle').checked
 | |
|                 };
 | |
|             })(publicKey);
 | |
|         
 | |
|             try {
 | |
|                 const response = await fetch(`${apiUrl}/${tribe}/${messageObj.thread}`, {
 | |
|                     method: "POST",
 | |
|                     headers: {
 | |
|                         "Content-Type": "application/json",
 | |
|                         "xdays": admin.headers.xdays,
 | |
|                         "xalias": admin.headers.xalias,
 | |
|                         "xlang": admin.headers.xlang,
 | |
|                         "xtribe": admin.headers.xtribe,
 | |
|                         "xapp": admin.headers.xapp,
 | |
|                         "xuuid": admin.headers.xuuid
 | |
|                     },
 | |
|                     body: JSON.stringify(messageObj)
 | |
|                 });
 | |
| 
 | |
|                 if (response.ok === false) {
 | |
|                     throw new Error("HTTP error");
 | |
|                 };
 | |
| 
 | |
|                 await apx.indexedDB.set("privatri", "threads", { uuid: messageObj.thread, privateKey: privateKey });
 | |
|                 await apx.indexedDB.set("privatri", "messages", messageObj);
 | |
| 
 | |
|                 bodyEl.insertAdjacentHTML("beforeend", await displayToastAlert("Thread created successfully."));
 | |
|             } catch (error) {
 | |
|                 bodyEl.insertAdjacentHTML("beforeend", await displayToastAlert("An error occurred while creating the thread. Please try again later."));
 | |
|             };
 | |
|         });
 | |
|     },
 | |
|     inviteAlias: async () => {
 | |
|         const url = "https://www.facebook.com/";
 | |
| 
 | |
|         const bodyEl = document.querySelector("body");
 | |
|         const qrCodeCanvasEl = document.querySelector("#qrCodeCanvas");
 | |
|         const invitationLinkInputEl = document.querySelector("#invitationLinkInput");
 | |
| 
 | |
|         const displayToastAlert = async (message) => {
 | |
|             return await apx.privatri.loadwco("toastAlert", { message });
 | |
|         };
 | |
|         
 | |
|         const qrCode = new QRCodeStyling({
 | |
|             width: 425,
 | |
|             height: 425,
 | |
|             type: "svg",
 | |
|             data: url,
 | |
|             image: "static/img/icons/privatri.png",
 | |
|             dotsOptions: {
 | |
|                 color: "#ffffff",
 | |
|                 type: "rounded"
 | |
|             },
 | |
|             backgroundOptions: {
 | |
|                 color: getComputedStyle(bodyEl).backgroundColor || "#000000",
 | |
|             },
 | |
|             imageOptions: {
 | |
|                 crossOrigin: "anonymous",
 | |
|                 margin: 20
 | |
|             }
 | |
|         });
 | |
|         
 | |
|         qrCode.append(qrCodeCanvasEl);
 | |
| 
 | |
|         invitationLinkInputEl.value = url;
 | |
| 
 | |
|         copyBtn.addEventListener("click", async () => {
 | |
|             navigator.clipboard.writeText(invitationLinkInputEl.value);
 | |
| 
 | |
|             bodyEl.insertAdjacentHTML("beforeend", await displayToastAlert("Invitation link copied to clipboard."));
 | |
| 
 | |
|             setTimeout(() => {
 | |
|                 bodyEl.lastElementChild.remove();
 | |
|             }, 3000);
 | |
|         });
 | |
|     },
 | |
|     threadAliasList: async () => {
 | |
|         const bodyEl = document.querySelector("body");
 | |
|         const aliasListContainerEl = document.querySelector("#aliasListContainer");
 | |
| 
 | |
|         let aliasesArray = [];
 | |
| 
 | |
|         const newAlias = async (alias) => {
 | |
|             return await apx.privatri.loadwco("alias", { alias });
 | |
|         };
 | |
| 
 | |
|         const displayToastAlert = async (message) => {
 | |
|             return await apx.privatri.loadwco("toastAlert", { message });
 | |
|         };
 | |
| 
 | |
|         const params = new URLSearchParams(window.location.search);
 | |
|         const uuid = params.get("uuid");
 | |
| 
 | |
|         const privateKey = (await apx.indexedDB.get("privatri", "threads", uuid)).privateKey;
 | |
|         
 | |
|         const privatriidArray = await getOldestPrivatriids("privatri", "messages");
 | |
|         
 | |
|         privatriidArray.forEach(async privatriid => {
 | |
|             if (privatriid.thread === uuid) {
 | |
|                 messageObj = privatriid;
 | |
|                 aliasesArray = messageObj.aliases;
 | |
|                 const ownerAlias = messageObj.owner;
 | |
|                 
 | |
|                 for (const alias of aliasesArray) {
 | |
|                     aliasListContainerEl.insertAdjacentHTML("beforeend", await newAlias({
 | |
|                         decrypted: (await apx.crypto.decryptMessage(alias, privateKey)).data,
 | |
|                         crypted: alias
 | |
|                     }));
 | |
|                 };
 | |
| 
 | |
|                 document.querySelectorAll("button.removeAliasBtn").forEach(btn => {
 | |
|                     btn.addEventListener("click", async () => {
 | |
|                         if (JSON.parse(localStorage.getItem("admin")).headers.xalias === (await apx.crypto.decryptMessage(ownerAlias, privateKey)).data) {
 | |
|                             if (btn.getAttribute("data-alias") !== ownerAlias) {
 | |
|                                 const alias = btn.getAttribute("data-alias");
 | |
|                                 aliasesArray = aliasesArray.filter(a => a !== alias);
 | |
|                                 btn.parentElement.parentElement.remove();
 | |
|                     
 | |
|                                 privatriidArray.forEach(async privatriid => {
 | |
|                                     if (privatriid.split("_")[0] === uuid) {
 | |
|                                         const messageObj = await apx.indexedDB.get("privatri", "messages", privatriid);
 | |
|                                         messageObj.aliases = aliasesArray;
 | |
|                                         
 | |
|                                         await apx.indexedDB.set("privatri", "messages", messageObj);
 | |
|                                     };
 | |
|                                 });
 | |
|                             } else {
 | |
|                                 bodyEl.insertAdjacentHTML("beforeend", await displayToastAlert("You cannot remove the owner of the thread."));
 | |
| 
 | |
|                                 setTimeout(() => {
 | |
|                                     bodyEl.lastElementChild.remove();
 | |
|                                 }, 3000);
 | |
|                             };
 | |
|                         } else {
 | |
|                             bodyEl.insertAdjacentHTML("beforeend", await displayToastAlert("You don't have the permissions to remove this alias."));
 | |
| 
 | |
|                             setTimeout(() => {
 | |
|                                 bodyEl.lastElementChild.remove();
 | |
|                             }, 3000);
 | |
|                         };
 | |
|                     });
 | |
|                 });
 | |
|             };
 | |
|         });
 | |
|     },
 | |
|     threadSettings: async () => {
 | |
|         const bodyEl = document.querySelector("body");
 | |
|         const threadNameInputEl = document.querySelector("#threadNameInput");
 | |
|         const autoDeletionBtnElArray = document.querySelectorAll("li.autoDeletionBtn");
 | |
|         const applyModificationsBtnEl = document.querySelector("#applyModificationsBtn");
 | |
| 
 | |
|         const displayToastAlert = async (message) => {
 | |
|             return await apx.privatri.loadwco("toastAlert", { message });
 | |
|         };
 | |
| 
 | |
|         let messageObj = {};
 | |
| 
 | |
|         const params = new URLSearchParams(window.location.search);
 | |
|         const uuid = params.get("uuid");
 | |
| 
 | |
|         const privateKey = (await apx.indexedDB.get("privatri", "threads", uuid)).privateKey;
 | |
|         
 | |
|         const privatriidArray = await getOldestPrivatriids("privatri", "messages");
 | |
|         let ownerAlias = "";
 | |
|         
 | |
|         privatriidArray.forEach(async privatriid => {
 | |
|             if (privatriid.thread === uuid) {
 | |
|                 messageObj = privatriid;
 | |
|                 ownerAlias = messageObj.owner;
 | |
|                 
 | |
|                 threadNameInputEl.value = (await apx.crypto.decryptMessage(messageObj.title, privateKey)).data;
 | |
| 
 | |
|                 (Array.from(autoDeletionBtnElArray).find(el => el.getAttribute("data-auto-deletion") === String(messageObj.dt_autodestruction))).classList.add("bg-base-200");
 | |
|             
 | |
|                 if (messageObj.urgencydeletion === true) {
 | |
|                     document.querySelector('input[type="checkbox"].toggle').checked = true;
 | |
|                 };
 | |
|             };
 | |
|         });
 | |
| 
 | |
|         applyModificationsBtnEl.addEventListener("click", async () => {
 | |
|             if (JSON.parse(localStorage.getItem("admin")).headers.xalias === (await apx.crypto.decryptMessage(ownerAlias, privateKey)).data) {
 | |
|                 messageObj.title = await apx.crypto.encryptMessage(threadNameInputEl.value, privateKey);
 | |
|                 messageObj.dt_autodestruction = (() => {
 | |
|                     const selectedBtn = Array.from(autoDeletionBtnElArray).find(btn => btn.classList.contains("bg-base-200"));
 | |
|                     return parseInt(selectedBtn ? selectedBtn.getAttribute("data-auto-deletion") : 0, 10);
 | |
|                 })();
 | |
|                 messageObj.urgencydeletion = document.querySelector('input[type="checkbox"].toggle').checked;
 | |
| 
 | |
|                 await apx.indexedDB.set("privatri", "messages", messageObj);
 | |
|             } else {
 | |
|                 bodyEl.insertAdjacentHTML("beforeend", await displayToastAlert("You don't have the permissions to edit this thread."));
 | |
| 
 | |
|                 setTimeout(() => {
 | |
|                     bodyEl.lastElementChild.remove();
 | |
|                 }, 3000);
 | |
|             };
 | |
|         });
 | |
| 
 | |
|         autoDeletionBtnElArray.forEach(btn => {
 | |
|             btn.addEventListener("click", () => {
 | |
|                 autoDeletionBtnElArray.forEach(btn => btn.classList.remove("bg-base-200"));
 | |
|                 btn.classList.add("bg-base-200");
 | |
|             });
 | |
|         });
 | |
|     }
 | |
| };
 | |
| 
 | |
| apx.ready(async () => {
 | |
|     document.querySelector("body").innerHTML = await apx.privatri.loadwco("main", JSON.parse(localStorage.getItem("admin")).tpldata.privatri_main_privatri);
 | |
|     
 | |
|     const bodyEl = document.querySelector("body");
 | |
|     const searchInputEl = document.querySelector("#threadSearchBar");
 | |
|     const threadFilterOptionsElArray = document.querySelector("#threadFilterOptions").querySelectorAll("li");
 | |
|     const threadsContainerEl = document.querySelector("#threadsContainer");
 | |
|     const threadPageEl = document.querySelector("#threadPage");
 | |
|     const messageInputEl = document.getElementById("messageInput");
 | |
|     const messagesContainerEl = document.querySelector("#messagesContainer");
 | |
|     const attachmentsInputEl = document.querySelector("#attachmentsInput");
 | |
| 
 | |
|     await (async () => {
 | |
|         const lastConnection = JSON.parse(localStorage.getItem("lastConnection")) || Date.now();
 | |
|     
 | |
|         // await apx.privatri.syncronizeBackend(apx.data.headers.xalias, lastConnection);
 | |
|     
 | |
|         const privatriidArray = await getOldestPrivatriids("privatri", "messages");
 | |
|         console.log(privatriidArray);
 | |
|     
 | |
|         const thread = async (name, uuid) => {
 | |
|             return await apx.privatri.loadwco("thread", { uuid, name });
 | |
|         };
 | |
|     
 | |
|         for (const privatriidObj of privatriidArray) {
 | |
|             const obj = await apx.indexedDB.get("privatri", "messages", privatriidObj.privatriid)
 | |
|             
 | |
|             const privateKey = (await apx.indexedDB.get("privatri", "threads", obj.thread)).privateKey;
 | |
|             const name = (await apx.crypto.decryptMessage(obj.title, privateKey)).data;
 | |
|     
 | |
|             threadsContainerEl.insertAdjacentHTML("beforeend", await thread(name, obj.thread));
 | |
|         };
 | |
|     
 | |
|         Array.from(threadsContainerEl.children).forEach(child => {
 | |
|             child.addEventListener("click", async () => {
 | |
|                 const uuid = child.getAttribute("data-uuid");
 | |
|     
 | |
|                 const messagesObj = await new Promise((resolve, reject) => {
 | |
|                     const request = indexedDB.open("privatri", 1);
 | |
|     
 | |
|                     request.onsuccess = (event) => {
 | |
|                         const db = event.target.result;
 | |
|     
 | |
|                         const transaction = db.transaction("messages", "readonly");
 | |
|                         const store = transaction.objectStore("messages");
 | |
|                         const cursorRequest = store.openCursor();
 | |
|     
 | |
|                         const result = [];
 | |
|                         cursorRequest.onsuccess = (event) => {
 | |
|                             const cursor = event.target.result;
 | |
|     
 | |
|                             if (cursor) {
 | |
|                                 const obj = cursor.value;
 | |
|                                 const keyUuid = obj.privatriid.split("_")[0];
 | |
|     
 | |
|                                 if (keyUuid === uuid) {
 | |
|                                     result.push(obj);
 | |
|                                 };
 | |
|                                 cursor.continue();
 | |
|                             } else {
 | |
|                                 resolve(result);
 | |
|                             };
 | |
|                         };
 | |
|     
 | |
|                         cursorRequest.onerror = (event) => reject(event);
 | |
|                     };
 | |
|     
 | |
|                     request.onerror = (event) => reject(event);
 | |
|                 });
 | |
|     
 | |
|                 threadPageEl.setAttribute("data-uuid", uuid);
 | |
|                 threadPageEl.querySelector("#threadName").innerText = (await apx.crypto.decryptMessage(messagesObj[0].title, (await apx.indexedDB.get("privatri", "threads", uuid)).privateKey)).data;
 | |
|     
 | |
|                 messagesContainerEl.innerHTML = "";
 | |
|     
 | |
|                 let privateKey = "";
 | |
|     
 | |
|                 for (const message of messagesObj) {
 | |
|                     if (privateKey === "") {
 | |
|                         privateKey = (await apx.indexedDB.get("privatri", "threads", message.thread)).privateKey;
 | |
|                     };
 | |
|     
 | |
|                     const decryptedMessage = (await apx.crypto.decryptMessage(message.message, privateKey)).data;
 | |
|     
 | |
|                     let decryptedAttachments = [];
 | |
|     
 | |
|                     if (message.attachments !== undefined) {
 | |
|                         decryptedAttachments = await (async () => {
 | |
|                             const attachmentsArray = [];
 | |
|         
 | |
|                             if (message.attachments.length > 0) {
 | |
|                                 for (const attachment of message.attachments) {
 | |
|                                     attachmentsArray.push({
 | |
|                                         fileType: attachment.fileType,
 | |
|                                         filename: attachment.filename,
 | |
|                                         content: (await apx.crypto.decryptMessage(attachment.content, privateKey)).data
 | |
|                                     });
 | |
|                                 };
 | |
|                             };
 | |
|         
 | |
|                             return attachmentsArray;
 | |
|                         })();
 | |
|                     };
 | |
|     
 | |
|                     messagesContainerEl.insertAdjacentHTML("beforeend", await sendNewMessage(message.timestamp, decryptedMessage, decryptedAttachments));
 | |
|                 };
 | |
|             });
 | |
|         });
 | |
| 
 | |
|         threadsContainerEl.children[0]?.click();
 | |
|         
 | |
|         document.querySelectorAll("a").forEach(link => {
 | |
|             link.addEventListener("click", async (event) => {
 | |
|                 event.preventDefault();
 | |
| 
 | |
|                 window.history.replaceState({}, document.title, window.location.pathname);
 | |
|     
 | |
|                 const templateName = link.getAttribute("data-template");
 | |
|     
 | |
|                 if (templateName === "threadAliasList" || templateName === "threadSettings") {
 | |
|                     window.history.pushState({}, "", `${window.location.pathname}?uuid=${threadPageEl.getAttribute("data-uuid")}`);
 | |
|                 };
 | |
|     
 | |
|                 bodyEl.innerHTML = await apx.privatri.loadwco(templateName);
 | |
|     
 | |
|                 await apx.privatri.templates.scripts[templateName]();
 | |
|             });
 | |
|         });
 | |
|     })();
 | |
| 
 | |
|     function formatDate(timestamp) {
 | |
|         const date = new Date(timestamp);
 | |
|         const day = String(date.getDate()).padStart(2, "0");
 | |
|         const month = String(date.getMonth() + 1).padStart(2, "0");
 | |
|         const year = date.getFullYear();
 | |
|         const hours = String(date.getHours()).padStart(2, "0");
 | |
|         const minutes = String(date.getMinutes()).padStart(2, "0");
 | |
| 
 | |
|         return `${day}/${month}/${year} ${hours}:${minutes}`;
 | |
|     };
 | |
| 
 | |
|     searchInputEl.addEventListener("input", () => {
 | |
|         const value = searchInputEl.value.toLowerCase();
 | |
| 
 | |
|         Array.from(threadsContainerEl.children).forEach(child => {
 | |
|             if (child.querySelector("li > div").textContent.toLowerCase().includes(value)) {
 | |
|                 child.style.display = "block";
 | |
|             } else {
 | |
|                 child.style.display = "none";
 | |
|             };
 | |
|         });
 | |
|     });
 | |
| 
 | |
|     threadFilterOptionsElArray.forEach(option => {
 | |
|         option.addEventListener("click", async () => {
 | |
|             const filter = option.getAttribute("data-filter");
 | |
| 
 | |
|             if (filter === "date") {
 | |
|                 Array.from(threadsContainerEl.children).sort((a, b) => {
 | |
|                     return a.querySelector("li > span").getAttribute("data-timestamp") - b.querySelector("li > span").getAttribute("data-timestamp");
 | |
|                 }).forEach(child => {
 | |
|                     threadsContainerEl.appendChild(child);
 | |
|                 });
 | |
|             } else if (filter === "name") {
 | |
|                 Array.from(threadsContainerEl.children).sort((a, b) => {
 | |
|                     return a.querySelector("li > div").textContent.localeCompare(b.querySelector("li > div").textContent);
 | |
|                 }).forEach(child => {
 | |
|                     threadsContainerEl.appendChild(child);
 | |
|                 });
 | |
|             };
 | |
|         });
 | |
|     });
 | |
| 
 | |
|     const sendNewMessage = async (timestamp, message, attachmentsArray) => {
 | |
|         const messageTemplate = await apx.privatri.loadwco("message", { timestamp, message, date: formatDate(timestamp) });
 | |
| 
 | |
|         const tempNode = document.createElement("div");
 | |
|         tempNode.innerHTML = messageTemplate;
 | |
|         const messageNode = tempNode.firstElementChild;
 | |
| 
 | |
|         attachmentsArray.forEach(attachment => {
 | |
|             messageNode.querySelector("div.attachmentsContainer").insertAdjacentHTML("beforeend", `<img onclick="attachEventDisplayAttachment(this)" class="w-20 h-20 object-cover rounded" src="data:${attachment.fileType};base64,${attachment.content}" alt="${attachment.filename}"/>`);
 | |
|         });
 | |
|         
 | |
|         return messageNode.outerHTML;
 | |
|     };
 | |
| 
 | |
|     const editMessage = async (timestamp, message) => {
 | |
|         return await apx.privatri.loadwco("editMessage", { message, date: formatDate(timestamp) });
 | |
|     };
 | |
| 
 | |
|     const displayToastAlert = async (message) => {
 | |
|         return await apx.privatri.loadwco("toastAlert", { message });
 | |
|     };
 | |
| 
 | |
|     sendMessageBtn.addEventListener("click", async () => {
 | |
|         const message = messageInputEl.value.trim();
 | |
| 
 | |
|         if (message !== "") {
 | |
|             const messageObj = await (async (publicKey) => {
 | |
|                 const timestamp = Date.now();
 | |
|                 const alias = await apx.crypto.encryptMessage(JSON.parse(localStorage.getItem("admin")).headers.xalias, publicKey);
 | |
|         
 | |
|                 return {
 | |
|                     privatriid: `${threadPageEl.getAttribute("data-uuid")}_${timestamp}`,
 | |
|                     timestamp: timestamp,
 | |
|                     sender_alias: alias,
 | |
|                     message: await apx.crypto.encryptMessage(message, publicKey),
 | |
|                     attachments: await (async () => {
 | |
|                         const attachmentsArray = [];
 | |
|                         const localAttachmentsArray = JSON.parse(sessionStorage.getItem(`attachmentsArray_${threadPageEl.getAttribute("data-uuid")}`)) || [];
 | |
| 
 | |
|                         if (localAttachmentsArray.length > 0) {
 | |
|                             for (const attachment of localAttachmentsArray) {
 | |
|                                 attachmentsArray.push(
 | |
|                                     {
 | |
|                                         fileType: attachment.fileType,
 | |
|                                         filename: attachment.filename,
 | |
|                                         content: await apx.crypto.encryptMessage(attachment.content, publicKey)
 | |
|                                     }
 | |
|                                 );
 | |
|                             };
 | |
|                         };
 | |
| 
 | |
|                         return attachmentsArray;
 | |
|                     })()
 | |
|                 };
 | |
|             })((await apx.indexedDB.get("privatri", "messages", `${threadPageEl.getAttribute("data-uuid")}_${messagesContainerEl.firstElementChild.getAttribute("data-timestamp")}`)).publicKey);
 | |
|             
 | |
|             const newMessageHTML = await sendNewMessage(messageObj.timestamp, message, JSON.parse(sessionStorage.getItem(`attachmentsArray_${threadPageEl.getAttribute("data-uuid")}`)) || []);
 | |
| 
 | |
|             sessionStorage.removeItem(`attachmentsArray_${threadPageEl.getAttribute("data-uuid")}`);
 | |
|             
 | |
|             messagesContainerEl.insertAdjacentHTML("beforeend", newMessageHTML);
 | |
|             
 | |
|             messageInputEl.value = "";
 | |
| 
 | |
|             try {
 | |
|                 const response = await fetch(`${apiUrl}/`);
 | |
| 
 | |
|                 if (response.ok === false) {
 | |
|                     throw new Error("HTTP error");
 | |
|                 };
 | |
| 
 | |
|                 await apx.indexedDB.set("privatri", "messages", messageObj);
 | |
|             } catch (error) {
 | |
|                 bodyEl.insertAdjacentHTML("beforeend", await displayToastAlert("An error occurred while sending the message. Please try again later."));
 | |
|             };
 | |
|         };
 | |
|     });
 | |
| 
 | |
|     window.addEventListener("beforeunload", () => {
 | |
|         localStorage.setItem("lastConnection", JSON.stringify(Date.now()));
 | |
|     });
 | |
| 
 | |
|     window.attachDeleteMessageEvent = async function attachDeleteMessageEvent(btn) {
 | |
|         const messageEl = btn.parentElement.parentElement.parentElement;
 | |
| 
 | |
|         const privateKey = (await apx.indexedDB.get("privatri", "threads", threadPageEl.getAttribute("data-uuid"))).privateKey;
 | |
| 
 | |
|         const signatureMessage = `${JSON.parse(localStorage.getItem("admin")).headers.xalias}_${threadPageEl.getAttribute("data-uuid")}_${messageEl.getAttribute("data-timestamp")}`;
 | |
|         const signature = await apx.crypto.sign(signatureMessage, privateKey);
 | |
|         
 | |
|         let verified = false;
 | |
| 
 | |
|         // try {
 | |
|         //     verified = await fetch("", {
 | |
|         //         method: "GET",
 | |
|         //         body: JSON.stringify({
 | |
|         //             message: signatureMessage,
 | |
|         //             signature: signature,
 | |
|         //             uuid: threadPageEl.getAttribute("data-uuid"),
 | |
|         //             timestamp: messageEl.getAttribute("data-timestamp"),
 | |
|         //         })
 | |
|         //     });
 | |
|         // } catch (error) {
 | |
|         //     console.error("Error while verifying signature:", error);
 | |
|         // };
 | |
| 
 | |
|         const authorAlias = (await apx.crypto.decryptMessage((await apx.indexedDB.get("privatri", "messages", `${threadPageEl.getAttribute("data-uuid")}_${messageEl.getAttribute("data-timestamp")}`)).sender_alias, privateKey)).data;
 | |
| 
 | |
|         if ((JSON.parse(localStorage.getItem("admin")).headers.xalias === authorAlias) && (verified === true)) {
 | |
|             await apx.indexedDB.del("privatri", "messages", `${threadPageEl.getAttribute("data-uuid")}_${messageEl.getAttribute("data-timestamp")}`);
 | |
|         
 | |
|             messageEl.remove();
 | |
|         } else {
 | |
|             bodyEl.insertAdjacentHTML("beforeend", await displayToastAlert("You don't have the permissions to delete this message."));
 | |
| 
 | |
|             setTimeout(() => {
 | |
|                 bodyEl.lastElementChild.remove();
 | |
|             }, 3000);
 | |
|         };
 | |
|     };
 | |
| 
 | |
|     window.attachEditMessageEvent = async function attachEditMessageEvent(btn) {
 | |
|         const messageEl = btn.parentElement.parentElement.parentElement;
 | |
|     
 | |
|         const authorAlias = (await apx.crypto.decryptMessage((await apx.indexedDB.get("privatri", "messages", `${threadPageEl.getAttribute("data-uuid")}_${messageEl.getAttribute("data-timestamp")}`)).sender_alias, (await apx.indexedDB.get("privatri", "threads", threadPageEl.getAttribute("data-uuid"))).privateKey)).data;
 | |
|         
 | |
|         if (JSON.parse(localStorage.getItem("admin")).headers.xalias === authorAlias) {
 | |
|             const messageValue = messageEl.querySelector("div.message").innerText;
 | |
| 
 | |
|             const attachmentsArray = (() => {
 | |
|                 const attachmentsArray = [];
 | |
| 
 | |
|                 messageEl.querySelector("div.attachmentsContainer").querySelectorAll("img").forEach(img => {
 | |
|                     attachmentsArray.push(
 | |
|                         {
 | |
|                             fileType: img.getAttribute("src").match(/^data:(.*);base64,/)[1],
 | |
|                             filename: img.getAttribute("alt"),
 | |
|                             content: img.getAttribute("src").split(",")[1]
 | |
|                         }
 | |
|                     );
 | |
|                 });
 | |
| 
 | |
|                 return attachmentsArray;
 | |
|             })();
 | |
| 
 | |
|             messageEl.innerHTML = await editMessage(parseInt(messageEl.getAttribute("data-timestamp"), 10), messageEl.querySelector("div.message").innerText);
 | |
| 
 | |
|             messageEl.querySelector("button.cancelEditBtn").addEventListener("click", async () => {
 | |
|                 const messageObj = await apx.indexedDB.get("privatri", "messages", `${threadPageEl.getAttribute("data-uuid")}_${messageEl.getAttribute("data-timestamp")}`);
 | |
| 
 | |
|                 messageEl.innerHTML = await sendNewMessage(messageObj.timestamp, messageValue, attachmentsArray);
 | |
|             });
 | |
| 
 | |
|             messageEl.querySelector("button.saveEditBtn").addEventListener("click", async () => {
 | |
|                 const newMessageValue = messageEl.querySelector("textarea").value.trim();
 | |
| 
 | |
|                 if (newMessageValue !== "") {
 | |
|                     const privateKey = (await apx.indexedDB.get("privatri", "threads", threadPageEl.getAttribute("data-uuid"))).privateKey;
 | |
| 
 | |
|                     const messageObj = await apx.indexedDB.get("privatri", "messages", `${threadPageEl.getAttribute("data-uuid")}_${messageEl.getAttribute("data-timestamp")}`);
 | |
| 
 | |
|                     messageObj.message = await apx.crypto.encryptMessage(newMessageValue, privateKey),
 | |
| 
 | |
|                     await apx.indexedDB.set("privatri", "messages", messageObj);
 | |
| 
 | |
|                     messageEl.innerHTML = await sendNewMessage(messageObj.timestamp, newMessageValue, attachmentsArray);
 | |
|                 };
 | |
|             });
 | |
|         } else {
 | |
|             bodyEl.insertAdjacentHTML("beforeend", await displayToastAlert("You don't have the permissions to edit this message."));
 | |
| 
 | |
|             setTimeout(() => {
 | |
|                 bodyEl.lastElementChild.remove();
 | |
|             }, 3000);
 | |
|         };
 | |
|     };
 | |
| 
 | |
|     window.attachEventDisplayAttachment = function(img) {
 | |
|         let overlay = document.getElementById("imageOverlay");
 | |
| 
 | |
|         if (overlay === null) {
 | |
|             overlay = document.createElement('div');
 | |
| 
 | |
|             overlay.id = "imageOverlay";
 | |
|             overlay.style.position = "fixed";
 | |
|             overlay.style.top = 0;
 | |
|             overlay.style.left = 0;
 | |
|             overlay.style.width = "100vw";
 | |
|             overlay.style.height = "100vh";
 | |
|             overlay.style.background = "rgba(0, 0, 0, 0.75)";
 | |
|             overlay.style.display = "flex";
 | |
|             overlay.style.alignItems = "center";
 | |
|             overlay.style.justifyContent = "center";
 | |
|             overlay.style.zIndex = 999;
 | |
| 
 | |
|             overlay.onclick = () => {
 | |
|                 overlay.remove();
 | |
|             };
 | |
| 
 | |
|             document.body.appendChild(overlay);
 | |
|         } else {
 | |
|             overlay.innerHTML = "";
 | |
|             overlay.style.display = "flex";
 | |
|         };
 | |
| 
 | |
|         const fullScreenImage = document.createElement("img");
 | |
| 
 | |
|         fullScreenImage.src = img.src;
 | |
|         fullScreenImage.alt = img.alt;
 | |
|         fullScreenImage.style.maxWidth = "90vw";
 | |
|         fullScreenImage.style.maxHeight = "90vh";
 | |
|         overlay.appendChild(fullScreenImage);
 | |
|     };
 | |
| 
 | |
|     document.querySelector("#attachmentsBtn").addEventListener("click", () => {
 | |
|         attachmentsInputEl.click();
 | |
|     });
 | |
| 
 | |
|     attachmentsInputEl.addEventListener("change", async () => {
 | |
|         const filesArray = Array.from(attachmentsInputEl.files);
 | |
|         const maxFileSize = 5 * 1024 * 1024;
 | |
|         const maxSize = 512;
 | |
| 
 | |
|         for (const file of filesArray) {
 | |
|             if (file.size <= maxFileSize) {
 | |
|                 const attachmentObj = await new Promise((resolve, reject) => {
 | |
|                     const attachmentObj = {
 | |
|                         fileType: file.type,
 | |
|                         filename: file.name,
 | |
|                         content: ""
 | |
|                     };
 | |
| 
 | |
|                     const img = new Image();
 | |
|                     
 | |
|                     img.onload = () => {
 | |
|                         if (img.width > maxSize || img.height > maxSize) {
 | |
|                             let width = img.width;
 | |
|                             let height = img.height;
 | |
| 
 | |
|                             if (width > maxSize) {
 | |
|                                 height *= maxSize / width;
 | |
|                                 width = maxSize;
 | |
|                             };
 | |
| 
 | |
|                             if (height > maxSize) {
 | |
|                                 width *= maxSize / height;
 | |
|                                 height = maxSize;
 | |
|                             };
 | |
| 
 | |
|                             const canvas = document.createElement("canvas");
 | |
|                             canvas.width = width;
 | |
|                             canvas.height = height;
 | |
| 
 | |
|                             const ctx = canvas.getContext("2d");
 | |
| 
 | |
|                             ctx.drawImage(img, 0, 0, width, height);
 | |
| 
 | |
|                             canvas.toBlob(blob => {
 | |
|                                 const reader = new FileReader();
 | |
| 
 | |
|                                 reader.onload = (event) => {
 | |
|                                     attachmentObj.content = event.target.result.split(",")[1];
 | |
| 
 | |
|                                     resolve(attachmentObj);
 | |
|                                 };
 | |
| 
 | |
|                                 reader.onerror = reject;
 | |
|                                 reader.readAsDataURL(blob);
 | |
|                             }, "image/jpeg", 0.8);
 | |
|                         } else {
 | |
|                             const reader = new FileReader();
 | |
| 
 | |
|                             reader.onload = (event) => {
 | |
|                                 attachmentObj.content = event.target.result.split(",")[1];
 | |
| 
 | |
|                                 resolve(attachmentObj);
 | |
|                             };
 | |
| 
 | |
|                             reader.onerror = reject;
 | |
|                             reader.readAsDataURL(file);
 | |
|                         };
 | |
|                     };
 | |
| 
 | |
|                     img.onerror = reject;
 | |
|                     img.src = URL.createObjectURL(file);
 | |
| 
 | |
|                     return attachmentObj;
 | |
|                 });
 | |
| 
 | |
|                 if (sessionStorage.getItem(`attachmentsArray_${threadPageEl.getAttribute("data-uuid")}`) === null) {
 | |
|                     sessionStorage.setItem(`attachmentsArray_${threadPageEl.getAttribute("data-uuid")}`, JSON.stringify([attachmentObj]));
 | |
|                 } else {
 | |
|                     const attachmentsArray = JSON.parse(sessionStorage.getItem(`attachmentsArray_${threadPageEl.getAttribute("data-uuid")}`));
 | |
|                     attachmentsArray.push(attachmentObj);
 | |
| 
 | |
|                     sessionStorage.setItem(`attachmentsArray_${threadPageEl.getAttribute("data-uuid")}`, JSON.stringify(attachmentsArray));
 | |
|                 };
 | |
|             };
 | |
|         };
 | |
|     });
 | |
| }); |