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 register(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 }); } async function message(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 = ""; const el = document.createElement("div"); el.classList.add("message"); el.innerHTML = `${window.name}: ${msg}`; document.getElementById("messages").appendChild(el); } function startThreadPicker() { document.getElementById("chat").classList.add("hidden"); document.getElementById("threads").classList.remove("hidden"); window.socket.emit("list_threads"); } function chooseThread(thread) { window.currentThreadId = thread.id; window.earliestMessage = null; document.getElementById("loadmore").classList.remove("hidden"); document.getElementById("chat").classList.remove("hidden"); document.getElementById("messages").innerHTML = ""; document.getElementById("threadname").innerHTML = thread.name; document.getElementById("threads").classList.add("hidden"); } function createThread(e) { e.preventDefault(); window.socket.emit("create_thread", { name: document.getElementById("newthreadname").value, }); } 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(); } async function loadMessages() { window.socket.emit( "get_history", window.earliestMessage ? { before: window.earliestMessage, thread: window.currentThreadId } : { thread: window.currentThreadId } ); } 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("send_message", (msg) => { 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"); }); window.socket.on("authenticate", (msg) => { if (msg.success) { document.getElementById("register").classList.add("hidden"); document.getElementById("chat").classList.remove("hidden"); } 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.on("list_threads", (msg) => { document.getElementById("threadlist").innerHTML = ""; for (let thread of msg.threads) { 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); el.appendChild(btn); document.getElementById("threadlist").appendChild(el); } }); window.socket.on("create_thread", (msg) => { chooseThread({ name: document.getElementById("newthreadname").value, id: msg.id, }); document.getElementById("newthreadname").value = ""; }); const keys = localStorage.getItem("keys"); if (keys) { window.name = localStorage.getItem("name"); loadKeys(JSON.parse(keys)).then(() => {}); } else document.getElementById("register").classList.remove("hidden"); document.getElementById("registerform").onsubmit = register; document.getElementById("msginput").onsubmit = message; document.getElementById("loadmore").onclick = loadMessages; document.getElementById("change").onclick = startThreadPicker; document.getElementById("createthread").onsubmit = createThread; };