import WebSocket, { WebSocketServer } from "ws"; import http from "http"; import path from "path"; import fs from "fs"; import { Session } from "./models/session.js"; import { GameSeeder } from "./seeders/gameSeeder.js"; import { Config } from "./config.js"; import { gGame } from "./models/globals.js"; import { AuthenticationScene } from "./scenes/authentication/authenticationScene.js"; import { MessageType, WebsocketMessage, formatMessage } from "./utils/messages.js"; // __ __ _ _ ____ ____ // | \/ | | | | _ \ / ___| ___ _ ____ _____ _ __ // | |\/| | | | | | | | \___ \ / _ \ '__\ \ / / _ \ '__| // | | | | |_| | |_| | ___) | __/ | \ V / __/ | // |_| |_|\___/|____/ |____/ \___|_| \_/ \___|_| // ----------------------------------------------------- class MudServer { constructor() { new GameSeeder().seed(); } // ____ ___ _ _ _ _ _____ ____ _____ _____ ____ // / ___/ _ \| \ | | \ | | ____/ ___|_ _| ____| _ \ // | | | | | | \| | \| | _|| | | | | _| | | | | // | |__| |_| | |\ | |\ | |__| |___ | | | |___| |_| | // \____\___/|_| \_|_| \_|_____\____| |_| |_____|____/ //------------------------------------------------------ // Handle New Socket Connections //------------------------------ /** @param {WebSocket} websocket */ onConnectionEstabished(websocket) { console.info("New connection established"); const session = new Session(websocket, gGame); if (Config.dev) { websocket.send(formatMessage(MessageType.SYSTEM, "dev", true)); } // ____ _ ___ ____ _____ // / ___| | / _ \/ ___|| ____| // | | | | | | | \___ \| _| // | |___| |__| |_| |___) | |___ // \____|_____\___/|____/|_____| //------------------------------- // Handle Socket Closing //---------------------- websocket.on("close", () => { try { this.close(session); } catch (e) { console.error("Failed during closing of websocket"); } }); websocket.on("message", (data) => { try { this.onMessage(session, data); } catch (error) { console.error(error, data.toString(), data); websocket.send(formatMessage(MessageType.CALAMITY, error)); session.close(); } }); session.setScene(new AuthenticationScene(session)); } // _ _ _____ _____ ____ ____ _____ _ ____ _____ // | | | |_ _|_ _| _ \ / ___|_ _|/ \ | _ \_ _| // | |_| | | | | | | |_) |___\___ \ | | / _ \ | |_) || | // | _ | | | | | | __/_____|__) || |/ ___ \| _ < | | // |_| |_| |_| |_| |_| |____/ |_/_/ \_\_| \_\|_| //---------------------------------------------------------- // // Start the server // //---------------------------------------------------------- start() { // // The file types we allow to be served. const contentTypes = { ".css": "text/css", ".html": "text/html", ".ico": "image/x-icon", ".map": "application/json", ".js": "application/javascript", ".json": "application/json", ".png": "image/png", }; // // Create HTTP server for serving the client - Consider moving to own file const httpServer = http.createServer((req, res) => { let filePath = path.join("public", req.url === "/" ? "index.html" : req.url); const ext = path.extname(filePath); const contentType = contentTypes[ext]; // // Check if the requested file has a legal file type. if (!contentType) { // // Invalid file, pretend it did not exist! res.writeHead(404); res.end(`File not found`); console.warn("Bad http request", req.url); return; } // // Check if the file exists. fs.readFile(filePath, (err, data) => { if (err) { res.writeHead(404); res.end(`File not found`); console.warn("Bad http request", req.url); return; } res.writeHead(200, { "Content-Type": contentType }); res.end(data); }); }); // // Create WebSocket server const websocketServer = new WebSocketServer({ server: httpServer }); websocketServer.on("connection", (ws) => { this.onConnectionEstabished(ws); }); console.info(`Environment: ${Config.env}`); httpServer.listen(Config.port, () => { console.info(`NUUHD server running on port ${Config.port}`); }); } // __ __ _____ ____ ____ _ ____ _____ // | \/ | ____/ ___/ ___| / \ / ___| ____| // | |\/| | _| \___ \___ \ / _ \| | _| _| // | | | | |___ ___) |__) / ___ \ |_| | |___ // |_| |_|_____|____/____/_/ \_\____|_____| //-------------------------------------------- /** * Handle incoming message * @param {Session} session * @param {WebSocket.RawData} data */ onMessage(session, data) { // // Check if message too big if (data.byteLength > Config.maxIncomingMessageSize) { console.error("Message was too big!", Config.maxIncomingMessageSize, data.byteLength); session.calamity("batman"); return; } console.debug("Incoming websocket message %s", data); // // Sanity check. Do we even have a scene to route the message to? if (!session.scene) { console.error("No scene!", data.toString()); session.calamity("We received a message, but we're not in a state to handle it. Zark!"); return; } const msgObj = new WebsocketMessage(data.toString()); // // Handle replies to prompts. The main workhorse of the game. if (msgObj.isReply()) { return session.scene.prompt.onReply(msgObj.text); } // // Handle :help commands if (msgObj.isHelp()) { return session.scene.prompt.onHelp(msgObj.text); } // // Handle MessageType.QUIT messages. When the player types :quit if (msgObj.isQuit()) { session.scene.onQuit(); session.close(0, "Closing the socket, graceful goodbye!"); return; } // // Handle any text that starts with ":" that isn't :help or :quit if (msgObj.isColon()) { return session.scene.prompt.onColon(msgObj.command, msgObj.args); } // // Handle system messages if (msgObj.isSysMessage()) { console.log("SYS message", msgObj); return; } // // Handle debug messages if (msgObj.isDebug()) { console.log("DBG message", msgObj); return; } // // How did we end up down here? console.warn("Unknown message type: >>%s<<", msgObj.type, msgObj); } // ____ _ ___ ____ _____ // / ___| | / _ \/ ___|| ____| // | | | | | | | \___ \| _| // | |___| |__| |_| |___) | |___ // \____|_____\___/|____/|_____| //------------------------------- // Handle Socket Closing //---------------------- close(session) { const playerName = session.player ? session.player.username : "[unauthenticated]"; console.info(playerName + " disconnected"); session.close(); } } // __ __ _ ___ _ _ // | \/ | / \ |_ _| \ | | // | |\/| | / _ \ | || \| | // | | | |/ ___ \ | || |\ | // |_| |_/_/ \_\___|_| \_| //--------------------------- // Code entry point //----------------- const mudserver = new MudServer(/* location of crypto key for saving games */); mudserver.start();