audio streaming
parent
4416a3766b
commit
8ef0172d11
|
@ -1,6 +1,7 @@
|
|||
import { render, html } from '/uhtml.js';
|
||||
import loadMessages from '/message.js';
|
||||
import loadSpace from '/space.js';
|
||||
import loadStreams from '/stream.js';
|
||||
|
||||
function setVisibility() {
|
||||
document.getElementById('visibility').innerText = `${
|
||||
|
@ -33,6 +34,7 @@ function chooseThread() {
|
|||
document.getElementById('threadname').textContent = this.thread.name;
|
||||
this.classList.add('active');
|
||||
window.currentThread = this.thread;
|
||||
loadStreams();
|
||||
if (this.tab)
|
||||
switchTab(document.getElementById(this.tab));
|
||||
else // load first tab that has any content
|
||||
|
@ -40,8 +42,16 @@ function chooseThread() {
|
|||
if (messages.length)
|
||||
switchTab(document.getElementById(this.tab = 'messagetab'));
|
||||
else
|
||||
loadSpace(spans => switchTab(
|
||||
document.getElementById(this.tab = spans.length ? 'spacetab' : 'messagetab')));
|
||||
loadSpace(spans => {
|
||||
if (spans)
|
||||
switchTab(document.getElementById(this.tab = 'spacetab'));
|
||||
else if (window.currentThread.streams.length)
|
||||
switchTab(document.getElementById(this.tab = 'streamtab'));
|
||||
else
|
||||
switchTab(document.getElementById(
|
||||
this.tab = spans.length ? 'spacetab' :
|
||||
window.currentThread.streams.length ? 'streamtab' : 'messagetab'));
|
||||
});
|
||||
});
|
||||
window.emit('get_thread', { thread: this.thread.id }, msg => {
|
||||
window.currentThread = msg.thread;
|
||||
|
@ -148,7 +158,6 @@ function newThread() {
|
|||
<input type='radio' name='newpermissions'
|
||||
id='private_view' value='private_view' />
|
||||
<label for='private_view'>only members can view and post</label><br>
|
||||
<br>
|
||||
<label class='heading' for='membername'>members</label>
|
||||
<input type='text' id='membername' placeholder='username' onkeydown=${event => {
|
||||
if (event.key === 'Enter') {
|
||||
|
@ -267,7 +276,8 @@ document.body.append(html.node`
|
|||
<div id='buttonbar'>
|
||||
<div id='tabs'>
|
||||
<button id='messagetab' class='tab active' onclick=${clickedTab}>messages
|
||||
</button><button id='spacetab' class='tab' onclick=${clickedTab}>space</button>
|
||||
</button><button id='spacetab' class='tab' onclick=${clickedTab}>space
|
||||
</button><button id='streamtab' class='tab' onclick=${clickedTab}>streams</button>
|
||||
</div>
|
||||
<button id='showmembers' onclick=${() =>
|
||||
document.getElementById('members').classList.toggle('hidden')
|
||||
|
@ -275,6 +285,7 @@ document.body.append(html.node`
|
|||
</div>
|
||||
<div id='message' class='tabcontent'></div>
|
||||
<div id='space' class='tabcontent hidden'></div>
|
||||
<div id='stream' class='tabcontent hidden'></div>
|
||||
</div>
|
||||
<hr class='separator' color='#505050'>
|
||||
<div id='members' class='hidden'>
|
||||
|
|
|
@ -81,8 +81,11 @@
|
|||
background-color: #4f4f4f;
|
||||
color: #fff;
|
||||
}
|
||||
p {
|
||||
margin: 5px 1px;
|
||||
}
|
||||
label.heading {
|
||||
margin-bottom: 5px;
|
||||
margin: 10px 1px 4px;
|
||||
display: block;
|
||||
}
|
||||
h3 {
|
||||
|
@ -218,9 +221,6 @@
|
|||
white-space: pre-line;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.member {
|
||||
margin: 5px 0;
|
||||
}
|
||||
#space {
|
||||
margin: -2px; /* offset column margin */
|
||||
position: relative;
|
||||
|
@ -230,6 +230,13 @@
|
|||
position: absolute;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#stream {
|
||||
margin: 4px 2px;
|
||||
}
|
||||
#play {
|
||||
line-height: 1.4;
|
||||
font-family: 'Segoe UI Symbol', math;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body></body>
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
import { render, html } from '/uhtml.js';
|
||||
|
||||
let streamid;
|
||||
|
||||
let handle;
|
||||
let mediaStream;
|
||||
let recorder;
|
||||
|
||||
async function stream() {
|
||||
if (handle) {
|
||||
clearInterval(handle);
|
||||
handle = null;
|
||||
if (recorder.state === 'recording')
|
||||
recorder.stop();
|
||||
window.emit('stream', {
|
||||
id: streamid,
|
||||
thread: window.currentThread.id,
|
||||
stop: true
|
||||
});
|
||||
document.getElementById('streaming').innerText = 'start streaming';
|
||||
return;
|
||||
}
|
||||
if (!mediaStream)
|
||||
mediaStream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: {
|
||||
autoGainControl: false,
|
||||
echoCancellation: false,
|
||||
noiseSuppression: false,
|
||||
sampleRate: 48000,
|
||||
sampleSize: 16
|
||||
}
|
||||
});
|
||||
if (!mediaStream)
|
||||
return;
|
||||
window.emit('stream', {
|
||||
thread: window.currentThread.id,
|
||||
name: document.getElementById('streamname').value
|
||||
}, msg => {
|
||||
if (!msg.success) {
|
||||
console.log('stream failed: ', msg.message);
|
||||
return;
|
||||
}
|
||||
streamid = msg.id;
|
||||
document.getElementById('streaming').innerText = 'stop streaming';
|
||||
});
|
||||
function record() {
|
||||
let r = recorder = new MediaRecorder(mediaStream);
|
||||
let chunks = [];
|
||||
r.ondataavailable = event => {
|
||||
if (!event.data.size)
|
||||
return;
|
||||
chunks.push(event.data);
|
||||
};
|
||||
r.onstop = async () => {
|
||||
if (!chunks.length || !handle)
|
||||
return;
|
||||
//console.log(`${Date.now()} ${chunks.length}`);
|
||||
window.emit('streamdata', {
|
||||
id: streamid,
|
||||
audio: await (new Blob(chunks, { type: chunks[0].type })).arrayBuffer()
|
||||
}, msg => {
|
||||
if (!msg.success)
|
||||
console.log('streamdata failed: ', msg.message);
|
||||
});
|
||||
};
|
||||
r.onstart = () => {
|
||||
setTimeout(() => r.state === 'recording' && r.stop(), 100);
|
||||
};
|
||||
r.start();
|
||||
}
|
||||
record();
|
||||
handle = setInterval(record, 100);
|
||||
}
|
||||
|
||||
let audioctx;
|
||||
let streaming = {};
|
||||
|
||||
function addStream(stream) {
|
||||
let p = html.node`
|
||||
<p>
|
||||
<button id='play' onclick=${e => {
|
||||
if (stream.playing) {
|
||||
audioctx.suspend();
|
||||
delete streaming[stream.id];
|
||||
stream.playing = false;
|
||||
e.target.innerText = '▶';
|
||||
}
|
||||
else {
|
||||
audioctx = new AudioContext();
|
||||
streaming[stream.id] = stream;
|
||||
stream.playing = true;
|
||||
e.target.innerText = '⏹';
|
||||
}
|
||||
window.emit('play_stream', {
|
||||
id: stream.id,
|
||||
thread: window.currentThread.id,
|
||||
playing: stream.playing
|
||||
}, msg => {
|
||||
if (!msg.success)
|
||||
console.log('play stream failed: ', msg.message);
|
||||
});
|
||||
}}>▶</button>
|
||||
${stream.user}<span id='name'>${stream.name ? ` - ${stream.name}` : ''}</span>
|
||||
</p>`;
|
||||
p.id = 'stream' + stream.id;
|
||||
document.getElementById('streams').append(p);
|
||||
}
|
||||
|
||||
function loadStreams() {
|
||||
let div = document.getElementById('stream');
|
||||
div.innerHTML = '';
|
||||
if (window.currentThread.permissions.post) {
|
||||
// why doesn't html` work here? html.node` does
|
||||
render(div, html.node`
|
||||
<button id='streaming' onclick=${stream}>start streaming</button>
|
||||
<span>stream name:</span>
|
||||
<input id='streamname'>`);
|
||||
div.children['streamname'].oninput = event => {
|
||||
if (handle)
|
||||
window.emit('stream', {
|
||||
id: streamid,
|
||||
thread: window.currentThread.id,
|
||||
name: div.children['streamname'].value
|
||||
});
|
||||
};
|
||||
}
|
||||
div.insertAdjacentHTML('beforeend', `
|
||||
<p>streams:</p>
|
||||
<div id='streams'></div>`);
|
||||
for (let stream of window.currentThread.streams)
|
||||
addStream(stream);
|
||||
}
|
||||
|
||||
window.socket.on('stream', async msg => {
|
||||
if (msg.thread !== window.currentThread.id)
|
||||
return;
|
||||
let p = document.getElementById('stream' + msg.id);
|
||||
if (p) {
|
||||
if (msg.stopped) {
|
||||
p.remove();
|
||||
window.currentThread.streams.splice(
|
||||
window.currentThread.streams.findIndex(s => s.id === msg.id), 1);
|
||||
}
|
||||
else
|
||||
p.children['name'].innerText = msg.name ? ' - ' + msg.name : '';
|
||||
}
|
||||
else if (!msg.stopped) {
|
||||
window.currentThread.streams.push(msg);
|
||||
addStream(msg);
|
||||
}
|
||||
});
|
||||
|
||||
window.socket.on('streamdata', async msg => {
|
||||
if (!streaming[msg.id])
|
||||
return;
|
||||
let source = audioctx.createBufferSource();
|
||||
source.buffer = await audioctx.decodeAudioData(msg.audio);
|
||||
source.connect(audioctx.destination);
|
||||
source.start(/*audioStartTime*/);
|
||||
});
|
||||
|
||||
export default loadStreams;
|
41
index.js
41
index.js
|
@ -4,10 +4,12 @@ const http = require('http');
|
|||
const { Server } = require('socket.io');
|
||||
const compression = require('compression');
|
||||
|
||||
const events = Object.fromEntries(
|
||||
fs.readdirSync('./src/event')
|
||||
.map(event => [event.slice(0, -3), require('./src/event/' + event)])
|
||||
);
|
||||
const events = {};
|
||||
for (let file of fs.readdirSync('./src/events')) {
|
||||
file = require('./src/events/' + file);
|
||||
for (const event in file)
|
||||
events[event] = file[event];
|
||||
}
|
||||
|
||||
const PORT = process.env.PORT || 3435;
|
||||
|
||||
|
@ -16,10 +18,18 @@ app.use(compression());
|
|||
const server = http.createServer(app);
|
||||
const io = new Server(server, {
|
||||
cors: {
|
||||
origin: true,
|
||||
},
|
||||
origin: true
|
||||
}
|
||||
});
|
||||
|
||||
app.use(express.static('client'));
|
||||
|
||||
global.vybe = {
|
||||
users: {},
|
||||
threads: {},
|
||||
streams: {}
|
||||
};
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
for (let event in events) {
|
||||
socket.on(event, (msg, callback) => {
|
||||
|
@ -27,22 +37,27 @@ io.on('connection', (socket) => {
|
|||
callback('no such event ' + event);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
events[event](msg, callback, socket);
|
||||
}
|
||||
catch (e) {
|
||||
console.log(`${event} threw exception: `, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
socket.on('disconnect', reason => {
|
||||
let user = vybe.users[socket.username];
|
||||
if (user)
|
||||
user.sockets.splice(user.sockets.indexOf(socket), 1);
|
||||
})
|
||||
for (let id in vybe.streams) {
|
||||
const stream = vybe.streams[id];
|
||||
delete stream.listeners[socket.id];
|
||||
if (stream.socket === socket.id)
|
||||
stream.stop();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
global.vybe = {
|
||||
users: {}
|
||||
};
|
||||
|
||||
server.listen(PORT, () => {
|
||||
console.log('server running on port ' + PORT);
|
||||
});
|
||||
|
||||
app.use(express.static('client'));
|
||||
|
|
|
@ -1,119 +0,0 @@
|
|||
const db = require('../db');
|
||||
const authwrap = require('../authwrap');
|
||||
|
||||
const create_thread = async (msg, respond) => {
|
||||
// validate inputs
|
||||
if (typeof msg.name !== 'string') {
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'thread name required'
|
||||
});
|
||||
}
|
||||
if (msg.name.length > 200) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'thread name 200 chars max'
|
||||
});
|
||||
}
|
||||
// add to db
|
||||
const insert = await db.query(
|
||||
'insert into thread (name, creator) values (?, ?) returning id',
|
||||
[msg.name, msg.auth_user.id]
|
||||
);
|
||||
const thread_id = insert.rows[0].id;
|
||||
// set up permissions
|
||||
if (!msg.permissions || !msg.permissions.view_limited) {
|
||||
await db.query(
|
||||
`insert into permission (thread, type, mutable, permission, value)
|
||||
values (?, ?, ?, ?, ?)`,
|
||||
[thread_id, 'everyone', true, 'view', 'true']
|
||||
);
|
||||
if (!msg.permissions || !msg.permissions.post_limited) {
|
||||
await db.query(
|
||||
`insert into permission (thread, type, mutable, permission, value)
|
||||
values (?, ?, ?, ?, ?)`,
|
||||
[thread_id, 'everyone', true, 'post', 'true']
|
||||
);
|
||||
} else {
|
||||
await db.query(
|
||||
`insert into permission (thread, type, mutable, permission, value)
|
||||
values (?, ?, ?, ?, ?)`,
|
||||
[thread_id, 'members', true, 'post', 'true']
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await db.query(
|
||||
`insert into permission (thread, type, mutable, permission, value)
|
||||
values (?, ?, ?, ?, ?)`,
|
||||
[thread_id, 'members', true, 'view', 'true']
|
||||
);
|
||||
await db.query(
|
||||
`insert into permission (thread, type, mutable, permission, value)
|
||||
values (?, ?, ?, ?, ?)`,
|
||||
[thread_id, 'members', true, 'post', 'true']
|
||||
);
|
||||
}
|
||||
// add members
|
||||
if (Array.isArray(msg.members)) {
|
||||
for (let member of msg.members) {
|
||||
if (!member) continue;
|
||||
// get user id
|
||||
const id = await db.query('select id from user where name = ?', [
|
||||
member.name,
|
||||
]);
|
||||
if (id.rows.length === 0) {
|
||||
console.log('user not found: ' + member.name);
|
||||
continue;
|
||||
}
|
||||
await db.query(
|
||||
'insert into member (thread, user) values (?, ?)',
|
||||
[thread_id, id.rows[0].id]
|
||||
);
|
||||
if (typeof member.permissions === 'object')
|
||||
for (let permission in member.permissions)
|
||||
await db.query(`
|
||||
insert into permission (thread, type, user, mutable, permission, value)
|
||||
values (?, ?, ?, ?, ?, ?)`,
|
||||
[thread_id, 'user', id.rows[0].id, true, permission, member.permissions[permission]]);
|
||||
}
|
||||
}
|
||||
if (!msg.permissions || !msg.permissions.view_limited) {
|
||||
for (let username in vybe.users) {
|
||||
for (let socket of vybe.users[username].sockets) {
|
||||
socket.emit('thread', {
|
||||
name: msg.name,
|
||||
id: insert.rows[0].id,
|
||||
permissions: {
|
||||
is_member: false,
|
||||
view: true,
|
||||
post: !msg.permissions || !msg.permissions.post_limited
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Array.isArray(msg.members)) {
|
||||
for (let member of msg.members) {
|
||||
if (!vybe.users[member.name])
|
||||
continue;
|
||||
for (let socket of vybe.users[member.name].sockets) {
|
||||
socket.emit('thread', {
|
||||
name: msg.name,
|
||||
id: insert.rows[0].id,
|
||||
permissions: {
|
||||
is_member: true,
|
||||
view: true,
|
||||
post: true
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// respond
|
||||
return respond({
|
||||
success: true,
|
||||
id: thread_id
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = authwrap(create_thread);
|
|
@ -1,55 +0,0 @@
|
|||
const db = require('../db');
|
||||
const openpgp = require('openpgp');
|
||||
|
||||
const create_user = async (msg, respond) => {
|
||||
// validate inputs
|
||||
if (!msg.name) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'username required'
|
||||
});
|
||||
}
|
||||
if (!msg.pubkey) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'public key required'
|
||||
});
|
||||
}
|
||||
// ensure username is not taken
|
||||
const result = await db.query('select * from user where name = ?', [
|
||||
msg.name
|
||||
]);
|
||||
if (result.rows.length > 0) {
|
||||
console.log(`username already exists: ${result}`);
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'a user with this name already exists on this server'
|
||||
});
|
||||
}
|
||||
// validate public key
|
||||
try {
|
||||
await openpgp.readKey({ armoredKey: msg.pubkey });
|
||||
} catch (err) {
|
||||
console.err('error in create_user readkey: ' + err);
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'public key invalid'
|
||||
});
|
||||
}
|
||||
// add to db
|
||||
const insert = await db.query(
|
||||
'insert into user (name) values (?) returning id',
|
||||
[msg.name]
|
||||
);
|
||||
await db.query(
|
||||
'insert into key (user, pubkey, active) values (?, ?, true)',
|
||||
[insert.rows[0].id, msg.pubkey]
|
||||
)
|
||||
// respond
|
||||
return respond({
|
||||
success: true,
|
||||
id: insert.rows[0].id
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = create_user;
|
|
@ -1,155 +0,0 @@
|
|||
const db = require('../db');
|
||||
const authwrap = require('../authwrap');
|
||||
const check_permission = require('../check_permission');
|
||||
|
||||
const edit_thread = async (msg, respond) => {
|
||||
// validate inputs
|
||||
if (!msg.id || typeof msg.name !== 'string') {
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'invalid msg'
|
||||
});
|
||||
}
|
||||
if (msg.name.length > 200) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'thread name 200 chars max'
|
||||
});
|
||||
}
|
||||
const perms = await check_permission(msg.auth_user.id, msg.id);
|
||||
if (!perms.admin) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: "user doesn't have permission"
|
||||
});
|
||||
}
|
||||
// update name
|
||||
await db.query(
|
||||
'update thread set name = ? where id = ?',
|
||||
[msg.name, msg.id]
|
||||
);
|
||||
// update permissions
|
||||
let permissions = {};
|
||||
for (const p of (await db.query(
|
||||
`select type, permission, value, mutable
|
||||
from permission
|
||||
where type != 'user' and thread = ?`,
|
||||
[msg.id]
|
||||
)).rows) {
|
||||
(permissions[p.type] || (permissions[p.type] = {}))
|
||||
[p.permission] = {
|
||||
value: p.value,
|
||||
mutable: p.mutable
|
||||
};
|
||||
}
|
||||
async function setPermission(type, permission, value) {
|
||||
if (permissions[type] && permissions[type][permission]) {
|
||||
if (!permissions[type][permission].mutable) {
|
||||
respond({
|
||||
success: false,
|
||||
message: 'permission not mutable'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (permissions[type][permission].value !== value) {
|
||||
await db.query(`
|
||||
update permission set value = ?
|
||||
where thread = ? and type = ? and permission = ?`,
|
||||
[value, msg.id, type, permission]
|
||||
);
|
||||
permissions[type][permission].value = value;
|
||||
}
|
||||
}
|
||||
else {
|
||||
await db.query(
|
||||
`insert into permission (thread, type, mutable, permission, value)
|
||||
values (?, ?, ?, ?, ?)`,
|
||||
[msg.id, type, true, permission, value]
|
||||
);
|
||||
(permissions[type] || (permissions[type] = {}))
|
||||
[permission] = {
|
||||
value,
|
||||
mutable: true
|
||||
};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!msg.permissions || !msg.permissions.view_limited) {
|
||||
if (!await setPermission('everyone', 'view', 'true'))
|
||||
return;
|
||||
if (!msg.permissions || !msg.permissions.post_limited) {
|
||||
if (!await setPermission('everyone', 'post', 'true'))
|
||||
return;
|
||||
}
|
||||
else {
|
||||
if (!await setPermission('members', 'post', 'true'))
|
||||
return;
|
||||
if (!await setPermission('everyone', 'post', 'false'))
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!await setPermission('members', 'view', 'true'))
|
||||
return;
|
||||
if (!await setPermission('members', 'post', 'true'))
|
||||
return;
|
||||
if (!await setPermission('everyone', 'view', 'false'))
|
||||
return;
|
||||
if (!await setPermission('everyone', 'post', 'false'))
|
||||
return;
|
||||
}
|
||||
permissions.everyone.view.value = permissions.everyone.view.value === 'true';
|
||||
permissions.everyone.post.value = permissions.everyone.post.value === 'true';
|
||||
if (!permissions.members) permissions.members = {};
|
||||
if (!permissions.members.view)
|
||||
permissions.members.view = { value: 'true', mutable: true };
|
||||
if (!permissions.members.post)
|
||||
permissions.members.post = { value: 'true', mutable: true };
|
||||
permissions.members.view.value = permissions.members.view.value === 'true';
|
||||
permissions.members.post.value = permissions.members.post.value === 'true';
|
||||
if (!msg.permissions || !msg.permissions.view_limited) {
|
||||
for (let username in vybe.users) {
|
||||
for (let socket of vybe.users[username].sockets) {
|
||||
socket.emit('thread', {
|
||||
name: msg.name,
|
||||
id: msg.id,
|
||||
permissions: {
|
||||
is_member: false,
|
||||
view: true,
|
||||
post: !msg.permissions || !msg.permissions.post_limited,
|
||||
...permissions
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (let member of (await db.query(
|
||||
`select user.name, user.id from thread
|
||||
join member on thread.id = member.thread
|
||||
join user on user.id = member.user
|
||||
where thread.id = ?`,
|
||||
[msg.id]
|
||||
)).rows) {
|
||||
if (!vybe.users[member.name])
|
||||
continue;
|
||||
for (let socket of vybe.users[member.name].sockets) {
|
||||
socket.emit('thread', {
|
||||
name: msg.name,
|
||||
id: msg.id,
|
||||
permissions: {
|
||||
is_member: true,
|
||||
view: true,
|
||||
post: true,
|
||||
admin: member.id === msg.auth_user.id && perms.admin,
|
||||
...permissions
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return respond({
|
||||
success: true
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = authwrap(edit_thread);
|
|
@ -1,42 +0,0 @@
|
|||
const db = require('../db');
|
||||
const authwrap = require('../authwrap');
|
||||
const check_permission = require('../check_permission');
|
||||
|
||||
const get_history = async (msg, respond) => {
|
||||
if (msg.before && isNaN(Number(msg.before))) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'before must be a number',
|
||||
});
|
||||
}
|
||||
if (!msg.thread) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'thread ID required',
|
||||
});
|
||||
}
|
||||
if (!(await check_permission(msg.auth_user.id, msg.thread)).view) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: "you can't view this thread",
|
||||
});
|
||||
}
|
||||
const messages = await db.query(
|
||||
`select user.name, post.id, content from post
|
||||
join user on post.user = user.id
|
||||
${msg.before ? 'where post.id < ? and' : 'where'}
|
||||
thread = ?
|
||||
order by post.created desc
|
||||
limit 101`,
|
||||
msg.before ? [msg.before, msg.thread] : [msg.thread]
|
||||
);
|
||||
return respond({
|
||||
success: true,
|
||||
messages: messages.rows
|
||||
.slice(0, 100)
|
||||
.map((i) => ({ id: i.id, name: i.name, message: i.content })),
|
||||
more: messages.rows.length > 100,
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = authwrap(get_history);
|
|
@ -1,30 +0,0 @@
|
|||
const db = require('../db');
|
||||
const authwrap = require('../authwrap');
|
||||
|
||||
const get_keys = async (msg, respond) => {
|
||||
// validate inputs
|
||||
if (!msg.names) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'user names required'
|
||||
});
|
||||
}
|
||||
if (typeof msg.names !== 'object') {
|
||||
return respond({
|
||||
success: false,
|
||||
message: "can't iterate user names"
|
||||
});
|
||||
}
|
||||
const keys = await db.query( // todo: encryption !
|
||||
`select name from user where name in
|
||||
(${msg.names.map((i) => '?').join(',')})`,
|
||||
msg.names
|
||||
);
|
||||
// respond
|
||||
return respond({
|
||||
success: true,
|
||||
keys: keys.rows
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = authwrap(get_keys);
|
|
@ -1,28 +0,0 @@
|
|||
const db = require('../db');
|
||||
const authwrap = require('../authwrap');
|
||||
const check_permission = require('../check_permission');
|
||||
|
||||
const get_space = async (msg, respond) => {
|
||||
if (!msg.thread) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'thread ID required'
|
||||
});
|
||||
}
|
||||
if (!(await check_permission(msg.auth_user.id, msg.thread)).view) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: "you can't view this thread"
|
||||
});
|
||||
}
|
||||
const spans = await db.query(
|
||||
'select id, content, x, y, scale from span where thread = ? and deleted = false',
|
||||
[msg.thread]
|
||||
);
|
||||
return respond({
|
||||
success: true,
|
||||
spans: spans.rows
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = authwrap(get_space);
|
|
@ -1,76 +0,0 @@
|
|||
const db = require('../db');
|
||||
const authwrap = require('../authwrap');
|
||||
const check_permission = require('../check_permission');
|
||||
|
||||
const get_thread = async (msg, respond) => {
|
||||
if (!msg.thread) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'thread ID required'
|
||||
});
|
||||
}
|
||||
let perms = await check_permission(msg.auth_user.id, msg.thread);
|
||||
if (!perms.view) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: "you can't view this thread"
|
||||
});
|
||||
}
|
||||
const thread = await db.query(
|
||||
`select thread.name, user.name as user, user.id from thread
|
||||
left join member on thread.id = member.thread
|
||||
left join user on user.id = member.user
|
||||
where thread.id = ?`,
|
||||
[msg.thread]
|
||||
);
|
||||
const permissions = await db.query(
|
||||
`select type, user, permission, value, mutable
|
||||
from permission where thread = ?`,
|
||||
[msg.thread]
|
||||
);
|
||||
let members = Object.fromEntries(thread.rows.map(member =>
|
||||
[member.id, { name: member.user }]
|
||||
));
|
||||
for (let permission of permissions.rows) {
|
||||
const member = members[permission.user];
|
||||
if (member)
|
||||
(member.permissions || (member.permissions = {}))
|
||||
[permission.permission] = {
|
||||
value: permission.value,
|
||||
mutable: permission.mutable
|
||||
};
|
||||
else
|
||||
(perms[permission.type] || (perms[permission.type] = {}))
|
||||
[permission.permission] = {
|
||||
value: permission.value,
|
||||
mutable: permission.mutable
|
||||
};
|
||||
}
|
||||
function makeBool(type, permission) {
|
||||
if (perms[type]) {
|
||||
if (perms[type][permission])
|
||||
perms[type][permission].value = perms[type][permission].value === 'true';
|
||||
else
|
||||
perms[type][permission] = { value: false, mutable: true };
|
||||
} else
|
||||
(perms[type] = {})[permission] = { value: false, mutable: true };
|
||||
}
|
||||
makeBool('everyone', 'view');
|
||||
makeBool('everyone', 'post');
|
||||
makeBool('members', 'view');
|
||||
makeBool('members', 'post');
|
||||
return respond({
|
||||
success: true,
|
||||
thread: {
|
||||
id: msg.thread,
|
||||
name: thread.rows[0].name,
|
||||
permissions: perms,
|
||||
members: Object.entries(members).map(member => ({
|
||||
id: member[0],
|
||||
...member[1]
|
||||
}))
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = authwrap(get_thread);
|
|
@ -1,32 +0,0 @@
|
|||
const db = require('../db');
|
||||
const authwrap = require('../authwrap');
|
||||
const check_permission = require('../check_permission');
|
||||
|
||||
const list_threads = async (msg, respond) => {
|
||||
const threads = await db.query(
|
||||
`select name, id from thread
|
||||
join permission on thread.id = permission.thread
|
||||
left join member on thread.id = member.thread
|
||||
where permission.permission = 'view'
|
||||
and permission.value = 'true'
|
||||
and ((permission.type = 'everyone') or
|
||||
permission.type = 'members' and member.user = ?)
|
||||
group by thread.id
|
||||
order by thread.created desc`,
|
||||
[msg.auth_user.id]
|
||||
);
|
||||
// respond
|
||||
const rows = [];
|
||||
for (let thread of threads.rows) {
|
||||
rows.push({
|
||||
...thread,
|
||||
permissions: await check_permission(msg.auth_user.id, thread.id)
|
||||
});
|
||||
}
|
||||
return respond({
|
||||
success: true,
|
||||
threads: rows
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = authwrap(list_threads);
|
|
@ -2,7 +2,7 @@ const db = require('../db');
|
|||
const authwrap = require('../authwrap');
|
||||
const check_permission = require('../check_permission');
|
||||
|
||||
const send_message = async (msg, respond) => {
|
||||
async function send_message(msg, respond) {
|
||||
if (!msg.thread) {
|
||||
return respond({
|
||||
success: false,
|
||||
|
@ -30,8 +30,7 @@ const send_message = async (msg, respond) => {
|
|||
// get perms
|
||||
const permissions = await db.query(
|
||||
"select * from permission where thread = ? and type = 'everyone' and value = 'true' and permission = 'view'",
|
||||
[msg.thread]
|
||||
);
|
||||
[msg.thread]);
|
||||
for (let username in vybe.users) {
|
||||
if (permissions.rows.length > 0 || members.includes(username)) {
|
||||
for (let s of vybe.users[username].sockets) {
|
||||
|
@ -48,6 +47,46 @@ const send_message = async (msg, respond) => {
|
|||
success: true,
|
||||
id: id.rows[0].id
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = authwrap(send_message);
|
||||
async function get_history(msg, respond) {
|
||||
if (msg.before && isNaN(Number(msg.before))) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'before must be a number'
|
||||
});
|
||||
}
|
||||
if (!msg.thread) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'thread ID required'
|
||||
});
|
||||
}
|
||||
if (!(await check_permission(msg.auth_user.id, msg.thread)).view) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: "you can't view this thread"
|
||||
});
|
||||
}
|
||||
const messages = await db.query(
|
||||
`select user.name, post.id, content from post
|
||||
join user on post.user = user.id
|
||||
${msg.before ? 'where post.id < ? and' : 'where'}
|
||||
thread = ?
|
||||
order by post.created desc
|
||||
limit 101`,
|
||||
msg.before ? [msg.before, msg.thread] : [msg.thread]
|
||||
);
|
||||
return respond({
|
||||
success: true,
|
||||
messages: messages.rows
|
||||
.slice(0, 100)
|
||||
.map((i) => ({ id: i.id, name: i.name, message: i.content })),
|
||||
more: messages.rows.length > 100
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
send_message: authwrap(send_message),
|
||||
get_history: authwrap(get_history)
|
||||
};
|
|
@ -2,7 +2,30 @@ const db = require('../db');
|
|||
const authwrap = require('../authwrap');
|
||||
const check_permission = require('../check_permission');
|
||||
|
||||
const save_span = async (msg, respond, socket) => {
|
||||
async function get_space(msg, respond) {
|
||||
if (!msg.thread) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'thread ID required'
|
||||
});
|
||||
}
|
||||
if (!(await check_permission(msg.auth_user.id, msg.thread)).view) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: "you can't view this thread"
|
||||
});
|
||||
}
|
||||
const spans = await db.query(
|
||||
'select id, content, x, y, scale from span where thread = ? and deleted = false',
|
||||
[msg.thread]
|
||||
);
|
||||
return respond({
|
||||
success: true,
|
||||
spans: spans.rows
|
||||
});
|
||||
}
|
||||
|
||||
async function save_span(msg, respond, socket) {
|
||||
if (!msg.thread) {
|
||||
return respond({
|
||||
success: false,
|
||||
|
@ -62,6 +85,9 @@ const save_span = async (msg, respond, socket) => {
|
|||
success: true,
|
||||
id
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = authwrap(save_span);
|
||||
module.exports = {
|
||||
get_space: authwrap(get_space),
|
||||
save_span: authwrap(save_span)
|
||||
};
|
|
@ -0,0 +1,132 @@
|
|||
const db = require('../db');
|
||||
const authwrap = require('../authwrap');
|
||||
const check_permission = require('../check_permission');
|
||||
|
||||
let newstreamid = 0;
|
||||
|
||||
async function stream(msg, respond, socket) {
|
||||
if (!(await check_permission(msg.auth_user.id, msg.thread)).post) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: "user doesn't have permission"
|
||||
});
|
||||
}
|
||||
let stream;
|
||||
let thread = vybe.threads[msg.thread] || (vybe.threads[msg.thread] = {
|
||||
streams: []
|
||||
});
|
||||
async function send() {
|
||||
const permissions = await db.query(
|
||||
"select * from permission where thread = ? and type = 'everyone' and value = 'true' and permission = 'view'",
|
||||
[msg.thread]);
|
||||
if (permissions.rows.length) {
|
||||
for (let user in vybe.users) {
|
||||
user = vybe.users[user];
|
||||
for (let socket of user.sockets)
|
||||
socket.emit('stream', stream);
|
||||
}
|
||||
} else {
|
||||
const members = (
|
||||
await db.query(
|
||||
'select name from user join member on member.user = user.id where member.thread = ?',
|
||||
[msg.thread]
|
||||
)
|
||||
).rows.map((i) => i.name);
|
||||
for (let member of members) {
|
||||
let user = vybe.users[member];
|
||||
if (user)
|
||||
for (let socket of user.sockets)
|
||||
socket.emit('stream', stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (msg.id) {
|
||||
stream = vybe.streams[msg.id];
|
||||
if (!stream)
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'stream not found'
|
||||
});
|
||||
if (msg.stop) {
|
||||
stream.stop();
|
||||
return respond({
|
||||
success: true
|
||||
});
|
||||
}
|
||||
stream = stream.stream;
|
||||
stream.name = msg.name;
|
||||
}
|
||||
else {
|
||||
stream = {
|
||||
id: newstreamid++,
|
||||
thread: msg.thread,
|
||||
user: msg.auth_user.name,
|
||||
name: msg.name
|
||||
};
|
||||
thread.streams.push(stream);
|
||||
vybe.streams[stream.id] = {
|
||||
stream,
|
||||
listeners: {},
|
||||
socket: socket.id,
|
||||
stop: () => {
|
||||
stream.stopped = true;
|
||||
thread.streams.splice(thread.streams.findIndex(s => s.id === stream.id), 1);
|
||||
delete vybe.streams[stream.id];
|
||||
send();
|
||||
}
|
||||
};
|
||||
}
|
||||
await send();
|
||||
respond({
|
||||
success: true,
|
||||
id: stream.id
|
||||
});
|
||||
}
|
||||
|
||||
async function streamdata(msg, respond) {
|
||||
let stream = vybe.streams[msg.id];
|
||||
if (!stream) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'stream not found'
|
||||
});
|
||||
}
|
||||
if (stream.stream.user !== msg.auth_user.name) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: "stream doesn't belong to user"
|
||||
});
|
||||
}
|
||||
for (let id in stream.listeners)
|
||||
stream.listeners[id].emit('streamdata', msg);
|
||||
respond({
|
||||
success: true
|
||||
});
|
||||
}
|
||||
|
||||
async function play_stream(msg, respond, socket) {
|
||||
if (!(await check_permission(msg.auth_user.id, msg.thread)).view) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: "user doesn't have permission"
|
||||
});
|
||||
}
|
||||
if (!vybe.streams[msg.id])
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'stream not found'
|
||||
});
|
||||
if (msg.playing)
|
||||
vybe.streams[msg.id].listeners[socket.id] = socket;
|
||||
else
|
||||
delete vybe.streams[msg.id].listeners[socket.id];
|
||||
respond({
|
||||
success: true
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
stream: authwrap(stream),
|
||||
streamdata: authwrap(streamdata),
|
||||
play_stream: authwrap(play_stream)
|
||||
};
|
|
@ -0,0 +1,377 @@
|
|||
const db = require('../db');
|
||||
const authwrap = require('../authwrap');
|
||||
const check_permission = require('../check_permission');
|
||||
|
||||
async function create_thread(msg, respond) {
|
||||
// validate inputs
|
||||
if (typeof msg.name !== 'string') {
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'thread name required'
|
||||
});
|
||||
}
|
||||
if (msg.name.length > 200) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'thread name 200 chars max'
|
||||
});
|
||||
}
|
||||
// add to db
|
||||
const insert = await db.query(
|
||||
'insert into thread (name, creator) values (?, ?) returning id',
|
||||
[msg.name, msg.auth_user.id]
|
||||
);
|
||||
const thread_id = insert.rows[0].id;
|
||||
// set up permissions
|
||||
if (!msg.permissions || !msg.permissions.view_limited) {
|
||||
await db.query(
|
||||
`insert into permission (thread, type, mutable, permission, value)
|
||||
values (?, ?, ?, ?, ?)`,
|
||||
[thread_id, 'everyone', true, 'view', 'true']
|
||||
);
|
||||
if (!msg.permissions || !msg.permissions.post_limited) {
|
||||
await db.query(
|
||||
`insert into permission (thread, type, mutable, permission, value)
|
||||
values (?, ?, ?, ?, ?)`,
|
||||
[thread_id, 'everyone', true, 'post', 'true']
|
||||
);
|
||||
} else {
|
||||
await db.query(
|
||||
`insert into permission (thread, type, mutable, permission, value)
|
||||
values (?, ?, ?, ?, ?)`,
|
||||
[thread_id, 'members', true, 'post', 'true']
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await db.query(
|
||||
`insert into permission (thread, type, mutable, permission, value)
|
||||
values (?, ?, ?, ?, ?)`,
|
||||
[thread_id, 'members', true, 'view', 'true']
|
||||
);
|
||||
await db.query(
|
||||
`insert into permission (thread, type, mutable, permission, value)
|
||||
values (?, ?, ?, ?, ?)`,
|
||||
[thread_id, 'members', true, 'post', 'true']
|
||||
);
|
||||
}
|
||||
// add members
|
||||
if (Array.isArray(msg.members)) {
|
||||
for (let member of msg.members) {
|
||||
if (!member) continue;
|
||||
// get user id
|
||||
const id = await db.query('select id from user where name = ?', [
|
||||
member.name,
|
||||
]);
|
||||
if (id.rows.length === 0) {
|
||||
console.log('user not found: ' + member.name);
|
||||
continue;
|
||||
}
|
||||
await db.query(
|
||||
'insert into member (thread, user) values (?, ?)',
|
||||
[thread_id, id.rows[0].id]
|
||||
);
|
||||
if (typeof member.permissions === 'object')
|
||||
for (let permission in member.permissions)
|
||||
await db.query(`
|
||||
insert into permission (thread, type, user, mutable, permission, value)
|
||||
values (?, ?, ?, ?, ?, ?)`,
|
||||
[thread_id, 'user', id.rows[0].id, true, permission, member.permissions[permission]]);
|
||||
}
|
||||
}
|
||||
if (!msg.permissions || !msg.permissions.view_limited) {
|
||||
for (let username in vybe.users) {
|
||||
for (let socket of vybe.users[username].sockets) {
|
||||
socket.emit('thread', {
|
||||
name: msg.name,
|
||||
id: insert.rows[0].id,
|
||||
permissions: {
|
||||
is_member: false,
|
||||
view: true,
|
||||
post: !msg.permissions || !msg.permissions.post_limited
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Array.isArray(msg.members)) {
|
||||
for (let member of msg.members) {
|
||||
if (!vybe.users[member.name])
|
||||
continue;
|
||||
for (let socket of vybe.users[member.name].sockets) {
|
||||
socket.emit('thread', {
|
||||
name: msg.name,
|
||||
id: insert.rows[0].id,
|
||||
permissions: {
|
||||
is_member: true,
|
||||
view: true,
|
||||
post: true
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// respond
|
||||
return respond({
|
||||
success: true,
|
||||
id: thread_id
|
||||
});
|
||||
}
|
||||
|
||||
async function list_threads(msg, respond) {
|
||||
// this can be optimized by merging the permission check into here
|
||||
let threads = await db.query(
|
||||
`select name, id from thread
|
||||
join permission on thread.id = permission.thread
|
||||
left join member on thread.id = member.thread
|
||||
where permission.permission = 'view'
|
||||
and permission.value = 'true'
|
||||
and ((permission.type = 'everyone') or
|
||||
permission.type = 'members' and member.user = ?)
|
||||
group by thread.id
|
||||
order by thread.created desc`,
|
||||
[msg.auth_user.id]
|
||||
);
|
||||
threads = await Promise.all(threads.rows.map(async thread => {
|
||||
if (vybe.threads[thread.id])
|
||||
Object.assign(thread, vybe.threads[thread.id]);
|
||||
else
|
||||
thread.streams = [];
|
||||
thread.permissions = await check_permission(msg.auth_user.id, thread.id);
|
||||
return thread;
|
||||
}));
|
||||
return respond({
|
||||
success: true,
|
||||
threads
|
||||
});
|
||||
}
|
||||
|
||||
async function get_thread(msg, respond) {
|
||||
if (!msg.thread) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'thread ID required'
|
||||
});
|
||||
}
|
||||
let perms = await check_permission(msg.auth_user.id, msg.thread);
|
||||
if (!perms.view) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: "you can't view this thread"
|
||||
});
|
||||
}
|
||||
let thread = await db.query(
|
||||
`select thread.name, user.name as user, user.id from thread
|
||||
left join member on thread.id = member.thread
|
||||
left join user on user.id = member.user
|
||||
where thread.id = ?`,
|
||||
[msg.thread]
|
||||
);
|
||||
const permissions = await db.query(
|
||||
`select type, user, permission, value, mutable
|
||||
from permission where thread = ?`,
|
||||
[msg.thread]
|
||||
);
|
||||
let members = Object.fromEntries(thread.rows.map(member =>
|
||||
[member.id, { name: member.user }]
|
||||
));
|
||||
for (let permission of permissions.rows) {
|
||||
const member = members[permission.user];
|
||||
if (member)
|
||||
(member.permissions || (member.permissions = {}))
|
||||
[permission.permission] = {
|
||||
value: permission.value,
|
||||
mutable: permission.mutable
|
||||
};
|
||||
else
|
||||
(perms[permission.type] || (perms[permission.type] = {}))
|
||||
[permission.permission] = {
|
||||
value: permission.value,
|
||||
mutable: permission.mutable
|
||||
};
|
||||
}
|
||||
function makeBool(type, permission, fallback) {
|
||||
if (perms[type]) {
|
||||
if (perms[type][permission])
|
||||
perms[type][permission].value = perms[type][permission].value === 'true';
|
||||
else
|
||||
perms[type][permission] = { value: fallback, mutable: true };
|
||||
} else
|
||||
(perms[type] = {})[permission] = { value: fallback, mutable: true };
|
||||
}
|
||||
makeBool('everyone', 'view', false);
|
||||
makeBool('everyone', 'post', false);
|
||||
makeBool('members', 'view', true);
|
||||
makeBool('members', 'post', true);
|
||||
thread = {
|
||||
id: msg.thread,
|
||||
name: thread.rows[0].name,
|
||||
permissions: perms,
|
||||
members: Object.entries(members).map(member => ({
|
||||
id: member[0],
|
||||
...member[1]
|
||||
}))
|
||||
};
|
||||
if (vybe.threads[thread.id])
|
||||
Object.assign(thread, vybe.threads[thread.id]);
|
||||
else
|
||||
thread.streams = [];
|
||||
return respond({
|
||||
success: true,
|
||||
thread
|
||||
});
|
||||
}
|
||||
|
||||
async function edit_thread(msg, respond) {
|
||||
// validate inputs
|
||||
if (!msg.id || typeof msg.name !== 'string') {
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'invalid msg'
|
||||
});
|
||||
}
|
||||
if (msg.name.length > 200) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'thread name 200 chars max'
|
||||
});
|
||||
}
|
||||
const perms = await check_permission(msg.auth_user.id, msg.id);
|
||||
if (!perms.admin) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: "user doesn't have permission"
|
||||
});
|
||||
}
|
||||
// update name
|
||||
await db.query(
|
||||
'update thread set name = ? where id = ?',
|
||||
[msg.name, msg.id]
|
||||
);
|
||||
// update permissions
|
||||
let permissions = {};
|
||||
for (const p of (await db.query(
|
||||
`select type, permission, value, mutable
|
||||
from permission
|
||||
where type != 'user' and thread = ?`,
|
||||
[msg.id]
|
||||
)).rows) {
|
||||
(permissions[p.type] || (permissions[p.type] = {}))
|
||||
[p.permission] = {
|
||||
value: p.value,
|
||||
mutable: p.mutable
|
||||
};
|
||||
}
|
||||
async function setPermission(type, permission, value) {
|
||||
if (permissions[type] && permissions[type][permission]) {
|
||||
if (!permissions[type][permission].mutable) {
|
||||
respond({
|
||||
success: false,
|
||||
message: 'permission not mutable'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (permissions[type][permission].value !== value) {
|
||||
await db.query(`
|
||||
update permission set value = ?
|
||||
where thread = ? and type = ? and permission = ?`,
|
||||
[value, msg.id, type, permission]
|
||||
);
|
||||
permissions[type][permission].value = value;
|
||||
}
|
||||
}
|
||||
else {
|
||||
await db.query(
|
||||
`insert into permission (thread, type, mutable, permission, value)
|
||||
values (?, ?, ?, ?, ?)`,
|
||||
[msg.id, type, true, permission, value]
|
||||
);
|
||||
(permissions[type] || (permissions[type] = {}))
|
||||
[permission] = {
|
||||
value,
|
||||
mutable: true
|
||||
};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!msg.permissions || !msg.permissions.view_limited) {
|
||||
if (!await setPermission('everyone', 'view', 'true'))
|
||||
return;
|
||||
if (!msg.permissions || !msg.permissions.post_limited) {
|
||||
if (!await setPermission('everyone', 'post', 'true'))
|
||||
return;
|
||||
}
|
||||
else {
|
||||
if (!await setPermission('members', 'post', 'true'))
|
||||
return;
|
||||
if (!await setPermission('everyone', 'post', 'false'))
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!await setPermission('members', 'view', 'true'))
|
||||
return;
|
||||
if (!await setPermission('members', 'post', 'true'))
|
||||
return;
|
||||
if (!await setPermission('everyone', 'view', 'false'))
|
||||
return;
|
||||
if (!await setPermission('everyone', 'post', 'false'))
|
||||
return;
|
||||
}
|
||||
permissions.everyone.view.value = permissions.everyone.view.value === 'true';
|
||||
permissions.everyone.post.value = permissions.everyone.post.value === 'true';
|
||||
if (!permissions.members) permissions.members = {};
|
||||
if (!permissions.members.view)
|
||||
permissions.members.view = { value: 'true', mutable: true };
|
||||
if (!permissions.members.post)
|
||||
permissions.members.post = { value: 'true', mutable: true };
|
||||
permissions.members.view.value = permissions.members.view.value === 'true';
|
||||
permissions.members.post.value = permissions.members.post.value === 'true';
|
||||
let members = Object.fromEntries((await db.query(
|
||||
`select user.name, user.id from thread
|
||||
join member on thread.id = member.thread
|
||||
join user on user.id = member.user
|
||||
where thread.id = ?`,
|
||||
[msg.id]
|
||||
)).rows.map(row => [row.name, row.id]));
|
||||
if (!msg.permissions || !msg.permissions.view_limited) {
|
||||
for (let username in vybe.users)
|
||||
for (let socket of vybe.users[username].sockets)
|
||||
socket.emit('thread', {
|
||||
name: msg.name,
|
||||
id: msg.id,
|
||||
permissions: {
|
||||
is_member: username in members,
|
||||
view: true,
|
||||
post: !msg.permissions || !msg.permissions.post_limited,
|
||||
...permissions
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
for (let member in members) {
|
||||
if (!vybe.users[member])
|
||||
continue;
|
||||
for (let socket of vybe.users[member].sockets)
|
||||
socket.emit('thread', {
|
||||
name: msg.name,
|
||||
id: msg.id,
|
||||
permissions: {
|
||||
is_member: true,
|
||||
view: true,
|
||||
post: true,
|
||||
admin: member.id === msg.auth_user.id && perms.admin,
|
||||
...permissions
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return respond({
|
||||
success: true
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
create_thread: authwrap(create_thread),
|
||||
list_threads: authwrap(list_threads),
|
||||
get_thread: authwrap(get_thread),
|
||||
edit_thread: authwrap(edit_thread)
|
||||
};
|
|
@ -1,7 +1,59 @@
|
|||
const db = require('../db');
|
||||
const authwrap = require('../authwrap');
|
||||
const openpgp = require('openpgp');
|
||||
|
||||
const authenticate = async (msg, respond, socket) => {
|
||||
async function create_user(msg, respond) {
|
||||
// validate inputs
|
||||
if (!msg.name) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'username required'
|
||||
});
|
||||
}
|
||||
if (!msg.pubkey) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'public key required'
|
||||
});
|
||||
}
|
||||
// ensure username is not taken
|
||||
const result = await db.query('select * from user where name = ?', [
|
||||
msg.name
|
||||
]);
|
||||
if (result.rows.length > 0) {
|
||||
console.log(`username already exists: ${result}`);
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'a user with this name already exists on this server'
|
||||
});
|
||||
}
|
||||
// validate public key
|
||||
try {
|
||||
await openpgp.readKey({ armoredKey: msg.pubkey });
|
||||
} catch (err) {
|
||||
console.err('error in create_user readkey: ' + err);
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'public key invalid'
|
||||
});
|
||||
}
|
||||
// add to db
|
||||
const insert = await db.query(
|
||||
'insert into user (name) values (?) returning id',
|
||||
[msg.name]
|
||||
);
|
||||
await db.query(
|
||||
'insert into key (user, pubkey, active) values (?, ?, true)',
|
||||
[insert.rows[0].id, msg.pubkey]
|
||||
)
|
||||
// respond
|
||||
return respond({
|
||||
success: true,
|
||||
id: insert.rows[0].id
|
||||
});
|
||||
}
|
||||
|
||||
async function authenticate(msg, respond, socket) {
|
||||
if (!msg.name || !msg.message) {
|
||||
return respond({
|
||||
success: false,
|
||||
|
@ -91,6 +143,36 @@ const authenticate = async (msg, respond, socket) => {
|
|||
message: 'message signature verification failed'
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = authenticate;
|
||||
async function get_keys(msg, respond) {
|
||||
// validate inputs
|
||||
if (!msg.names) {
|
||||
return respond({
|
||||
success: false,
|
||||
message: 'user names required'
|
||||
});
|
||||
}
|
||||
if (typeof msg.names !== 'object') {
|
||||
return respond({
|
||||
success: false,
|
||||
message: "can't iterate user names"
|
||||
});
|
||||
}
|
||||
const keys = await db.query( // todo: encryption !
|
||||
`select name from user where name in
|
||||
(${msg.names.map((i) => '?').join(',')})`,
|
||||
msg.names
|
||||
);
|
||||
// respond
|
||||
return respond({
|
||||
success: true,
|
||||
keys: keys.rows
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
create_user,
|
||||
authenticate,
|
||||
get_keys: authwrap(get_keys)
|
||||
};
|
Loading…
Reference in New Issue