- no-direct-socket-io: extend allowlist to cover apps/*/src/**/*.test.ts so the realtime-ping e2e integration test can import socket.io/socket.io-client directly; add two valid test cases to keep the rule's own test suite green - tsconfig.json (root): add root-level tsconfig with experimentalDecorators + emitDecoratorMetadata and no "include" so tsx 4.21.0's createFilesMatcher resolves decorator config for all workspace packages, not just web-next's own source tree - web-next dev script: pass TSX_TSCONFIG_PATH=../../tsconfig.json so the custom Node server uses the root tsconfig for all modules it loads - next.config.mjs: add @repo/core-events and @repo/core-realtime to transpilePackages so Next.js webpack can resolve their workspace source files - server.ts: replace static authContainer import with a dynamic import inside IRealtimeAuthenticator.authenticate so Inversify decorators are applied only after bindAll() has already populated the container All CI gates pass: lint (0 errors), typecheck, 20 tests (incl. realtime-ping e2e), boundaries (0 issues). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
67 lines
2.2 KiB
TypeScript
67 lines
2.2 KiB
TypeScript
// 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 { AUTH_SYMBOLS } from "@repo/auth/di/symbols";
|
|
import { bindAll } from "./src/server/bind-production.js";
|
|
|
|
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;
|
|
// Lazy-import the auth container after bindAll() has already populated it.
|
|
// Dynamic import defers tsx's module transformation to runtime, which lets
|
|
// reflect-metadata and the DI decorators resolve correctly in this server
|
|
// entry point.
|
|
const { authContainer } = await import("@repo/auth/di/container");
|
|
const authService = authContainer.get<{
|
|
validateSession: (id: string) => Promise<{ user: { id: string }; session: unknown } | null>;
|
|
}>(AUTH_SYMBOLS.IAuthenticationService);
|
|
const result = await authService.validateSession(sessionId);
|
|
return result
|
|
? {
|
|
userId: result.user.id,
|
|
// Roles are not yet in the session shape; extend here when DB-backed roles ship.
|
|
roles: (result as unknown as { roles?: string[] }).roles ?? [],
|
|
}
|
|
: null;
|
|
},
|
|
};
|
|
|
|
const realtimeServer = new SocketIORealtimeServer({
|
|
httpServer,
|
|
io,
|
|
authenticator,
|
|
registry,
|
|
});
|
|
await realtimeServer.start();
|
|
|
|
httpServer.listen(port, () => {
|
|
console.log(`> Ready on http://localhost:${port}`);
|
|
});
|