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) => { console.log("websocket message received: ", msg) } broadcast(event) { if (!this.wss) return; let message = JSON.stringify(event) this.wss.clients.forEach(ws => { if (ws.readyState === WebSocket.OPEN) { ws.send(message); } }); } }