()
| 29 | } |
| 30 | |
| 31 | async function main() { |
| 32 | const httpServer = createServer() |
| 33 | const PORT = env.PORT |
| 34 | |
| 35 | logger.info('Starting Socket.IO server...', { |
| 36 | port: PORT, |
| 37 | nodeEnv: env.NODE_ENV, |
| 38 | hasDatabase: !!env.DATABASE_URL, |
| 39 | hasAuth: !!env.BETTER_AUTH_SECRET, |
| 40 | hasRedis: !!env.REDIS_URL, |
| 41 | }) |
| 42 | |
| 43 | // Register the HTTP handler before Socket.IO attaches: engine.io captures |
| 44 | // pre-existing `request` listeners and forwards only non-`/socket.io/` |
| 45 | // requests to them, making it the single dispatcher for the shared port. |
| 46 | // The handler itself is assigned after the room manager exists, before listen(). |
| 47 | // biome-ignore lint/style/useConst: must be declared before the request listener closure; assigned only after the room manager exists |
| 48 | let httpHandler: ReturnType<typeof createHttpHandler> | undefined |
| 49 | httpServer.on('request', (req, res) => httpHandler?.(req, res)) |
| 50 | |
| 51 | // Create Socket.IO server with Redis adapter if configured |
| 52 | const io = await createSocketIOServer(httpServer) |
| 53 | |
| 54 | // Initialize room manager (Redis or in-memory based on config) |
| 55 | const roomManager = await createRoomManager(io) |
| 56 | |
| 57 | // Set up authentication middleware |
| 58 | io.use(authenticateSocket) |
| 59 | |
| 60 | // Set up HTTP handler for health checks and internal APIs |
| 61 | httpHandler = createHttpHandler(roomManager, logger) |
| 62 | |
| 63 | // Global error handlers |
| 64 | process.on('uncaughtException', (error) => { |
| 65 | logger.error('Uncaught Exception:', error) |
| 66 | }) |
| 67 | |
| 68 | process.on('unhandledRejection', (reason, promise) => { |
| 69 | if (reason instanceof Error && reason.message === 'The client is closed') { |
| 70 | logger.warn('Redis client is closed — suppressing unhandled rejection') |
| 71 | return |
| 72 | } |
| 73 | logger.error('Unhandled Rejection at:', promise, 'reason:', reason) |
| 74 | }) |
| 75 | |
| 76 | httpServer.on('error', (error: NodeJS.ErrnoException) => { |
| 77 | logger.error('HTTP server error:', error) |
| 78 | if (error.code === 'EADDRINUSE' || error.code === 'EACCES') { |
| 79 | process.exit(1) |
| 80 | } |
| 81 | }) |
| 82 | |
| 83 | io.engine.on('connection_error', (err) => { |
| 84 | logger.error('Socket.IO connection error:', { |
| 85 | req: err.req?.url, |
| 86 | code: err.code, |
| 87 | message: err.message, |
| 88 | context: err.context, |
no test coverage detected