diff --git a/wco/itm/privatri.json b/wco/itm/privatri.json new file mode 100644 index 0000000..33d1656 --- /dev/null +++ b/wco/itm/privatri.json @@ -0,0 +1,17 @@ +{ + "wconame": "privatri", + "owner": "philc", + "price": 0, + "aliascode": [], + "codehash": "123", + "thumbnail": "", + "title": "Gestion des accès privés", + "description": "Composant pour gérer les accès et les données privées.", + "lang": ["fr"], + "tpl": { + "privatrimain": "apxtri/objects/wco/privatri/main.mustache" + }, + "tpldatamodel": { "privatri": "apxtri/objects/wco/privatri/exampleprivatri" }, + "schema": [], + "ref": {} +} diff --git a/wco/privatri/exampleprivatri.json b/wco/privatri/exampleprivatri.json new file mode 100644 index 0000000..ac5694d --- /dev/null +++ b/wco/privatri/exampleprivatri.json @@ -0,0 +1,9 @@ +{ + "title": "Titre du Composant Privatri", + "description": "Ceci est la description par défaut du composant, chargée depuis son modèle de données d'exemple.", + "items": [ + "Élément 1 par défaut", + "Élément 2 par défaut", + "Élément 3 par défaut" + ] +} diff --git a/wco/privatri/main.mustache b/wco/privatri/main.mustache new file mode 100644 index 0000000..b4e0423 --- /dev/null +++ b/wco/privatri/main.mustache @@ -0,0 +1,88 @@ +
+
+ + + +
+
+ + + + + +
+
+
+
+ {threadName} +
+ +
+
+
+ +
+ + +
+
\ No newline at end of file diff --git a/wco/privatri/privatri.js b/wco/privatri/privatri.js index f1d28b5..a7baf37 100644 --- a/wco/privatri/privatri.js +++ b/wco/privatri/privatri.js @@ -1,506 +1,25 @@ -import apx from "./apx.js"; -import { getOldestPrivatriids, syncronizeBackend } from "./utils.js"; - -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"); - -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) => { - let messageTemplate = ""; - - await fetch("./message.mustache") - .then(res => res.text()) - .then(template => { - messageTemplate = template; - }); - - messageTemplate = Mustache.render(messageTemplate, { 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", `${attachment.filename}`); - }); - - return messageNode.outerHTML; -}; - -const editMessage = async (timestamp, message) => { - let editMessageTemplate = ""; - - await fetch("./editMessage.mustache") - .then(res => res.text()) - .then(template => { - editMessageTemplate = template; - }); - - return Mustache.render(editMessageTemplate, { message, date: formatDate(timestamp) }); -}; - -const displayToastAlert = async (message) => { - let toastAlertTemplate = ""; - - await fetch("./toastAlert.mustache") - .then(res => res.text()) - .then(template => { - toastAlertTemplate = template; - }); - - return Mustache.render(toastAlertTemplate, { 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("apx")).data.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 = ""; - - // Faire un post sur l'endpoint /privatri - - await apx.indexedDB.set("privatri", "messages", messageObj); - }; -}); - -document.addEventListener("DOMContentLoaded", async () => { - const lastConnection = JSON.parse(localStorage.getItem("lastConnection")) || Date.now(); - - await syncronizeBackend(lastConnection); - - const privatriidArray = await getOldestPrivatriids("privatri", "messages"); - - const thread = async (name, uuid) => { - let threadTemplate = ""; - - await fetch("./thread.mustache") - .then(res => res.text()) - .then(template => { - threadTemplate = template; - }); - - return Mustache.render(threadTemplate, { uuid, name }); - }; - - for (const privatriid of privatriidArray) { - const obj = await apx.indexedDB.get("privatri", "messages", 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)); - }; - }); - }); - - 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")}`); - }; - - await fetch(`./${templateName}.mustache`) - .then(res => res.text()) - .then(template => { - template = Mustache.render(template, {}); - - bodyEl.innerHTML = template; - }); - - const script = document.createElement("script"); - - script.type = "module"; - script.src = `./${templateName}.js`; - - bodyEl.appendChild(script); - }); - }); -}); - -window.addEventListener("beforeunload", () => { - if (apx) - 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("apx")).data.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("apx")).data.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("apx")).data.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)); - }; - }; - }; -}); \ No newline at end of file +var apx = apx || {}; +apx.privatri = {}; +apx.privatri.loadwco = async (id, ctx) => { + // 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 + )}` + ); + //refresh thread from BE + const threadlist = apx.privatri.getthread(apx.data.headers.xalias,1000000) + const tpldataname = `${apx.data.pagename}_${id}_privatri`; + const privatriid = document.getElementById(id); + privatriid.innerHTML = Mustache.render(apx.data.tpl.privatrimain, threadlist); + +} + +apx.privatri.getthread=async(alias,timestamp)=>{ + //store in indexdb the thread + return true +} \ No newline at end of file diff --git a/wco/privatri/alias.mustache b/wco/privatri_mathieu/alias.mustache similarity index 100% rename from wco/privatri/alias.mustache rename to wco/privatri_mathieu/alias.mustache diff --git a/wco/privatri/apx.js b/wco/privatri_mathieu/apx.js similarity index 100% rename from wco/privatri/apx.js rename to wco/privatri_mathieu/apx.js diff --git a/wco/privatri/assets/icon.png b/wco/privatri_mathieu/assets/icon.png similarity index 100% rename from wco/privatri/assets/icon.png rename to wco/privatri_mathieu/assets/icon.png diff --git a/wco/privatri/createThread.js b/wco/privatri_mathieu/createThread.js similarity index 100% rename from wco/privatri/createThread.js rename to wco/privatri_mathieu/createThread.js diff --git a/wco/privatri/createThread.mustache b/wco/privatri_mathieu/createThread.mustache similarity index 100% rename from wco/privatri/createThread.mustache rename to wco/privatri_mathieu/createThread.mustache diff --git a/wco/privatri/editMessage.mustache b/wco/privatri_mathieu/editMessage.mustache similarity index 100% rename from wco/privatri/editMessage.mustache rename to wco/privatri_mathieu/editMessage.mustache diff --git a/wco/privatri/inviteAlias.js b/wco/privatri_mathieu/inviteAlias.js similarity index 100% rename from wco/privatri/inviteAlias.js rename to wco/privatri_mathieu/inviteAlias.js diff --git a/wco/privatri/inviteAlias.mustache b/wco/privatri_mathieu/inviteAlias.mustache similarity index 100% rename from wco/privatri/inviteAlias.mustache rename to wco/privatri_mathieu/inviteAlias.mustache diff --git a/wco/privatri/main_fr.html b/wco/privatri_mathieu/main_fr.html similarity index 100% rename from wco/privatri/main_fr.html rename to wco/privatri_mathieu/main_fr.html diff --git a/wco/privatri/main_fr.mustache b/wco/privatri_mathieu/main_fr.mustache similarity index 100% rename from wco/privatri/main_fr.mustache rename to wco/privatri_mathieu/main_fr.mustache diff --git a/wco/privatri/message.mustache b/wco/privatri_mathieu/message.mustache similarity index 100% rename from wco/privatri/message.mustache rename to wco/privatri_mathieu/message.mustache diff --git a/wco/privatri/output.css b/wco/privatri_mathieu/output.css similarity index 100% rename from wco/privatri/output.css rename to wco/privatri_mathieu/output.css diff --git a/wco/privatri_mathieu/privatri.js b/wco/privatri_mathieu/privatri.js new file mode 100644 index 0000000..f1d28b5 --- /dev/null +++ b/wco/privatri_mathieu/privatri.js @@ -0,0 +1,506 @@ +import apx from "./apx.js"; +import { getOldestPrivatriids, syncronizeBackend } from "./utils.js"; + +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"); + +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) => { + let messageTemplate = ""; + + await fetch("./message.mustache") + .then(res => res.text()) + .then(template => { + messageTemplate = template; + }); + + messageTemplate = Mustache.render(messageTemplate, { 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", `${attachment.filename}`); + }); + + return messageNode.outerHTML; +}; + +const editMessage = async (timestamp, message) => { + let editMessageTemplate = ""; + + await fetch("./editMessage.mustache") + .then(res => res.text()) + .then(template => { + editMessageTemplate = template; + }); + + return Mustache.render(editMessageTemplate, { message, date: formatDate(timestamp) }); +}; + +const displayToastAlert = async (message) => { + let toastAlertTemplate = ""; + + await fetch("./toastAlert.mustache") + .then(res => res.text()) + .then(template => { + toastAlertTemplate = template; + }); + + return Mustache.render(toastAlertTemplate, { 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("apx")).data.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 = ""; + + // Faire un post sur l'endpoint /privatri + + await apx.indexedDB.set("privatri", "messages", messageObj); + }; +}); + +document.addEventListener("DOMContentLoaded", async () => { + const lastConnection = JSON.parse(localStorage.getItem("lastConnection")) || Date.now(); + + await syncronizeBackend(lastConnection); + + const privatriidArray = await getOldestPrivatriids("privatri", "messages"); + + const thread = async (name, uuid) => { + let threadTemplate = ""; + + await fetch("./thread.mustache") + .then(res => res.text()) + .then(template => { + threadTemplate = template; + }); + + return Mustache.render(threadTemplate, { uuid, name }); + }; + + for (const privatriid of privatriidArray) { + const obj = await apx.indexedDB.get("privatri", "messages", 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)); + }; + }); + }); + + 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")}`); + }; + + await fetch(`./${templateName}.mustache`) + .then(res => res.text()) + .then(template => { + template = Mustache.render(template, {}); + + bodyEl.innerHTML = template; + }); + + const script = document.createElement("script"); + + script.type = "module"; + script.src = `./${templateName}.js`; + + bodyEl.appendChild(script); + }); + }); +}); + +window.addEventListener("beforeunload", () => { + if (apx) + 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("apx")).data.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("apx")).data.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("apx")).data.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)); + }; + }; + }; +}); \ No newline at end of file diff --git a/wco/privatri/thread.mustache b/wco/privatri_mathieu/thread.mustache similarity index 100% rename from wco/privatri/thread.mustache rename to wco/privatri_mathieu/thread.mustache diff --git a/wco/privatri/threadAliasList.js b/wco/privatri_mathieu/threadAliasList.js similarity index 100% rename from wco/privatri/threadAliasList.js rename to wco/privatri_mathieu/threadAliasList.js diff --git a/wco/privatri/threadAliasList.mustache b/wco/privatri_mathieu/threadAliasList.mustache similarity index 100% rename from wco/privatri/threadAliasList.mustache rename to wco/privatri_mathieu/threadAliasList.mustache diff --git a/wco/privatri/threadSettings.js b/wco/privatri_mathieu/threadSettings.js similarity index 100% rename from wco/privatri/threadSettings.js rename to wco/privatri_mathieu/threadSettings.js diff --git a/wco/privatri/threadSettings.mustache b/wco/privatri_mathieu/threadSettings.mustache similarity index 100% rename from wco/privatri/threadSettings.mustache rename to wco/privatri_mathieu/threadSettings.mustache diff --git a/wco/privatri/toastAlert.mustache b/wco/privatri_mathieu/toastAlert.mustache similarity index 100% rename from wco/privatri/toastAlert.mustache rename to wco/privatri_mathieu/toastAlert.mustache diff --git a/wco/privatri/utils.js b/wco/privatri_mathieu/utils.js similarity index 100% rename from wco/privatri/utils.js rename to wco/privatri_mathieu/utils.js diff --git a/wwws/admin/src/privatri_fr.html b/wwws/admin/src/privatri_fr.html new file mode 100644 index 0000000..9b2ebad --- /dev/null +++ b/wwws/admin/src/privatri_fr.html @@ -0,0 +1,57 @@ + + + + + Accès Privé + + + + + + + + + + + + + + + + + +
+
+ + \ No newline at end of file diff --git a/wwws/admin/src/tpldata/privatri_main_privatri.json b/wwws/admin/src/tpldata/privatri_main_privatri.json new file mode 100644 index 0000000..1c240d9 --- /dev/null +++ b/wwws/admin/src/tpldata/privatri_main_privatri.json @@ -0,0 +1,30 @@ +{ + "menuapp": [ + { + "onclick": "onclick='apx.privatri.notificationsMenu();'", + "title": "Notification", + "comment": "./notificationsMenu.html" + }, + { + "onclick": "onclick='apx.privatri.addthread();'", + "title": "Nouveau fil", + "comment": "./newMenu3.html" + }, + { + "onclick": "href='apxid_fr.html'", + "title": "Compte", + "comment": "" + } + ], + "searchplaceholder": "Recherche parmis les fils", + "sortoptions": [ + { + "title": "Tri par date", + "onclick": "onclick='apx.privatri.sortby('bydate');'" + }, + { + "title": "Tri par NOM DE FIL", + "onclick": "onclick='apx.privatri.sortby('bytitlethread');'" + } + ] +} \ No newline at end of file diff --git a/wwws/itm/admin.json b/wwws/itm/admin.json index e39c4f0..47e3c5b 100644 --- a/wwws/itm/admin.json +++ b/wwws/itm/admin.json @@ -73,6 +73,22 @@ "Pagans": "apxtri/models//tplstrings/Pagans", "Persons": "apxtri/models/tplstrings/Persons" } + }, + "privatri": { + "version": 1, + "profils": [ + "anonymous" + ], + "tpl": { + "privatrimain": "apxtri/objects/wco/privatri/main.mustache" + }, + "tpldata": { + "privatri_main_privatri": "apxtri/objects/wwws/admin/src/tpldata/privatri_main_privatri" + }, + "schema": [], + "ref": {}, + "wco": {}, + "appdata": {} } } }