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 { appLogger, watchLogs } from './utilities/logger' import CharacterManager from './managers/characterManager' import QueueManager from './managers/queueManager' 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 watchLogs() // Check prisma connection try { await prisma.$connect() appLogger.info('Database connected') } catch (error: any) { appLogger.error(`Database connection failed: ${error.message}`) } // Start the server try { this.http.listen(config.PORT, config.HOST) appLogger.info(`Socket.IO running on port ${config.PORT}`) } catch (error: any) { appLogger.error(`Socket.IO failed to start: ${error.message}`) } // Add http API routes await addHttpRoutes(this.app) // Load user manager await UserManager.boot() await QueueManager.boot(); // Load zoneEditor manager await ZoneManager.boot() // Load character manager await CharacterManager.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) { appLogger.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 { appLogger.warn(`Unrecognized export in ${file.name}`) } } catch (error: any) { appLogger.error(`Error loading event handler ${file.name}: ${error.message}`) } } } } } // Start the server const server = new Server() server.start() appLogger.info('Server started')