bugfixes and style improvements
parent
0b830174d3
commit
05bbdc0d60
173
client/app.js
173
client/app.js
|
@ -1,23 +1,33 @@
|
|||
import { render, html } from '/uhtml.js';
|
||||
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');
|
||||
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)
|
||||
|
@ -29,30 +39,26 @@ function loadMessages() {
|
|||
}
|
||||
if (!msg.more)
|
||||
document.getElementById("loadmore").classList.add("hidden");
|
||||
else
|
||||
document.getElementById("loadmore").classList.remove("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>`;
|
||||
<button class='thread tab' onclick=${() => chooseThread(thread)}>${
|
||||
thread.name
|
||||
}</button>`;
|
||||
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 = "";
|
||||
}
|
||||
|
||||
|
@ -60,31 +66,34 @@ async function createThread(e) {
|
|||
e.preventDefault();
|
||||
let name = document.getElementById("newthreadname");
|
||||
if (!name.value) {
|
||||
name.insertAdjacentHTML('afterend', `<p>name cannot be empty</p>`);
|
||||
name.insertAdjacentHTML("afterend", `<p>name cannot be empty</p>`);
|
||||
return;
|
||||
}
|
||||
let members = window.threadmembers.map(name => ({ name }));
|
||||
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 = {
|
||||
view_limited: false,
|
||||
post_limited: false
|
||||
post_limited: false,
|
||||
};
|
||||
} else if (perms === "private_post") {
|
||||
permissions = {
|
||||
view_limited: false,
|
||||
post_limited: true
|
||||
post_limited: true,
|
||||
};
|
||||
} else if (perms === "private_view") {
|
||||
permissions = {
|
||||
view_limited: true,
|
||||
post_limited: true
|
||||
post_limited: true,
|
||||
};
|
||||
// generate key
|
||||
/* wip
|
||||
|
@ -101,45 +110,50 @@ 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) {
|
||||
e.preventDefault();
|
||||
const msg = document.getElementById("msg").value;
|
||||
if (!msg)
|
||||
return;
|
||||
if (!msg) return;
|
||||
window.emit("send_message", {
|
||||
message: msg,
|
||||
thread: window.currentThreadId
|
||||
thread: window.currentThreadId,
|
||||
});
|
||||
document.getElementById("msg").value = "";
|
||||
}
|
||||
|
||||
function newThread(e) {
|
||||
let form = document.getElementById('createthread');
|
||||
function newThread() {
|
||||
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`
|
||||
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,65 +179,68 @@ 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";
|
||||
}
|
||||
}
|
||||
|
||||
function switchTab(event){
|
||||
for (let tab of document.querySelectorAll('.tab'))
|
||||
tab.classList.remove('active');
|
||||
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))
|
||||
.classList.remove('hidden');
|
||||
function switchTab(event) {
|
||||
for (let tab of document.querySelectorAll(".tab"))
|
||||
tab.classList.remove("active");
|
||||
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))
|
||||
.classList.remove("hidden");
|
||||
}
|
||||
|
||||
render(document.body, html`
|
||||
render(
|
||||
document.body,
|
||||
html`
|
||||
<div id="threads" class="column">
|
||||
<h3>vybe</h3>
|
||||
<h4>threads</h4>
|
||||
<div id="threadlist">loading...</div>
|
||||
<button id='newthread' onclick=${newThread}>create</button>
|
||||
<button id="newthread" onclick=${newThread}>create</button>
|
||||
</div>
|
||||
<div id="thread" class="column">
|
||||
<div id='title'>
|
||||
thread: <strong id="threadname">meow</strong>
|
||||
</div>
|
||||
<button id='messagetab' class='tab active' onclick=${switchTab}>messages</button>
|
||||
<button id='spacetab' class='tab' onclick=${switchTab}>space</button>
|
||||
<button id="loadmore" class="hidden" onclick=${loadMessages}>load more messages</button>
|
||||
<div id='message' class='tabcontent'>
|
||||
<div id='messages'>
|
||||
</div>
|
||||
<div id="title">thread: <strong id="threadname">meow</strong></div>
|
||||
<button id="messagetab" class="tab active" onclick=${switchTab}>
|
||||
messages
|
||||
</button>
|
||||
<button id="spacetab" class="tab" onclick=${switchTab}>space</button>
|
||||
<button id="loadmore" class="hidden" onclick=${loadMessages}>
|
||||
load more messages
|
||||
</button>
|
||||
<div id="message" class="tabcontent">
|
||||
<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 id='space' class='tabcontent'></div>
|
||||
<div id="space" class="tabcontent"></div>
|
||||
</div>
|
||||
`);
|
||||
`
|
||||
);
|
||||
|
||||
window.socket.on("new_message", (msg) => {
|
||||
if (msg.thread !== window.currentThreadId)
|
||||
return;
|
||||
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;
|
||||
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 => {
|
||||
window.emit("list_threads", {}, (msg) => {
|
||||
document.getElementById("threadlist").innerHTML = "";
|
||||
for (let thread of msg.threads)
|
||||
addThread(thread);
|
||||
for (let thread of msg.threads) addThread(thread);
|
||||
chooseThread(msg.threads[0]);
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { render, html } from '/uhtml.js';
|
||||
import { render, html } from "/uhtml.js";
|
||||
|
||||
function rand() {
|
||||
let str = "";
|
||||
|
@ -16,19 +16,26 @@ async function auth() {
|
|||
message: new openpgp.CleartextMessage("vybe_auth " + window.session, ""),
|
||||
signingKeys: window.keys.priv,
|
||||
});
|
||||
window.socket.emit("authenticate", { name: window.name, message: sig },
|
||||
msg => {
|
||||
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');
|
||||
});
|
||||
import("/app.js");
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
render(document.body, html`
|
||||
<div id="register" class='hidden'>
|
||||
render(
|
||||
document.body,
|
||||
html`
|
||||
<div id="register" class="hidden">
|
||||
<h1>welcome to vybe</h1>
|
||||
<h3>a communication network (beta)</h3>
|
||||
<p>
|
||||
|
@ -36,20 +43,26 @@ 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)
|
||||
return;
|
||||
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.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 +70,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) {
|
||||
|
@ -81,10 +108,13 @@ window.onload = async () => {
|
|||
keys = JSON.parse(keys);
|
||||
window.keys = {
|
||||
priv: await openpgp.readKey({ armoredKey: keys.privateKey }),
|
||||
pub: await openpgp.readKey({ armoredKey: keys.publicKey })
|
||||
pub: await openpgp.readKey({ armoredKey: keys.publicKey }),
|
||||
};
|
||||
await auth();
|
||||
}
|
||||
else
|
||||
document.getElementById('register').classList.remove('hidden');
|
||||
} else document.getElementById("register").classList.remove("hidden");
|
||||
|
||||
window.socket.io.on("reconnect", async (attempt) => {
|
||||
await gensession();
|
||||
if (localStorage.getItem("keys")) await auth();
|
||||
});
|
||||
};
|
||||
|
|
|
@ -18,20 +18,47 @@
|
|||
body,
|
||||
button,
|
||||
input {
|
||||
background: #020202;
|
||||
color: #eaeaea;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
body {
|
||||
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: #4f4f4f;
|
||||
}
|
||||
input {
|
||||
background: #2f2f2f;
|
||||
border-bottom: 2px solid transparent;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
input:focus {
|
||||
border-bottom: 2px solid #4f4f4f;
|
||||
}
|
||||
input::placeholder {
|
||||
color: #aaa;
|
||||
}
|
||||
button:hover,
|
||||
.tab:hover {
|
||||
background-color: #3b3b3b;
|
||||
}
|
||||
label.heading {
|
||||
margin-bottom: 5px;
|
||||
display: block;
|
||||
}
|
||||
h3,
|
||||
h4 {
|
||||
margin: 10px 0;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
|
@ -44,11 +71,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;
|
||||
|
@ -63,9 +89,9 @@
|
|||
margin: 4px 2px;
|
||||
}
|
||||
.tab {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
margin-top: 2px;
|
||||
padding: 5px 7px;
|
||||
color: #ccc;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
@ -73,9 +99,6 @@
|
|||
background-color: #4f4f4f;
|
||||
color: #fff;
|
||||
}
|
||||
.tab:hover {
|
||||
background-color:#3b3b3b;
|
||||
}
|
||||
#messages {
|
||||
margin: 4px 2px;
|
||||
}
|
||||
|
@ -97,6 +120,5 @@
|
|||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
<body></body>
|
||||
</html>
|
||||
|
|
27
index.js
27
index.js
|
@ -3,15 +3,17 @@ const http = require("http");
|
|||
const { Server } = require("socket.io");
|
||||
const compression = require("compression");
|
||||
|
||||
const events = Object.fromEntries([
|
||||
'create_user',
|
||||
'get_history',
|
||||
'send_message',
|
||||
'authenticate',
|
||||
'create_thread',
|
||||
'list_threads',
|
||||
'get_keys',
|
||||
].map(event => [event, require('./src/' + event)]));
|
||||
const events = Object.fromEntries(
|
||||
[
|
||||
"create_user",
|
||||
"get_history",
|
||||
"send_message",
|
||||
"authenticate",
|
||||
"create_thread",
|
||||
"list_threads",
|
||||
"get_keys",
|
||||
].map((event) => [event, require("./src/" + event)])
|
||||
);
|
||||
|
||||
const app = express();
|
||||
app.use(compression());
|
||||
|
@ -32,11 +34,10 @@ io.on("connection", (socket) => {
|
|||
events[event](msg, callback, socket, io)
|
||||
);
|
||||
}
|
||||
socket.on('disconnect', reason => {
|
||||
socket.on("disconnect", (reason) => {
|
||||
let sockets = io.cache[socket.username];
|
||||
if (sockets)
|
||||
sockets.splice(sockets.indexOf(socket.id), 1);
|
||||
})
|
||||
if (sockets) sockets.splice(sockets.indexOf(socket.id), 1);
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(PORT, () => {
|
||||
|
|
|
@ -3,7 +3,7 @@ const authwrap = require("./authwrap");
|
|||
|
||||
const create_thread = async (msg, respond, socket, io) => {
|
||||
// validate inputs
|
||||
if (typeof msg.name !== 'string') {
|
||||
if (typeof msg.name !== "string") {
|
||||
return respond({
|
||||
success: false,
|
||||
message: "thread name required",
|
||||
|
@ -77,24 +77,23 @@ const create_thread = async (msg, respond, socket, io) => {
|
|||
permissions: {
|
||||
is_member: false,
|
||||
view: true,
|
||||
post: !msg.permissions || !msg.permissions.post_limited
|
||||
post: !msg.permissions || !msg.permissions.post_limited,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} 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,
|
||||
permissions: {
|
||||
is_member: true,
|
||||
view: true,
|
||||
post: true
|
||||
post: true,
|
||||
},
|
||||
key: member.key
|
||||
key: member.key,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue