Add channels and migrate to bun

This commit is contained in:
Hri7566 2023-09-09 05:06:27 -04:00
parent c068545958
commit 99609bf2ef
15 changed files with 470 additions and 402 deletions

BIN
bun.lockb Executable file

Binary file not shown.

View File

@ -6,9 +6,20 @@ lobbySettings:
lobby: true
chat: true
crownsolo: false
color: "#eeeeee"
color2: "#888888"
visible: true
defaultSettings:
chat: true
crownsolo: false
color: "#480505"
color2: "#000000"
visible: true
lobbyRegexes:
- "^lobby\\d\\d$"
- "^test/.*$"
- "^lobby[1-9]?[1-9]?$"
- "^test/.+$"
lobbyBackdoor: "lolwutsecretlobbybackdoor"
fullChannel: "test/awkward"

View File

@ -4,9 +4,9 @@
"description": "Hri7566's MPP Server",
"main": "out/index.js",
"scripts": {
"start": "node .",
"build": "npx tsc",
"dev": "pnpm build && pnpm start"
"start": "bun .",
"build": "bun build ./src/index.ts --outdir=out",
"dev": "bun run src/index.ts"
},
"keywords": [],
"author": "Hri7566",
@ -14,13 +14,14 @@
"dependencies": {
"@prisma/client": "5.2.0",
"@t3-oss/env-core": "^0.6.1",
"bun": "^1.0.0",
"bun-types": "^1.0.1",
"date-holidays": "^3.21.5",
"dotenv": "^8.6.0",
"events": "^3.3.0",
"fancy-text-converter": "^1.0.9",
"keccak": "^2.1.0",
"mppclone-client": "^1.1.3",
"uWebSockets.js": "uNetworking/uWebSockets.js#v20.31.0",
"unique-names-generator": "^4.7.1",
"yaml": "^2.3.2",
"zod": "^3.22.2"

View File

@ -1,292 +0,0 @@
lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
dependencies:
'@prisma/client':
specifier: 5.2.0
version: 5.2.0(prisma@5.2.0)
'@t3-oss/env-core':
specifier: ^0.6.1
version: 0.6.1(typescript@5.2.2)(zod@3.22.2)
date-holidays:
specifier: ^3.21.5
version: 3.21.5
dotenv:
specifier: ^8.6.0
version: 8.6.0
events:
specifier: ^3.3.0
version: 3.3.0
fancy-text-converter:
specifier: ^1.0.9
version: 1.0.9
keccak:
specifier: ^2.1.0
version: 2.1.0
mppclone-client:
specifier: ^1.1.3
version: 1.1.3
uWebSockets.js:
specifier: github:uNetworking/uWebSockets.js#v20.31.0
version: github.com/uNetworking/uWebSockets.js/809b99d2d7d12e2cbf89b7135041e9b41ff84084
unique-names-generator:
specifier: ^4.7.1
version: 4.7.1
yaml:
specifier: ^2.3.2
version: 2.3.2
zod:
specifier: ^3.22.2
version: 3.22.2
devDependencies:
'@types/node':
specifier: ^20.5.9
version: 20.5.9
prisma:
specifier: ^5.2.0
version: 5.2.0
typescript:
specifier: ^5.2.2
version: 5.2.2
packages:
/@prisma/client@5.2.0(prisma@5.2.0):
resolution: {integrity: sha512-AiTjJwR4J5Rh6Z/9ZKrBBLel3/5DzUNntMohOy7yObVnVoTNVFi2kvpLZlFuKO50d7yDspOtW6XBpiAd0BVXbQ==}
engines: {node: '>=16.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

View File

@ -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<ChannelSettings>;
defaultSettings: Partial<ChannelSettings>;
lobbyRegexes: string[];
lobbyBackdoor: string;
fullChannel: string;
}
export const config = loadConfig<ChannelConfig>("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<Channel>();
export class Channel {
constructor(private _id: string) {}
private settings: Partial<ChannelSettings> = config.defaultSettings;
private ppl = new Array<Participant>();
getID() {
public logger: Logger;
// TODO Add the crown
constructor(
private _id: string,
set: Partial<ChannelSettings> = 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<ChannelSettings>,
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<string, boolean>)[key]) {
// Change setting
(this.settings as Record<string, ChannelSettingValue>)[key] = (
set as Record<string, ChannelSettingValue>
)[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));
}

View File

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

5
src/util/types.d.ts vendored
View File

@ -100,9 +100,10 @@ declare interface ChannelInfo {
id: string;
_id: string;
crown?: Crown;
settings: ChannelSettings;
settings: Partial<ChannelSettings>;
}
// 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;

View File

@ -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<ChannelSettings>;
set: Partial<ChannelSettings> | undefined;
} = {
_id: undefined,
set: {}
};
constructor(private ws: WebSocket<unknown>) {
super();
this.ip = decoder.decode(this.ws.getRemoteAddressAsText());
public currentChannelID: string | undefined;
// Participant ID
constructor(private ws: ServerWebSocket<unknown>) {
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<ChannelSettings>) {
public getParticipantID() {
return this.id;
}
public setChannel(_id: string, set?: Partial<ChannelSettings>) {
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<EventID extends keyof ClientEvents>(
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;
}
}

View File

@ -1,2 +1,3 @@
import "./events/user";
import "./events/admin";
// Bun hoists import, but not require?
require("./events/user");
require("./events/admin");

View File

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

View File

@ -12,7 +12,7 @@ export const hi: ServerEventListener<"hi"> = {
_id: socket.getUserID(),
name: "Anonymous",
color: "#777",
id: socket.getParticipantID()
id: socket.getUserID()
};
}

View File

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

View File

@ -1,4 +1,3 @@
import { WebSocket } from "uWebSockets.js";
import { Logger } from "../util/Logger";
import { Socket } from "./Socket";
import { hasOwn } from "../util/helpers";

View File

@ -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());
const usersByPartID = new Map<string, Socket>();
export function findSocketByPartID(id: string) {
for (const key of usersByPartID.keys()) {
if (key == id) return usersByPartID.get(key);
}
}
// 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);
// // 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: 25,
// maxBackpressure: 1024,
// maxPayloadLength: 8192,
// compression: DEDICATED_COMPRESSOR_8KB,
// open: ((ws: WebSocket<unknown> & { socket: Socket }) => {
// ws.socket = new Socket(ws);
// // logger.debug("Connection at " + ws.socket.getIP());
// usersByPartID.set(ws.socket.getParticipantID(), ws.socket);
// }) as (ws: WebSocket<unknown>) => void,
// 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,
// close: ((
// ws: WebSocket<unknown> & { socket: Socket },
// code: number,
// message: ArrayBuffer
// ) => {
// logger.debug("Close called");
// ws.socket.destroy();
// usersByPartID.delete(ws.socket.getParticipantID());
// }) as (
// ws: WebSocket<unknown>,
// 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 = join("./public/", url);
const file = path.join("./public/", url);
// TODO Cleaner file serving
try {
const stats = lstatSync(file);
if (fs.lstatSync(file).isFile()) {
const data = Bun.file(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 :(");
if (data) {
return new Response(data);
} else {
return void res.writeStatus(`200 OK`).end(index);
return new Response(Bun.file("./public/index.html"));
}
} else {
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);
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<unknown> & { socket: Socket }) => {
ws.socket = new Socket(ws);
// logger.debug("Connection at " + ws.socket.getIP());
}) as (ws: WebSocket<unknown>) => void,
usersByPartID.set(socket.getParticipantID(), socket);
},
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) => {
const msg = message.toString();
handleMessage((ws as unknown as any).socket, msg);
},
close: ((
ws: WebSocket<unknown> & { socket: Socket },
code: number,
message: ArrayBuffer
) => {
// TODO handle close event
}) as (
ws: WebSocket<unknown>,
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());
}
}
});

View File

@ -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. */