import fs from 'fs' import express, { Application } from 'express' import config from './utilities/config' import { getAppPath } from './utilities/storage' 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 { Authentication } from './middleware/authentication' import { TSocket } from './utilities/types' import prisma from './utilities/prisma' import { appLogger, watchLogs } from './utilities/logger' import ZoneManager from './managers/zoneManager' import UserManager from './managers/userManager' import CommandManager from './managers/commandManager' import CharacterManager from './managers/characterManager' import QueueManager from './managers/queueManager' import DateManager from './managers/dateManager' 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 queue manager await QueueManager.boot(this.io) // Load user manager await UserManager.boot() // Load date manager await DateManager.boot(this.io) // Load zoneEditor manager await ZoneManager.boot() // Load character manager await CharacterManager.boot() // Load command manager 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) { try { await this.loadEventHandlers('socketEvents', '', socket) } catch (error: any) { appLogger.error(`Failed to load event handlers: ${error.message}`) } } private async loadEventHandlers(baseDir: string, subDir: string, socket: TSocket) { try { const fullDir = getAppPath(baseDir, subDir) const files = await fs.promises.readdir(fullDir, { withFileTypes: true }) for (const file of files) { const filePath = getAppPath(baseDir, subDir, file.name) if (file.isDirectory()) { await this.loadEventHandlers(baseDir, `${subDir}/${file.name}`, socket) continue } if (!file.isFile() || (!file.name.endsWith('.ts') && !file.name.endsWith('.js'))) { continue } try { const module = await import(filePath) if (typeof module.default !== 'function') { appLogger.warn(`Unrecognized export in ${file.name}`) continue } const EventClass = module.default const eventInstance = new EventClass(this.io, socket) eventInstance.listen() } catch (error) { appLogger.error(`Error loading event handler ${file.name}: ${error instanceof Error ? error.message : String(error)}`) } } } catch (error) { appLogger.error(`Error reading directory: ${error instanceof Error ? error.message : String(error)}`) } } } // Start the server const server = new Server() server.start() appLogger.info('Server started')