code reorganization and meows
							parent
							
								
									e594746b4c
								
							
						
					
					
						commit
						9e4e2ba299
					
				|  | @ -0,0 +1,212 @@ | |||
| import { render, html } from '/uhtml.js'; | ||||
| 
 | ||||
| window.currentThreadId = 1; | ||||
| 
 | ||||
| function chooseThread(thread) { | ||||
| 	if (window.currentThreadId) | ||||
| 		document.getElementById(`thread${window.currentThreadId}`).classList.remove('selected'); | ||||
| 	document.getElementById(`thread${thread.id}`).classList.add('selected'); | ||||
| 	window.currentThreadId = thread.id; | ||||
| 	window.earliestMessage = null; | ||||
| 	document.getElementById("messages").innerHTML = ""; | ||||
| 	document.getElementById("threadname").innerHTML = thread.name; | ||||
| 	loadMessages(); | ||||
| } | ||||
| 
 | ||||
| function loadMessages() { | ||||
| 	window.emit("get_history", { | ||||
| 		before: window.earliestMessage, | ||||
| 		thread: window.currentThreadId, | ||||
| 	}, msg => { | ||||
| 		if (msg.messages.length > 0) { | ||||
| 			window.earliestMessage = msg.messages[msg.messages.length - 1].id; | ||||
| 			for (let message of msg.messages) | ||||
| 				document.getElementById("messages").prepend(html.node` | ||||
| 					<div class='message'> | ||||
| 						<strong>${message.name}: </strong> | ||||
| 						${message.message} | ||||
| 					</div>`); | ||||
| 		} | ||||
| 		if (!msg.more) | ||||
| 			document.getElementById("loadmore").classList.add("hidden"); | ||||
| 		else | ||||
| 			document.getElementById("loadmore").classList.remove("hidden"); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| function addThread(thread) { | ||||
| 	let node = html.node` | ||||
| 		<div class='thread' onclick=${() => { | ||||
| 			chooseThread(thread); | ||||
| 			if (!thread.permissions.post) { | ||||
| 				document.getElementById("msginput").classList.add("hidden"); | ||||
| 			} else { | ||||
| 				document.getElementById("msginput").classList.remove("hidden"); | ||||
| 			} | ||||
| 		}}>${thread.name}</div>`; | ||||
| 	node.id = `thread${thread.id}`; | ||||
| 	document.getElementById("threadlist").appendChild(node); | ||||
| } | ||||
| 
 | ||||
| function addMember() { | ||||
| 	const name = document.getElementById("membername").value; | ||||
| 	window.threadmembers.push(name); | ||||
| 	document.getElementById("memberlist").appendChild(html.node` | ||||
| 		<p class='member'>${name}</p> | ||||
| 	`);
 | ||||
| 	document.getElementById("membername").value = ""; | ||||
| } | ||||
| 
 | ||||
| async function createThread(e) { | ||||
| 	e.preventDefault(); | ||||
| 	let members = window.threadmembers.map(name => { name }); | ||||
| 	const perms = document.querySelector( | ||||
| 		'input[name="permissions"]:checked' | ||||
| 	).value; | ||||
| 	if (perms === "private_view") | ||||
| 		members = (await new Promise(resolve => | ||||
| 			window.emit("get_keys", { names: window.threadmembers }, resolve) | ||||
| 			)).keys; | ||||
| 	let permissions; | ||||
| 	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.emit("create_thread", { | ||||
| 		name: document.getElementById("newthreadname").value, | ||||
| 		permissions, | ||||
| 		members | ||||
| 	}, msg => { | ||||
| 		chooseThread({ | ||||
| 			name: document.getElementById("newthreadname").value, | ||||
| 			id: msg.id | ||||
| 		}); | ||||
| 		document.getElementById('createthread').remove(); | ||||
| 		document.getElementById("loadmore").classList.add("hidden"); | ||||
| 		document.getElementById("msginput").classList.remove("hidden"); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| function sendMessage(e) { | ||||
| 	e.preventDefault(); | ||||
| 	const msg = document.getElementById("msg").value; | ||||
| 	if (!msg) | ||||
| 		return; | ||||
| 	window.emit("send_message", { | ||||
| 		message: msg, | ||||
| 		thread: window.currentThreadId | ||||
| 	}); | ||||
| 	document.getElementById("msg").value = ""; | ||||
| } | ||||
| 
 | ||||
| function newThread(e) { | ||||
| 	let form = document.getElementById('createthread'); | ||||
| 	if (form) { | ||||
| 		form.remove(); | ||||
| 		e.target.textContent = 'create'; | ||||
| 	} | ||||
| 	else { | ||||
| 		window.threadmembers = [ window.name ]; | ||||
| 		document.getElementById('threads').insertAdjacentElement('afterend', html.node` | ||||
| 			<form id="createthread" class='column' onsubmit=${createThread}> | ||||
| 				<h3>create thread</h3> | ||||
| 				<label for="newthreadname">thread name</label> | ||||
| 				<input type="text" id="newthreadname" /> | ||||
| 				<p id='permissions'>thread permissions</p> | ||||
| 				<input type="radio" id="public" name="permissions" value="public" /> | ||||
| 				<label for="public">anyone can view and post</label><br /> | ||||
| 				<input type="radio" id="private_post" | ||||
| 					name="permissions" value="private_post" | ||||
| 				/> | ||||
| 				<label for="private_post">only members can post, anyone can view</label><br/> | ||||
| 				<input type="radio" id="private_view" | ||||
| 					name="permissions" value="private_view" | ||||
| 				/> | ||||
| 				<label for="private_view">only members can view and post</label | ||||
| 				><br /><br /> | ||||
| 				<span>members</span><br /> | ||||
| 				<input type="text" id="membername" placeholder="username" onkeydown=${(e) => { | ||||
| 					if (e.key == "Enter") { | ||||
| 						e.preventDefault(); | ||||
| 						addMember(); | ||||
| 					} | ||||
| 				}}/> | ||||
| 				<button id="addmember" onclick=${addMember}>add</button> | ||||
| 				<div id="memberlist"> | ||||
| 					<p class='member'>${window.name}</p> | ||||
| 				</div> | ||||
| 				<br /> | ||||
| 				<input id="submitthread" type="submit" value="create" /> | ||||
| 			</form> | ||||
| 		`);
 | ||||
| 		e.target.textContent = 'cancel'; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| render(document.body, html` | ||||
| 	<div id="app"> | ||||
| 		<div id="threads" class="column"> | ||||
| 			<h1>vybe</h1> | ||||
| 			<h3>threads</h3> | ||||
| 			<div id="threadlist">loading...</div> | ||||
| 			<button id='newthread' onclick=${newThread}>create</button> | ||||
| 		</div> | ||||
| 		<div id="chat" class="column"> | ||||
| 			<h3> | ||||
| 				thread: <strong id="threadname">meow</strong> | ||||
| 			</h3> | ||||
| 			<h3>messages will appear below as they are sent</h3> | ||||
| 			<button id="loadmore" class="hidden" onclick=${loadMessages}>load more messages</button> | ||||
| 			<div id="messages"></div> | ||||
| 			<form id="msginput" onsubmit=${sendMessage}> | ||||
| 				<input type="text" placeholder="write a message..." id="msg" /> | ||||
| 				<button type="submit" class="hidden" id="sendmsg"></button> | ||||
| 			</form> | ||||
| 		</div> | ||||
| 	</div>`); | ||||
| 
 | ||||
| window.socket.on("new_message", (msg) => { | ||||
| 	if (msg.thread !== window.currentThreadId) | ||||
| 		return; | ||||
| 	document.getElementById("messages").appendChild(html.node` | ||||
| 		<div class='message'> | ||||
| 			<strong>${msg.name}: </strong> | ||||
| 			${msg.message} | ||||
| 		</div>`); | ||||
| 	if (!window.earliestMessage) | ||||
| 		window.earliestMessage = msg.id; | ||||
| }); | ||||
| window.socket.on("new_thread", addThread); | ||||
| 
 | ||||
| window.emit("list_threads", {}, msg => { | ||||
| 	document.getElementById("threadlist").innerHTML = ""; | ||||
| 	for (let thread of msg.threads) | ||||
| 		addThread(thread); | ||||
| 	chooseThread(msg.threads[0]); | ||||
| }); | ||||
|  | @ -0,0 +1,89 @@ | |||
| import { render, html } from '/uhtml.js'; | ||||
| 
 | ||||
| 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() { | ||||
| 	const sig = await openpgp.sign({ | ||||
| 		message: new openpgp.CleartextMessage("vybe_auth " + window.session, ""), | ||||
| 		signingKeys: window.keys.priv, | ||||
| 	}); | ||||
| 	window.socket.emit("authenticate", { name: window.name, message: sig }, | ||||
| 		msg => { | ||||
| 			if (!msg.success) { | ||||
| 				console.log('authenticate failed'); | ||||
| 				return; | ||||
| 			} | ||||
| 			document.getElementById("register").remove(); | ||||
| 			import('/app.js'); | ||||
| 		}); | ||||
| } | ||||
| 
 | ||||
| render(document.body, html` | ||||
| 	<div id="register" class='hidden'> | ||||
| 		<h1>welcome to vybe</h1> | ||||
| 		<h3>a communication network (beta)</h3> | ||||
| 		<p> | ||||
| 			to get started, you'll need an account. we use public key cryptography | ||||
| 			for security, rather than passwords. your keys are stored in your | ||||
| 			browser storage only, so do this on a browser you can access again. | ||||
| 		</p> | ||||
| 		<form 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.emit("create_user", { name, pubkey: keys.publicKey }, msg => { | ||||
| 				if (!msg.success) { | ||||
| 					console.log('create user failed'); | ||||
| 					return; | ||||
| 				} | ||||
| 				auth(); | ||||
| 			}); | ||||
| 		}} id="registerform"> | ||||
| 			<label for="name">username: </label> | ||||
| 			<input id="name" type="text" /> | ||||
| 			<input id="submit" type="submit" value='generate keys & register'> | ||||
| 		</form> | ||||
| 	</div> | ||||
| `);
 | ||||
| 
 | ||||
| window.onload = async () => { | ||||
| 	window.socket = io(); | ||||
| 
 | ||||
| 	window.session = rand(); | ||||
| 	window.emit = (type, data, callback) => window.socket.emit(type, { | ||||
| 		...data, | ||||
| 		__session: window.session, | ||||
| 	}, callback); | ||||
| 
 | ||||
| 	let keys = localStorage.getItem("keys"); | ||||
| 	if (keys) { | ||||
| 		window.name = localStorage.getItem("name"); | ||||
| 		keys = JSON.parse(keys); | ||||
| 		window.keys = { | ||||
| 			priv: await openpgp.readKey({ armoredKey: keys.privateKey }), | ||||
| 			pub: await openpgp.readKey({ armoredKey: keys.publicKey }) | ||||
| 		}; | ||||
| 		await auth(); | ||||
| 	} | ||||
| 	else | ||||
| 		document.getElementById('register').classList.remove('hidden'); | ||||
| }; | ||||
|  | @ -4,7 +4,7 @@ | |||
| 		<script src="/openpgp.min.js"></script> | ||||
| 		<script src="/socket.io.min.v4.6.1.js"></script> | ||||
| 		<script src="/aes.js"></script> | ||||
| 		<script type="module" src="/ui.js"></script> | ||||
| 		<script type="module" src="/auth.js"></script> | ||||
| 		<meta charset="UTF-8" /> | ||||
| 		<meta http-equiv="X-UA-Compatible" content="IE=edge" /> | ||||
| 		<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||||
|  | @ -26,15 +26,12 @@ | |||
| 				align-items: stretch; | ||||
| 				margin: 0; | ||||
| 				min-width: min-content; | ||||
| 				padding: 0 20px; | ||||
| 			} | ||||
| 			#app { | ||||
| 				display: contents; | ||||
| 			h1 { | ||||
| 				margin: 0; | ||||
| 			} | ||||
| 			.column { | ||||
| 				flex: 1; | ||||
| 				max-width: 50vw; | ||||
| 				overflow: hidden; | ||||
| 			h3 { | ||||
| 				margin: 11px 0; | ||||
| 			} | ||||
| 			button { | ||||
| 				border-color: #767676; | ||||
|  | @ -42,6 +39,29 @@ | |||
| 			.hidden { | ||||
| 				display: none; | ||||
| 			} | ||||
| 			#app { | ||||
| 				display: contents; | ||||
| 			} | ||||
| 			#app.hidden { | ||||
| 				display: none; | ||||
| 			} | ||||
| 			.column { | ||||
| 				flex: 1; | ||||
| 				margin: 5px; | ||||
| 				overflow: hidden; | ||||
| 			} | ||||
| 			#threads { | ||||
| 				max-width: 250px; | ||||
| 			} | ||||
| 			.thread:hover { | ||||
| 				background-color: #3b3b3b; | ||||
| 			} | ||||
| 			.thread.selected { | ||||
| 				background-color: #4f4f4f; | ||||
| 			} | ||||
| 			#newthread { | ||||
| 				margin-top: 5px; | ||||
| 			} | ||||
| 			#permissions { | ||||
| 				margin-bottom: 5px; | ||||
| 			} | ||||
|  | @ -50,7 +70,7 @@ | |||
| 			} | ||||
| 			.message { | ||||
| 				margin-bottom: 5px; | ||||
| 				overflow-wrap: break-word; | ||||
| 				overflow-wrap: anywhere; | ||||
| 			} | ||||
| 			#loadmore { | ||||
| 				margin-bottom: 10px; | ||||
|  |  | |||
							
								
								
									
										305
									
								
								client/ui.js
								
								
								
								
							
							
						
						
									
										305
									
								
								client/ui.js
								
								
								
								
							|  | @ -1,305 +0,0 @@ | |||
| import { render, html } from '/uhtml.js'; | ||||
| 
 | ||||
| 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.emit = (type, data, callback) => window.socket.emit(type, { | ||||
| 		...data, | ||||
| 		__session: window.session, | ||||
| 	}, callback); | ||||
| 	window.socket.emit("authenticate", { name: window.name, message: sig }, | ||||
| 		msg => { | ||||
| 			if (!msg.success) { | ||||
| 				document.getElementById("register").classList.remove("hidden"); | ||||
| 				return; | ||||
| 			} | ||||
| 			document.getElementById("register").classList.add("hidden"); | ||||
| 			document.getElementById("app").classList.remove("hidden"); | ||||
| 			window.emit("list_threads", {}, msg => { | ||||
| 				document.getElementById("threadlist").innerHTML = ""; | ||||
| 				for (let thread of msg.threads) | ||||
| 					addThread(thread); | ||||
| 			}); | ||||
| 			loadMessages(); | ||||
| 		}); | ||||
| } | ||||
| 
 | ||||
| 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.emit("create_user", { name, pubkey: keys.publicKey }, auth); | ||||
| } | ||||
| 
 | ||||
| function chooseThread(thread) { | ||||
| 	window.currentThreadId = thread.id; | ||||
| 	window.earliestMessage = null; | ||||
| 	document.getElementById("messages").innerHTML = ""; | ||||
| 	document.getElementById("threadname").innerHTML = thread.name; | ||||
| } | ||||
| 
 | ||||
| function loadMessages() { | ||||
| 	window.emit("get_history", { | ||||
| 		before: window.earliestMessage, | ||||
| 		thread: window.currentThreadId, | ||||
| 	}, 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"); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| 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); | ||||
| 	} | ||||
| 	document.getElementById("memberlist").appendChild(html.node` | ||||
| 		<p class='member'>${name}</p> | ||||
| 	`);
 | ||||
| 	document.getElementById("membername").value = ""; | ||||
| } | ||||
| 
 | ||||
| async function createThread(e) { | ||||
| 	e.preventDefault(); | ||||
| 	let members = window.threadmembers.map(name => { name }); | ||||
| 	const perms = document.querySelector( | ||||
| 		'input[name="permissions"]:checked' | ||||
| 	).value; | ||||
| 	if (perms === "private_view") | ||||
| 		members = (await new Promise(resolve => | ||||
| 			window.emit("get_keys", { names: window.threadmembers }, resolve) | ||||
| 			)).keys; | ||||
| 	let permissions; | ||||
| 	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.emit("create_thread", { | ||||
| 		name: document.getElementById("newthreadname").value, | ||||
| 		permissions, | ||||
| 		members, | ||||
| 	}, msg => { | ||||
| 		chooseThread({ | ||||
| 			name: document.getElementById("newthreadname").value, | ||||
| 			id: msg.id, | ||||
| 		}); | ||||
| 		document.getElementById('createthread').remove(); | ||||
| 		document.getElementById("loadmore").classList.add("hidden"); | ||||
| 		document.getElementById("msginput").classList.remove("hidden"); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| function sendMessage(e) { | ||||
| 	e.preventDefault(); | ||||
| 	const msg = document.getElementById("msg").value; | ||||
| 	if (!msg) | ||||
| 		return; | ||||
| 	window.emit("send_message", { | ||||
| 		message: msg, | ||||
| 		thread: window.currentThreadId, | ||||
| 	}); | ||||
| 	document.getElementById("msg").value = ""; | ||||
| } | ||||
| 
 | ||||
| function newThread(e) { | ||||
| 	let form = document.getElementById('createthread'); | ||||
| 	if (form) { | ||||
| 		form.remove(); | ||||
| 		e.target.textContent = 'create thread'; | ||||
| 	} | ||||
| 	else { | ||||
| 		window.threadmembers = [ window.name ]; | ||||
| 		document.getElementById('threads').insertAdjacentElement('afterend', html.node` | ||||
| 			<form id="createthread" class='column' onsubmit=${createThread}> | ||||
| 				<h3>create thread</h3> | ||||
| 				<label for="newthreadname">thread name</label> | ||||
| 				<input type="text" id="newthreadname" /> | ||||
| 				<p id='permissions'>thread permissions</p> | ||||
| 				<input type="radio" id="public" name="permissions" value="public" /> | ||||
| 				<label for="public">anyone can view and post</label><br /> | ||||
| 				<input | ||||
| 					type="radio" | ||||
| 					id="private_post" | ||||
| 					name="permissions" | ||||
| 					value="private_post" | ||||
| 				/> | ||||
| 				<label for="private_post">only members can post, anyone can view</label | ||||
| 				><br /> | ||||
| 				<input | ||||
| 					type="radio" | ||||
| 					id="private_view" | ||||
| 					name="permissions" | ||||
| 					value="private_view" | ||||
| 				/> | ||||
| 				<label for="private_view">only members can view and post</label | ||||
| 				><br /><br /> | ||||
| 				<span>members</span><br /> | ||||
| 				<input type="text" id="membername" placeholder="username" onkeydown=${(e) => { | ||||
| 					if (e.key == "Enter") { | ||||
| 						e.preventDefault(); | ||||
| 						addMember(); | ||||
| 					} | ||||
| 				}}/> | ||||
| 				<button id="addmember" onclick=${addMember}>add</button> | ||||
| 				<div id="memberlist"> | ||||
| 					<p class='member'>${window.name}</p> | ||||
| 				</div> | ||||
| 				<br /> | ||||
| 				<input id="submitthread" type="submit" value="create" /> | ||||
| 			</form> | ||||
| 		`);
 | ||||
| 		e.target.textContent = 'cancel'; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| render(document.body, html` | ||||
| 	<div id="register" class="hidden"> | ||||
| 		<h1>welcome to vybe</h1> | ||||
| 		<h3>a communication network (beta)</h3> | ||||
| 		<p> | ||||
| 			to get started, you'll need an account. we use public key cryptography | ||||
| 			for security, rather than passwords. your keys are stored in your | ||||
| 			browser storage only, so do this on a browser you can access again. | ||||
| 		</p> | ||||
| 		<form onsubmit=${register} id="registerform"> | ||||
| 			<label for="name">username: </label> | ||||
| 			<input type="text" id="name" /> | ||||
| 			<input id="submit" type="submit" value='generate keys & register'> | ||||
| 		</form> | ||||
| 	</div> | ||||
| 	<div id="app" class="hidden"> | ||||
| 		<div id="threads" class="column"> | ||||
| 			<h1>vybe</h1> | ||||
| 			<h3>threads</h3> | ||||
| 			<div id="threadlist">loading...</div> | ||||
| 			<button id='newthread' onclick=${newThread}>create thread</button> | ||||
| 		</div> | ||||
| 		<div id="chat" class="column"> | ||||
| 			<h3 class="thread"> | ||||
| 				thread: <strong id="threadname">meow</strong> | ||||
| 			</h3> | ||||
| 			<h3>messages will appear below as they are sent</h3> | ||||
| 			<button id="loadmore" class="hidden" onclick=${loadMessages}>load more messages</button> | ||||
| 			<div id="messages"></div> | ||||
| 			<form id="msginput" onsubmit=${sendMessage}> | ||||
| 				<input type="text" placeholder="write a message..." id="msg" /> | ||||
| 				<button type="submit" class="hidden" id="sendmsg"></button> | ||||
| 			</form> | ||||
| 		</div> | ||||
| 	</div> | ||||
| `);
 | ||||
| 
 | ||||
| window.onload = async () => { | ||||
| 	window.currentThreadId = 1; | ||||
| 	window.socket = io(); | ||||
| 	window.socket.on("new_message", (msg) => { | ||||
| 		if (msg.thread !== window.currentThreadId) | ||||
| 			return; | ||||
| 		document.getElementById("messages").appendChild(html.node` | ||||
| 			<div class='message'> | ||||
| 				<strong>${msg.name}: </strong> | ||||
| 				${msg.message} | ||||
| 			</div>`); | ||||
| 		if (!window.earliestMessage) | ||||
| 			window.earliestMessage = msg.id; | ||||
| 	}); | ||||
| 	window.socket.on("new_thread", addThread); | ||||
| 
 | ||||
| 	let keys = localStorage.getItem("keys"); | ||||
| 	if (keys) { | ||||
| 		window.name = localStorage.getItem("name"); | ||||
| 		keys = JSON.parse(keys); | ||||
| 		const priv = await openpgp.readKey({ armoredKey: keys.privateKey }); | ||||
| 		const pub = await openpgp.readKey({ armoredKey: keys.publicKey }); | ||||
| 		window.keys = { priv, pub }; | ||||
| 		await auth(); | ||||
| 		document.getElementById('app').classList.remove('hidden'); | ||||
| 	} | ||||
| 	else | ||||
| 		document.getElementById('register').classList.remove('hidden'); | ||||
| }; | ||||
|  | @ -14,7 +14,7 @@ const create_user = async (msg, respond) => { | |||
|     msg.name, | ||||
|   ]); | ||||
|   if (result.rows.length > 0) { | ||||
|     console.log(result); | ||||
|     console.log(`username already exists: ${result}`); | ||||
|     return respond({ | ||||
|       success: false, | ||||
|       message: "A user with this name already exists on this server", | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue