bugfixes and style improvements

main
june moretz 2023-12-28 14:59:17 -06:00
parent 0b830174d3
commit 05bbdc0d60
5 changed files with 524 additions and 455 deletions

View File

@ -1,93 +1,102 @@
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');
window.currentThreadId = thread.id;
window.earliestMessage = null;
document.getElementById("messages").innerHTML = "";
document.getElementById("threadname").textContent = thread.name;
loadMessages();
if (window.currentThreadId)
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", {
before: window.earliestMessage,
thread: window.currentThreadId,
}, msg => {
if (msg.messages.length > 0) {
window.earliestMessage = msg.messages[msg.messages.length - 1].id;
for (let message of msg.messages)
document.getElementById("messages").prepend(html.node`
window.emit(
"get_history",
{
before: window.earliestMessage,
thread: window.currentThreadId,
},
(msg) => {
if (msg.messages.length > 0) {
window.earliestMessage = msg.messages[msg.messages.length - 1].id;
for (let message of msg.messages)
document.getElementById("messages").prepend(html.node`
<div class='message'>
<strong>${message.name}: </strong>
${message.message}
</div>`);
}
if (!msg.more)
document.getElementById("loadmore").classList.add("hidden");
else
document.getElementById("loadmore").classList.remove("hidden");
});
}
if (!msg.more)
document.getElementById("loadmore").classList.add("hidden");
else document.getElementById("loadmore").classList.remove("hidden");
}
);
}
function addThread(thread) {
let node = html.node`
<div class='thread' onclick=${() => {
chooseThread(thread);
if (!thread.permissions.post) {
document.getElementById("msginput").classList.add("hidden");
} else {
document.getElementById("msginput").classList.remove("hidden");
}
}}>${thread.name}</div>`;
node.id = `thread${thread.id}`;
document.getElementById("threadlist").appendChild(node);
function addThread(thread, top) {
let node = html.node`
<button class='thread tab' onclick=${() => chooseThread(thread)}>${
thread.name
}</button>`;
node.id = `thread${thread.id}`;
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("membername").value = "";
const name = document.getElementById("membername").value;
window.threadmembers.push(name);
document
.getElementById("memberlist")
.appendChild(html.node`<p class='member'>${name}</p>`);
document.getElementById("membername").value = "";
}
async function createThread(e) {
e.preventDefault();
let name = document.getElementById("newthreadname");
if (!name.value) {
name.insertAdjacentHTML('afterend', `<p>name cannot be empty</p>`);
return;
}
let members = window.threadmembers.map(name => ({ name }));
const perms = document.querySelector(
'input[name="permissions"]:checked').value;
if (perms === "private_view")
members = (await new Promise(resolve =>
window.emit("get_keys", { names: window.threadmembers }, resolve)
)).keys;
let permissions;
if (perms === "public") {
permissions = {
view_limited: false,
post_limited: false
};
} else if (perms === "private_post") {
permissions = {
view_limited: false,
post_limited: true
};
} else if (perms === "private_view") {
permissions = {
view_limited: true,
post_limited: true
};
// generate key
/* wip
e.preventDefault();
let name = document.getElementById("newthreadname");
if (!name.value) {
name.insertAdjacentHTML("afterend", `<p>name cannot be empty</p>`);
return;
}
let members = window.threadmembers.map((name) => ({ name }));
const perms = document.querySelector(
'input[name="permissions"]:checked'
).value;
if (perms === "private_view")
members = (
await new Promise((resolve) =>
window.emit("get_keys", { names: window.threadmembers }, resolve)
)
).keys;
let permissions;
if (perms === "public") {
permissions = {
view_limited: false,
post_limited: false,
};
} else if (perms === "private_post") {
permissions = {
view_limited: false,
post_limited: true,
};
} else if (perms === "private_view") {
permissions = {
view_limited: true,
post_limited: true,
};
// generate key
/* wip
var buf = new Uint8Array(32);
crypto.getRandomValues(buf);
const key = aesjs.utils.hex.fromBytes(Array.from(buf));
@ -100,46 +109,51 @@ async function createThread(e) {
});
}
*/
}
window.emit("create_thread", {
name: name.value,
permissions,
members
}, msg => {
chooseThread({
name: name.value,
id: msg.id
});
document.getElementById('createthread').remove();
document.getElementById("loadmore").classList.add("hidden");
document.getElementById("msginput").classList.remove("hidden");
});
}
window.emit(
"create_thread",
{
name: name.value,
permissions,
members,
},
(msg) => {
chooseThread({
name: name.value,
id: msg.id,
});
// 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;
window.emit("send_message", {
message: msg,
thread: window.currentThreadId
});
document.getElementById("msg").value = "";
e.preventDefault();
const msg = document.getElementById("msg").value;
if (!msg) return;
window.emit("send_message", {
message: msg,
thread: window.currentThreadId,
});
document.getElementById("msg").value = "";
}
function newThread(e) {
let form = document.getElementById('createthread');
if (form) {
form.remove();
e.target.textContent = 'create';
}
else {
window.threadmembers = [ window.name ];
document.getElementById('threads').insertAdjacentElement('afterend', html.node`
function newThread() {
let form = document.getElementById("createthread");
if (form) {
form.remove();
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,77 +167,80 @@ 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();
addMember();
}
}}/>
if (e.key == "Enter") {
e.preventDefault();
addMember();
}
}}/>
<button id="addmember" onclick=${addMember}>add</button>
<div id="memberlist">
<p class='member'>${window.name}</p>
</div>
<br />
<input id="submitthread" type="submit" value="create" />
<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`
<div id="threads" class="column">
<h3>vybe</h3>
<h4>threads</h4>
<div id="threadlist">loading...</div>
<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>
<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>
`);
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>
</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>
<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>
`
);
window.socket.on("new_message", (msg) => {
if (msg.thread !== window.currentThreadId)
return;
document.getElementById("messages").appendChild(html.node`
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 => {
document.getElementById("threadlist").innerHTML = "";
for (let thread of msg.threads)
addThread(thread);
chooseThread(msg.threads[0]);
window.emit("list_threads", {}, (msg) => {
document.getElementById("threadlist").innerHTML = "";
for (let thread of msg.threads) addThread(thread);
chooseThread(msg.threads[0]);
});

View File

@ -1,90 +1,120 @@
import { render, html } from '/uhtml.js';
import { render, html } from "/uhtml.js";
function rand() {
let str = "";
const lookups =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".split("");
while (str.length < 16) {
const n = Math.random() * lookups.length;
str += lookups[Math.floor(n)];
}
return str;
let str = "";
const lookups =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".split("");
while (str.length < 16) {
const n = Math.random() * lookups.length;
str += lookups[Math.floor(n)];
}
return str;
}
async function auth() {
const sig = await openpgp.sign({
message: new openpgp.CleartextMessage("vybe_auth " + window.session, ""),
signingKeys: window.keys.priv,
});
window.socket.emit("authenticate", { name: window.name, message: sig },
msg => {
if (!msg.success) {
console.log('authenticate failed');
return;
}
document.getElementById("register").remove();
import('/app.js');
});
const sig = await openpgp.sign({
message: new openpgp.CleartextMessage("vybe_auth " + window.session, ""),
signingKeys: window.keys.priv,
});
window.socket.emit(
"authenticate",
{ name: window.name, message: sig },
(msg) => {
if (!msg.success) {
console.log("authenticate failed", msg);
return;
}
if (document.getElementById("register")) {
document.getElementById("register").remove();
import("/app.js");
}
}
);
}
render(document.body, html`
<div id="register" class='hidden'>
<h1>welcome to vybe</h1>
<h3>a communication network (beta)</h3>
<p>
to get started, you'll need an account. we use public key cryptography
for security, rather than passwords. your keys are stored in your
browser storage only, so do this on a browser you can access again.
</p>
<form onsubmit=${async e => {
e.preventDefault();
const name = document.getElementById("name").value;
if (!name)
return;
const keys = await openpgp.generateKey({
userIDs: [{ name }],
});
const priv = await openpgp.readKey({ armoredKey: keys.privateKey });
const pub = await openpgp.readKey({ armoredKey: keys.publicKey });
window.emit("create_user", { name, pubkey: keys.publicKey }, msg => {
if (!msg.success) {
document.querySelector('#registerform').insertAdjacentHTML('afterend', `
<p>${msg.message}</p>`);
return;
}
window.keys = { priv, pub };
localStorage.setItem("keys", JSON.stringify(keys));
localStorage.setItem("name", name);
window.name = name;
auth();
});
}} id="registerform">
<label for="name">username: </label>
<input id="name" type="text" />
<input id="submit" type="submit" value='generate keys & register'>
</form>
</div>
`);
render(
document.body,
html`
<div id="register" class="hidden">
<h1>welcome to vybe</h1>
<h3>a communication network (beta)</h3>
<p>
to get started, you'll need an account. we use public key cryptography
for security, rather than passwords. your keys are stored in your
browser storage only, so do this on a browser you can access again.
</p>
<form
onsubmit=${async (e) => {
e.preventDefault();
const name = document.getElementById("name").value;
if (!name) return;
const keys = await openpgp.generateKey({
userIDs: [{ name }],
});
const priv = await openpgp.readKey({ armoredKey: keys.privateKey });
const pub = await openpgp.readKey({ armoredKey: keys.publicKey });
window.emit(
"create_user",
{ name, pubkey: keys.publicKey },
(msg) => {
if (!msg.success) {
document.querySelector("#registerform").insertAdjacentHTML(
"afterend",
`
<p>${msg.message}</p>`
);
return;
}
window.keys = { priv, pub };
localStorage.setItem("keys", JSON.stringify(keys));
localStorage.setItem("name", name);
window.name = name;
auth();
}
);
}}
id="registerform"
>
<label for="name">username: </label>
<input id="name" type="text" />
<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.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) {
window.name = localStorage.getItem("name");
keys = JSON.parse(keys);
window.keys = {
priv: await openpgp.readKey({ armoredKey: keys.privateKey }),
pub: await openpgp.readKey({ armoredKey: keys.publicKey })
};
await auth();
}
else
document.getElementById('register').classList.remove('hidden');
let keys = localStorage.getItem("keys");
if (keys) {
window.name = localStorage.getItem("name");
keys = JSON.parse(keys);
window.keys = {
priv: await openpgp.readKey({ armoredKey: keys.privateKey }),
pub: await openpgp.readKey({ armoredKey: keys.publicKey }),
};
await auth();
} else document.getElementById("register").classList.remove("hidden");
window.socket.io.on("reconnect", async (attempt) => {
await gensession();
if (localStorage.getItem("keys")) await auth();
});
};

View File

@ -1,102 +1,124 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script src="/openpgp.min.js"></script>
<script src="/socket.io.min.v4.6.1.js"></script>
<script src="/aes.js"></script>
<script type="module" src="/auth.js"></script>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>vybe</title>
<style>
* {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue",
sans-serif;
}
body,
button,
input {
background: #020202;
color: #eaeaea;
}
body {
display: flex;
align-items: stretch;
margin: 0;
min-width: min-content;
}
h3, h4 {
margin: 10px 0;
}
button {
border-color: #767676;
}
.hidden {
display: none;
}
.column {
flex: 1;
margin: 5px;
overflow: hidden;
}
#threads {
max-width: 250px;
}
.thread.selected {
background-color: #4f4f4f;
}
.thread:hover {
background-color: #3b3b3b;
}
#newthread {
margin-top: 5px;
}
#createthread {
max-width: 350px;
}
#permissions {
margin-bottom: 5px;
}
#title {
margin: 4px 2px;
}
.tab {
border: 0;
margin-top: 2px;
padding: 5px 7px;
color: #ccc;
font-weight: 500;
}
.tab.active {
background-color: #4f4f4f;
color: #fff;
}
.tab:hover {
background-color:#3b3b3b;
}
#messages {
margin: 4px 2px;
}
#msginput {
margin-top: 15px;
}
.message {
margin-bottom: 5px;
overflow-wrap: anywhere;
}
#loadmore {
margin-bottom: 10px;
}
.channel {
font-weight: normal;
}
.member {
margin: 5px 0;
}
</style>
</head>
<body>
</body>
<head>
<script src="/openpgp.min.js"></script>
<script src="/socket.io.min.v4.6.1.js"></script>
<script src="/aes.js"></script>
<script type="module" src="/auth.js"></script>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>vybe</title>
<style>
* {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue",
sans-serif;
}
body,
button,
input {
color: #eaeaea;
border: none;
outline: none;
}
body {
background: #020202;
display: flex;
align-items: stretch;
margin: 0;
min-width: min-content;
}
button,
input,
.tab {
padding: 5px 7px;
}
button {
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;
}
.column {
flex: 1;
margin: 5px;
overflow: hidden;
}
#threads {
max-width: 250px;
}
.thread {
display: block;
width: 100%;
text-align: left;
}
#newthread {
margin-top: 5px;
}
#createthread {
max-width: 350px;
}
#permissions {
margin-bottom: 5px;
}
#title {
margin: 4px 2px;
}
.tab {
background: transparent;
border: 0;
margin-top: 2px;
color: #ccc;
font-weight: 500;
}
.tab.active {
background-color: #4f4f4f;
color: #fff;
}
#messages {
margin: 4px 2px;
}
#msginput {
margin-top: 15px;
}
.message {
margin-bottom: 5px;
overflow-wrap: anywhere;
}
#loadmore {
margin-bottom: 10px;
}
.channel {
font-weight: normal;
}
.member {
margin: 5px 0;
}
</style>
</head>
<body></body>
</html>

View File

@ -3,23 +3,25 @@ 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());
const server = http.createServer(app);
const io = new Server(server, {
cors: {
origin: true,
},
cors: {
origin: true,
},
});
const PORT = process.env.PORT || 3435;
@ -27,20 +29,19 @@ const PORT = process.env.PORT || 3435;
io.cache = {};
io.on("connection", (socket) => {
for (let event in events) {
socket.on(event, (msg, callback) =>
events[event](msg, callback, socket, io)
);
}
socket.on('disconnect', reason => {
let sockets = io.cache[socket.username];
if (sockets)
sockets.splice(sockets.indexOf(socket.id), 1);
})
for (let event in events) {
socket.on(event, (msg, callback) =>
events[event](msg, callback, socket, io)
);
}
socket.on("disconnect", (reason) => {
let sockets = io.cache[socket.username];
if (sockets) sockets.splice(sockets.indexOf(socket.id), 1);
});
});
server.listen(PORT, () => {
console.log("server running on port " + PORT);
console.log("server running on port " + PORT);
});
app.use(express.static("client"));

View File

@ -2,108 +2,107 @@ const db = require("../db");
const authwrap = require("./authwrap");
const create_thread = async (msg, respond, socket, io) => {
// 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 threads (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 permissions (thread, type, mutable, permission, value)
// 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 threads (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 permissions (thread, type, mutable, permission, value)
values (?, ?, ?, ?, ?)`,
[thread_id, "everyone", false, "view", "true"]
);
if (!msg.permissions || !msg.permissions.post_limited) {
await db.query(
`insert into permissions (thread, type, mutable, permission, value)
[thread_id, "everyone", false, "view", "true"]
);
if (!msg.permissions || !msg.permissions.post_limited) {
await db.query(
`insert into permissions (thread, type, mutable, permission, value)
values (?, ?, ?, ?, ?)`,
[thread_id, "everyone", false, "post", "true"]
);
} else {
await db.query(
`insert into permissions (thread, type, mutable, permission, value)
[thread_id, "everyone", false, "post", "true"]
);
} else {
await db.query(
`insert into permissions (thread, type, mutable, permission, value)
values (?, ?, ?, ?, ?)`,
[thread_id, "members", false, "post", "true"]
);
}
} else {
await db.query(
`insert into permissions (thread, type, mutable, permission, value)
[thread_id, "members", false, "post", "true"]
);
}
} else {
await db.query(
`insert into permissions (thread, type, mutable, permission, value)
values (?, ?, ?, ?, ?)`,
[thread_id, "members", false, "view", "true"]
);
await db.query(
`insert into permissions (thread, type, mutable, permission, value)
[thread_id, "members", false, "view", "true"]
);
await db.query(
`insert into permissions (thread, type, mutable, permission, value)
values (?, ?, ?, ?, ?)`,
[thread_id, "members", false, "post", "true"]
);
}
// add members
for (let user of msg.members) {
if (!user) continue;
// get user id
const id = await db.query("select id from users where name = ?", [
user.name,
]);
if (id.rows.length > 0) {
const user_id = id.rows[0].id;
await db.query(
"insert into members (thread, user, key_delivery) values (?, ?, ?)",
[thread_id, user_id, user.key]
);
}
}
if (!msg.permissions || !msg.permissions.view_limited) {
for (let username in io.cache) {
for (let socket of io.cache[username]) {
io.to(socket).emit("new_thread", {
name: msg.name,
id: insert.rows[0].id,
permissions: {
is_member: false,
view: true,
post: !msg.permissions || !msg.permissions.post_limited
},
});
}
}
}
else {
for (let member of msg.members) {
for (let socket of io.cache[member.user]) {
io.to(socket).emit("new_thread", {
name: msg.name,
id: insert.rows[0].id,
permissions: {
is_member: true,
view: true,
post: true
},
key: member.key
});
}
}
}
// respond
return respond({
success: true,
id: insert.rows[0].id,
});
[thread_id, "members", false, "post", "true"]
);
}
// add members
for (let user of msg.members) {
if (!user) continue;
// get user id
const id = await db.query("select id from users where name = ?", [
user.name,
]);
if (id.rows.length > 0) {
const user_id = id.rows[0].id;
await db.query(
"insert into members (thread, user, key_delivery) values (?, ?, ?)",
[thread_id, user_id, user.key]
);
}
}
if (!msg.permissions || !msg.permissions.view_limited) {
for (let username in io.cache) {
for (let socket of io.cache[username]) {
io.to(socket).emit("new_thread", {
name: msg.name,
id: insert.rows[0].id,
permissions: {
is_member: false,
view: true,
post: !msg.permissions || !msg.permissions.post_limited,
},
});
}
}
} else {
for (let member of msg.members) {
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,
},
key: member.key,
});
}
}
}
// respond
return respond({
success: true,
id: insert.rows[0].id,
});
};
module.exports = authwrap(create_thread);