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