116 lines
3.5 KiB
TypeScript
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()
|