import fs from 'fs' import { createServer as httpServer, Server as HTTPServer } from 'http' import { pathToFileURL } from 'url' import cors from 'cors' import express, { Application } from 'express' import { Server as SocketServer } from 'socket.io' import config from '#application/config' import Database from '#application/database' import Logger, { LoggerType } from '#application/logger' import { getAppPath } from '#application/storage' import { TSocket } from '#application/types' import ConsoleManager from '#managers/consoleManager' import DateManager from '#managers/dateManager' import HttpManager from '#managers/httpManager' import QueueManager from '#managers/queueManager' import UserManager from '#managers/userManager' import WeatherManager from '#managers/weatherManager' import ZoneManager from '#managers/zoneManager' import { Authentication } from '#middleware/authentication' export class Server { private readonly app: Application private readonly http: HTTPServer private readonly io: SocketServer private readonly logger = Logger.type(LoggerType.APP) /** * Creates an instance of GameServer. */ constructor() { this.app = express() this.app.use( cors({ origin: config.CLIENT_URL, methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], // Add supported methods allowedHeaders: ['Content-Type', 'Authorization'], // Add allowed headers credentials: true }) ) 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() { // Connect to database try { await Database.initialize() } catch (error: any) { this.logger.error(`Database connection failed: ${error.message}`) process.exit(1) // Exit if database connection fails } // Start the server try { this.http.listen(config.PORT, config.HOST) this.logger.info(`Socket.IO running on port ${config.PORT}`) } catch (error: any) { this.logger.error(`Socket.IO failed to start: ${error.message}`) } // Load HTTP manager await HttpManager.boot(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 weather manager // await WeatherManager.boot(this.io) // Load zoneEditor manager await ZoneManager.boot() // Load console manager await ConsoleManager.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('events', '', socket) } catch (error: any) { this.logger.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(pathToFileURL(filePath).href) if (typeof module.default !== 'function') { this.logger.warn(`Unrecognized export in ${file.name}`) continue } const EventClass = module.default const eventInstance = new EventClass(this.io, socket) eventInstance.listen() } catch (error) { this.logger.error(`Error loading event handler ${file.name}: ${error instanceof Error ? error.message : String(error)}`) } } } catch (error) { this.logger.error(`Error reading directory: ${error instanceof Error ? error.message : String(error)}`) } } } // Start the server const server = new Server() server.start()