From 828443cb93cab21d65d123e0ec59ca17e5ecbcbf Mon Sep 17 00:00:00 2001 From: Hri7566 Date: Fri, 2 Aug 2024 23:22:23 -0400 Subject: [PATCH] Fix memory issues and insert unhinged comments --- src/channel/forceLoad.ts | 14 ++++++++++ src/index.ts | 32 ++++++++++++++++++++++ src/util/Logger.ts | 12 +++++--- src/util/helpers.ts | 33 ++++++++++++++++++++-- src/util/readline/commands.ts | 46 +++++++++++++++++++++++++++++++ src/ws/Socket.ts | 47 ++++++++++++++++++++++++++++++-- src/ws/ratelimit/limits/admin.ts | 12 ++++---- src/ws/ratelimit/limits/crown.ts | 12 ++++---- src/ws/ratelimit/limits/user.ts | 12 ++++---- src/ws/server.ts | 12 +++++--- 10 files changed, 201 insertions(+), 31 deletions(-) diff --git a/src/channel/forceLoad.ts b/src/channel/forceLoad.ts index 61a669c..9c497dd 100644 --- a/src/channel/forceLoad.ts +++ b/src/channel/forceLoad.ts @@ -7,6 +7,11 @@ import { ChannelList } from "./ChannelList"; const logger = new Logger("Channel Forceloader"); +/** + * Forceloads a channel + * @param id The channel ID + * @returns Whether the channel was loaded + */ export function forceloadChannel(id: string) { try { logger.info("Forceloading", id); @@ -17,6 +22,11 @@ export function forceloadChannel(id: string) { } } +/** + * Unforceloads a channel + * @param id The channel ID + * @returns Whether the channel was unloaded + */ export function unforceloadChannel(id: string) { const ch = ChannelList.getList().find(ch => ch.getID() == id); if (!ch) return false; @@ -27,6 +37,10 @@ export function unforceloadChannel(id: string) { return true; } +/** + * Forceloads all forceload-configured channels + * This is meant to be called on startup + **/ export function loadForcedStartupChannels() { let hasFullChannel = false; diff --git a/src/index.ts b/src/index.ts index be0f79b..7ee320f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,15 +4,47 @@ * by Hri7566 */ +// There are a lot of unhinged bs comments in this repo +// Pay no attention to the ones that cuss you out + // If you don't load the server first, bun will literally segfault import "./ws/server"; import { loadForcedStartupChannels } from "./channel/forceLoad"; import { Logger } from "./util/Logger"; +// Let's construct an entire object just for one thing to be printed +// and then keep it in memory for the entirety of runtime const logger = new Logger("Main"); logger.info("Forceloading startup channels..."); loadForcedStartupChannels(); +// This literally breaks editors and they stick all the imports here instead of at the top import "./util/readline"; +import { Socket, socketsBySocketID } from "./ws/Socket"; +import { createSocketID } from "./util/id"; +// Nevermind we use it twice logger.info("Ready"); + +// Connect 5 sockets +const sockets = []; + +for (let i = 0; i < 5; i++) { + setTimeout(() => { + const socket = new Socket(undefined, createSocketID()); + + socket.on("ready", () => { + logger.info(`Socket ${i} is ready`); + }); + + socket.setChannel("lobby"); + + sockets.push(socket); + + if (socket.socketID == undefined) { + socket.socketID = createSocketID(); + } + + socketsBySocketID.set(socket.socketID, socket); + }, i * 5000); +} diff --git a/src/util/Logger.ts b/src/util/Logger.ts index dc17870..d8044e2 100644 --- a/src/util/Logger.ts +++ b/src/util/Logger.ts @@ -1,11 +1,15 @@ import { padNum, unimportant } from "./helpers"; +/** + * A logger that doesn't fuck with the readline prompt + * timestamps are likely wrong because of js timezones + **/ export class Logger { private static log(method: string, ...args: any[]) { - // Clear current line + // Un-fuck the readline prompt process.stdout.write("\x1b[2K\r"); - // Log our stuff + // Log (console as unknown as Record any>)[ method ]( @@ -14,7 +18,7 @@ export class Logger { ...args ); - // Fix the readline prompt (spooky cringe code) + // Re-print the readline prompt (spooky cringe global variable) if ((globalThis as unknown as any).rl) (globalThis as unknown as any).rl.prompt(); } @@ -38,7 +42,7 @@ export class Logger { return new Date().toISOString().split("T")[0]; } - constructor(public id: string) {} + constructor(public id: string) { } public info(...args: any[]) { Logger.log("log", `[${this.id}]`, `\x1b[34m[info]\x1b[0m`, ...args); diff --git a/src/util/helpers.ts b/src/util/helpers.ts index 00d22e1..d736c00 100644 --- a/src/util/helpers.ts +++ b/src/util/helpers.ts @@ -1,9 +1,20 @@ -// Gray console text +/** + * Gray console text maker + * @param str String to gray + * @returns Gray string to put in console + **/ export function unimportant(str: string) { return `\x1b[90m${str}\x1b[0m`; } -// Pad time strings +/** + * Pad time strings + * @param num Number to pad + * @param padAmount Amount of padding + * @param padChar Character to pad with + * @param left Whether to pad left or right + * @returns Padded string + **/ export function padNum( num: number, padAmount: number, @@ -21,6 +32,9 @@ export const encoder = new TextEncoder(); /** * Check if an object has a property + * @param obj Object to check + * @param property Property to check + * @returns Whether the object has the property **/ export function hasOwn(obj: any, property: string | number | Symbol) { return (Object as unknown as any).hasOwn(obj, property); @@ -47,7 +61,15 @@ export function darken(color: string, amount = 0x40) { return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`; } -// Brandon made this literally eons ago and it's fucking hilarious + +// spooky.jsaurus +// NOT the same as poop_text + +/** + * Make text spoopy + * @param message Message to spoop + * @returns Spooped message + **/ export function spoop_text(message: string) { var old = message; message = ""; @@ -64,6 +86,11 @@ export function spoop_text(message: string) { return message; } +/** + * Mix two objects + * @param obj1 Object to mix into + * @param obj2 Object to mix + **/ export function mixin(obj1: any, obj2: any) { for (const key of Object.keys(obj2)) { obj1[key] = obj2[key]; diff --git a/src/util/readline/commands.ts b/src/util/readline/commands.ts index 3af3541..87c5baf 100644 --- a/src/util/readline/commands.ts +++ b/src/util/readline/commands.ts @@ -75,3 +75,49 @@ Command.addCommand( } }) ); + +Command.addCommand( + new Command(["js", "eval"], "js ", async msg => { + function roughSizeOfObject(object: any) { + const objectList: any[] = []; + const stack = [object]; + let bytes = 0; + + while (stack.length) { + const value = stack.pop(); + + switch (typeof value) { + case 'boolean': + bytes += 4; + break; + case 'string': + bytes += value.length * 2; + break; + case 'number': + bytes += 8; + break; + case 'object': + if (!objectList.includes(value)) { + objectList.push(value); + for (const prop in value) { + if (value.hasOwnProperty(prop)) { + stack.push(value[prop]); + } + } + } + break; + } + } + + return bytes; + } + if (msg.args.length > 1) { + try { + const output = eval(msg.args[1]); + return output; + } catch (err) { + return err; + } + } + }) +); diff --git a/src/ws/Socket.ts b/src/ws/Socket.ts index 9e92d84..3a7d3a2 100644 --- a/src/ws/Socket.ts +++ b/src/ws/Socket.ts @@ -36,6 +36,11 @@ const logger = new Logger("Sockets"); type CursorValue = string | number; +/** + * Extended websocket thing + * Most poeple call this "Client" but it's not on the client... + * This is likely the source of my memory leaks + **/ export class Socket extends EventEmitter { private id: string; private _id: string; @@ -65,6 +70,7 @@ export class Socket extends EventEmitter { ) { super(); + // real user? if (ws) { // Real user this.ip = ws.data.ip; @@ -82,13 +88,18 @@ export class Socket extends EventEmitter { let foundSocket; let count = 0; + // big boi loop for (const socket of socketsBySocketID.values()) { + // Skip us if (socket.socketID == this.socketID) continue; + // Are they real? if (socket.ws) { + // Are they connected? if (socket.ws.readyState !== 1) continue; } + // Same user ID? if (socket.getUserID() == this.getUserID()) { foundSocket = socket; count++; @@ -96,11 +107,14 @@ export class Socket extends EventEmitter { } if (count >= 4) { + // Too many go away this.destroy(); } // logger.debug("Found socket?", foundSocket); + // If there is another socket, use their ID for some reason I forgot + // otherwise, make a new one if (!foundSocket) { // Use new session ID this.id = createID(); @@ -109,18 +123,24 @@ export class Socket extends EventEmitter { this.id = foundSocket.id; // Break us off + // didn't work nvm //this.id = "broken"; //this.destroy(); } + // Load stuff (async () => { + // Load our user data await this.loadUser(); + // Set our rate limits this.resetRateLimits(); this.setNoteQuota(NoteQuota.PARAMS_RIDICULOUS); + // Bind a bunch of our event listeners so we do stuff when told to this.bindEventListeners(); + // Send a challenge to the browser for MPP.net frontends if (config.browserChallenge == "basic") { this.sendArray([{ m: "b", @@ -131,6 +151,7 @@ export class Socket extends EventEmitter { } })(); + // all done this.emit("ready"); } @@ -143,7 +164,7 @@ export class Socket extends EventEmitter { } /** - * Get the user ID of this socket + * Get the user ID (_id) of this socket * @returns User ID **/ public getUserID() { @@ -151,7 +172,7 @@ export class Socket extends EventEmitter { } /** - * Get the participant ID of this socket + * Get the participant ID (id) of this socket * @returns Participant ID **/ public getParticipantID() { @@ -744,6 +765,9 @@ export class Socket extends EventEmitter { /** * Ban this socket's user for doing bad things + * this doesn't actually ban the user, it just sends a notification right now FIXME + * @param duration Duration of the ban in milliseconds + * @param reason Reason for the ban **/ public ban(duration: number, reason: string) { // TODO cleaner ban system @@ -763,13 +787,26 @@ export class Socket extends EventEmitter { } export const socketsBySocketID = new Map(); +(globalThis as any).socketsBySocketID = socketsBySocketID; +/** + * Find a socket by their participant ID + * bad don't use for unique sockets + * @param id Participant ID to find + * @returns Socket object + **/ export function findSocketByPartID(id: string) { for (const socket of socketsBySocketID.values()) { if (socket.getParticipantID() == id) return socket; } } +/** + * Find all sockets by their user ID + * also not unique + * @param _id User ID to find + * @returns Socket objects + **/ export function findSocketsByUserID(_id: string) { const sockets = []; @@ -781,6 +818,12 @@ export function findSocketsByUserID(_id: string) { return sockets; } +/** + * Find a socket by their IP + * probably not unique if they're on different tabs + * @param ip IP to find + * @returns Socket object + **/ export function findSocketByIP(ip: string) { for (const socket of socketsBySocketID.values()) { if (socket.getIP() == ip) { diff --git a/src/ws/ratelimit/limits/admin.ts b/src/ws/ratelimit/limits/admin.ts index 7b7bb98..18894ca 100644 --- a/src/ws/ratelimit/limits/admin.ts +++ b/src/ws/ratelimit/limits/admin.ts @@ -22,18 +22,18 @@ export const adminLimits: RateLimitConstructorList = { chains: { userset: () => new RateLimitChain( - config.admin.chains.userset.interval, - config.admin.chains.userset.num + config.admin.chains.userset.num, + config.admin.chains.userset.interval ), chset: () => new RateLimitChain( - config.admin.chains.chset.interval, - config.admin.chains.userset.num + config.admin.chains.chset.num, + config.admin.chains.userset.interval ), n: () => new RateLimitChain( - config.admin.chains.n.interval, - config.admin.chains.userset.num + config.admin.chains.n.num, + config.admin.chains.userset.interval ) } }; diff --git a/src/ws/ratelimit/limits/crown.ts b/src/ws/ratelimit/limits/crown.ts index c7e0e4b..a9b102c 100644 --- a/src/ws/ratelimit/limits/crown.ts +++ b/src/ws/ratelimit/limits/crown.ts @@ -22,18 +22,18 @@ export const crownLimits: RateLimitConstructorList = { chains: { userset: () => new RateLimitChain( - config.crown.chains.userset.interval, - config.crown.chains.userset.num + config.crown.chains.userset.num, + config.crown.chains.userset.interval ), chset: () => new RateLimitChain( - config.crown.chains.chset.interval, - config.crown.chains.userset.num + config.crown.chains.chset.num, + config.crown.chains.userset.interval ), n: () => new RateLimitChain( - config.crown.chains.n.interval, - config.crown.chains.userset.num + config.crown.chains.n.num, + config.crown.chains.userset.interval ) } }; diff --git a/src/ws/ratelimit/limits/user.ts b/src/ws/ratelimit/limits/user.ts index 9fb3cbd..0266b7d 100644 --- a/src/ws/ratelimit/limits/user.ts +++ b/src/ws/ratelimit/limits/user.ts @@ -22,18 +22,18 @@ export const userLimits: RateLimitConstructorList = { chains: { userset: () => new RateLimitChain( - config.user.chains.userset.interval, - config.user.chains.userset.num + config.user.chains.userset.num, + config.user.chains.userset.interval ), chset: () => new RateLimitChain( - config.user.chains.chset.interval, - config.user.chains.userset.num + config.user.chains.chset.num, + config.user.chains.userset.interval ), n: () => new RateLimitChain( - config.user.chains.n.interval, - config.user.chains.userset.num + config.user.chains.n.num, + config.user.chains.userset.interval ) } }; diff --git a/src/ws/server.ts b/src/ws/server.ts index 402dde1..66e4934 100644 --- a/src/ws/server.ts +++ b/src/ws/server.ts @@ -19,6 +19,8 @@ async function getIndex() { // nobody realistically uses templates in 2024 and documents // it well enough to say what library they used + // I totally forget if this even works + const index = Bun.file("./public/index.html"); const rendered = nunjucks.renderString(await index.text(), { @@ -91,11 +93,15 @@ export const app = Bun.serve<{ ip: string }>({ // logger.debug("Connection at " + socket.getIP()); // Let's put it in the dinner bucket. - socketsBySocketID.set((socket.socketID as any), socket); + if (socket.socketID == undefined) { + socket.socketID = createSocketID(); + } + + socketsBySocketID.set(socket.socketID, socket); }, message: (ws, message) => { - // "Let's make it binary" said all websocket developers for some reason + // Fucking string const msg = message.toString(); // Let's find out wtf they even sent @@ -103,8 +109,6 @@ export const app = Bun.serve<{ ip: string }>({ }, close: (ws, code, message) => { - // logger.debug("Close called"); - // This usually gets called when someone leaves, // but it's also used internally just in case // some dickhead can't close their tab like a