use socketio callbacks and uhtml
parent
99cb595e07
commit
ca78144083
|
@ -4,7 +4,7 @@
|
|||
<script src="/openpgp.min.js"></script>
|
||||
<script src="/socket.io.min.v4.6.1.js"></script>
|
||||
<script src="/aes.js"></script>
|
||||
<script src="/chat.js"></script>
|
||||
<script type="module" src="/ui.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" />
|
||||
|
@ -28,6 +28,9 @@
|
|||
min-width: min-content;
|
||||
padding: 0 20px;
|
||||
}
|
||||
#app {
|
||||
display: contents;
|
||||
}
|
||||
.column {
|
||||
flex: 1;
|
||||
max-width: 50vw;
|
||||
|
@ -58,69 +61,5 @@
|
|||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<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. for now your keys are stored in
|
||||
your browser storage only.
|
||||
</p>
|
||||
<form id="registerform">
|
||||
<label for="name">name/username: </label>
|
||||
<input type="text" id="name" />
|
||||
<button id="submit" type="submit">generate keys & register</button>
|
||||
</form>
|
||||
</div>
|
||||
<div id="threads" class="column hidden">
|
||||
<h1>vybe</h1>
|
||||
<h3>threads</h3>
|
||||
<div id="threadlist">loading...</div>
|
||||
<h3>create thread</h3>
|
||||
<form id="createthread">
|
||||
<label for="newthreadname">thread name</label>
|
||||
<input type="text" id="newthreadname" /><br /><br />
|
||||
<span>thread permissions</span><br />
|
||||
<input type="radio" id="public" name="permissions" value="public" />
|
||||
<label for="public">anyone can view and post</label><br />
|
||||
<input
|
||||
type="radio"
|
||||
id="private_post"
|
||||
name="permissions"
|
||||
value="private_post"
|
||||
/>
|
||||
<label for="private_post">only members can post, anyone can view</label
|
||||
><br />
|
||||
<input
|
||||
type="radio"
|
||||
id="private_view"
|
||||
name="permissions"
|
||||
value="private_view"
|
||||
/>
|
||||
<label for="private_view">only members can view and post</label
|
||||
><br /><br />
|
||||
<span>members</span><br />
|
||||
<input type="text" id="membername" placeholder="username" /><button
|
||||
id="addmember"
|
||||
>
|
||||
add
|
||||
</button>
|
||||
<div id="memberlist"></div>
|
||||
<br />
|
||||
<button id="submitthread" type="submit">create</button>
|
||||
</form>
|
||||
</div>
|
||||
<div id="chat" class="column hidden">
|
||||
<h3 class="thread">
|
||||
current thread: <strong id="threadname">meow</strong>
|
||||
</h3>
|
||||
<h3>messages will appear below as they are sent</h3>
|
||||
<button id="loadmore" class="hidden">load more messages</button>
|
||||
<div id="messages"></div>
|
||||
<form id="msginput">
|
||||
<input type="text" placeholder="write a message..." id="msg" />
|
||||
<button type="submit" class="hidden" id="sendmsg"></button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,3 +1,5 @@
|
|||
import { render, html } from '/uhtml.js';
|
||||
|
||||
function rand() {
|
||||
let str = "";
|
||||
const lookups =
|
||||
|
@ -16,14 +18,46 @@ async function auth() {
|
|||
signingKeys: window.keys.priv,
|
||||
});
|
||||
window.session = session;
|
||||
window.socket.emit("authenticate", { name: window.name, message: sig });
|
||||
window.emit = (type, data, callback) => window.socket.emit(type, {
|
||||
...data,
|
||||
__session: window.session,
|
||||
}, callback);
|
||||
window.socket.emit("authenticate", { name: window.name, message: sig },
|
||||
msg => {
|
||||
if (!msg.success) {
|
||||
document.getElementById("register").classList.remove("hidden");
|
||||
return;
|
||||
}
|
||||
document.getElementById("register").classList.add("hidden");
|
||||
document.getElementById("app").classList.remove("hidden");
|
||||
const member = document.createElement("p");
|
||||
member.textContent = window.name;
|
||||
member.classList.add("member");
|
||||
document.getElementById("memberlist").appendChild(member);
|
||||
window.emit("list_threads", {}, msg => {
|
||||
document.getElementById("threadlist").innerHTML = "";
|
||||
for (let thread of msg.threads)
|
||||
addThread(thread);
|
||||
});
|
||||
loadMessages();
|
||||
});
|
||||
}
|
||||
|
||||
async function loadKeys(keys) {
|
||||
async function register(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.keys = { priv, pub };
|
||||
await auth();
|
||||
localStorage.setItem("keys", JSON.stringify(keys));
|
||||
localStorage.setItem("name", name);
|
||||
window.name = name;
|
||||
window.emit("create_user", { name, pubkey: keys.publicKey }, auth);
|
||||
}
|
||||
|
||||
function chooseThread(thread) {
|
||||
|
@ -34,9 +68,25 @@ function chooseThread(thread) {
|
|||
}
|
||||
|
||||
function loadMessages() {
|
||||
window.socket.emit("get_history", {
|
||||
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) {
|
||||
const el = document.createElement("div");
|
||||
el.classList.add("message");
|
||||
const strong = document.createElement("strong");
|
||||
strong.textContent = message.name + ": ";
|
||||
el.append(strong, message.message);
|
||||
document.getElementById("messages").prepend(el);
|
||||
}
|
||||
}
|
||||
if (!msg.more)
|
||||
document.getElementById("loadmore").classList.add("hidden");
|
||||
else
|
||||
document.getElementById("loadmore").classList.remove("hidden");
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -73,12 +123,18 @@ function addMember() {
|
|||
document.getElementById("membername").value = "";
|
||||
}
|
||||
|
||||
async function createThread(members) {
|
||||
async function createThread(e) {
|
||||
e.preventDefault();
|
||||
let members = window.threadmembers
|
||||
? window.threadmembers.map((i) => ({ name: i }))
|
||||
: [{ name: window.name }];
|
||||
const perms = document.querySelector(
|
||||
'input[name="permissions"]:checked'
|
||||
).value;
|
||||
if (perms === "private_view")
|
||||
members = (await new Promise(resolve =>
|
||||
window.emit("get_keys", { names: members.map((i) => i.name) }, resolve))).keys;
|
||||
let permissions;
|
||||
let newmembers = members;
|
||||
if (perms === "public") {
|
||||
permissions = {
|
||||
view_limited: false,
|
||||
|
@ -109,11 +165,6 @@ async function createThread(members) {
|
|||
}
|
||||
*/
|
||||
}
|
||||
window.socket.emit("create_thread", {
|
||||
name: document.getElementById("newthreadname").value,
|
||||
permissions,
|
||||
newmembers,
|
||||
});
|
||||
document.getElementById(perms).checked = false;
|
||||
window.threadmembers = null;
|
||||
document.getElementById("memberlist").innerHTML = "";
|
||||
|
@ -121,73 +172,11 @@ async function createThread(members) {
|
|||
member.textContent = window.name;
|
||||
member.classList.add("member");
|
||||
document.getElementById("memberlist").appendChild(member);
|
||||
}
|
||||
|
||||
function decryptMessage(thread, message) {}
|
||||
|
||||
function encryptMessage(thread, message) {}
|
||||
|
||||
window.onload = () => {
|
||||
window.currentThreadId = 1;
|
||||
window.socket = io();
|
||||
window.socket.on("create_user", auth);
|
||||
window.socket.on("new_message", (msg) => {
|
||||
if (msg.thread !== window.currentThreadId) return;
|
||||
const el = document.createElement("div");
|
||||
el.classList.add("message");
|
||||
const strong = document.createElement("strong");
|
||||
strong.textContent = msg.name + ": ";
|
||||
el.append(strong, msg.message);
|
||||
document.getElementById("messages").appendChild(el);
|
||||
if (!window.earliestMessage) window.earliestMessage = msg.id;
|
||||
});
|
||||
window.socket.on("get_history", (msg) => {
|
||||
if (msg.messages.length > 0) {
|
||||
window.earliestMessage = msg.messages[msg.messages.length - 1].id;
|
||||
for (let message of msg.messages) {
|
||||
const el = document.createElement("div");
|
||||
el.classList.add("message");
|
||||
const strong = document.createElement("strong");
|
||||
strong.textContent = message.name + ": ";
|
||||
el.append(strong, message.message);
|
||||
document.getElementById("messages").prepend(el);
|
||||
}
|
||||
}
|
||||
if (!msg.more)
|
||||
document.getElementById("loadmore").classList.add("hidden");
|
||||
else
|
||||
document.getElementById("loadmore").classList.remove("hidden");
|
||||
});
|
||||
window.socket.on("authenticate", (msg) => {
|
||||
if (msg.success) {
|
||||
document.getElementById("register").classList.add("hidden");
|
||||
document.getElementById("threads").classList.remove("hidden");
|
||||
document.getElementById("chat").classList.remove("hidden");
|
||||
const member = document.createElement("p");
|
||||
member.textContent = window.name;
|
||||
member.classList.add("member");
|
||||
document.getElementById("memberlist").appendChild(member);
|
||||
let emitter = window.socket.emit;
|
||||
window.socket.emit = (type, data) => {
|
||||
if (data)
|
||||
return emitter.call(window.socket, type, {
|
||||
...data,
|
||||
__session: window.session,
|
||||
});
|
||||
else return emitter.call(window.socket, type);
|
||||
};
|
||||
window.socket.emit("list_threads", {});
|
||||
loadMessages();
|
||||
} else {
|
||||
document.getElementById("register").classList.remove("hidden");
|
||||
}
|
||||
});
|
||||
window.socket.on("list_threads", (msg) => {
|
||||
document.getElementById("threadlist").innerHTML = "";
|
||||
for (let thread of msg.threads) addThread(thread);
|
||||
});
|
||||
window.socket.on("new_thread", addThread);
|
||||
window.socket.on("create_thread", (msg) => {
|
||||
window.emit("create_thread", {
|
||||
name: document.getElementById("newthreadname").value,
|
||||
permissions,
|
||||
members,
|
||||
}, msg => {
|
||||
chooseThread({
|
||||
name: document.getElementById("newthreadname").value,
|
||||
id: msg.id,
|
||||
|
@ -196,62 +185,118 @@ window.onload = () => {
|
|||
document.getElementById("loadmore").classList.add("hidden");
|
||||
document.getElementById("msginput").classList.remove("hidden");
|
||||
});
|
||||
window.socket.on("get_keys", (msg) => {
|
||||
createThread(msg.keys);
|
||||
});
|
||||
document.getElementById("registerform").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.keys = { priv, pub };
|
||||
localStorage.setItem("keys", JSON.stringify(keys));
|
||||
localStorage.setItem("name", name);
|
||||
window.name = name;
|
||||
window.socket.emit("create_user", { name, pubkey: keys.publicKey });
|
||||
};
|
||||
document.getElementById("msginput").onsubmit = (e) => {
|
||||
}
|
||||
|
||||
function sendMessage(e) {
|
||||
e.preventDefault();
|
||||
const msg = document.getElementById("msg").value;
|
||||
if (!msg) return;
|
||||
window.socket.emit("send_message", {
|
||||
if (!msg)
|
||||
return;
|
||||
window.emit("send_message", {
|
||||
message: msg,
|
||||
thread: window.currentThreadId,
|
||||
});
|
||||
document.getElementById("msg").value = "";
|
||||
};
|
||||
document.getElementById("loadmore").onclick = (e) => {
|
||||
loadMessages();
|
||||
};
|
||||
document.getElementById("createthread").onsubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const members = window.threadmembers
|
||||
? window.threadmembers.map((i) => ({ name: i }))
|
||||
: [{ name: window.name }];
|
||||
const perms = document.querySelector(
|
||||
'input[name="permissions"]:checked'
|
||||
).value;
|
||||
console.log(perms, members);
|
||||
if (perms === "private_view") {
|
||||
window.socket.emit("get_keys", { names: members.map((i) => i.name) });
|
||||
} else {
|
||||
createThread(members);
|
||||
}
|
||||
};
|
||||
document.getElementById("membername").onkeydown = (e) => {
|
||||
}
|
||||
|
||||
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=${register} id="registerform">
|
||||
<label for="name">username: </label>
|
||||
<input type="text" id="name" />
|
||||
<input id="submit" type="submit" value='generate keys & register'>
|
||||
</form>
|
||||
</div>
|
||||
<div id="app" class="hidden">
|
||||
<div id="threads" class="column">
|
||||
<h1>vybe</h1>
|
||||
<h3>threads</h3>
|
||||
<div id="threadlist">loading...</div>
|
||||
<form id="createthread" onsubmit=${createThread}>
|
||||
<h3>create thread</h3>
|
||||
<label for="newthreadname">thread name</label>
|
||||
<input type="text" id="newthreadname" /><br /><br />
|
||||
<span>thread permissions</span><br />
|
||||
<input type="radio" id="public" name="permissions" value="public" />
|
||||
<label for="public">anyone can view and post</label><br />
|
||||
<input
|
||||
type="radio"
|
||||
id="private_post"
|
||||
name="permissions"
|
||||
value="private_post"
|
||||
/>
|
||||
<label for="private_post">only members can post, anyone can view</label
|
||||
><br />
|
||||
<input
|
||||
type="radio"
|
||||
id="private_view"
|
||||
name="permissions"
|
||||
value="private_view"
|
||||
/>
|
||||
<label for="private_view">only members can view and post</label
|
||||
><br /><br />
|
||||
<span>members</span><br />
|
||||
<input type="text" id="membername" placeholder="username" onkeydown=${(e) => {
|
||||
if (e.key == "Enter") {
|
||||
e.preventDefault();
|
||||
addMember();
|
||||
}
|
||||
};
|
||||
document.getElementById("addmember").onclick = addMember;
|
||||
}}/>
|
||||
<button id="addmember" onclick=${addMember}>add</button>
|
||||
<div id="memberlist"></div>
|
||||
<br />
|
||||
<input id="submitthread" type="submit" value="create" />
|
||||
</form>
|
||||
</div>
|
||||
<div id="chat" class="column">
|
||||
<h3 class="thread">
|
||||
current thread: <strong id="threadname">meow</strong>
|
||||
</h3>
|
||||
<h3>messages will appear below as they are sent</h3>
|
||||
<button id="loadmore" class="hidden" onclick=${loadMessages}>load more messages</button>
|
||||
<div id="messages"></div>
|
||||
<form id="msginput" onsubmit=${sendMessage}>
|
||||
<input type="text" placeholder="write a message..." id="msg" />
|
||||
<button type="submit" class="hidden" id="sendmsg"></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
const keys = localStorage.getItem("keys");
|
||||
window.onload = async () => {
|
||||
window.currentThreadId = 1;
|
||||
window.socket = io();
|
||||
window.socket.on("new_message", (msg) => {
|
||||
if (msg.thread !== window.currentThreadId)
|
||||
return;
|
||||
const el = document.createElement("div");
|
||||
el.classList.add("message");
|
||||
const strong = document.createElement("strong");
|
||||
strong.textContent = msg.name + ": ";
|
||||
el.append(strong, msg.message);
|
||||
document.getElementById("messages").appendChild(el);
|
||||
if (!window.earliestMessage)
|
||||
window.earliestMessage = msg.id;
|
||||
});
|
||||
window.socket.on("new_thread", addThread);
|
||||
|
||||
let keys = localStorage.getItem("keys");
|
||||
if (keys) {
|
||||
window.name = localStorage.getItem("name");
|
||||
loadKeys(JSON.parse(keys)).then(() => {});
|
||||
} else document.getElementById("register").classList.remove("hidden");
|
||||
keys = JSON.parse(keys);
|
||||
const priv = await openpgp.readKey({ armoredKey: keys.privateKey });
|
||||
const pub = await openpgp.readKey({ armoredKey: keys.publicKey });
|
||||
window.keys = { priv, pub };
|
||||
await auth();
|
||||
document.getElementById('app').classList.remove('hidden');
|
||||
}
|
||||
else
|
||||
document.getElementById('register').classList.remove('hidden');
|
||||
};
|
1
db.js
1
db.js
|
@ -1,5 +1,6 @@
|
|||
const sqlite3 = require("sqlite3");
|
||||
const fs = require("fs");
|
||||
|
||||
const db = new sqlite3.Database("vybe.db");
|
||||
|
||||
db.query = function (sql, params) {
|
||||
|
|
23
index.js
23
index.js
|
@ -3,6 +3,16 @@ 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 app = express();
|
||||
app.use(compression());
|
||||
const server = http.createServer(app);
|
||||
|
@ -14,19 +24,12 @@ const io = new Server(server, {
|
|||
|
||||
const PORT = process.env.PORT || 3435;
|
||||
|
||||
const actions = require("./src/actions");
|
||||
|
||||
io.cache = {};
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
for (let action in actions) {
|
||||
socket.on(action, (msg) =>
|
||||
actions[action](
|
||||
msg,
|
||||
(response) => socket.emit(action, response),
|
||||
socket,
|
||||
io
|
||||
)
|
||||
for (let event in events) {
|
||||
socket.on(event, (msg, callback) =>
|
||||
events[event](msg, callback, socket, io)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
const create_user = require("./create_user");
|
||||
const get_history = require("./get_history");
|
||||
const send_message = require("./send_message");
|
||||
const authenticate = require("./authenticate");
|
||||
const create_thread = require("./create_thread");
|
||||
const list_threads = require("./list_threads");
|
||||
const get_keys = require("./get_keys");
|
||||
|
||||
module.exports = {
|
||||
create_user,
|
||||
get_history,
|
||||
send_message,
|
||||
authenticate,
|
||||
create_thread,
|
||||
list_threads,
|
||||
get_keys,
|
||||
};
|
|
@ -1,6 +1,8 @@
|
|||
const db = require("../db");
|
||||
|
||||
const authwrap = (fn) => async (msg, respond, socket, io) => {
|
||||
if (!respond)
|
||||
respond = () => {};
|
||||
if (!msg || !msg.__session) {
|
||||
return respond({
|
||||
success: false,
|
||||
|
|
Loading…
Reference in New Issue