column view n live threads

main
jerl 2023-05-15 17:19:24 -07:00
parent 55fdc45073
commit b2b15231df
5 changed files with 174 additions and 170 deletions

View File

@ -1,168 +1,159 @@
function rand() { function rand() {
let str = ""; let str = "";
const lookups = const lookups =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".split(""); "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".split("");
while (str.length < 16) { while (str.length < 16) {
const n = Math.random() * lookups.length; const n = Math.random() * lookups.length;
str += lookups[Math.floor(n)]; str += lookups[Math.floor(n)];
} }
return str; return str;
} }
async function auth() { async function auth() {
let session = rand(); let session = rand();
const sig = await openpgp.sign({ const sig = await openpgp.sign({
message: new openpgp.CleartextMessage("vybe_auth " + session, ""), message: new openpgp.CleartextMessage("vybe_auth " + session, ""),
signingKeys: window.keys.priv, signingKeys: window.keys.priv,
}); });
window.session = session; window.session = session;
window.socket.emit("authenticate", { name: window.name, message: sig }); 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 = `<strong>${window.name}: </strong>${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) { function chooseThread(thread) {
window.currentThreadId = thread.id; window.currentThreadId = thread.id;
window.earliestMessage = null; window.earliestMessage = null;
document.getElementById("loadmore").classList.remove("hidden"); document.getElementById("loadmore").classList.remove("hidden");
document.getElementById("chat").classList.remove("hidden"); document.getElementById("chat").classList.remove("hidden");
document.getElementById("messages").innerHTML = ""; document.getElementById("messages").innerHTML = "";
document.getElementById("threadname").innerHTML = thread.name; 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) { async function loadKeys(keys) {
const priv = await openpgp.readKey({ armoredKey: keys.privateKey }); const priv = await openpgp.readKey({ armoredKey: keys.privateKey });
const pub = await openpgp.readKey({ armoredKey: keys.publicKey }); const pub = await openpgp.readKey({ armoredKey: keys.publicKey });
window.keys = { priv, pub }; window.keys = { priv, pub };
await auth(); await auth();
}
async function loadMessages() {
window.socket.emit(
"get_history",
{ before: window.earliestMessage, thread: window.currentThreadId }
);
} }
window.onload = () => { window.onload = () => {
window.currentThreadId = 1; window.currentThreadId = 1;
window.socket = io(); window.socket = io();
window.socket.on("create_user", auth); window.socket.on("create_user", auth);
window.socket.on("new_message", (msg) => { window.socket.on("new_message", (msg) => {
if (msg.thread !== window.currentThreadId) return; if (msg.thread !== window.currentThreadId) return;
const el = document.createElement("div"); const el = document.createElement("div");
el.classList.add("message"); el.classList.add("message");
const strong = document.createElement('strong'); const strong = document.createElement('strong');
strong.textContent = msg.name + ': '; strong.textContent = msg.name + ': ';
el.append(strong, msg.message); el.append(strong, msg.message);
document.getElementById("messages").appendChild(el); document.getElementById("messages").appendChild(el);
if (!window.earliestMessage) window.earliestMessage = msg.id; if (!window.earliestMessage) window.earliestMessage = msg.id;
}); });
window.socket.on("send_message", (msg) => { window.socket.on("send_message", (msg) => {
if (!window.earliestMessage) window.earliestMessage = msg.id; if (!window.earliestMessage) window.earliestMessage = msg.id;
}); });
window.socket.on("get_history", (msg) => { window.socket.on("get_history", (msg) => {
if (msg.messages.length > 0) { if (msg.messages.length > 0) {
window.earliestMessage = msg.messages[msg.messages.length - 1].id; window.earliestMessage = msg.messages[msg.messages.length - 1].id;
for (let message of msg.messages) { for (let message of msg.messages) {
const el = document.createElement("div"); const el = document.createElement("div");
el.classList.add("message"); el.classList.add("message");
const strong = document.createElement('strong'); const strong = document.createElement('strong');
strong.textContent = message.name + ': '; strong.textContent = message.name + ': ';
el.append(strong, message.message); el.append(strong, message.message);
document.getElementById("messages").prepend(el); document.getElementById("messages").prepend(el);
} }
} }
if (!msg.more) document.getElementById("loadmore").classList.add("hidden"); if (!msg.more) document.getElementById("loadmore").classList.add("hidden");
}); });
window.socket.on("authenticate", (msg) => { window.socket.on("authenticate", (msg) => {
if (msg.success) { if (msg.success) {
document.getElementById("register").classList.add("hidden"); document.getElementById("register").classList.add("hidden");
document.getElementById("chat").classList.remove("hidden"); document.getElementById("threads").classList.remove("hidden");
} document.getElementById("chat").classList.remove("hidden");
let emitter = window.socket.emit; window.socket.emit("list_threads");
window.socket.emit = (type, data) => { }
if (data) let emitter = window.socket.emit;
return emitter.call(window.socket, type, { window.socket.emit = (type, data) => {
...data, if (data)
__session: window.session, return emitter.call(window.socket, type, {
}); ...data,
else return emitter.call(window.socket, type); __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) { function addThread(thread) {
const el = document.createElement("div"); const el = document.createElement("div");
el.classList.add("thread"); el.classList.add("thread");
el.innerHTML = thread.name; el.innerHTML = thread.name;
const btn = document.createElement("button"); const btn = document.createElement("button");
btn.innerHTML = "choose"; btn.innerHTML = "choose";
btn.onclick = () => chooseThread(thread); btn.onclick = () => chooseThread(thread);
el.appendChild(btn); el.appendChild(btn);
document.getElementById("threadlist").appendChild(el); document.getElementById("threadlist").appendChild(el);
} }
}); window.socket.on("list_threads", (msg) => {
window.socket.on("create_thread", (msg) => { document.getElementById("threadlist").innerHTML = "";
chooseThread({ for (let thread of msg.threads)
name: document.getElementById("newthreadname").value, addThread(thread);
id: msg.id, });
}); window.socket.on('new_thread', addThread);
document.getElementById("newthreadname").value = ""; window.socket.on("create_thread", (msg) => {
}); chooseThread({
const keys = localStorage.getItem("keys"); name: document.getElementById("newthreadname").value,
if (keys) { id: msg.id,
window.name = localStorage.getItem("name"); });
loadKeys(JSON.parse(keys)).then(() => {}); document.getElementById("newthreadname").value = "";
} });
else
document.getElementById("register").classList.remove("hidden"); document.getElementById("registerform").onsubmit = async e => {
document.getElementById("registerform").onsubmit = register; e.preventDefault();
document.getElementById("msginput").onsubmit = message; const name = document.getElementById("name").value;
document.getElementById("loadmore").onclick = loadMessages; if (!name) return;
document.getElementById("change").onclick = startThreadPicker; const keys = await openpgp.generateKey({
document.getElementById("createthread").onsubmit = createThread; 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 = "";
const el = document.createElement("div");
el.classList.add("message");
el.innerHTML = `<strong>${window.name}: </strong>${msg}`;
document.getElementById("messages").appendChild(el);
};
document.getElementById("loadmore").onclick = e => {
window.socket.emit(
"get_history",
{ before: window.earliestMessage, thread: window.currentThreadId }
);
};
document.getElementById("createthread").onsubmit = e => {
e.preventDefault();
window.socket.emit("create_thread", {
name: 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");
}; };

View File

@ -15,6 +15,15 @@
background: #020202; background: #020202;
color: #eaeaea; color: #eaeaea;
} }
body {
display: flex;
align-items: stretch;
margin: 0;
min-width: min-content;
}
.column {
flex-grow: 1;
}
button { button {
border-color: #767676; border-color: #767676;
} }
@ -50,8 +59,19 @@
<button id="submit" type="submit">generate keys & register</button> <button id="submit" type="submit">generate keys & register</button>
</form> </form>
</div> </div>
<div id="chat" class="hidden"> <div id="threads" class="column hidden">
<h1>vybe</h1> <h1>vybe</h1>
<h1>threads</h1>
<h3>create thread</h3>
<form id="createthread">
<label for="newthreadname">thread name</label>
<input type="text" id="newthreadname" />
<button id="submitthread" type="submit">create</button>
</form>
<h3>choose existing thread</h3>
<div id="threadlist">loading...</div>
</div>
<div id="chat" class="column hidden">
<h3 class="thread"> <h3 class="thread">
current thread: <strong id="threadname">meow</strong> current thread: <strong id="threadname">meow</strong>
<button id="change">change thread</button> <button id="change">change thread</button>
@ -64,17 +84,6 @@
<button type="submit" class="hidden" id="sendmsg"></button> <button type="submit" class="hidden" id="sendmsg"></button>
</form> </form>
</div> </div>
<div id="threads" class="hidden">
<h1>threads</h1>
<h3>create thread</h3>
<form id="createthread">
<label for="newthreadname">thread name</label>
<input type="text" id="newthreadname" />
<button id="submitthread" type="submit">create</button>
</form>
<h3>choose existing thread</h3>
<div id="threadlist">loading...</div>
</div>
<script src="/openpgp.min.js"></script> <script src="/openpgp.min.js"></script>
<script src="/chat.js"></script> <script src="/chat.js"></script>
<script src="/socket.io.min.v4.6.1.js"></script> <script src="/socket.io.min.v4.6.1.js"></script>

View File

@ -17,7 +17,7 @@ const actions = require("./src/actions");
io.on("connection", (socket) => { io.on("connection", (socket) => {
for (let action in actions) { for (let action in actions) {
socket.on(action, (msg) => socket.on(action, (msg) =>
actions[action](msg, (response) => socket.emit(action, response), socket) actions[action](msg, (response) => socket.emit(action, response), socket, io)
); );
} }
}); });

View File

@ -1,6 +1,6 @@
const db = require("../db"); const db = require("../db");
const authwrap = (fn) => async (msg, respond, socket) => { const authwrap = (fn) => async (msg, respond, socket, io) => {
if (!msg.__session) { if (!msg.__session) {
return respond({ return respond({
success: false, success: false,
@ -19,7 +19,7 @@ const authwrap = (fn) => async (msg, respond, socket) => {
message: "User not found", message: "User not found",
}); });
} }
return await fn({ ...msg, auth_user: result.rows[0] }, respond, socket); return await fn({ ...msg, auth_user: result.rows[0] }, respond, socket, io);
}; };
module.exports = authwrap; module.exports = authwrap;

View File

@ -1,7 +1,7 @@
const db = require("../db"); const db = require("../db");
const authwrap = require("./authwrap"); const authwrap = require("./authwrap");
const create_thread = async (msg, respond) => { const create_thread = async (msg, respond, socket, io) => {
// validate inputs // validate inputs
if (!msg.name) { if (!msg.name) {
return respond({ return respond({
@ -14,6 +14,10 @@ const create_thread = async (msg, respond) => {
"insert into threads (name, creator) values (?, ?) returning id", "insert into threads (name, creator) values (?, ?) returning id",
[msg.name, msg.auth_user.id] [msg.name, msg.auth_user.id]
); );
io.emit('new_thread', {
name: msg.name,
id: insert.rows[0].id
});
// respond // respond
return respond({ return respond({
success: true, success: true,