too much effort

This commit is contained in:
Holly Stubbs 2024-04-18 23:18:49 +01:00
parent b4a652c81c
commit bc66f08e4c
Signed by: tgpholly
GPG Key ID: B8583C4B7D18119E
13 changed files with 2833 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
server/node_modules
server/config.json
server/build

File diff suppressed because one or more lines are too long

14
client/index.html Normal file
View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>UserScript test page</title>
</head>
<body>
<h1>Test page :)</h1>
<script src="./Terminal-00-Multiuser.user.js"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
export enum MessageType {
KeepAlive,
ClientDetails,
CursorPos,
ClientJoined,
Clients,
ClientLeft,
Ping
}

88
server/index.ts Normal file
View File

@ -0,0 +1,88 @@
import { createReader, createWriter, Endian } from "bufferstuff";
import { WebSocketServer } from "ws";
import Config from "./objects/Config";
import FunkyArray from "./objects/FunkyArray";
import User from "./objects/User";
import { MessageType } from "./enums/MessageType";
const users = new FunkyArray<string, User>();
const server = new WebSocketServer({
port: Config.port
}, () => console.log(`Server listening at ${Config.port}`));
function sendToAllButSelf(user:User, data:Buffer) {
users.forEach(otherUser => {
if (otherUser.id !== user.id && otherUser.currentURL === user.currentURL) {
otherUser.send(data);
}
});
}
server.on("connection", (socket) => {
const myUUID = crypto.randomUUID();
let user:User;
function closeOrError() {
if (users.has(myUUID)) {
users.remove(myUUID);
const userLeftPacket = createWriter(Endian.LE, 5).writeByte(MessageType.ClientLeft).writeUInt(user.id).toBuffer();
users.forEach(otherUser => otherUser.send(userLeftPacket));
}
}
socket.on("close", closeOrError);
socket.on("error", closeOrError);
socket.on("message", async (data) => {
const reader = createReader(Endian.LE, data as Buffer);
// There is absolutely no reason we should ever get
// more than 50 bytes legit.
if (reader.length > 0 && reader.length < 50) {
switch (reader.readUByte()) {
case MessageType.ClientDetails:
if (user !== undefined) {
return;
}
const username = reader.readShortString();
let page = reader.readString().toLowerCase().replace(".htm", "").replace(".html", "");
if (page === "index") {
page = "";
}
let lengthOfUsernames = 0;
await users.forEach(otherUser => {
lengthOfUsernames += otherUser.username.length + 1; // + 1 for length byte
});
const usersToSend = createWriter(Endian.LE, 3 + (users.length * 4) + lengthOfUsernames).writeByte(MessageType.Clients).writeUShort(users.length);
await users.forEach(otherUser => {
usersToSend.writeUInt(otherUser.id).writeShortString(otherUser.username);
});
user = users.set(myUUID, new User(socket, username, page));
sendToAllButSelf(user, createWriter(Endian.LE, 6 + username.length).writeByte(MessageType.ClientJoined).writeUInt(user.id).writeShortString(username).toBuffer());
user.send(usersToSend.toBuffer());
break;
case MessageType.CursorPos:
{
if (user === undefined) {
return;
}
const cursorX = reader.readFloat();
const cursorY = reader.readInt();
sendToAllButSelf(user, createWriter(Endian.LE, 13).writeByte(MessageType.CursorPos).writeUInt(user.id).writeFloat(cursorX).writeInt(cursorY).toBuffer());
break;
}
case MessageType.Ping:
{
const cursorX = reader.readFloat();
const cursorY = reader.readInt();
const packet = createWriter(Endian.LE, 9).writeByte(MessageType.Ping).writeFloat(cursorX).writeInt(cursorY).toBuffer();
users.forEach(otherUser => {
otherUser.send(packet);
});
break;
}
}
}
});
});

9
server/objects/Config.ts Normal file
View File

@ -0,0 +1,9 @@
import { readFileSync } from "fs";
const config = JSON.parse(readFileSync("./config.json").toString());
export default class Config {
public constructor() { throw new Error("Static Class"); }
public static port:number = config.port;
}

View File

@ -0,0 +1,77 @@
export default class FunkyArray<T, TT> {
private items:Map<T, TT> = new Map<T, TT>();
private itemKeys:Array<T> = new Array<T>();
private _getKeys() : Array<T> {
const keyArray = new Array<T>();
let result:IteratorResult<T, T>;
const iterator = this.items.keys();
while (!(result = iterator.next()).done) {
keyArray.push(result.value);
}
return keyArray;
}
public set(key:T, item:TT, regenerate:boolean = true) : TT {
this.items.set(key, item);
if (regenerate) {
this.itemKeys = this._getKeys();
}
return item;
}
public remove(key:T, regenerate:boolean = true) {
const success = this.items.delete(key);
if (regenerate) {
this.itemKeys = this._getKeys();
}
return success;
}
public removeFirst(regenerate:boolean = true) {
const success = this.items.delete(this.items.keys().next().value);
if (regenerate) {
this.itemKeys = this._getKeys();
}
return success;
}
public first() : TT {
return this.items.values().next().value;
}
public get length() : number {
return this.items.size;
}
public get(key:T) : TT | undefined {
return this.items.get(key);
}
public has(key:T) : boolean {
return this.itemKeys.includes(key);
}
public get keys() : Array<T> {
return this.itemKeys;
}
public forEach(callback: (value:TT) => void) {
return new Promise<boolean>(async (resolve, reject) => {
if (this.items.size === 0) {
return resolve(true);
}
try {
const iterator = this.items.values();
let result:IteratorResult<TT, TT>;
while (!(result = iterator.next()).done) {
await callback(result.value);
}
resolve(true);
} catch (e) {
reject(e);
}
});
}
}

21
server/objects/User.ts Normal file
View File

@ -0,0 +1,21 @@
import { WebSocket } from "ws";
export default class User {
private static USER_IDS = 0;
private readonly socket:WebSocket;
public readonly id:number;
public readonly username:string;
public readonly currentURL:string;
constructor(socket:WebSocket, username:string, currentURL:string) {
this.socket = socket;
this.id = User.USER_IDS++;
this.username = username;
this.currentURL = currentURL;
}
send(data:Buffer) {
this.socket.send(data);
}
}

2187
server/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

32
server/package.json Normal file
View File

@ -0,0 +1,32 @@
{
"name": "t00-mp",
"description": "",
"version": "1.0.0",
"main": "build/index.js",
"scripts": {
"updateCheck": "check-outdated",
"dev": "nodemon --watch './**/*.ts' index.ts",
"build": "npm-run-all build:*",
"build:build": "ncc build index.ts -o build",
"build:mangle": "ts-node ./tooling/mangle.ts",
"build:cleanup": "ts-node ./tooling/cleanup.ts"
},
"keywords": [],
"author": "tgpholly",
"license": "MIT",
"dependencies": {
"bufferstuff": "^1.5.1",
"ws": "^8.16.0"
},
"devDependencies": {
"@types/node": "^20.12.7",
"@types/ws": "^8.5.10",
"@vercel/ncc": "^0.38.1",
"check-outdated": "^2.12.0",
"nodemon": "^3.1.0",
"npm-run-all": "^4.1.5",
"terser": "^5.30.3",
"ts-node": "^10.9.2",
"typescript": "^5.4.5"
}
}

12
server/tooling/cleanup.ts Normal file
View File

@ -0,0 +1,12 @@
import { readdirSync, rmSync, renameSync } from "fs";
const libFiles = readdirSync("./build");
for (const file of libFiles) {
if (!file.startsWith("index.min.js")) {
rmSync(`./build/${file}`, { recursive: true });
}
}
//renameSync("./build/combined.js", "./build/index.js");
//renameSync("./build/combined.d.ts", "./build/index.d.ts");

10
server/tooling/mangle.ts Normal file
View File

@ -0,0 +1,10 @@
import { readFileSync, writeFileSync } from "fs";
import { minify } from "terser";
(async () => {
const mangled = await minify(readFileSync("./build/index.js").toString(), {
mangle: true,
toplevel: true,
});
writeFileSync("./build/index.min.js", `${mangled.code?.replaceAll("new Array", "[]")}`);
})();

12
server/tsconfig.json Normal file
View File

@ -0,0 +1,12 @@
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"target": "ES2022",
"esModuleInterop": true,
"resolveJsonModule": true,
"rootDir": "./",
"outDir": "./build",
"strict": true
}
}