165 lines
No EOL
4.9 KiB
TypeScript
165 lines
No EOL
4.9 KiB
TypeScript
import { createReader, createWriter, Endian } from "bufferstuff";
|
|
import { WebSocketServer } from "ws";
|
|
import Fastify from "fastify";
|
|
import FastifyView from "@fastify/view";
|
|
import EJS from "ejs";
|
|
import Config from "./objects/Config";
|
|
import FunkyArray from "./objects/FunkyArray";
|
|
import RemoteUser from "./objects/RemoteUser";
|
|
import { MessageType } from "./enums/MessageType";
|
|
import Database from "./objects/Database";
|
|
import { Console } from "hsconsole";
|
|
|
|
Console.customHeader(`MultiProbe server started at ${new Date()}`);
|
|
|
|
const users = new FunkyArray<string, RemoteUser>();
|
|
|
|
new Database(Config.database.address, Config.database.port, Config.database.username, Config.database.password, Config.database.name);
|
|
|
|
const fastify = Fastify({
|
|
logger: false
|
|
});
|
|
|
|
fastify.register(FastifyView, {
|
|
engine: {
|
|
ejs: EJS
|
|
}
|
|
})
|
|
|
|
fastify.get("/", async (req, res) => {
|
|
return res.view("templates/index.ejs", { });
|
|
});
|
|
|
|
const websocketServer = new WebSocketServer({
|
|
port: Config.ports.ws
|
|
}, () => {
|
|
Console.printInfo(`WebsocketServer listening at ws://localhost:${Config.ports.ws}`);
|
|
fastify.listen({ port: Config.ports.http }, (err, address) => {
|
|
if (err) {
|
|
Console.printError(`Error occured while spinning up fastify:\n${err}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
Console.printInfo(`Fastify listening at ${address.replace("[::1]", "localhost")}`);
|
|
|
|
Console.printInfo("MultiProbe is ready to go!");
|
|
});
|
|
});
|
|
|
|
function sendToAllButSelf(user:RemoteUser, data:Buffer) {
|
|
users.forEach(otherUser => {
|
|
if (otherUser.id !== user.id && otherUser.currentURL === user.currentURL) {
|
|
otherUser.send(data);
|
|
}
|
|
});
|
|
}
|
|
|
|
function sendToAll(user:RemoteUser, data:Buffer) {
|
|
users.forEach(otherUser => {
|
|
if (otherUser.currentURL === user.currentURL) {
|
|
otherUser.send(data);
|
|
}
|
|
});
|
|
}
|
|
|
|
websocketServer.on("connection", (socket) => {
|
|
const myUUID = crypto.randomUUID();
|
|
let user:RemoteUser;
|
|
|
|
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();
|
|
const rawURL = reader.readString();
|
|
let page = rawURL.toLowerCase().replace(".htm", "").replace(".html", "");
|
|
if (page === "index") {
|
|
page = "";
|
|
}
|
|
let lengthOfUsernames = 0;
|
|
const usersOnPage = new Array<RemoteUser>();
|
|
await users.forEach(otherUser => {
|
|
if (otherUser.currentURL === page) {
|
|
usersOnPage.push(otherUser);
|
|
lengthOfUsernames += otherUser.username.length + 1; // + 1 for length byte
|
|
}
|
|
});
|
|
const usersToSend = createWriter(Endian.LE, 3 + (usersOnPage.length * 12) + lengthOfUsernames).writeByte(MessageType.Clients).writeUShort(usersOnPage.length);
|
|
for (const otherUser of usersOnPage) {
|
|
usersToSend.writeUInt(otherUser.id).writeShortString(otherUser.username).writeFloat(otherUser.cursorX).writeInt(otherUser.cursorY);
|
|
}
|
|
user = users.set(myUUID, new RemoteUser(socket, username, page, rawURL));
|
|
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;
|
|
}
|
|
user.cursorX = reader.readFloat();
|
|
user.cursorY = reader.readInt();
|
|
sendToAllButSelf(user, createWriter(Endian.LE, 13).writeByte(MessageType.CursorPos).writeUInt(user.id).writeFloat(user.cursorX).writeInt(user.cursorY).toBuffer());
|
|
break;
|
|
}
|
|
case MessageType.Ping:
|
|
{
|
|
if (user === undefined) {
|
|
return;
|
|
}
|
|
|
|
if ((performance.now() - user.lastPingReset) >= 1000) {
|
|
user.allowedPings = 10;
|
|
user.lastPingReset = performance.now();
|
|
}
|
|
|
|
if (user.allowedPings > 0) {
|
|
user.allowedPings--;
|
|
const cursorX = reader.readFloat();
|
|
const cursorY = reader.readInt();
|
|
const packet = createWriter(Endian.LE, 9).writeByte(MessageType.Ping).writeFloat(cursorX).writeInt(cursorY).toBuffer();
|
|
sendToAll(user, packet);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
let isShuttingDown = false;
|
|
function shutdown() {
|
|
if (isShuttingDown) {
|
|
return;
|
|
}
|
|
isShuttingDown = true;
|
|
|
|
Console.printInfo("Shutting down...");
|
|
websocketServer.close(async () => {
|
|
await fastify.close();
|
|
Console.cleanup();
|
|
console.log("Goodbye!");
|
|
});
|
|
}
|
|
|
|
process.on("SIGQUIT", shutdown);
|
|
process.on("SIGINT", shutdown);
|
|
process.on("SIGUSR2", shutdown); |