column view n live threads
							parent
							
								
									55fdc45073
								
							
						
					
					
						commit
						b2b15231df
					
				
							
								
								
									
										299
									
								
								client/chat.js
								
								
								
								
							
							
						
						
									
										299
									
								
								client/chat.js
								
								
								
								
							|  | @ -1,168 +1,159 @@ | |||
| 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; | ||||
| 	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 = `<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"); | ||||
| 	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 }); | ||||
| } | ||||
| 
 | ||||
| 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, | ||||
|   }); | ||||
| 	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; | ||||
| } | ||||
| 
 | ||||
| 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", | ||||
|     { before: window.earliestMessage, thread: window.currentThreadId } | ||||
|   ); | ||||
| 	const priv = await openpgp.readKey({ armoredKey: keys.privateKey }); | ||||
| 	const pub = await openpgp.readKey({ armoredKey: keys.publicKey }); | ||||
| 	window.keys = { priv, pub }; | ||||
| 	await auth(); | ||||
| } | ||||
| 
 | ||||
| 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; | ||||
| 	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("threads").classList.remove("hidden"); | ||||
| 			document.getElementById("chat").classList.remove("hidden"); | ||||
| 			window.socket.emit("list_threads"); | ||||
| 		} | ||||
| 		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); | ||||
| 		}; | ||||
| 	}); | ||||
| 	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); | ||||
| 		el.appendChild(btn); | ||||
| 		document.getElementById("threadlist").appendChild(el); | ||||
| 	} | ||||
| 	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("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 = ""; | ||||
| 		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"); | ||||
| }; | ||||
|  |  | |||
|  | @ -15,6 +15,15 @@ | |||
| 				background: #020202; | ||||
| 				color: #eaeaea; | ||||
| 			} | ||||
| 			body { | ||||
| 				display: flex; | ||||
| 				align-items: stretch; | ||||
| 				margin: 0; | ||||
| 				min-width: min-content; | ||||
| 			} | ||||
| 			.column { | ||||
| 				flex-grow: 1; | ||||
| 			} | ||||
| 			button { | ||||
| 				border-color: #767676; | ||||
| 			} | ||||
|  | @ -50,8 +59,19 @@ | |||
| 				<button id="submit" type="submit">generate keys & register</button> | ||||
| 			</form> | ||||
| 		</div> | ||||
| 		<div id="chat" class="hidden"> | ||||
| 		<div id="threads" class="column hidden"> | ||||
| 			<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"> | ||||
| 				current thread: <strong id="threadname">meow</strong> | ||||
| 				<button id="change">change thread</button> | ||||
|  | @ -64,17 +84,6 @@ | |||
| 				<button type="submit" class="hidden" id="sendmsg"></button> | ||||
| 			</form> | ||||
| 		</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="/chat.js"></script> | ||||
| 		<script src="/socket.io.min.v4.6.1.js"></script> | ||||
|  |  | |||
							
								
								
									
										2
									
								
								index.js
								
								
								
								
							
							
						
						
									
										2
									
								
								index.js
								
								
								
								
							|  | @ -17,7 +17,7 @@ const actions = require("./src/actions"); | |||
| io.on("connection", (socket) => { | ||||
|   for (let action in actions) { | ||||
|     socket.on(action, (msg) => | ||||
|       actions[action](msg, (response) => socket.emit(action, response), socket) | ||||
|       actions[action](msg, (response) => socket.emit(action, response), socket, io) | ||||
|     ); | ||||
|   } | ||||
| }); | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| const db = require("../db"); | ||||
| 
 | ||||
| const authwrap = (fn) => async (msg, respond, socket) => { | ||||
| const authwrap = (fn) => async (msg, respond, socket, io) => { | ||||
|   if (!msg.__session) { | ||||
|     return respond({ | ||||
|       success: false, | ||||
|  | @ -19,7 +19,7 @@ const authwrap = (fn) => async (msg, respond, socket) => { | |||
|       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; | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| const db = require("../db"); | ||||
| const authwrap = require("./authwrap"); | ||||
| 
 | ||||
| const create_thread = async (msg, respond) => { | ||||
| const create_thread = async (msg, respond, socket, io) => { | ||||
|   // validate inputs
 | ||||
|   if (!msg.name) { | ||||
|     return respond({ | ||||
|  | @ -14,6 +14,10 @@ const create_thread = async (msg, respond) => { | |||
|     "insert into threads (name, creator) values (?, ?) returning id", | ||||
|     [msg.name, msg.auth_user.id] | ||||
|   ); | ||||
|   io.emit('new_thread', { | ||||
|     name: msg.name, | ||||
|     id: insert.rows[0].id | ||||
|   }); | ||||
|   // respond
 | ||||
|   return respond({ | ||||
|     success: true, | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue