This commit is contained in:
metacryst
2026-01-04 07:58:23 -06:00
parent b50468eb5a
commit 6a435ac11a
122 changed files with 13995 additions and 19 deletions

View File

@@ -0,0 +1,43 @@
import { z } from 'zod';
const sendSchema = z.object({
forum: z.string(),
text: z.string(),
})
.strict()
const getSchema = z.object({
forum: z.string(),
number: z.number()
})
.strict()
export default class ForumHandler {
static handleSend(msg, ws) {
try {
global.db.posts.add(msg.text, msg.forum, ws.userEmail)
global.Socket.broadcast({event: "new-post", app: "FORUM", forum: msg.forum, msg: this.handleGet({forum: msg.forum, number: 100})})
return {success: true}
} catch(e) {
console.error(e)
}
}
static handleGet(msg) {
let data = global.db.posts.get(msg.forum, msg.number)
return data
}
static handle(operation, msg, ws) {
switch(operation) {
case "SEND":
if(!sendSchema.safeParse(msg).success) throw new Error("Incorrectly formatted Forum ws message!")
return this.handleSend(msg, ws)
case "GET":
if(!getSchema.safeParse(msg).success) throw new Error("Incorrectly formatted Forum ws message!")
return this.handleGet(msg)
}
}
}

View File

@@ -0,0 +1,40 @@
import { z } from 'zod';
const sendSchema = z.object({
conversation: z.string(),
text: z.string(),
})
.strict()
export default class MessagesHandler {
static handleSend(msg, ws) {
let user = global.db.members.getByEmail(ws.userEmail)
let convo = global.db.conversations.get(msg.conversation)
if(convo.between.includes(`MEMBER-${user.id}`)) {
global.db.messages.add(msg.conversation, msg.text, `MEMBER-${user.id}`)
global.Socket.broadcast({event: "new-message", app: "MESSAGES", msg: {conversationID: convo.id, messages: global.db.messages.getByConversation(`CONVERSATION-${msg.conversation}`)}})
} else {
throw new Error("Can't add to a conversation user is not a part of!")
}
return {success: true}
}
static handleGet(ws) {
let user = global.db.members.getByEmail(ws.userEmail)
let data = global.db.conversations.getByMember(`MEMBER-${user.id}`)
return data
}
static handle(operation, msg, ws) {
switch(operation) {
case "GET":
return this.handleGet(ws)
case "SEND":
if(!sendSchema.safeParse(msg).success) throw new Error("Incorrectly formatted Forum ws message!")
return this.handleSend(msg, ws)
}
}
}

109
server/ws/ws.js Normal file
View File

@@ -0,0 +1,109 @@
import { WebSocket, WebSocketServer } from 'ws';
import { z } from 'zod';
import jwt from 'jsonwebtoken';
import ForumHandler from "./handlers/ForumHandler.js"
import MessagesHandler from "./handlers/MessagesHandler.js"
export default class Socket {
wss;
messageSchema = z.object({
id: z.string(),
app: z.string(),
operation: z.string().optional(),
msg: z.union([
z.object({}).passthrough(), // allows any object
z.array(z.any()) // allows any array
]).optional()
})
.superRefine((data, ctx) => {
if (data.operation !== "GET" && data.msg === undefined) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["msg"],
message: "msg is required when operation is not GET"
})
}
})
.strict()
constructor(server) {
this.wss = new WebSocketServer({ server });
this.wss.on('connection', (ws, req) => {
console.log('✅ New WebSocket client connected');
function parseCookies(cookieHeader = "") {
return Object.fromEntries(
cookieHeader.split(";").map(c => {
const [key, ...v] = c.trim().split("=");
return [key, decodeURIComponent(v.join("="))];
})
);
}
const cookies = parseCookies(req.headers.cookie);
const token = cookies.auth_token;
if (!token) throw new Error("No auth token");
const payload = jwt.verify(token, process.env.JWT_SECRET);
ws.userEmail = payload.email;
ws.on('message', (msg) => {
this.handleMessage(msg, ws);
});
ws.on('close', () => {
console.log('Client disconnected');
});
});
console.log('WebSocket server initialized');
}
// Build a system where the ws obj is updated every time on navigate, so it already has context
// this way, we can only send broadcast messages to clients that actually have that app / subapp open
handleMessage = (msg, ws) => {
try {
const text = msg.toString();
const req = JSON.parse(text);
if(!this.messageSchema.safeParse(req).success) throw new Error("Socket.handleMessage: Incoming ws message has incorrect format!")
let responseData;
switch (req.app) {
case "FORUM":
responseData = ForumHandler.handle(req.operation, req.msg, ws)
break;
case "MESSAGES":
responseData = MessagesHandler.handle(req.operation, req.msg, ws)
break;
default:
console.error("unknown ws message")
}
let response = {
...req
}
response.msg = responseData
if(!this.messageSchema.safeParse(response).success) throw new Error("Socket.handleMessage: Outgoing ws message has incorrect format!")
ws.send(JSON.stringify(response))
} catch (e) {
console.error("Invalid WS message:", e);
}
}
broadcast(event) {
if (!this.wss) return;
let message = JSON.stringify(event)
this.wss.clients.forEach(ws => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(message);
}
});
}
}