too much effort
This commit is contained in:
parent
b4a652c81c
commit
bc66f08e4c
13 changed files with 2833 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
server/node_modules
|
||||||
|
server/config.json
|
||||||
|
server/build
|
359
client/Terminal-00-Multiuser.user.js
Normal file
359
client/Terminal-00-Multiuser.user.js
Normal file
File diff suppressed because one or more lines are too long
14
client/index.html
Normal file
14
client/index.html
Normal 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>
|
9
server/enums/MessageType.ts
Normal file
9
server/enums/MessageType.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
export enum MessageType {
|
||||||
|
KeepAlive,
|
||||||
|
ClientDetails,
|
||||||
|
CursorPos,
|
||||||
|
ClientJoined,
|
||||||
|
Clients,
|
||||||
|
ClientLeft,
|
||||||
|
Ping
|
||||||
|
}
|
88
server/index.ts
Normal file
88
server/index.ts
Normal 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
9
server/objects/Config.ts
Normal 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;
|
||||||
|
}
|
77
server/objects/FunkyArray.ts
Normal file
77
server/objects/FunkyArray.ts
Normal 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
21
server/objects/User.ts
Normal 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
2187
server/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
32
server/package.json
Normal file
32
server/package.json
Normal 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
12
server/tooling/cleanup.ts
Normal 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
10
server/tooling/mangle.ts
Normal 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
12
server/tsconfig.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"target": "ES2022",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"rootDir": "./",
|
||||||
|
"outDir": "./build",
|
||||||
|
"strict": true
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue