Implement events

This commit is contained in:
Hri7566 2023-09-09 01:38:47 -04:00
parent 683f14ddbc
commit d46078efa1
12 changed files with 346 additions and 14 deletions

3
src/ws/Gateway.ts Normal file
View File

@ -0,0 +1,3 @@
export class Gateway {
public hasSentDevices: boolean = false;
}

148
src/ws/Socket.ts Normal file
View File

@ -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<UsersConfig>("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<ChannelSettings>;
} = {
_id: undefined,
set: {}
};
constructor(private ws: WebSocket<unknown>) {
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<ChannelSettings>) {
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<EventID extends keyof ClientEvents>(
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;
}
}

2
src/ws/events.inc.ts Normal file
View File

@ -0,0 +1,2 @@
import "./events/user";
import "./events/admin";

18
src/ws/events.ts Normal file
View File

@ -0,0 +1,18 @@
import { ServerEventListener, ServerEvents } from "../util/types";
export class EventGroup {
public eventList = new Array<ServerEventListener<any>>();
constructor(public id: string) {}
public add(listener: ServerEventListener<any>) {
this.eventList.push(listener);
}
public remove(listener: ServerEventListener<any>) {
this.eventList.splice(this.eventList.indexOf(listener), 1);
}
}
export const eventGroups = new Array<EventGroup>();
import "./events.inc";

View File

@ -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
}
};

View File

@ -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);

View File

@ -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;
}
};

View File

@ -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
}
]);
}
};

View File

@ -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);

29
src/ws/message.ts Normal file
View File

@ -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);
}
}

View File

View File

@ -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 { Logger } from "../util/Logger";
import { createUserID } from "../util/id"; 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"); const logger = new Logger("WebSocket Server");
export const app = App() export const app = App()
.get("/", (res, req) => { .get("/*", async (res, req) => {
const url = req.getUrl(); const url = req.getUrl();
logger.debug(`${req.getMethod()} ${url} ${req}`); const ip = decoder.decode(res.getRemoteAddressAsText());
res.writeStatus(`200 OK`).end("HI!"); // 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("/*", { .ws("/*", {
idleTimeout: 30, idleTimeout: 180,
maxBackpressure: 1024, maxBackpressure: 1024,
maxPayloadLength: 8192, maxPayloadLength: 8192,
compression: DEDICATED_COMPRESSOR_8KB, compression: DEDICATED_COMPRESSOR_8KB,
open: ws => { open: ((ws: WebSocket<unknown> & { socket: Socket }) => {
const ip = String(ws.getRemoteAddressAsText()); ws.socket = new Socket(ws);
const _id = createUserID(ip); // logger.debug("Connection at " + ws.socket.getIP());
}) as (ws: WebSocket<unknown>) => void,
logger.debug(ip, _id); message: ((
}, ws: WebSocket<unknown> & { socket: Socket },
message,
isBinary
) => {
const msg = decoder.decode(message);
handleMessage(ws.socket, msg);
}) as (
ws: WebSocket<unknown>,
message: ArrayBuffer,
isBinary: boolean
) => void,
message: (ws, message, isBinary) => { close: ((
const msg = String(message); ws: WebSocket<unknown> & { socket: Socket },
logger.debug(msg); code: number,
} message: ArrayBuffer
) => {
// TODO handle close event
}) as (
ws: WebSocket<unknown>,
code: number,
message: ArrayBuffer
) => void
}); });