From 99609bf2ef178f662006682aba94b932fdecf226 Mon Sep 17 00:00:00 2001 From: Hri7566 Date: Sat, 9 Sep 2023 05:06:27 -0400 Subject: [PATCH] Add channels and migrate to bun --- bun.lockb | Bin 0 -> 17710 bytes config/channels.yml | 15 +- package.json | 9 +- pnpm-lock.yaml | 292 ------------------------------ src/channel/Channel.ts | 215 +++++++++++++++++++++- src/index.ts | 8 +- src/util/types.d.ts | 5 +- src/ws/Socket.ts | 73 +++++++- src/ws/events.inc.ts | 5 +- src/ws/events/user/handlers/ch.ts | 11 ++ src/ws/events/user/handlers/hi.ts | 2 +- src/ws/events/user/index.ts | 2 + src/ws/message.ts | 1 - src/ws/server.ts | 222 +++++++++++++++-------- tsconfig.json | 12 +- 15 files changed, 470 insertions(+), 402 deletions(-) create mode 100755 bun.lockb delete mode 100644 pnpm-lock.yaml create mode 100644 src/ws/events/user/handlers/ch.ts diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..bf0fa414029ea676d319da24510b044e06967116 GIT binary patch literal 17710 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.13'} - requiresBuild: true - peerDependencies: - prisma: '*' - peerDependenciesMeta: - prisma: - optional: true - dependencies: - '@prisma/engines-version': 5.2.0-25.2804dc98259d2ea960602aca6b8e7fdc03c1758f - prisma: 5.2.0 - dev: false - - /@prisma/engines-version@5.2.0-25.2804dc98259d2ea960602aca6b8e7fdc03c1758f: - resolution: {integrity: sha512-jsnKT5JIDIE01lAeCj2ghY9IwxkedhKNvxQeoyLs6dr4ZXynetD0vTy7u6wMJt8vVPv8I5DPy/I4CFaoXAgbtg==} - dev: false - - /@prisma/engines@5.2.0: - resolution: {integrity: sha512-dT7FOLUCdZmq+AunLqB1Iz+ZH/IIS1Fz2THmKZQ6aFONrQD/BQ5ecJ7g2wGS2OgyUFf4OaLam6/bxmgdOBDqig==} - requiresBuild: true - - /@t3-oss/env-core@0.6.1(typescript@5.2.2)(zod@3.22.2): - resolution: {integrity: sha512-KQD7qEDJtkWIWWmTVjNvk0wnHpkvAQ6CRbUxbWMFNG/fiosBQDQvtRpBNu6USxBscJCoC4z6y7P9MN52/mLOzw==} - peerDependencies: - typescript: '>=4.7.2' - zod: ^3.0.0 - dependencies: - typescript: 5.2.2 - zod: 3.22.2 - dev: false - - /@types/node@20.5.9: - resolution: {integrity: sha512-PcGNd//40kHAS3sTlzKB9C9XL4K0sTup8nbG5lC14kzEteTNuAFh9u5nA0o5TWnSG2r/JNPRXFVcHJIIeRlmqQ==} - dev: true - - /argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - dev: false - - /astronomia@4.1.1: - resolution: {integrity: sha512-TcJD9lUC5eAo0/Ji7rnQauX/yQbi0yZWM+JsNr77W3OA5fsrgvuFgubLMFwfw4VlZ29cu9dG/yfJbfvuTSftjg==} - engines: {node: '>=12.0.0'} - dev: false - - /bindings@1.5.0: - resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} - dependencies: - file-uri-to-path: 1.0.0 - dev: false - - /caldate@2.0.5: - resolution: {integrity: sha512-JndhrUuDuE975KUhFqJaVR1OQkCHZqpOrJur/CFXEIEhWhBMjxO85cRSK8q4FW+B+yyPq6GYua2u4KvNzTcq0w==} - engines: {node: '>=12.0.0'} - dependencies: - moment-timezone: 0.5.43 - dev: false - - /date-bengali-revised@2.0.2: - resolution: {integrity: sha512-q9iDru4+TSA9k4zfm0CFHJj6nBsxP7AYgWC/qodK/i7oOIlj5K2z5IcQDtESfs/Qwqt/xJYaP86tkazd/vRptg==} - engines: {node: '>=12.0.0'} - dev: false - - /date-chinese@2.1.4: - resolution: {integrity: sha512-WY+6+Qw92ZGWFvGtStmNQHEYpNa87b8IAQ5T8VKt4wqrn24lBXyyBnWI5jAIyy7h/KVwJZ06bD8l/b7yss82Ww==} - engines: {node: '>=12.0.0'} - dependencies: - astronomia: 4.1.1 - dev: false - - /date-easter@1.0.2: - resolution: {integrity: sha512-mpC1izx7lUSLYl4B88V2W57eNB4xS2ic+ahxK2AYUsaBTsCeHzT6K5ymUKzL9YPFf/GlygFqpiD4/NO1hxDsLw==} - engines: {node: '>=12.0.0'} - dev: false - - /date-holidays-parser@3.4.4: - resolution: {integrity: sha512-R5aO4oT8H51ZKdvApqHrqYEiNBrqT6tRj2PFXNcZfqMI4nxY7KKKly0ZsmquR5gY+x9ldKR8SAMdozzIInaoXg==} - engines: {node: '>=12.0.0'} - dependencies: - astronomia: 4.1.1 - caldate: 2.0.5 - date-bengali-revised: 2.0.2 - date-chinese: 2.1.4 - date-easter: 1.0.2 - deepmerge: 4.3.1 - jalaali-js: 1.2.6 - moment-timezone: 0.5.43 - dev: false - - /date-holidays@3.21.5: - resolution: {integrity: sha512-5X/UK7FunfIiM/q7CwepNfzh1XkkukdZNfTPyKlD5kx01MQzJ9DqKyTcFNzlQJ+HgpAxqUqSs3+F8mwV9bzo/w==} - engines: {node: '>=12.0.0'} - hasBin: true - dependencies: - date-holidays-parser: 3.4.4 - js-yaml: 4.1.0 - lodash.omit: 4.5.0 - lodash.pick: 4.4.0 - prepin: 1.0.3 - dev: false - - /deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} - dev: false - - /dotenv@8.6.0: - resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} - engines: {node: '>=10'} - dev: false - - /events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} - dev: false - - /fancy-text-converter@1.0.9: - resolution: {integrity: sha512-tFUAWpEfZOYhdsjILVu7c0PL9Ud9pTQmonm/2mdvFC7WcEHIYi9NYS5irJYFdBJDIRSqi64XV+IhHPc/ngxtyw==} - dev: false - - /file-uri-to-path@1.0.0: - resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - dev: false - - /inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: false - - /jalaali-js@1.2.6: - resolution: {integrity: sha512-io974va+Qyu+UfuVX3UIAgJlxLhAMx9Y8VMfh+IG00Js7hXQo1qNQuwSiSa0xxco0SVgx5HWNkaiCcV+aZ8WPw==} - dev: false - - /js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true - dependencies: - argparse: 2.0.1 - dev: false - - /keccak@2.1.0: - resolution: {integrity: sha512-m1wbJRTo+gWbctZWay9i26v5fFnYkOn7D5PCxJ3fZUGUEb49dE1Pm4BREUYCt/aoO6di7jeoGmhvqN9Nzylm3Q==} - engines: {node: '>=5.12.0'} - requiresBuild: true - dependencies: - bindings: 1.5.0 - inherits: 2.0.4 - nan: 2.17.0 - safe-buffer: 5.2.1 - dev: false - - /lodash.omit@4.5.0: - resolution: {integrity: sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==} - dev: false - - /lodash.pick@4.4.0: - resolution: {integrity: sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==} - dev: false - - /moment-timezone@0.5.43: - resolution: {integrity: sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ==} - dependencies: - moment: 2.29.4 - dev: false - - /moment@2.29.4: - resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} - dev: false - - /mppclone-client@1.1.3: - resolution: {integrity: sha512-5DSkQmZOj823/BPwi6CQa4UWkoAX7itfNxf6L26NJS/qj9AljuKoqnIZxhtSKdak75qZd5Jgx+zD1aXflRNxHg==} - dependencies: - ws: 8.14.0 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - dev: false - - /nan@2.17.0: - resolution: {integrity: sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==} - dev: false - - /prepin@1.0.3: - resolution: {integrity: sha512-0XL2hreherEEvUy0fiaGEfN/ioXFV+JpImqIzQjxk6iBg4jQ2ARKqvC4+BmRD8w/pnpD+lbxvh0Ub+z7yBEjvA==} - hasBin: true - dev: false - - /prisma@5.2.0: - resolution: {integrity: sha512-FfFlpjVCkZwrqxDnP4smlNYSH1so+CbfjgdpioFzGGqlQAEm6VHAYSzV7jJgC3ebtY9dNOhDMS2+4/1DDSM7bQ==} - engines: {node: '>=16.13'} - hasBin: true - requiresBuild: true - dependencies: - '@prisma/engines': 5.2.0 - - /safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - dev: false - - /typescript@5.2.2: - resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} - engines: {node: '>=14.17'} - hasBin: true - - /unique-names-generator@4.7.1: - resolution: {integrity: sha512-lMx9dX+KRmG8sq6gulYYpKWZc9RlGsgBR6aoO8Qsm3qvkSJ+3rAymr+TnV8EDMrIrwuFJ4kruzMWM/OpYzPoow==} - engines: {node: '>=8'} - dev: false - - /ws@8.14.0: - resolution: {integrity: sha512-WR0RJE9Ehsio6U4TuM+LmunEsjQ5ncHlw4sn9ihD6RoJKZrVyH9FWV3dmnwu8B2aNib1OvG2X6adUCyFpQyWcg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - dev: false - - /yaml@2.3.2: - resolution: {integrity: sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==} - engines: {node: '>= 14'} - dev: false - - /zod@3.22.2: - resolution: {integrity: sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==} - dev: false - - github.com/uNetworking/uWebSockets.js/809b99d2d7d12e2cbf89b7135041e9b41ff84084: - resolution: {tarball: https://codeload.github.com/uNetworking/uWebSockets.js/tar.gz/809b99d2d7d12e2cbf89b7135041e9b41ff84084} - name: uWebSockets.js - version: 20.31.0 - dev: false diff --git a/src/channel/Channel.ts b/src/channel/Channel.ts index a8ccb48..a36f398 100644 --- a/src/channel/Channel.ts +++ b/src/channel/Channel.ts @@ -1,11 +1,218 @@ -// TODO Load channel config file +import { Logger } from "../util/Logger"; +import { loadConfig } from "../util/config"; +import { + ChannelSettingValue, + ChannelSettings, + Participant +} from "../util/types"; +import { Socket } from "../ws/Socket"; +import { app, findSocketByPartID } from "../ws/server"; +import { validateChannelSettings } from "./settings"; + +interface ChannelConfig { + forceLoad: string[]; + lobbySettings: Partial; + defaultSettings: Partial; + lobbyRegexes: string[]; + lobbyBackdoor: string; + fullChannel: string; +} + +export const config = loadConfig("config/channels.yml", { + forceLoad: ["lobby", "test/awkward"], + lobbySettings: { + lobby: true, + chat: true, + crownsolo: false, + visible: true + }, + defaultSettings: { + chat: true, + crownsolo: false, + color: "#480505", + color2: "#000000", + visible: true + }, + // TODO Test this regex + lobbyRegexes: ["^lobby[1-9]?[1-9]?$", "^test/.+$"], + lobbyBackdoor: "lolwutsecretlobbybackdoor", + fullChannel: "test/awkward" +}); + +export const channelList = new Array(); export class Channel { - constructor(private _id: string) {} + private settings: Partial = config.defaultSettings; + private ppl = new Array(); - getID() { + public logger: Logger; + + // TODO Add the crown + + constructor( + private _id: string, + set: Partial = config.defaultSettings + ) { + this.logger = new Logger("Channel - " + _id); + // Verify default settings just in case + this.changeSettings(this.settings, true); + + if (this.isLobby()) { + set = config.lobbySettings; + } + + this.changeSettings(set); + } + + public getID() { return this._id; } - isLobby() {} + public isLobby() { + for (const reg of config.lobbyRegexes) { + let exp = new RegExp(reg, "g"); + + if (this.getID().match(exp)) { + return true; + } + } + + return false; + } + + public changeSettings( + set: Partial, + admin: boolean = false + ) { + if (!admin) { + if (set.lobby) set.lobby = undefined; + if (set.owner_id) set.owner_id = undefined; + } + + // Verify settings + const validSettings = validateChannelSettings(set); + + for (const key of Object.keys(validSettings)) { + // Setting is valid? + if ((validSettings as Record)[key]) { + // Change setting + (this.settings as Record)[key] = ( + set as Record + )[key]; + } + } + } + + public join(socket: Socket) { + const part = socket.getParticipant(); + + // Unknown side-effects, but for type safety... + if (!part) return; + + let hasChangedChannel = false; + + this.logger.debug("Has user?", this.hasUser(part)); + + // Is user in this channel? + if (this.hasUser(part)) { + // Alreay in channel, disconnect old + + const oldSocket = findSocketByPartID(part.id); + + if (oldSocket) { + oldSocket.destroy(); + } + + // Add to channel + this.ppl.push(part); + hasChangedChannel = true; + } else { + // Are we full? + if (!this.isFull()) { + // Add to channel + this.ppl.push(part); + hasChangedChannel = true; + } else { + // Put us in full channel + 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)) { + ch.leave(socket); + } + } + } + + this.logger.debug("Participant list:", this.ppl); + + // Send our data back + socket.sendArray([ + { + m: "ch", + ch: this.getInfo(), + p: part.id, + ppl: this.getParticipantList() + } + ]); + + // TODO Broadcast channel update + } + + public leave(socket: Socket) { + this.logger.debug("Leave called"); + const part = socket.getParticipant(); + + // Same as above... + if (!part) return; + + if (this.hasUser(part)) { + this.ppl.splice(this.ppl.indexOf(part), 1); + } + // TODO Broadcast channel update + } + + public isFull() { + // TODO Use limit setting + + if (this.isLobby() && this.ppl.length >= 20) { + return true; + } + + return false; + } + + public getInfo() { + return { + _id: this.getID(), + id: this.getID(), + count: this.ppl.length, + settings: this.settings + }; + } + + public getParticipantList() { + return this.ppl; + } + + public hasUser(part: Participant) { + const foundPart = this.ppl.find(p => p._id == part._id); + return !!foundPart; + } +} + +// Forceloader +let hasFullChannel = false; + +for (const id of config.forceLoad) { + channelList.push(new Channel(id)); + if (id == config.fullChannel) hasFullChannel = true; +} + +if (!hasFullChannel) { + channelList.push(new Channel(config.fullChannel)); } diff --git a/src/index.ts b/src/index.ts index 7e0587a..d902f96 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,6 @@ import env from "./util/env"; -import { app } from "./ws/server"; +// import { app } from "./ws/server"; +import "./ws/server"; import { Logger } from "./util/Logger"; const logger = new Logger("Main"); - -// No IPv6 (yet) -app.listen("0.0.0.0", env.PORT, () => { - logger.info("Listening on :" + env.PORT); -}); diff --git a/src/util/types.d.ts b/src/util/types.d.ts index 67c8c22..bdd8cc6 100644 --- a/src/util/types.d.ts +++ b/src/util/types.d.ts @@ -100,9 +100,10 @@ declare interface ChannelInfo { id: string; _id: string; crown?: Crown; - settings: ChannelSettings; + settings: Partial; } +// Events copied from Hri7566/mppclone-client typedefs declare interface ServerEvents { a: { m: "a"; @@ -246,6 +247,7 @@ declare interface ClientEvents { m: "ch"; p: string; ch: ChannelInfo; + ppl: Participant[]; }; custom: { @@ -284,6 +286,7 @@ declare interface ClientEvents { }; notification: { + m: "notification"; duration?: number; class?: string; id?: string; diff --git a/src/ws/Socket.ts b/src/ws/Socket.ts index 5d5f40d..2185d4c 100644 --- a/src/ws/Socket.ts +++ b/src/ws/Socket.ts @@ -1,4 +1,3 @@ -import { WebSocket } from "uWebSockets.js"; import { createColor, createID, createUserID } from "../util/id"; import { decoder, encoder } from "../util/helpers"; import EventEmitter from "events"; @@ -8,6 +7,8 @@ import { createUser, readUser } from "../data/user"; import { eventGroups } from "./events"; import { loadConfig } from "../util/config"; import { Gateway } from "./Gateway"; +import { Channel, channelList } from "../channel/Channel"; +import { ServerWebSocket } from "bun"; interface UsersConfig { defaultName: string; @@ -31,17 +32,17 @@ export class Socket extends EventEmitter { public desiredChannel: { _id: string | undefined; - set: Partial; + set: Partial | undefined; } = { _id: undefined, set: {} }; - constructor(private ws: WebSocket) { - super(); - this.ip = decoder.decode(this.ws.getRemoteAddressAsText()); + public currentChannelID: string | undefined; - // Participant ID + constructor(private ws: ServerWebSocket) { + super(); + this.ip = ws.remoteAddress; // Participant ID this.id = createID(); // User ID @@ -61,9 +62,40 @@ export class Socket extends EventEmitter { return this._id; } - public setChannel(_id: string, set: Partial) { + public getParticipantID() { + return this.id; + } + + public setChannel(_id: string, set?: Partial) { + if (this.isDestroyed()) return; + this.desiredChannel._id = _id; this.desiredChannel.set = set; + + let channel; + try { + for (const ch of channelList.values()) { + if (ch.getID() == this.desiredChannel._id) { + channel = ch; + break; + } + } + } catch (err) {} + + // Does channel exist? + if (channel) { + // Exists, join normally + channel.join(this); + } else { + // Doesn't exist, join with crown + channel = new Channel( + this.desiredChannel._id, + this.desiredChannel.set + ); + + channel.join(this); + // TODO Give the crown upon joining + } } private bindEventListeners() { @@ -80,7 +112,8 @@ export class Socket extends EventEmitter { public sendArray( arr: ClientEvents[EventID][] ) { - this.ws.send(encoder.encode(JSON.stringify(arr))); + if (this.isDestroyed()) return; + this.ws.send(JSON.stringify(arr)); } private async loadUser() { @@ -142,7 +175,27 @@ export class Socket extends EventEmitter { } } - public getParticipantID() { - return this.id; + private destroyed = false; + + public destroy() { + // Socket was closed or should be closed, clear data + + // 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); + } + + this.destroyed = true; + } + + public isDestroyed() { + return this.destroyed == true; } } diff --git a/src/ws/events.inc.ts b/src/ws/events.inc.ts index 0ff6c3c..26c35a1 100644 --- a/src/ws/events.inc.ts +++ b/src/ws/events.inc.ts @@ -1,2 +1,3 @@ -import "./events/user"; -import "./events/admin"; +// Bun hoists import, but not require? +require("./events/user"); +require("./events/admin"); diff --git a/src/ws/events/user/handlers/ch.ts b/src/ws/events/user/handlers/ch.ts new file mode 100644 index 0000000..303bd1b --- /dev/null +++ b/src/ws/events/user/handlers/ch.ts @@ -0,0 +1,11 @@ +import { ServerEventListener } from "../../../../util/types"; +import { eventGroups } from "../../../events"; + +export const ch: ServerEventListener<"ch"> = { + id: "ch", + callback: (msg, socket) => { + // Switch channel + if (!msg._id) return; + socket.setChannel(msg._id, msg.set); + } +}; diff --git a/src/ws/events/user/handlers/hi.ts b/src/ws/events/user/handlers/hi.ts index 8cdab68..8c18cc1 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.getParticipantID() + id: socket.getUserID() }; } diff --git a/src/ws/events/user/index.ts b/src/ws/events/user/index.ts index 795b762..0220091 100644 --- a/src/ws/events/user/index.ts +++ b/src/ws/events/user/index.ts @@ -4,8 +4,10 @@ export const EVENTGROUP_USER = new EventGroup("user"); import { hi } from "./handlers/hi"; import { devices } from "./handlers/devices"; +import { ch } from "./handlers/ch"; EVENTGROUP_USER.add(hi); EVENTGROUP_USER.add(devices); +EVENTGROUP_USER.add(ch); eventGroups.push(EVENTGROUP_USER); diff --git a/src/ws/message.ts b/src/ws/message.ts index f9931a2..841a96b 100644 --- a/src/ws/message.ts +++ b/src/ws/message.ts @@ -1,4 +1,3 @@ -import { WebSocket } from "uWebSockets.js"; import { Logger } from "../util/Logger"; import { Socket } from "./Socket"; import { hasOwn } from "../util/helpers"; diff --git a/src/ws/server.ts b/src/ws/server.ts index 4a9aebb..4f495b9 100644 --- a/src/ws/server.ts +++ b/src/ws/server.ts @@ -1,97 +1,165 @@ -import { - App, - DEDICATED_COMPRESSOR_8KB, - HttpRequest, - HttpResponse, - WebSocket -} 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 fs from "fs"; +// import { join } from "path"; +import path from "path"; import { handleMessage } from "./message"; import { decoder } from "../util/helpers"; import { Socket } from "./Socket"; +import { serve, file } from "bun"; +import env from "../util/env"; const logger = new Logger("WebSocket Server"); -export const app = App() - .get("/*", async (res, req) => { - const url = req.getUrl(); - const ip = decoder.decode(res.getRemoteAddressAsText()); - // logger.debug(`${req.getMethod()} ${url} ${ip}`); - // res.writeStatus(`200 OK`).end("HI!"); - const file = join("./public/", url); +const usersByPartID = new Map(); - // TODO Cleaner file serving - try { - const stats = lstatSync(file); +export function findSocketByPartID(id: string) { + for (const key of usersByPartID.keys()) { + if (key == id) return usersByPartID.get(key); + } +} - let data; - if (!stats.isDirectory()) { - data = readFileSync(file); - } +// Original uWebSockets code +// export const app = App() +// .get("/*", async (res, req) => { +// const url = req.getUrl(); +// const ip = decoder.decode(res.getRemoteAddressAsText()); +// // logger.debug(`${req.getMethod()} ${url} ${ip}`); +// // res.writeStatus(`200 OK`).end("HI!"); +// const file = join("./public/", url); - // logger.debug(filename); +// // TODO Cleaner file serving +// try { +// const stats = lstatSync(file); - if (!data) { - const index = readFileSync("./public/index.html"); +// let data; +// if (!stats.isDirectory()) { +// data = readFileSync(file); +// } - if (!index) { - return void res - .writeStatus(`404 Not Found`) - .end("uh oh :("); +// // 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: 25, +// maxBackpressure: 1024, +// maxPayloadLength: 8192, +// compression: DEDICATED_COMPRESSOR_8KB, + +// open: ((ws: WebSocket & { socket: Socket }) => { +// ws.socket = new Socket(ws); +// // logger.debug("Connection at " + ws.socket.getIP()); + +// usersByPartID.set(ws.socket.getParticipantID(), ws.socket); +// }) as (ws: WebSocket) => void, + +// message: (( +// ws: WebSocket & { socket: Socket }, +// message, +// isBinary +// ) => { +// const msg = decoder.decode(message); +// handleMessage(ws.socket, msg); +// }) as ( +// ws: WebSocket, +// message: ArrayBuffer, +// isBinary: boolean +// ) => void, + +// close: (( +// ws: WebSocket & { socket: Socket }, +// code: number, +// message: ArrayBuffer +// ) => { +// logger.debug("Close called"); +// ws.socket.destroy(); +// usersByPartID.delete(ws.socket.getParticipantID()); +// }) as ( +// ws: WebSocket, +// code: number, +// message: ArrayBuffer +// ) => void +// }); + +export const app = Bun.serve({ + port: env.PORT, + fetch: (req, server) => { + if (server.upgrade(req)) { + return; + } else { + const url = new URL(req.url).pathname; + // const ip = decoder.decode(res.getRemoteAddressAsText()); + // logger.debug(`${req.getMethod()} ${url} ${ip}`); + // res.writeStatus(`200 OK`).end("HI!"); + const file = path.join("./public/", url); + + try { + if (fs.lstatSync(file).isFile()) { + const data = Bun.file(file); + + if (data) { + return new Response(data); + } else { + return new Response(Bun.file("./public/index.html")); + } } else { - return void res.writeStatus(`200 OK`).end(index); + return new Response(Bun.file("./public/index.html")); } - } - - 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); + } catch (err) { + return new Response(Bun.file("./public/index.html")); } } - }) - .ws("/*", { - idleTimeout: 180, - maxBackpressure: 1024, - maxPayloadLength: 8192, - compression: DEDICATED_COMPRESSOR_8KB, + }, + websocket: { + open: ws => { + const socket = new Socket(ws); + (ws as unknown as any).socket = socket; + logger.debug("Connection at " + socket.getIP()); - open: ((ws: WebSocket & { socket: Socket }) => { - ws.socket = new Socket(ws); - // logger.debug("Connection at " + ws.socket.getIP()); - }) as (ws: WebSocket) => void, + usersByPartID.set(socket.getParticipantID(), socket); + }, - 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) => { + const msg = message.toString(); + handleMessage((ws as unknown as any).socket, msg); + }, - close: (( - ws: WebSocket & { socket: Socket }, - code: number, - message: ArrayBuffer - ) => { - // TODO handle close event - }) as ( - ws: WebSocket, - code: number, - message: ArrayBuffer - ) => void - }); + close: (ws, code, message) => { + logger.debug("Close called"); + const socket = (ws as unknown as any).socket as Socket; + socket.destroy(); + usersByPartID.delete(socket.getParticipantID()); + } + } +}); diff --git a/tsconfig.json b/tsconfig.json index ab7739d..8afd498 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,8 +11,10 @@ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ /* Language and Environment */ - "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + // "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + "target": "ESNext", // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + "lib": ["ESNext"], // "jsx": "preserve", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ @@ -23,20 +25,25 @@ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + "moduleDetection": "force", /* Modules */ - "module": "commonjs" /* Specify what module code is generated. */, + // "module": "commonjs" /* Specify what module code is generated. */, + "module": "ESNext", // "rootDir": "./", /* Specify the root folder within your source files. */ "rootDir": "./src/", // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + "moduleResolution": "Bundler", // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + "types": ["bun-types"], // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + "allowImportingTsExtensions": true, // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ @@ -60,6 +67,7 @@ "outDir": "./out/", // "removeComments": true, /* Disable emitting comments. */ // "noEmit": true, /* Disable emitting files from a compilation. */ + "noEmit": true, // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */