displaynames, unselect thread, other fixes

main
jerl 2024-05-27 18:39:16 -07:00
parent 8ef0172d11
commit 7deeb82a08
10 changed files with 122 additions and 71 deletions

View File

@ -17,16 +17,20 @@ function setVisibility() {
function chooseThread() { function chooseThread() {
const edit = document.getElementById('edit'); const edit = document.getElementById('edit');
if (window.currentThread) { if (window.currentThread) {
if (window.currentThread.id === this.thread.id) document.getElementById(`thread${window.currentThread.id}`).classList.remove('active');
return;
document.getElementById(`thread${window.currentThread.id}`)
.classList.remove('active');
let editform = document.getElementById('editthread'); let editform = document.getElementById('editthread');
if (editform) { if (editform) {
editform.remove(); editform.remove();
edit.textContent = 'edit'; edit.textContent = 'edit';
} }
if (window.currentThread.id === this.thread.id) {
document.getElementById('thread').classList.add('hidden');
window.currentThread = null;
return;
}
} }
else
document.getElementById('thread').classList.remove('hidden');
if (this.thread.permissions.admin) if (this.thread.permissions.admin)
edit.classList.remove('hidden'); edit.classList.remove('hidden');
else else
@ -43,7 +47,7 @@ function chooseThread() {
switchTab(document.getElementById(this.tab = 'messagetab')); switchTab(document.getElementById(this.tab = 'messagetab'));
else else
loadSpace(spans => { loadSpace(spans => {
if (spans) if (spans.length)
switchTab(document.getElementById(this.tab = 'spacetab')); switchTab(document.getElementById(this.tab = 'spacetab'));
else if (window.currentThread.streams.length) else if (window.currentThread.streams.length)
switchTab(document.getElementById(this.tab = 'streamtab')); switchTab(document.getElementById(this.tab = 'streamtab'));
@ -246,6 +250,21 @@ function clickedTab(event) {
document.getElementById(`thread${window.currentThread.id}`).tab = event.target.id; document.getElementById(`thread${window.currentThread.id}`).tab = event.target.id;
} }
function changeName(e) {
let displayname = document.getElementById('newname').value;
window.emit('update_user', {
displayname
}, msg => {
if (!msg.success) {
console.log('update_user error: ', msg.message);
document.getElementById('savename').classList.remove('hidden');
return;
}
window.displayname = displayname;
document.getElementById('savename').classList.add('hidden');
})
}
// main app html // main app html
document.body.append(html.node` document.body.append(html.node`
<div id='home' class='column'> <div id='home' class='column'>
@ -263,7 +282,17 @@ document.body.append(html.node`
<!-- create thread column goes here --> <!-- create thread column goes here -->
<hr class='separator' color='#505050'> <hr class='separator' color='#505050'>
<div id='profile' class='column hidden'> <div id='profile' class='column hidden'>
<p><strong>authentication requests</strong></p> <label class='heading'>display name</label>
<input id='newname' onkeyup=${function(event) {
if (window.displayname === this.value)
document.getElementById('savename').classList.add('hidden');
else if (event.key === 'Enter')
changeName();
else
document.getElementById('savename').classList.remove('hidden');
}}>
<button class='hidden' id='savename' onclick=${changeName}>save</button>
<label class='heading'>authentication requests</label>
<div id='authrequests'></div> <div id='authrequests'></div>
</div> </div>
<hr class='separator' color='#505050'> <hr class='separator' color='#505050'>
@ -298,6 +327,8 @@ document.body.append(html.node`
</div> </div>
`); `);
document.getElementById('newname').value = window.displayname;
function makeThread(thread) { function makeThread(thread) {
let node = html.node` let node = html.node`
<div class='thread' onclick=${chooseThread}>${ <div class='thread' onclick=${chooseThread}>${
@ -313,7 +344,7 @@ window.socket.on('thread', thread => {
if (el) { if (el) {
el.thread = thread; el.thread = thread;
el.textContent = thread.name; el.textContent = thread.name;
if (window.currentThread.id === thread.id) { if (window.currentThread?.id === thread.id) {
Object.assign(window.currentThread, thread); Object.assign(window.currentThread, thread);
document.getElementById('threadname').textContent = thread.name; document.getElementById('threadname').textContent = thread.name;
setVisibility(); setVisibility();
@ -323,21 +354,32 @@ window.socket.on('thread', thread => {
document.getElementById('threadlist').prepend(makeThread(thread)); document.getElementById('threadlist').prepend(makeThread(thread));
}); });
function authRequest(authrequest) {
const p = html.node`
<p>
<button onclick=${() => {
window.emit('authorize_key', {
id: authrequest.id
}, msg => {
if (!msg.success)
console.log('authorize_key failed: ', msg.message);
});
p.remove();
}}>approve</button>
session <strong>${authrequest.id}</strong>
</p>`;
document.getElementById('authrequests').append(p);
setTimeout(() => p.remove(), Date.now() - authrequest.time + 60000 * 5);
}
window.socket.on('authrequest', authRequest);
window.emit('list_threads', {}, msg => { window.emit('list_threads', {}, msg => {
const threadlist = document.getElementById('threadlist'); const threadlist = document.getElementById('threadlist');
threadlist.replaceChildren(...msg.threads.map(makeThread)); threadlist.replaceChildren(...msg.threads.map(makeThread));
chooseThread.call(threadlist.firstChild); chooseThread.call(threadlist.firstChild);
}); });
window.socket.on('authrequest', (msg, respond) => { export {
const div = html.node` authRequest
<div class='authrequest'> };
<button onclick=${() => {
respond(true);
div.remove();
}}>approve</button>
session <strong>${msg.id}</strong>
</div>`;
document.getElementById('authrequests').append(div);
setTimeout(() => div.remove(), Date.now() - msg.time + 60000 * 5);
});

View File

@ -23,7 +23,7 @@ async function auth() {
message: sig, message: sig,
pubkey: window.keys.armored.publicKey pubkey: window.keys.armored.publicKey
}, },
msg => { async msg => {
let register = document.getElementById('register'); let register = document.getElementById('register');
if (!msg.success) { if (!msg.success) {
console.log('authenticate failed', msg); console.log('authenticate failed', msg);
@ -34,7 +34,9 @@ async function auth() {
localStorage.setItem('keys', JSON.stringify(window.keys.armored)); localStorage.setItem('keys', JSON.stringify(window.keys.armored));
localStorage.setItem('name', window.name); localStorage.setItem('name', window.name);
register.classList.add('hidden'); register.classList.add('hidden');
import('/app.js'); window.displayname = msg.displayname;
const { authRequest } = await import('/app.js');
msg.authrequests.forEach(authRequest);
} }
); );
} }

View File

@ -99,7 +99,7 @@
} }
.column { .column {
flex: 1; flex: 1;
margin: 2px; margin: 3px;
} }
.separator { .separator {
margin: 8px 2px; margin: 8px 2px;
@ -116,7 +116,6 @@
justify-content: space-between; justify-content: space-between;
} }
#threads { #threads {
margin: 3px;
min-height: 0; min-height: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -130,15 +129,9 @@
} }
#profile { #profile {
max-width: 250px; max-width: 250px;
> * {
margin: 4px;
}
}
.authrequest {
margin-block: 3px;
} }
.thread { .thread {
padding: 2px 4px; padding: 2px 3px;
white-space: pre; white-space: pre;
} }
#newthread { #newthread {

View File

@ -16,7 +16,7 @@ function sendMessage(e) {
let earliestMessage; let earliestMessage;
window.socket.on('new_message', message => { window.socket.on('new_message', message => {
if (message.thread !== window.currentThread.id) if (message.thread !== window.currentThread?.id)
return; return;
const messages = document.getElementById('messages'); const messages = document.getElementById('messages');
let scroll = messages.scrollHeight - messages.scrollTop <= messages.clientHeight; let scroll = messages.scrollHeight - messages.scrollTop <= messages.clientHeight;
@ -73,7 +73,7 @@ function loadMessages(firstRender, callback) {
for (let message of msg.messages) for (let message of msg.messages)
messages.prepend(html.node` messages.prepend(html.node`
<div class='message'> <div class='message'>
<strong>${message.name}: </strong> <strong>${message.displayname}: </strong>
${message.message} ${message.message}
</div>`); </div>`);
} }

View File

@ -132,7 +132,7 @@ function loadStreams() {
} }
window.socket.on('stream', async msg => { window.socket.on('stream', async msg => {
if (msg.thread !== window.currentThread.id) if (msg.thread !== window.currentThread?.id)
return; return;
let p = document.getElementById('stream' + msg.id); let p = document.getElementById('stream' + msg.id);
if (p) { if (p) {

View File

@ -1,6 +1,7 @@
create table user ( create table user (
id integer primary key asc, id integer primary key asc,
name text, name text,
displayname text,
created timestamp default current_timestamp created timestamp default current_timestamp
); );
@ -60,11 +61,3 @@ create table span (
scale decimal, scale decimal,
foreign key(thread) references thread(id) foreign key(thread) references thread(id)
); );
insert into thread (name) values ("meow");
insert into permission
(thread, type, permission, value) values
(1, "everyone", "view", "true");
insert into permission
(thread, type, permission, value) values
(1, "everyone", "post", "true");

View File

@ -11,10 +11,7 @@ const authwrap = (fn) => async (msg, respond, socket) => {
} }
return await fn({ return await fn({
...msg, ...msg,
auth_user: { auth_user: vybe.users[socket.username]
id: vybe.users[socket.username].id,
name: socket.username
}
}, respond, socket); }, respond, socket);
}; };

View File

@ -36,7 +36,8 @@ async function send_message(msg, respond) {
for (let s of vybe.users[username].sockets) { for (let s of vybe.users[username].sockets) {
s.emit('new_message', { s.emit('new_message', {
id: id.rows[0].id, id: id.rows[0].id,
name: msg.auth_user.name, username: msg.auth_user.name,
displayname: msg.auth_user.displayname,
message: msg.message, message: msg.message,
thread: msg.thread thread: msg.thread
}); });

View File

@ -341,7 +341,9 @@ async function edit_thread(msg, respond) {
permissions: { permissions: {
is_member: username in members, is_member: username in members,
view: true, view: true,
post: !msg.permissions || !msg.permissions.post_limited, post: !msg.permissions || !msg.permissions.post_limited
|| username in members,
admin: username === msg.auth_user.name && perms.admin,
...permissions ...permissions
} }
}); });

View File

@ -47,7 +47,7 @@ async function create_user(msg, respond) {
[insert.rows[0].id, msg.pubkey] [insert.rows[0].id, msg.pubkey]
) )
// respond // respond
return respond({ respond({
success: true, success: true,
id: insert.rows[0].id id: insert.rows[0].id
}); });
@ -60,9 +60,9 @@ async function authenticate(msg, respond, socket) {
message: 'invalid message' message: 'invalid message'
}); });
} }
let userid = await db.query( let user = (await db.query(
`select user.id from user where name = ?`, [msg.name]); `select id, displayname from user where name = ?`, [msg.name])).rows[0];
if (userid.rows.length === 0) { if (!user) {
return respond({ return respond({
success: false, success: false,
message: 'user not found' message: 'user not found'
@ -91,13 +91,14 @@ async function authenticate(msg, respond, socket) {
message: 'bad auth message' message: 'bad auth message'
}); });
} }
let user = vybe.users[msg.name]; if (!user.displayname)
if (!user) user.displayname = msg.name;
user = vybe.users[msg.name] = { user = vybe.users[msg.name] || (vybe.users[msg.name] = {
id: userid.rows[0].id, ...user,
sockets: [], name: msg.name,
authrequests: {} sockets: [],
}; authrequests: {}
});
if (result.rows.length === 0) { if (result.rows.length === 0) {
// request auth from logged in sessions // request auth from logged in sessions
let id = key.getFingerprint().slice(0, 8); let id = key.getFingerprint().slice(0, 8);
@ -105,7 +106,7 @@ async function authenticate(msg, respond, socket) {
if (!await new Promise(resolve => { if (!await new Promise(resolve => {
user.authrequests[id] = { time, callback: resolve }; user.authrequests[id] = { time, callback: resolve };
for (let s of user.sockets) for (let s of user.sockets)
s.emit('authrequest', { id, time }, resolve); s.emit('authrequest', { id, time });
setTimeout(() => { setTimeout(() => {
delete user.authrequests[id]; delete user.authrequests[id];
resolve(false); resolve(false);
@ -124,27 +125,45 @@ async function authenticate(msg, respond, socket) {
socket.username = msg.name; socket.username = msg.name;
user.sockets.push(socket); user.sockets.push(socket);
respond({ respond({
success: true success: true,
displayname: user.displayname,
authrequests: Object.entries(user.authrequests).map(authrequest => ({
id: authrequest[0],
time: authrequest[1].time
}))
}); });
// send authenticated session any auth requests
if (result.rows.length)
setTimeout(() => {
for (let id in user.authrequests)
socket.emit('authrequest', {
id,
time: user.authrequests[id].time
}, user.authrequests[id].callback);
}, 1000); // todo: do this better
} }
catch (err) { catch (err) {
console.error('error in authentication: ' + err); console.error('error in authentication: ' + err);
return respond({ respond({
success: false, success: false,
message: 'message signature verification failed' message: 'message signature verification failed'
}); });
} }
} }
async function authorize_key(msg, respond) {
let authrequest = vybe.users[msg.auth_user.name].authrequests[msg.id];
if (!authrequest) {
return respond({
success: false,
message: 'auth request not found'
});
}
authrequest.callback(true);
respond({
success: true
});
}
async function update_user(msg, respond) {
await db.query(`update user set displayname = ?`, msg.displayname);
vybe.users[msg.auth_user.name].displayname = msg.displayname;
respond({
success: true
});
}
async function get_keys(msg, respond) { async function get_keys(msg, respond) {
// validate inputs // validate inputs
if (!msg.names) { if (!msg.names) {
@ -165,7 +184,7 @@ async function get_keys(msg, respond) {
msg.names msg.names
); );
// respond // respond
return respond({ respond({
success: true, success: true,
keys: keys.rows keys: keys.rows
}); });
@ -174,5 +193,7 @@ async function get_keys(msg, respond) {
module.exports = { module.exports = {
create_user, create_user,
authenticate, authenticate,
authorize_key: authwrap(authorize_key),
update_user: authwrap(update_user),
get_keys: authwrap(get_keys) get_keys: authwrap(get_keys)
}; };