Fix memory issues and insert unhinged comments

This commit is contained in:
Hri7566 2024-08-02 23:22:23 -04:00
parent 8ab74266e2
commit 828443cb93
10 changed files with 201 additions and 31 deletions

View File

@ -7,6 +7,11 @@ import { ChannelList } from "./ChannelList";
const logger = new Logger("Channel Forceloader"); 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) { export function forceloadChannel(id: string) {
try { try {
logger.info("Forceloading", id); 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) { export function unforceloadChannel(id: string) {
const ch = ChannelList.getList().find(ch => ch.getID() == id); const ch = ChannelList.getList().find(ch => ch.getID() == id);
if (!ch) return false; if (!ch) return false;
@ -27,6 +37,10 @@ export function unforceloadChannel(id: string) {
return true; return true;
} }
/**
* Forceloads all forceload-configured channels
* This is meant to be called on startup
**/
export function loadForcedStartupChannels() { export function loadForcedStartupChannels() {
let hasFullChannel = false; let hasFullChannel = false;

View File

@ -4,15 +4,47 @@
* by Hri7566 * 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 // If you don't load the server first, bun will literally segfault
import "./ws/server"; import "./ws/server";
import { loadForcedStartupChannels } from "./channel/forceLoad"; import { loadForcedStartupChannels } from "./channel/forceLoad";
import { Logger } from "./util/Logger"; 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"); const logger = new Logger("Main");
logger.info("Forceloading startup channels..."); logger.info("Forceloading startup channels...");
loadForcedStartupChannels(); loadForcedStartupChannels();
// This literally breaks editors and they stick all the imports here instead of at the top
import "./util/readline"; import "./util/readline";
import { Socket, socketsBySocketID } from "./ws/Socket";
import { createSocketID } from "./util/id";
// Nevermind we use it twice
logger.info("Ready"); 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);
}

View File

@ -1,11 +1,15 @@
import { padNum, unimportant } from "./helpers"; 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 { export class Logger {
private static log(method: string, ...args: any[]) { private static log(method: string, ...args: any[]) {
// Clear current line // Un-fuck the readline prompt
process.stdout.write("\x1b[2K\r"); process.stdout.write("\x1b[2K\r");
// Log our stuff // Log
(console as unknown as Record<string, (..._args: any[]) => any>)[ (console as unknown as Record<string, (..._args: any[]) => any>)[
method method
]( ](
@ -14,7 +18,7 @@ export class Logger {
...args ...args
); );
// Fix the readline prompt (spooky cringe code) // Re-print the readline prompt (spooky cringe global variable)
if ((globalThis as unknown as any).rl) if ((globalThis as unknown as any).rl)
(globalThis as unknown as any).rl.prompt(); (globalThis as unknown as any).rl.prompt();
} }
@ -38,7 +42,7 @@ export class Logger {
return new Date().toISOString().split("T")[0]; return new Date().toISOString().split("T")[0];
} }
constructor(public id: string) {} constructor(public id: string) { }
public info(...args: any[]) { public info(...args: any[]) {
Logger.log("log", `[${this.id}]`, `\x1b[34m[info]\x1b[0m`, ...args); Logger.log("log", `[${this.id}]`, `\x1b[34m[info]\x1b[0m`, ...args);

View File

@ -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) { export function unimportant(str: string) {
return `\x1b[90m${str}\x1b[0m`; 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( export function padNum(
num: number, num: number,
padAmount: number, padAmount: number,
@ -21,6 +32,9 @@ export const encoder = new TextEncoder();
/** /**
* Check if an object has a property * 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) { export function hasOwn(obj: any, property: string | number | Symbol) {
return (Object as unknown as any).hasOwn(obj, property); 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")}`; 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) { export function spoop_text(message: string) {
var old = message; var old = message;
message = ""; message = "";
@ -64,6 +86,11 @@ export function spoop_text(message: string) {
return message; return message;
} }
/**
* Mix two objects
* @param obj1 Object to mix into
* @param obj2 Object to mix
**/
export function mixin(obj1: any, obj2: any) { export function mixin(obj1: any, obj2: any) {
for (const key of Object.keys(obj2)) { for (const key of Object.keys(obj2)) {
obj1[key] = obj2[key]; obj1[key] = obj2[key];

View File

@ -75,3 +75,49 @@ Command.addCommand(
} }
}) })
); );
Command.addCommand(
new Command(["js", "eval"], "js <code>", 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;
}
}
})
);

View File

@ -36,6 +36,11 @@ const logger = new Logger("Sockets");
type CursorValue = string | number; 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 { export class Socket extends EventEmitter {
private id: string; private id: string;
private _id: string; private _id: string;
@ -65,6 +70,7 @@ export class Socket extends EventEmitter {
) { ) {
super(); super();
// real user?
if (ws) { if (ws) {
// Real user // Real user
this.ip = ws.data.ip; this.ip = ws.data.ip;
@ -82,13 +88,18 @@ export class Socket extends EventEmitter {
let foundSocket; let foundSocket;
let count = 0; let count = 0;
// big boi loop
for (const socket of socketsBySocketID.values()) { for (const socket of socketsBySocketID.values()) {
// Skip us
if (socket.socketID == this.socketID) continue; if (socket.socketID == this.socketID) continue;
// Are they real?
if (socket.ws) { if (socket.ws) {
// Are they connected?
if (socket.ws.readyState !== 1) continue; if (socket.ws.readyState !== 1) continue;
} }
// Same user ID?
if (socket.getUserID() == this.getUserID()) { if (socket.getUserID() == this.getUserID()) {
foundSocket = socket; foundSocket = socket;
count++; count++;
@ -96,11 +107,14 @@ export class Socket extends EventEmitter {
} }
if (count >= 4) { if (count >= 4) {
// Too many go away
this.destroy(); this.destroy();
} }
// logger.debug("Found socket?", foundSocket); // 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) { if (!foundSocket) {
// Use new session ID // Use new session ID
this.id = createID(); this.id = createID();
@ -109,18 +123,24 @@ export class Socket extends EventEmitter {
this.id = foundSocket.id; this.id = foundSocket.id;
// Break us off // Break us off
// didn't work nvm
//this.id = "broken"; //this.id = "broken";
//this.destroy(); //this.destroy();
} }
// Load stuff
(async () => { (async () => {
// Load our user data
await this.loadUser(); await this.loadUser();
// Set our rate limits
this.resetRateLimits(); this.resetRateLimits();
this.setNoteQuota(NoteQuota.PARAMS_RIDICULOUS); this.setNoteQuota(NoteQuota.PARAMS_RIDICULOUS);
// Bind a bunch of our event listeners so we do stuff when told to
this.bindEventListeners(); this.bindEventListeners();
// Send a challenge to the browser for MPP.net frontends
if (config.browserChallenge == "basic") { if (config.browserChallenge == "basic") {
this.sendArray([{ this.sendArray([{
m: "b", m: "b",
@ -131,6 +151,7 @@ export class Socket extends EventEmitter {
} }
})(); })();
// all done
this.emit("ready"); 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 * @returns User ID
**/ **/
public getUserID() { 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 * @returns Participant ID
**/ **/
public getParticipantID() { public getParticipantID() {
@ -744,6 +765,9 @@ export class Socket extends EventEmitter {
/** /**
* Ban this socket's user for doing bad things * 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) { public ban(duration: number, reason: string) {
// TODO cleaner ban system // TODO cleaner ban system
@ -763,13 +787,26 @@ export class Socket extends EventEmitter {
} }
export const socketsBySocketID = new Map<string, Socket>(); export const socketsBySocketID = new Map<string, Socket>();
(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) { export function findSocketByPartID(id: string) {
for (const socket of socketsBySocketID.values()) { for (const socket of socketsBySocketID.values()) {
if (socket.getParticipantID() == id) return socket; 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) { export function findSocketsByUserID(_id: string) {
const sockets = []; const sockets = [];
@ -781,6 +818,12 @@ export function findSocketsByUserID(_id: string) {
return sockets; 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) { export function findSocketByIP(ip: string) {
for (const socket of socketsBySocketID.values()) { for (const socket of socketsBySocketID.values()) {
if (socket.getIP() == ip) { if (socket.getIP() == ip) {

View File

@ -22,18 +22,18 @@ export const adminLimits: RateLimitConstructorList = {
chains: { chains: {
userset: () => userset: () =>
new RateLimitChain( new RateLimitChain(
config.admin.chains.userset.interval, config.admin.chains.userset.num,
config.admin.chains.userset.num config.admin.chains.userset.interval
), ),
chset: () => chset: () =>
new RateLimitChain( new RateLimitChain(
config.admin.chains.chset.interval, config.admin.chains.chset.num,
config.admin.chains.userset.num config.admin.chains.userset.interval
), ),
n: () => n: () =>
new RateLimitChain( new RateLimitChain(
config.admin.chains.n.interval, config.admin.chains.n.num,
config.admin.chains.userset.num config.admin.chains.userset.interval
) )
} }
}; };

View File

@ -22,18 +22,18 @@ export const crownLimits: RateLimitConstructorList = {
chains: { chains: {
userset: () => userset: () =>
new RateLimitChain( new RateLimitChain(
config.crown.chains.userset.interval, config.crown.chains.userset.num,
config.crown.chains.userset.num config.crown.chains.userset.interval
), ),
chset: () => chset: () =>
new RateLimitChain( new RateLimitChain(
config.crown.chains.chset.interval, config.crown.chains.chset.num,
config.crown.chains.userset.num config.crown.chains.userset.interval
), ),
n: () => n: () =>
new RateLimitChain( new RateLimitChain(
config.crown.chains.n.interval, config.crown.chains.n.num,
config.crown.chains.userset.num config.crown.chains.userset.interval
) )
} }
}; };

View File

@ -22,18 +22,18 @@ export const userLimits: RateLimitConstructorList = {
chains: { chains: {
userset: () => userset: () =>
new RateLimitChain( new RateLimitChain(
config.user.chains.userset.interval, config.user.chains.userset.num,
config.user.chains.userset.num config.user.chains.userset.interval
), ),
chset: () => chset: () =>
new RateLimitChain( new RateLimitChain(
config.user.chains.chset.interval, config.user.chains.chset.num,
config.user.chains.userset.num config.user.chains.userset.interval
), ),
n: () => n: () =>
new RateLimitChain( new RateLimitChain(
config.user.chains.n.interval, config.user.chains.n.num,
config.user.chains.userset.num config.user.chains.userset.interval
) )
} }
}; };

View File

@ -19,6 +19,8 @@ async function getIndex() {
// nobody realistically uses templates in 2024 and documents // nobody realistically uses templates in 2024 and documents
// it well enough to say what library they used // it well enough to say what library they used
// I totally forget if this even works
const index = Bun.file("./public/index.html"); const index = Bun.file("./public/index.html");
const rendered = nunjucks.renderString(await index.text(), { const rendered = nunjucks.renderString(await index.text(), {
@ -91,11 +93,15 @@ export const app = Bun.serve<{ ip: string }>({
// logger.debug("Connection at " + socket.getIP()); // logger.debug("Connection at " + socket.getIP());
// Let's put it in the dinner bucket. // 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) => { message: (ws, message) => {
// "Let's make it binary" said all websocket developers for some reason // Fucking string
const msg = message.toString(); const msg = message.toString();
// Let's find out wtf they even sent // Let's find out wtf they even sent
@ -103,8 +109,6 @@ export const app = Bun.serve<{ ip: string }>({
}, },
close: (ws, code, message) => { close: (ws, code, message) => {
// logger.debug("Close called");
// This usually gets called when someone leaves, // This usually gets called when someone leaves,
// but it's also used internally just in case // but it's also used internally just in case
// some dickhead can't close their tab like a // some dickhead can't close their tab like a