From 16219b504ad069f47831f31857c39bf81cf5f99a Mon Sep 17 00:00:00 2001 From: Hri7566 Date: Sun, 10 Sep 2023 06:07:52 -0400 Subject: [PATCH] Fix channel bug and add cursor messages --- src/channel/Channel.ts | 133 ++++++++++++++++++++++++------ src/util/types.d.ts | 13 ++- src/ws/Socket.ts | 88 +++++++++++++++++--- src/ws/events/user/handlers/hi.ts | 2 +- src/ws/events/user/handlers/m.ts | 10 +++ src/ws/events/user/index.ts | 2 + src/ws/server.ts | 13 +-- 7 files changed, 212 insertions(+), 49 deletions(-) create mode 100644 src/ws/events/user/handlers/m.ts diff --git a/src/channel/Channel.ts b/src/channel/Channel.ts index a5a0a85..574f83e 100644 --- a/src/channel/Channel.ts +++ b/src/channel/Channel.ts @@ -1,13 +1,15 @@ +import EventEmitter from "events"; import { Logger } from "../util/Logger"; import { loadConfig } from "../util/config"; import { ChannelSettingValue, ChannelSettings, + ClientEvents, Participant } from "../util/types"; import { Socket } from "../ws/Socket"; -import { app, findSocketByPartID } from "../ws/server"; import { validateChannelSettings } from "./settings"; +import { socketsBySocketID } from "../ws/server"; interface ChannelConfig { forceLoad: string[]; @@ -41,7 +43,7 @@ export const config = loadConfig("config/channels.yml", { export const channelList = new Array(); -export class Channel { +export class Channel extends EventEmitter { private settings: Partial = config.defaultSettings; private ppl = new Array(); @@ -50,6 +52,8 @@ export class Channel { // TODO Add the crown constructor(private _id: string, set?: Partial) { + super(); + this.logger = new Logger("Channel - " + _id); // Validate settings in set @@ -66,9 +70,10 @@ export class Channel { } if (this.isLobby()) { - this.logger.debug(config.lobbySettings); this.settings = config.lobbySettings; } + + this.bindEventListeners(); } public getID() { @@ -113,37 +118,40 @@ export class Channel { } public join(socket: Socket) { - const part = socket.getParticipant(); + const part = socket.getParticipant() as Participant; // Unknown side-effects, but for type safety... - if (!part) return; + // if (!part) return; let hasChangedChannel = false; + let oldChannelID = socket.currentChannelID; - this.logger.debug("Has user?", this.hasUser(part._id)); + // this.logger.debug("Has user?", this.hasUser(part._id)); // Is user in this channel? if (this.hasUser(part._id)) { - // Alreay in channel, add this part ID to IDs list - // TODO + // Alreay in channel, don't add to list, but tell them they're here + hasChangedChannel = true; + this.ppl.push(part); } else { // Are we full? if (!this.isFull()) { // Add to channel - this.ppl.push(part); hasChangedChannel = true; + this.ppl.push(part); } else { // Put us in full channel - socket.setChannel(config.fullChannel); + return socket.setChannel(config.fullChannel); } } if (hasChangedChannel) { - // Is user in any channel that isn't this one? - for (const ch of channelList) { - if (ch == this) continue; - if (ch.hasUser(part._id)) { - ch.leave(socket); + if (socket.currentChannelID) { + const ch = channelList.find( + ch => ch._id == socket.currentChannelID + ); + if (ch) { + ch?.leave(socket); } } @@ -160,29 +168,62 @@ export class Channel { } ]); - // TODO Broadcast channel update + const cursorPos = socket.getCursorPos(); + + // Broadcast participant update + this.sendArray([ + { + m: "p", + _id: part._id, + name: part.name, + color: part.color, + id: part.id, + x: cursorPos.x, + y: cursorPos.y + } + ]); } public leave(socket: Socket) { - this.logger.debug("Leave called"); - const part = socket.getParticipant(); + // this.logger.debug("Leave called"); + const part = socket.getParticipant() as Participant; - // Unknown side-effects, but for type safety... - if (!part) return; - - if (this.hasUser(part._id)) { - this.ppl.splice(this.ppl.indexOf(part), 1); + let dupeCount = 0; + for (const s of socketsBySocketID.values()) { + if (s.getParticipantID() == part.id) { + if (s.currentChannelID == this.getID()) { + dupeCount++; + } + } } - // TODO Broadcast channel update + // this.logger.debug("Dupes:", dupeCount); + + if (dupeCount == 1) { + const p = this.ppl.find(p => p.id == socket.getParticipantID()); + + if (p) { + this.ppl.splice(this.ppl.indexOf(p), 1); + } + } + + // Broadcast bye + this.sendArray([ + { + m: "bye", + p: part.id + } + ]); + + this.emit("update"); } public isFull() { // TODO Use limit setting - if (this.isLobby() && this.ppl.length >= 20) { - return true; - } + // if (this.isLobby() && this.ppl.length >= 20) { + // return true; + // } return false; } @@ -209,6 +250,44 @@ export class Channel { const foundPart = this.ppl.find(p => p.id == id); return !!foundPart; } + + public sendArray( + arr: ClientEvents[EventID][] + ) { + let i = 0; + for (const socket of socketsBySocketID.values()) { + for (const p of this.ppl) { + if (p.id == socket.getParticipantID()) { + // this.logger.debug( + // `Sending to ${socket.getParticipant()?.name} [${i}]:`, + // arr + // ); + socket.sendArray(arr); + i++; + } + } + } + } + + private alreadyBound = false; + + private bindEventListeners() { + if (this.alreadyBound) return; + this.alreadyBound = true; + + this.on("update", () => { + for (const socket of socketsBySocketID.values()) { + for (const p of this.ppl) { + if (socket.getParticipantID() == p.id) { + socket.sendChannelUpdate( + this.getInfo(), + this.getParticipantList() + ); + } + } + } + }); + } } // Forceloader diff --git a/src/util/types.d.ts b/src/util/types.d.ts index 5503ec6..dbdb659 100644 --- a/src/util/types.d.ts +++ b/src/util/types.d.ts @@ -274,8 +274,8 @@ declare interface ClientEvents { m: { m: "m"; - x: number; - y: number; + x: string; + y: string; id: string; }; @@ -306,8 +306,8 @@ declare interface ClientEvents { p: { m: "p"; - x: number; - y: number; + x: number | string; + y: number | string; } & Participant; t: { @@ -315,6 +315,11 @@ declare interface ClientEvents { t: number; e: number; }; + + bye: { + m: "bye"; + p: string; + }; } declare type ServerEventListener = { diff --git a/src/ws/Socket.ts b/src/ws/Socket.ts index 63886ef..e0727ac 100644 --- a/src/ws/Socket.ts +++ b/src/ws/Socket.ts @@ -1,7 +1,13 @@ 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 { + ChannelInfo, + ChannelSettings, + ClientEvents, + Participant, + UserFlags +} from "../util/types"; import { User } from "@prisma/client"; import { createUser, readUser } from "../data/user"; import { eventGroups } from "./events"; @@ -43,8 +49,12 @@ export class Socket extends EventEmitter { }; public currentChannelID: string | undefined; + private cursorPos = { + x: "-10.00", + y: "-10.00" + }; - constructor(private ws: ServerWebSocket) { + constructor(private ws: ServerWebSocket, public socketID: string) { super(); this.ip = ws.remoteAddress; // Participant ID @@ -56,13 +66,15 @@ export class Socket extends EventEmitter { let foundSocket; for (const socket of socketsBySocketID.values()) { - if (socket == this) continue; + if (socket.socketID == this.socketID) continue; if (socket.getUserID() == this.getUserID()) { foundSocket = socket; } } + // logger.debug("Found socket?", foundSocket); + if (!foundSocket) { // Use new session ID this.id = createID(); @@ -72,7 +84,6 @@ export class Socket extends EventEmitter { } this.loadUser(); - this.bindEventListeners(); } @@ -202,18 +213,23 @@ export class Socket extends EventEmitter { public destroy() { // Socket was closed or should be closed, clear data + // logger.debug("Destroying UID:", this._id); + + const foundCh = channelList.find( + ch => ch.getID() === this.currentChannelID + ); + + // logger.debug("(Destroying) Found channel:", foundCh); + + if (foundCh) { + foundCh.leave(this); + } // Simulate closure try { this.ws.close(); - } catch (err) {} - - if (this.currentChannelID) { - const foundCh = channelList.find( - ch => ch.getID() == this.currentChannelID - ); - - if (foundCh) foundCh.leave(this); + } catch (err) { + logger.warn("Problem closing socket:", err); } this.destroyed = true; @@ -222,4 +238,52 @@ export class Socket extends EventEmitter { public isDestroyed() { return this.destroyed == true; } + + public getCursorPos() { + return this.cursorPos; + } + + public setCursorPos(x: number | string, y: number | string) { + if (typeof x == "number") { + x = x.toFixed(2); + } + + if (typeof y == "number") { + y = y.toFixed(2); + } + + this.cursorPos.x = x; + this.cursorPos.y = y; + + // Send through channel + const ch = this.getCurrentChannel(); + if (!ch) return; + + const part = this.getParticipant(); + if (!part) return; + + ch.sendArray([ + { + m: "m", + id: part.id, + x: this.cursorPos.x, + y: this.cursorPos.y + } + ]); + } + + public getCurrentChannel() { + return channelList.find(ch => ch.getID() == this.currentChannelID); + } + + public sendChannelUpdate(ch: ChannelInfo, ppl: Participant[]) { + this.sendArray([ + { + m: "ch", + ch, + p: this.getParticipantID(), + ppl + } + ]); + } } diff --git a/src/ws/events/user/handlers/hi.ts b/src/ws/events/user/handlers/hi.ts index 2e0e6d4..e291670 100644 --- a/src/ws/events/user/handlers/hi.ts +++ b/src/ws/events/user/handlers/hi.ts @@ -12,7 +12,7 @@ export const hi: ServerEventListener<"hi"> = { _id: socket.getUserID(), name: "Anonymous", color: "#777", - id: socket.getUserID() + id: "" }; } diff --git a/src/ws/events/user/handlers/m.ts b/src/ws/events/user/handlers/m.ts new file mode 100644 index 0000000..ba80cf5 --- /dev/null +++ b/src/ws/events/user/handlers/m.ts @@ -0,0 +1,10 @@ +import { ServerEventListener } from "../../../../util/types"; +import { eventGroups } from "../../../events"; + +export const m: ServerEventListener<"m"> = { + id: "m", + callback: (msg, socket) => { + if (!msg.x || !msg.y) return; + socket.setCursorPos(msg.x, msg.y); + } +}; diff --git a/src/ws/events/user/index.ts b/src/ws/events/user/index.ts index 0220091..94ff256 100644 --- a/src/ws/events/user/index.ts +++ b/src/ws/events/user/index.ts @@ -5,9 +5,11 @@ export const EVENTGROUP_USER = new EventGroup("user"); import { hi } from "./handlers/hi"; import { devices } from "./handlers/devices"; import { ch } from "./handlers/ch"; +import { m } from "./handlers/m"; EVENTGROUP_USER.add(hi); EVENTGROUP_USER.add(devices); EVENTGROUP_USER.add(ch); +EVENTGROUP_USER.add(m); eventGroups.push(EVENTGROUP_USER); diff --git a/src/ws/server.ts b/src/ws/server.ts index 2ba00f6..f12d3c4 100644 --- a/src/ws/server.ts +++ b/src/ws/server.ts @@ -20,7 +20,7 @@ export function findSocketByPartID(id: string) { export function findSocketByUserID(_id: string) { for (const socket of socketsBySocketID.values()) { - logger.debug("User ID:", socket.getUserID()); + // logger.debug("User ID:", socket.getUserID()); if (socket.getUserID() == _id) return socket; } } @@ -35,6 +35,7 @@ export function findSocketByIP(ip: string) { export const app = Bun.serve({ port: env.PORT, + hostname: "0.0.0.0", fetch: (req, server) => { if (server.upgrade(req)) { return; @@ -64,11 +65,11 @@ export const app = Bun.serve({ }, websocket: { open: ws => { - const socket = new Socket(ws); + const socket = new Socket(ws, createSocketID()); (ws as unknown as any).socket = socket; - logger.debug("Connection at " + socket.getIP()); + // logger.debug("Connection at " + socket.getIP()); - socketsBySocketID.set(createSocketID(), socket); + socketsBySocketID.set(socket.socketID, socket); }, message: (ws, message) => { @@ -77,7 +78,7 @@ export const app = Bun.serve({ }, close: (ws, code, message) => { - logger.debug("Close called"); + // logger.debug("Close called"); const socket = (ws as unknown as any).socket as Socket; socket.destroy(); @@ -91,3 +92,5 @@ export const app = Bun.serve({ } } }); + +logger.info("Listening on port", env.PORT);