From d46078efa1a4a66b009046bf42fee234f032c566 Mon Sep 17 00:00:00 2001 From: Hri7566 Date: Sat, 9 Sep 2023 01:38:47 -0400 Subject: [PATCH] Implement events --- src/ws/Gateway.ts | 3 + src/ws/Socket.ts | 148 +++++++++++++++++++++++++ src/ws/events.inc.ts | 2 + src/ws/events.ts | 18 +++ src/ws/events/admin/handlers/color.ts | 9 ++ src/ws/events/admin/index.ts | 9 ++ src/ws/events/user/handlers/devices.ts | 9 ++ src/ws/events/user/handlers/hi.ts | 29 +++++ src/ws/events/user/index.ts | 9 ++ src/ws/message.ts | 29 +++++ src/ws/messages/hi.ts | 0 src/ws/server.ts | 95 +++++++++++++--- 12 files changed, 346 insertions(+), 14 deletions(-) create mode 100644 src/ws/Gateway.ts create mode 100644 src/ws/Socket.ts create mode 100644 src/ws/events.inc.ts create mode 100644 src/ws/events.ts create mode 100644 src/ws/events/admin/handlers/color.ts create mode 100644 src/ws/events/admin/index.ts create mode 100644 src/ws/events/user/handlers/devices.ts create mode 100644 src/ws/events/user/handlers/hi.ts create mode 100644 src/ws/events/user/index.ts create mode 100644 src/ws/message.ts delete mode 100644 src/ws/messages/hi.ts diff --git a/src/ws/Gateway.ts b/src/ws/Gateway.ts new file mode 100644 index 0000000..68abe28 --- /dev/null +++ b/src/ws/Gateway.ts @@ -0,0 +1,3 @@ +export class Gateway { + public hasSentDevices: boolean = false; +} diff --git a/src/ws/Socket.ts b/src/ws/Socket.ts new file mode 100644 index 0000000..5d5f40d --- /dev/null +++ b/src/ws/Socket.ts @@ -0,0 +1,148 @@ +import { WebSocket } from "uWebSockets.js"; +import { createColor, createID, createUserID } from "../util/id"; +import { decoder, encoder } from "../util/helpers"; +import EventEmitter from "events"; +import { ChannelSettings, ClientEvents, UserFlags } from "../util/types"; +import { User } from "@prisma/client"; +import { createUser, readUser } from "../data/user"; +import { eventGroups } from "./events"; +import { loadConfig } from "../util/config"; +import { Gateway } from "./Gateway"; + +interface UsersConfig { + defaultName: string; + defaultFlags: UserFlags; +} + +const usersConfig = loadConfig("config/users.yml", { + defaultName: "Anonymous", + defaultFlags: { + volume: 100 + } +}); + +export class Socket extends EventEmitter { + private id: string; + private _id: string; + private ip: string; + private user: User | null = null; + + public gateway = new Gateway(); + + public desiredChannel: { + _id: string | undefined; + set: Partial; + } = { + _id: undefined, + set: {} + }; + + constructor(private ws: WebSocket) { + super(); + this.ip = decoder.decode(this.ws.getRemoteAddressAsText()); + + // Participant ID + this.id = createID(); + + // User ID + this._id = createUserID(this.getIP()); + // *cough* lapis + + this.loadUser(); + + this.bindEventListeners(); + } + + public getIP() { + return this.ip; + } + + public getUserID() { + return this._id; + } + + public setChannel(_id: string, set: Partial) { + this.desiredChannel._id = _id; + this.desiredChannel.set = set; + } + + private bindEventListeners() { + for (const group of eventGroups) { + // TODO Check event group permissions + if (group.id == "admin") continue; + + for (const event of group.eventList) { + this.on(event.id, event.callback); + } + } + } + + public sendArray( + arr: ClientEvents[EventID][] + ) { + this.ws.send(encoder.encode(JSON.stringify(arr))); + } + + private async loadUser() { + let user = await readUser(this._id); + + if (!user) { + await createUser( + this._id, + usersConfig.defaultName, + createColor(this.ip), + usersConfig.defaultFlags + ); + + user = await readUser(this._id); + } + + this.user = user; + } + + public getUser() { + if (this.user) { + return this.user; + } + + return null; + } + + public getUserFlags() { + if (this.user) { + try { + return JSON.parse(this.user.flags) as UserFlags; + } catch (err) { + return {} as UserFlags; + } + } else { + return null; + } + } + + public getParticipant() { + if (this.user) { + const flags = this.getUserFlags(); + let facadeID = this._id; + + if (flags) { + if (flags.override_id) { + facadeID = flags.override_id; + } + } + + return { + _id: facadeID, + name: this.user.name, + color: this.user.color, + id: this.id + }; + } else { + return null; + } + } + + public getParticipantID() { + return this.id; + } +} diff --git a/src/ws/events.inc.ts b/src/ws/events.inc.ts new file mode 100644 index 0000000..0ff6c3c --- /dev/null +++ b/src/ws/events.inc.ts @@ -0,0 +1,2 @@ +import "./events/user"; +import "./events/admin"; diff --git a/src/ws/events.ts b/src/ws/events.ts new file mode 100644 index 0000000..7bc771b --- /dev/null +++ b/src/ws/events.ts @@ -0,0 +1,18 @@ +import { ServerEventListener, ServerEvents } from "../util/types"; + +export class EventGroup { + public eventList = new Array>(); + constructor(public id: string) {} + + public add(listener: ServerEventListener) { + this.eventList.push(listener); + } + + public remove(listener: ServerEventListener) { + this.eventList.splice(this.eventList.indexOf(listener), 1); + } +} + +export const eventGroups = new Array(); + +import "./events.inc"; diff --git a/src/ws/events/admin/handlers/color.ts b/src/ws/events/admin/handlers/color.ts new file mode 100644 index 0000000..b5b3c64 --- /dev/null +++ b/src/ws/events/admin/handlers/color.ts @@ -0,0 +1,9 @@ +import { ServerEventListener } from "../../../../util/types"; +import { eventGroups } from "../../../events"; + +export const color: ServerEventListener<"color"> = { + id: "color", + callback: (msg, socket) => { + // TODO color + } +}; diff --git a/src/ws/events/admin/index.ts b/src/ws/events/admin/index.ts new file mode 100644 index 0000000..ca0fb14 --- /dev/null +++ b/src/ws/events/admin/index.ts @@ -0,0 +1,9 @@ +import { EventGroup, eventGroups } from "../../events"; + +export const EVENT_GROUP_ADMIN = new EventGroup("user"); + +import { color } from "./handlers/color"; + +EVENT_GROUP_ADMIN.add(color); + +eventGroups.push(EVENT_GROUP_ADMIN); diff --git a/src/ws/events/user/handlers/devices.ts b/src/ws/events/user/handlers/devices.ts new file mode 100644 index 0000000..6555650 --- /dev/null +++ b/src/ws/events/user/handlers/devices.ts @@ -0,0 +1,9 @@ +import { ServerEventListener } from "../../../../util/types"; +import { eventGroups } from "../../../events"; + +export const devices: ServerEventListener<"devices"> = { + id: "devices", + callback: (msg, socket) => { + socket.gateway.hasSentDevices = true; + } +}; diff --git a/src/ws/events/user/handlers/hi.ts b/src/ws/events/user/handlers/hi.ts new file mode 100644 index 0000000..8cdab68 --- /dev/null +++ b/src/ws/events/user/handlers/hi.ts @@ -0,0 +1,29 @@ +import { ServerEventListener } from "../../../../util/types"; +import { eventGroups } from "../../../events"; + +export const hi: ServerEventListener<"hi"> = { + id: "hi", + callback: (msg, socket) => { + // TODO Hi message tokens + let part = socket.getParticipant(); + + if (!part) { + part = { + _id: socket.getUserID(), + name: "Anonymous", + color: "#777", + id: socket.getParticipantID() + }; + } + + socket.sendArray([ + { + m: "hi", + accountInfo: undefined, + permissions: undefined, + t: Date.now(), + u: part + } + ]); + } +}; diff --git a/src/ws/events/user/index.ts b/src/ws/events/user/index.ts new file mode 100644 index 0000000..a664d83 --- /dev/null +++ b/src/ws/events/user/index.ts @@ -0,0 +1,9 @@ +import { EventGroup, eventGroups } from "../../events"; + +export const EVENTGROUP_USER = new EventGroup("user"); + +import { hi } from "./handlers/hi"; + +EVENTGROUP_USER.add(hi); + +eventGroups.push(EVENTGROUP_USER); diff --git a/src/ws/message.ts b/src/ws/message.ts new file mode 100644 index 0000000..f9931a2 --- /dev/null +++ b/src/ws/message.ts @@ -0,0 +1,29 @@ +import { WebSocket } from "uWebSockets.js"; +import { Logger } from "../util/Logger"; +import { Socket } from "./Socket"; +import { hasOwn } from "../util/helpers"; + +const logger = new Logger("Message Handler"); + +export function handleMessage(socket: Socket, text: string) { + try { + const transmission = JSON.parse(text); + + if (!Array.isArray(transmission)) { + logger.warn( + "Received message that isn't an array! --", + transmission, + " -- from", + socket.getUserID() + ); + } else { + for (const msg of transmission) { + if (!hasOwn(msg, "m")) continue; + socket.emit(msg.m, msg, socket); + } + } + } catch (err) { + logger.warn("Unable to decode message from", socket.getIP()); + logger.error(err); + } +} diff --git a/src/ws/messages/hi.ts b/src/ws/messages/hi.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/ws/server.ts b/src/ws/server.ts index 492bc61..4a9aebb 100644 --- a/src/ws/server.ts +++ b/src/ws/server.ts @@ -1,30 +1,97 @@ -import { App, DEDICATED_COMPRESSOR_8KB } from "uWebSockets.js"; +import { + App, + DEDICATED_COMPRESSOR_8KB, + HttpRequest, + HttpResponse, + WebSocket +} from "uWebSockets.js"; import { Logger } from "../util/Logger"; import { createUserID } from "../util/id"; +import { readFileSync, lstatSync } from "fs"; +import { join } from "path/posix"; +import { handleMessage } from "./message"; +import { decoder } from "../util/helpers"; +import { Socket } from "./Socket"; const logger = new Logger("WebSocket Server"); export const app = App() - .get("/", (res, req) => { + .get("/*", async (res, req) => { const url = req.getUrl(); - logger.debug(`${req.getMethod()} ${url} ${req}`); - res.writeStatus(`200 OK`).end("HI!"); + const ip = decoder.decode(res.getRemoteAddressAsText()); + // logger.debug(`${req.getMethod()} ${url} ${ip}`); + // res.writeStatus(`200 OK`).end("HI!"); + const file = join("./public/", url); + + // TODO Cleaner file serving + try { + const stats = lstatSync(file); + + let data; + if (!stats.isDirectory()) { + data = readFileSync(file); + } + + // logger.debug(filename); + + if (!data) { + const index = readFileSync("./public/index.html"); + + if (!index) { + return void res + .writeStatus(`404 Not Found`) + .end("uh oh :("); + } else { + return void res.writeStatus(`200 OK`).end(index); + } + } + + res.writeStatus(`200 OK`).end(data); + } catch (err) { + logger.warn("Unable to serve file at", file); + logger.error(err); + const index = readFileSync("./public/index.html"); + + if (!index) { + return void res.writeStatus(`404 Not Found`).end("uh oh :("); + } else { + return void res.writeStatus(`200 OK`).end(index); + } + } }) .ws("/*", { - idleTimeout: 30, + idleTimeout: 180, maxBackpressure: 1024, maxPayloadLength: 8192, compression: DEDICATED_COMPRESSOR_8KB, - open: ws => { - const ip = String(ws.getRemoteAddressAsText()); - const _id = createUserID(ip); + open: ((ws: WebSocket & { socket: Socket }) => { + ws.socket = new Socket(ws); + // logger.debug("Connection at " + ws.socket.getIP()); + }) as (ws: WebSocket) => void, - logger.debug(ip, _id); - }, + message: (( + ws: WebSocket & { socket: Socket }, + message, + isBinary + ) => { + const msg = decoder.decode(message); + handleMessage(ws.socket, msg); + }) as ( + ws: WebSocket, + message: ArrayBuffer, + isBinary: boolean + ) => void, - message: (ws, message, isBinary) => { - const msg = String(message); - logger.debug(msg); - } + close: (( + ws: WebSocket & { socket: Socket }, + code: number, + message: ArrayBuffer + ) => { + // TODO handle close event + }) as ( + ws: WebSocket, + code: number, + message: ArrayBuffer + ) => void });