// apps/web-next/server.ts // SERVER-ONLY entry. Boots Next.js + Socket.IO on the same Node http server. import "reflect-metadata"; import { createServer } from "node:http"; import next from "next"; import { Server as IOServer } from "socket.io"; import { RealtimeHandlerRegistry, SocketIORealtimeBroadcaster, SocketIORealtimeServer, type IRealtimeAuthenticator, } from "@repo/core-realtime"; import { SESSION_COOKIE } from "@repo/auth"; import { authContainer } from "@repo/auth/di/container"; import { AUTH_SYMBOLS } from "@repo/auth/di/symbols"; import { bindAll } from "./src/server/bind-production.js"; // Real shape of IAuthenticationService.validateSession: returns non-nullable // on success and throws UnauthenticatedError on missing/invalid sessions. // Kept as an inline structural type to avoid leaking auth's internal interface. type AuthService = { validateSession: (id: string) => Promise<{ user: { id: string }; session: unknown }>; }; const dev = process.env.NODE_ENV !== "production"; const port = Number(process.env.PORT ?? 3000); const app = next({ dev }); const handle = app.getRequestHandler(); await app.prepare(); const httpServer = createServer((req, res) => handle(req, res)); const io = new IOServer(httpServer); const broadcaster = new SocketIORealtimeBroadcaster(io); const registry = new RealtimeHandlerRegistry(); await bindAll({ realtime: broadcaster, realtimeRegistry: registry }); const authenticator: IRealtimeAuthenticator = { authenticate: async ({ cookies }) => { const sessionId = cookies[SESSION_COOKIE]; if (!sessionId) return null; const authService = authContainer.get(AUTH_SYMBOLS.IAuthenticationService); try { const { user } = await authService.validateSession(sessionId); // Roles are not yet in the session shape; extend here when DB-backed roles ship. return { userId: user.id, roles: (user as { roles?: string[] }).roles ?? [] }; } catch { // Invalid/expired session → reject the connection. Real auth-service errors // (DB outages etc.) intentionally collapse to "unauthenticated" here too, // which is the conservative choice for a public-facing socket. return null; } }, }; const realtimeServer = new SocketIORealtimeServer({ httpServer, io, authenticator, registry, }); await realtimeServer.start(); httpServer.listen(port, () => { console.log(`> Ready on http://localhost:${port}`); });