import fs from 'fs' import path from 'path' import express, { Application } from 'express' import { createServer as httpServer, Server as HTTPServer } from 'http' import { addHttpRoutes } from './utilities/http' import cors from 'cors' import { Server as SocketServer } from 'socket.io' import { TSocket } from './utilities/types' import config from './utilities/config' import prisma from './utilities/prisma' import ZoneManager from './managers/zoneManager' import UserManager from './managers/userManager' import { Authentication } from './middleware/authentication' // import CommandManager from './managers/CommandManager' import { Dirent } from 'node:fs' import logger from './utilities/logger' export class Server { private readonly app: Application private readonly http: HTTPServer private readonly io: SocketServer /** * Creates an instance of GameServer. */ constructor() { this.app = express() this.app.use(cors()) this.app.use(express.json()) this.app.use(express.urlencoded({ extended: true })) this.http = httpServer(this.app) this.io = new SocketServer(this.http) this.io.use(Authentication) } /** * Start the server */ public async start() { // Read log file and print to console for debugging const logFile = path.join(__dirname, '../logs/app.log') fs.watchFile(logFile, (curr, prev) => { if (curr.size > prev.size) { const stream = fs.createReadStream(logFile, { start: prev.size, end: curr.size }) stream.on('data', (chunk) => { console.log(chunk.toString()) }) } }) // Check prisma connection try { await prisma.$connect() logger.info('Database connected') } catch (error: any) { logger.error(`Database connection failed: ${error.message}`) } // Start the server try { this.http.listen(config.PORT, config.HOST) logger.info(`Socket.IO running on port ${config.PORT}`) } catch (error: any) { logger.error(`Socket.IO failed to start: ${error.message}`) } // Add http API routes await addHttpRoutes(this.app) // Load user manager await UserManager.boot() // Load zoneEditor manager await ZoneManager.boot() // Load command manager - Disabled for now // await CommandManager.boot(this.io); // Listen for socket connections this.io.on('connection', this.handleConnection.bind(this)) } /** * Handle socket connection * @param socket * @private */ private async handleConnection(socket: TSocket) { const eventsPath = path.join(__dirname, 'events') try { await this.loadEventHandlers(eventsPath, socket) } catch (error: any) { logger.error(`Failed to load event handlers: ${error.message}`) } } private async loadEventHandlers(dir: string, socket: TSocket) { const files: Dirent[] = await fs.promises.readdir(dir, { withFileTypes: true }) for (const file of files) { const fullPath = path.join(dir, file.name) if (file.isDirectory()) { await this.loadEventHandlers(fullPath, socket) } else if (file.isFile() && (file.name.endsWith('.ts') || file.name.endsWith('.js'))) { try { const module = await import(fullPath) if (typeof module.default === 'function') { if (module.default.prototype && module.default.prototype.listen) { // This is a class-based event const EventClass = module.default const eventInstance = new EventClass(this.io, socket) eventInstance.listen() } else { // This is a function-based event module.default(socket, this.io) } } else { logger.warn(`Unrecognized export in ${file.name}`) } } catch (error: any) { logger.error(`Error loading event handler ${file.name}: ${error.message}`) } } } } } // Start the server const server = new Server() server.start() console.log('Server started')