function rand() { let str = ""; const lookups = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".split(""); while (str.length < 16) { const n = Math.random() * lookups.length; str += lookups[Math.floor(n)]; } return str; } async function auth() { let session = rand(); const sig = await openpgp.sign({ message: new openpgp.CleartextMessage("vybe_auth " + session, ""), signingKeys: window.keys.priv, }); window.session = session; window.socket.emit("authenticate", { name: window.name, message: sig }); } async function loadKeys(keys) { const priv = await openpgp.readKey({ armoredKey: keys.privateKey }); const pub = await openpgp.readKey({ armoredKey: keys.publicKey }); window.keys = { priv, pub }; await auth(); } function chooseThread(thread) { window.currentThreadId = thread.id; window.earliestMessage = null; document.getElementById("messages").innerHTML = ""; document.getElementById("threadname").innerHTML = thread.name; } function loadMessages() { window.socket.emit("get_history", { before: window.earliestMessage, thread: window.currentThreadId, }); } function addThread(thread) { const el = document.createElement("div"); el.classList.add("thread"); el.innerHTML = thread.name; const btn = document.createElement("button"); btn.innerHTML = "choose"; btn.onclick = () => { chooseThread(thread); loadMessages(); if (!thread.permissions.post) { document.getElementById("msginput").classList.add("hidden"); } else { document.getElementById("msginput").classList.remove("hidden"); } }; el.appendChild(btn); document.getElementById("threadlist").appendChild(el); } function addMember() { const name = document.getElementById("membername").value; if (!window.threadmembers) { window.threadmembers = [window.name, name]; } else { window.threadmembers.push(name); } const member = document.createElement("p"); member.textContent = name; member.classList.add("member"); document.getElementById("memberlist").appendChild(member); document.getElementById("membername").value = ""; } async function createThread(members) { const perms = document.querySelector( 'input[name="permissions"]:checked' ).value; let permissions; let newmembers = members; if (perms === "public") { permissions = { view_limited: false, post_limited: false, }; } else if (perms === "private_post") { permissions = { view_limited: false, post_limited: true, }; } else if (perms === "private_view") { permissions = { view_limited: true, post_limited: true, }; // generate key /* wip var buf = new Uint8Array(32); crypto.getRandomValues(buf); const key = aesjs.utils.hex.fromBytes(Array.from(buf)); // sign it to each of the members for (let i = 0; i < newmembers.length; i++) { const member = newmembers[i]; const sig = await openpgp.encrypt({ message: await openpgp.createMessage({ text: key }), signingKeys: window.keys.priv, }); } */ } window.socket.emit("create_thread", { name: document.getElementById("newthreadname").value, permissions, newmembers, }); document.getElementById(perms).checked = false; window.threadmembers = null; document.getElementById("memberlist").innerHTML = ""; const member = document.createElement("p"); member.textContent = window.name; member.classList.add("member"); document.getElementById("memberlist").appendChild(member); } function decryptMessage(thread, message) {} function encryptMessage(thread, message) {} window.onload = () => { window.currentThreadId = 1; window.socket = io(); window.socket.on("create_user", auth); window.socket.on("new_message", (msg) => { if (msg.thread !== window.currentThreadId) return; const el = document.createElement("div"); el.classList.add("message"); const strong = document.createElement("strong"); strong.textContent = msg.name + ": "; el.append(strong, msg.message); document.getElementById("messages").appendChild(el); if (!window.earliestMessage) window.earliestMessage = msg.id; }); window.socket.on("get_history", (msg) => { if (msg.messages.length > 0) { window.earliestMessage = msg.messages[msg.messages.length - 1].id; for (let message of msg.messages) { const el = document.createElement("div"); el.classList.add("message"); const strong = document.createElement("strong"); strong.textContent = message.name + ": "; el.append(strong, message.message); document.getElementById("messages").prepend(el); } } if (!msg.more) document.getElementById("loadmore").classList.add("hidden"); else document.getElementById("loadmore").classList.remove("hidden"); }); window.socket.on("authenticate", (msg) => { if (msg.success) { document.getElementById("register").classList.add("hidden"); document.getElementById("threads").classList.remove("hidden"); document.getElementById("chat").classList.remove("hidden"); const member = document.createElement("p"); member.textContent = window.name; member.classList.add("member"); document.getElementById("memberlist").appendChild(member); let emitter = window.socket.emit; window.socket.emit = (type, data) => { if (data) return emitter.call(window.socket, type, { ...data, __session: window.session, }); else return emitter.call(window.socket, type); }; window.socket.emit("list_threads", {}); loadMessages(); } else { document.getElementById("register").classList.remove("hidden"); } }); window.socket.on("list_threads", (msg) => { document.getElementById("threadlist").innerHTML = ""; for (let thread of msg.threads) addThread(thread); }); window.socket.on("new_thread", addThread); window.socket.on("create_thread", (msg) => { chooseThread({ name: document.getElementById("newthreadname").value, id: msg.id, }); document.getElementById("newthreadname").value = ""; document.getElementById("loadmore").classList.add("hidden"); document.getElementById("msginput").classList.remove("hidden"); }); window.socket.on("get_keys", (msg) => { createThread(msg.keys); }); document.getElementById("registerform").onsubmit = async (e) => { e.preventDefault(); const name = document.getElementById("name").value; if (!name) return; const keys = await openpgp.generateKey({ userIDs: [{ name }], }); const priv = await openpgp.readKey({ armoredKey: keys.privateKey }); const pub = await openpgp.readKey({ armoredKey: keys.publicKey }); window.keys = { priv, pub }; localStorage.setItem("keys", JSON.stringify(keys)); localStorage.setItem("name", name); window.name = name; window.socket.emit("create_user", { name, pubkey: keys.publicKey }); }; document.getElementById("msginput").onsubmit = (e) => { e.preventDefault(); const msg = document.getElementById("msg").value; if (!msg) return; window.socket.emit("send_message", { message: msg, thread: window.currentThreadId, }); document.getElementById("msg").value = ""; }; document.getElementById("loadmore").onclick = (e) => { loadMessages(); }; document.getElementById("createthread").onsubmit = (e) => { e.preventDefault(); const members = window.threadmembers ? window.threadmembers.map((i) => ({ name: i })) : [{ name: window.name }]; const perms = document.querySelector( 'input[name="permissions"]:checked' ).value; console.log(perms, members); if (perms === "private_view") { window.socket.emit("get_keys", { names: members.map((i) => i.name) }); } else { createThread(members); } }; document.getElementById("membername").onkeydown = (e) => { if (e.key == "Enter") { addMember(); } }; document.getElementById("addmember").onclick = addMember; const keys = localStorage.getItem("keys"); if (keys) { window.name = localStorage.getItem("name"); loadKeys(JSON.parse(keys)).then(() => {}); } else document.getElementById("register").classList.remove("hidden"); };