From ba12674e7cee1e28ecadb0b88eca2ffa92127ce2 Mon Sep 17 00:00:00 2001 From: Dennis Postma Date: Mon, 30 Dec 2024 02:39:31 +0100 Subject: [PATCH] Moved more socket logic into socket manager for easier DX --- src/managers/consoleManager.ts | 5 ++- src/managers/queueManager.ts | 5 ++- src/managers/socketManager.ts | 67 ++++++++++++++++++++++++------ src/server.ts | 76 ++++++++++------------------------ 4 files changed, 82 insertions(+), 71 deletions(-) diff --git a/src/managers/consoleManager.ts b/src/managers/consoleManager.ts index e2b48d5..60b7676 100644 --- a/src/managers/consoleManager.ts +++ b/src/managers/consoleManager.ts @@ -4,6 +4,7 @@ import { CommandRegistry } from '#application/console/commandRegistry' import { ConsolePrompt } from '#application/console/consolePrompt' import { LogReader } from '#application/console/logReader' import Logger, { LoggerType } from '#application/logger' +import SocketManager from '#managers/socketManager' export class ConsoleManager { private readonly logger = Logger.type(LoggerType.COMMAND) @@ -19,8 +20,8 @@ export class ConsoleManager { this.logReader = new LogReader(process.cwd()) } - public async boot(io: Server): Promise { - this.io = io + public async boot(): Promise { + this.io = SocketManager.getIO() await this.registry.loadCommands() this.logReader.start() diff --git a/src/managers/queueManager.ts b/src/managers/queueManager.ts index 51dde32..d57ea8d 100644 --- a/src/managers/queueManager.ts +++ b/src/managers/queueManager.ts @@ -8,6 +8,7 @@ import config from '#application/config' import Logger, { LoggerType } from '#application/logger' import { getAppPath } from '#application/storage' import { TSocket } from '#application/types' +import SocketManager from '#managers/socketManager' class QueueManager { private connection!: IORedis @@ -16,8 +17,8 @@ class QueueManager { private io!: SocketServer private logger = Logger.type(LoggerType.QUEUE) - public async boot(io: SocketServer) { - this.io = io + public async boot() { + this.io = SocketManager.getIO() this.connection = new IORedis(config.REDIS_URL, { maxRetriesPerRequest: null diff --git a/src/managers/socketManager.ts b/src/managers/socketManager.ts index 3f12f4b..eacd597 100644 --- a/src/managers/socketManager.ts +++ b/src/managers/socketManager.ts @@ -1,27 +1,53 @@ +import { Server as SocketServer } from 'socket.io' import fs from 'fs' import { pathToFileURL } from 'url' - -import { Server as SocketServer } from 'socket.io' +import { Server as HTTPServer } from 'http' +import { Application } from 'express' import Logger, { LoggerType } from '#application/logger' import { getAppPath } from '#application/storage' import { TSocket } from '#application/types' +import config from '#application/config' +import { Authentication } from '#middleware/authentication' class SocketManager { - private io: any - private logger = Logger.type(LoggerType.APP) + private io: SocketServer | null = null + private readonly logger = Logger.type(LoggerType.APP) - public async boot(io: SocketServer) { - this.io = io - io.on('connection', this.handleConnection.bind(this)) + /** + * Initialize Socket.IO server + */ + public async boot(app: Application, http: HTTPServer): Promise { + this.io = new SocketServer(http, { + cors: { + origin: config.CLIENT_URL, + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization'], + credentials: true + } + }) + + // Apply authentication middleware + this.io.use(Authentication) + + // Set up connection handler + this.io.on('connection', this.handleConnection.bind(this)) + } + + /** + * Get Socket.IO server instance + */ + public getIO(): SocketServer { + if (!this.io) { + throw new Error('Socket.IO has not been initialized') + } + return this.io } /** * Handle socket connection - * @param socket - * @private */ - private async handleConnection(socket: TSocket) { + private async handleConnection(socket: TSocket): Promise { try { await this.loadEventHandlers('events', '', socket) } catch (error: any) { @@ -29,7 +55,10 @@ class SocketManager { } } - private async loadEventHandlers(baseDir: string, subDir: string, socket: TSocket) { + /** + * Load event handlers recursively from the events directory + */ + private async loadEventHandlers(baseDir: string, subDir: string, socket: TSocket): Promise { try { const fullDir = getAppPath(baseDir, subDir) const files = await fs.promises.readdir(fullDir, { withFileTypes: true }) @@ -64,6 +93,20 @@ class SocketManager { this.logger.error(`Error reading directory: ${error instanceof Error ? error.message : String(error)}`) } } + + /** + * Emit event to all connected clients + */ + public emit(event: string, ...args: any[]): void { + this.getIO().emit(event, ...args) + } + + /** + * Emit event to specific room + */ + public emitToRoom(room: string, event: string, ...args: any[]): void { + this.getIO().to(room).emit(event, ...args) + } } -export default new SocketManager() +export default new SocketManager() \ No newline at end of file diff --git a/src/server.ts b/src/server.ts index 2245165..6f3b4c4 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,8 +1,6 @@ import { createServer as httpServer, Server as HTTPServer } from 'http' - -import cors from 'cors' import express, { Application } from 'express' -import { Server as SocketServer } from 'socket.io' +import cors from 'cors' import config from '#application/config' import Database from '#application/database' @@ -15,77 +13,45 @@ import SocketManager from '#managers/socketManager' 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(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() { - // Connect to database + public async start(): Promise { try { + // Initialize database 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 { + // Start HTTP server this.http.listen(config.PORT, config.HOST) - this.logger.info(`Socket.IO running on port ${config.PORT}`) + this.logger.info(`Server running on port ${config.PORT}`) + + // Initialize managers + await Promise.all([ + HttpManager.boot(this.app), + SocketManager.boot(this.app, this.http), + QueueManager.boot(), + UserManager.boot(), + // DateManager.boot(SocketManager.getIO()), + // WeatherManager.boot(SocketManager.getIO()), + ZoneManager.boot(), + ConsoleManager.boot() + ]) + } catch (error: any) { - this.logger.error(`Socket.IO failed to start: ${error.message}`) + this.logger.error(`Server failed to start: ${error.message}`) + process.exit(1) } - - // 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) - - // Load socket manager - await SocketManager.boot(this.io) } }