jerl 2024-02-19 23:42:44 -08:00
commit f347e659e9
5 changed files with 247 additions and 138 deletions

View File

@ -4,20 +4,30 @@ 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');
document
.getElementById(`thread${window.currentThreadId}`)
.classList.remove("active");
document.getElementById(`thread${thread.id}`).classList.add("active");
window.currentThreadId = thread.id;
window.earliestMessage = null;
document.getElementById("messages").innerHTML = "";
document.getElementById("threadname").textContent = thread.name;
if (!thread.permissions.post) {
document.getElementById("msginput").classList.add("hidden");
} else {
document.getElementById("msginput").classList.remove("hidden");
}
loadMessages();
}
function loadMessages() {
window.emit("get_history", {
window.emit(
"get_history",
{
before: window.earliestMessage,
thread: window.currentThreadId,
}, msg => {
},
msg => {
if (msg.messages.length > 0) {
window.earliestMessage = msg.messages[msg.messages.length - 1].id;
for (let message of msg.messages)
@ -31,28 +41,25 @@ function loadMessages() {
document.getElementById("loadmore").classList.add("hidden");
else
document.getElementById("loadmore").classList.remove("hidden");
});
}
);
}
function addThread(thread) {
function addThread(thread, top) {
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>`;
<div class='thread' onclick=${() => chooseThread(thread)}>${
thread.name
}</div>`;
node.id = `thread${thread.id}`;
document.getElementById("threadlist").appendChild(node);
document.getElementById("threadlist")[top ? "prepend" : "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("memberlist")
.appendChild(html.node`<p class='member'>${name}</p>`);
document.getElementById("membername").value = "";
}
@ -65,11 +72,14 @@ async function createThread(e) {
}
let members = window.threadmembers.map(name => ({ name }));
const perms = document.querySelector(
'input[name="permissions"]:checked').value;
'input[name="permissions"]:checked'
).value;
if (perms === "private_view")
members = (await new Promise(resolve =>
members = (
await new Promise(resolve =>
window.emit("get_keys", { names: window.threadmembers }, resolve)
)).keys;
)
).keys;
let permissions;
if (perms === "public") {
permissions = {
@ -101,19 +111,24 @@ async function createThread(e) {
}
*/
}
window.emit("create_thread", {
window.emit(
"create_thread",
{
name: name.value,
permissions,
members
}, msg => {
members,
},
msg => {
chooseThread({
name: name.value,
id: msg.id
id: msg.id,
});
document.getElementById('createthread').remove();
// since the form exists, this will perform cleanup
newThread();
document.getElementById("loadmore").classList.add("hidden");
document.getElementById("msginput").classList.remove("hidden");
});
}
);
}
function sendMessage(e) {
@ -128,18 +143,17 @@ function sendMessage(e) {
document.getElementById("msg").value = "";
}
function newThread(e) {
function newThread() {
let form = document.getElementById('createthread');
if (form) {
form.remove();
e.target.textContent = 'create';
}
else {
document.getElementById("newthread").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>
<label for="newthreadname" class="heading">thread name</label>
<input type="text" id="newthreadname" />
<p id='permissions'>thread permissions</p>
<input type="radio" id="public" name="permissions" value="public" checked />
@ -153,7 +167,7 @@ function newThread(e) {
/>
<label for="private_view">only members can view and post</label
><br /><br />
<span>members</span><br />
<label class="heading" for="membername">members</label>
<input type="text" id="membername" placeholder="username" onkeydown=${(e) => {
if (e.key == "Enter") {
e.preventDefault();
@ -165,10 +179,11 @@ function newThread(e) {
<p class='member'>${window.name}</p>
</div>
<br />
<input id="submitthread" type="submit" value="create" />
<button id="submitthread" type="submit">create</button>
</form>
`);
e.target.textContent = 'cancel';
`
);
document.getElementById("newthread").textContent = 'cancel';
}
}
@ -178,7 +193,8 @@ function switchTab(event){
for (let tab of document.querySelectorAll('.tabcontent'))
tab.classList.add('hidden');
event.target.classList.add('active');
document.getElementById(event.target.id.substring(0, event.target.id.length - 3))
document
.getElementById(event.target.id.substring(0, event.target.id.length - 3))
.classList.remove('hidden');
}
@ -194,10 +210,14 @@ render(document.body, html`
thread: <strong id="threadname">meow</strong>
</div>
<div id='tabs'>
<button id='messagetab' class='tab active' onclick=${switchTab}>messages</button><button id='spacetab' class='tab' onclick=${switchTab}>space</button>
<button id='messagetab' class='tab active' onclick=${switchTab}>
messages
</button><button id='spacetab' class='tab' onclick=${switchTab}>space</button>
</div>
<div id='message' class='tabcontent'>
<button id="loadmore" class="hidden" onclick=${loadMessages}>load more messages</button>
<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" />
@ -219,7 +239,7 @@ window.socket.on("new_message", (msg) => {
if (!window.earliestMessage)
window.earliestMessage = msg.id;
});
window.socket.on("new_thread", addThread);
window.socket.on("new_thread", thread => addThread(thread, true));
window.emit("list_threads", {}, msg => {
document.getElementById("threadlist").innerHTML = "";

View File

@ -16,18 +16,25 @@ async function auth() {
message: new openpgp.CleartextMessage("vybe_auth " + window.session, ""),
signingKeys: window.keys.priv,
});
window.socket.emit("authenticate", { name: window.name, message: sig },
window.socket.emit(
"authenticate",
{ name: window.name, message: sig },
msg => {
if (!msg.success) {
console.log('authenticate failed');
console.log('authenticate failed', msg);
return;
}
if (document.getElementById("register")) {
document.getElementById("register").remove();
import('/app.js');
});
}
}
);
}
render(document.body, html`
render(
document.body,
html`
<div id="register" class='hidden'>
<h1>welcome to vybe</h1>
<h3>a communication network (beta)</h3>
@ -36,7 +43,8 @@ render(document.body, html`
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 => {
<form
onsubmit=${async e => {
e.preventDefault();
const name = document.getElementById("name").value;
if (!name)
@ -46,10 +54,16 @@ render(document.body, html`
});
const priv = await openpgp.readKey({ armoredKey: keys.privateKey });
const pub = await openpgp.readKey({ armoredKey: keys.publicKey });
window.emit("create_user", { name, pubkey: keys.publicKey }, msg => {
window.emit(
"create_user",
{ name, pubkey: keys.publicKey },
(msg) => {
if (!msg.success) {
document.querySelector('#registerform').insertAdjacentHTML('afterend', `
<p>${msg.message}</p>`);
document.querySelector('#registerform').insertAdjacentHTML(
'afterend',
`
<p>${msg.message}</p>`
);
return;
}
window.keys = { priv, pub };
@ -57,23 +71,37 @@ render(document.body, html`
localStorage.setItem("name", name);
window.name = name;
auth();
});
}} id="registerform">
}
);
}}
id="registerform"
>
<label for="name">username: </label>
<input id="name" type="text" />
<input id="submit" type="submit" value='generate keys & register'>
<input id="submit" type="submit" value='generate keys & register' />
</form>
</div>
`);
`
);
const gensession = async () => {
window.session = rand();
window.emit = (type, data, callback) =>
window.socket.emit(
type,
{
...data,
__session: window.session,
},
callback
);
};
window.onload = async () => {
window.socket = io();
window.session = rand();
window.emit = (type, data, callback) => window.socket.emit(type, {
...data,
__session: window.session,
}, callback);
await gensession();
let keys = localStorage.getItem("keys");
if (keys) {
@ -87,4 +115,10 @@ window.onload = async () => {
}
else
document.getElementById('register').classList.remove('hidden');
window.socket.io.on("reconnect", async attempt => {
await gensession();
if (localStorage.getItem("keys"))
await auth();
});
};

View File

@ -18,23 +18,60 @@
body,
button,
input {
background: #020202;
color: #eaeaea;
border: none;
outline: none;
}
html {
height: 100%;
}
body {
height: 100%;
background: #020202;
display: flex;
align-items: stretch;
margin: 0;
min-width: min-content;
}
h3, h4 {
margin: 10px 0;
button,
input,
.tab {
padding: 5px 7px;
}
button {
border-color: #767676;
background: #303030;
}
input {
background: #171717;
border-bottom: 2px solid transparent;
padding-bottom: 3px;
}
input:focus {
border-bottom: 2px solid #4f4f4f;
}
input::placeholder {
color: #aaa;
}
.thread:hover,
.tab:hover {
background-color: #3b3b3b;
}
.tab.active,
.thread.active,
button:hover {
background-color: #4f4f4f;
color: #fff;
}
label.heading {
margin-bottom: 5px;
display: block;
}
h3,
h4 {
margin: 10px 0;
}
.hidden {
display: none;
display: none !important;
}
.column {
flex: 1;
@ -44,11 +81,10 @@
#threads {
max-width: 250px;
}
.thread.selected {
background-color: #4f4f4f;
}
.thread:hover {
background-color: #3b3b3b;
.thread {
display: block;
width: 100%;
text-align: left;
}
#newthread {
margin-top: 5px;
@ -59,28 +95,46 @@
#permissions {
margin-bottom: 5px;
}
#thread {
display: flex;
flex-direction: column;
}
#title {
margin: 4px 2px;
}
#tabs {
margin-block: 2px;
}
.tab {
padding: 5px 7px;
background-color: #1f1f1f;
border: 0;
margin-top: 2px;
padding: 5px 7px;
color: #ccc;
color: #ddd;
font-weight: 500;
}
.tab.active {
background-color: #4f4f4f;
color: #fff;
.tabcontent {
flex-grow: 1;
}
.tab:hover {
background-color:#3b3b3b;
#message {
display: flex;
flex-direction: column;
}
#messages {
margin: 4px 2px;
flex-grow: 1;
}
#msginput {
margin-top: 15px;
display: flex;
flex-direction: row;
}
#msg {
flex-grow: 1;
margin: 2px;
padding: 5px;
}
#sendmsg {
margin: 2px 3px;
}
.message {
margin-bottom: 5px;
@ -97,6 +151,5 @@
}
</style>
</head>
<body>
</body>
<body></body>
</html>

View File

@ -3,7 +3,8 @@ const http = require("http");
const { Server } = require("socket.io");
const compression = require("compression");
const events = Object.fromEntries([
const events = Object.fromEntries(
[
'create_user',
'get_history',
'send_message',
@ -11,7 +12,8 @@ const events = Object.fromEntries([
'create_thread',
'list_threads',
'get_keys',
].map(event => [event, require('./src/' + event)]));
].map(event => [event, require('./src/' + event)])
);
const app = express();
app.use(compression());

View File

@ -85,7 +85,7 @@ const create_thread = async (msg, respond, socket, io) => {
}
else {
for (let member of msg.members) {
for (let socket of io.cache[member.user]) {
for (let socket of io.cache[member.name]) {
io.to(socket).emit("new_thread", {
name: msg.name,
id: insert.rows[0].id,