server/src/managers/socketManager.ts
2025-02-18 17:52:25 +01:00

116 lines
3.5 KiB
TypeScript

import fs from 'fs'
import { Server as HTTPServer } from 'http'
import { pathToFileURL } from 'url'
import { SocketEvent } from '@/application/enums'
import Logger, { LoggerType } from '@/application/logger'
import Storage from '@/application/storage'
import type { TSocket, UUID } from '@/application/types'
import { Authentication } from '@/middleware/authentication'
import { Server as SocketServer } from 'socket.io'
class SocketManager {
private io: SocketServer | null = null
private readonly logger = Logger.type(LoggerType.APP)
/**
* Initialize Socket.IO server
*/
public async boot(http: HTTPServer): Promise<void> {
this.io = new SocketServer(http)
// Apply authentication middleware
this.io.use(Authentication)
// Set up connection handler
this.io.on(SocketEvent.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
*/
private async handleConnection(socket: TSocket): Promise<void> {
try {
await this.loadEventHandlers('events', '', socket)
} catch (error: any) {
this.logger.error(`Failed to load event handlers: ${error.message}`)
}
}
/**
* Load event handlers recursively from the events directory
*/
private async loadEventHandlers(baseDir: string, subDir: string, socket: TSocket): Promise<void> {
try {
const fullDir = Storage.getAppPath(baseDir, subDir)
const files = await fs.promises.readdir(fullDir, { withFileTypes: true })
for (const file of files) {
const filePath = Storage.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)}`)
}
}
/**
* 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)
}
public getSocketByUserId(userId: UUID): TSocket | undefined {
const sockets = Array.from(this.getIO().sockets.sockets.values())
return sockets.find((socket: TSocket) => socket.userId === userId)
}
public getSocketByCharacterId(characterId: UUID): TSocket | undefined {
const sockets = Array.from(this.getIO().sockets.sockets.values())
return sockets.find((socket: TSocket) => socket.characterId === characterId)
}
}
export default new SocketManager()