From 5a6472ffdbba4df4871141c6fb02ce9eebd363a7 Mon Sep 17 00:00:00 2001 From: PsiMagistr Date: Sun, 25 May 2025 13:17:37 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B2=20=D0=B2=20bc.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/bc.js | 91 +++++++++++++++++----------------------------------- 1 file changed, 29 insertions(+), 62 deletions(-) diff --git a/server/bc.js b/server/bc.js index 878b166..36b90a7 100644 --- a/server/bc.js +++ b/server/bc.js @@ -1,8 +1,5 @@ // /server/bc.js - Главный файл сервера Battle Club -// Загружаем переменные окружения из .env файла -// Убедитесь, что `dotenv` установлен (npm install dotenv) -// и этот вызов находится как можно раньше require('dotenv').config({ path: require('node:path').resolve(process.cwd(), '.env') }); const express = require('express'); @@ -12,17 +9,15 @@ const path = require('path'); const jwt = require('jsonwebtoken'); const cors = require('cors'); -// Импорт серверных модулей const authService = require('./auth/authService'); const GameManager = require('./game/GameManager'); -const db = require('./core/db'); // Предполагается, что db.js корректно инициализирует соединение -const GAME_CONFIG = require('./core/config'); // Глобальная конфигурация игры +const db = require('./core/db'); +const GAME_CONFIG = require('./core/config'); const app = express(); const server = http.createServer(app); // --- НАСТРОЙКА EXPRESS --- -// Определяем разрешенный источник для HTTP CORS const clientOrigin = process.env.CORS_ORIGIN_CLIENT || (process.env.NODE_ENV === 'development' ? '*' : undefined); console.log(`[BC Server] NODE_ENV: ${process.env.NODE_ENV}`); console.log(`[BC Server] process.env.CORS_ORIGIN_CLIENT: ${process.env.CORS_ORIGIN_CLIENT}`); @@ -33,13 +28,12 @@ if (!clientOrigin && process.env.NODE_ENV !== 'development') { } app.use(cors({ - origin: clientOrigin, // Используем рассчитанное значение + origin: clientOrigin, methods: ["GET", "POST"], - credentials: true // Если вы планируете использовать куки или заголовки авторизации с CORS + credentials: true })); app.use(express.json()); -// Раздача статических файлов из папки public уровнем выше (../public) const publicPath = path.join(__dirname, '..', 'public'); console.log(`[BC Server] Serving static files from: ${publicPath}`); app.use(express.static(publicPath)); @@ -79,9 +73,8 @@ app.post('/auth/login', async (req, res) => { res.status(401).json(result); } }); -// ------------------------------ -// Определяем разрешенный источник для Socket.IO CORS +// --- НАСТРОЙКА SOCKET.IO --- const socketCorsOrigin = process.env.CORS_ORIGIN_SOCKET || (process.env.NODE_ENV === 'development' ? '*' : undefined); console.log(`[BC Server] process.env.CORS_ORIGIN_SOCKET: ${process.env.CORS_ORIGIN_SOCKET}`); console.log(`[BC Server] Calculated Socket.IO CORS Origin (socketCorsOrigin): ${socketCorsOrigin === '*' ? "'*'" : socketCorsOrigin || 'Not explicitly set (will likely fail if not development)'}`); @@ -91,34 +84,29 @@ if (!socketCorsOrigin && process.env.NODE_ENV !== 'development') { } const io = new Server(server, { - // path: '/socket.io/', // Оставьте эту строку ЗАКОММЕНТИРОВАННОЙ, если клиент подключается к /socket.io/ по умолчанию, - // И ваш прокси НЕ отрезает /socket.io/ (т.е. stripPrefix: false для /socket.io в config.json) - // Если клиент подключается к /, а прокси добавляет /socket.io/, то здесь нужно '/'. - // Если клиент подключается к /socket.io/, а прокси ОТРЕЗАЕТ /socket.io/ (stripPrefix: true), то здесь не нужен path. - // Для вашей конфигурации прокси (stripPrefix: false для /socket.io), и если клиент обращается к /socket.io/, - // то здесь ЛИБО path: '/socket.io/', ЛИБО клиент должен обращаться к wss://domain.com/ (без /socket.io/) - // и тогда прокси должен добавлять /socket.io в target. - // САМЫЙ ПРОСТОЙ ВАРИАНТ: если клиент идет на /socket.io/ (стандарт), и прокси НЕ режет /socket.io/ (stripPrefix: false), - // то здесь path должен быть '/socket.io/'. - path: '/socket.io/', // <--- РАСКОММЕНТИРУЙТЕ ЭТО, если клиент подключается к /socket.io/ и прокси это не отрезает. + // ВАЖНО: Этот path должен соответствовать тому, как клиент подключается + // и как прокси настроен (особенно stripPrefix для /socket.io). + // Если клиент и прокси работают с /socket.io/ и прокси НЕ отрезает этот префикс + // (stripPrefix: false для /socket.io в config.json), то здесь ДОЛЖЕН быть path. + path: '/socket.io/', // <--- УБЕДИТЕСЬ, ЧТО ЭТА СТРОКА РАСКОММЕНТИРОВАНА И КОРРЕКТНА cors: { - origin: socketCorsOrigin, // Используем рассчитанное значение + origin: socketCorsOrigin, methods: ["GET", "POST"], credentials: true }, - // transports: ['websocket', 'polling'], // Можно явно указать транспорты + // transports: ['websocket', 'polling'], // Можно оставить по умолчанию или указать явно }); -console.log(`[BC Server] Socket.IO server configured with path: ${io.path()}`); +console.log(`[BC Server] Socket.IO server configured with path: ${io.path()} and CORS origin: ${socketCorsOrigin === '*' ? "'*'" : socketCorsOrigin || 'Not set'}`); -const gameManager = new GameManager(io); // GameManager должен быть инициализирован ПОСЛЕ io -const loggedInUsers = {}; // { socket.id: { userId, username } } +const gameManager = new GameManager(io); +const loggedInUsers = {}; // --- MIDDLEWARE АУТЕНТИФИКАЦИИ SOCKET.IO --- io.use(async (socket, next) => { const token = socket.handshake.auth.token; - const clientIp = socket.handshake.address; - console.log(`[BC Socket.IO Middleware] Auth attempt for socket ${socket.id} from IP ${clientIp}. Token ${token ? 'present' : 'absent'}. Origin: ${socket.handshake.headers.origin}`); + const clientIp = socket.handshake.address; // Может быть IP прокси, если xfwd не настроен на уровне Socket.IO + console.log(`[BC Socket.IO Middleware] Auth attempt for socket ${socket.id} from IP ${clientIp}. Token ${token ? 'present' : 'absent'}. Origin: ${socket.handshake.headers.origin}. Path: ${socket.nsp.name}`); if (token) { try { @@ -128,48 +116,37 @@ io.use(async (socket, next) => { return next(); } catch (err) { console.warn(`[BC Socket.IO Middleware] Socket ${socket.id} auth failed: Invalid token. Error: ${err.message}`); - // не вызываем next(new Error(...)) чтобы разрешить неаутентифицированные подключения, - // но socket.userData не будет установлен. Логика в 'connection' должна это учитывать. } } else { console.log(`[BC Socket.IO Middleware] Socket ${socket.id} has no token. Proceeding as unauthenticated.`); } - // Позволяем неаутентифицированным сокетам подключаться, - // но они не смогут выполнять действия, требующие userData next(); }); -// ------------------------------------ +// --- ОБРАБОТЧИКИ СОБЫТИЙ SOCKET.IO --- io.on('connection', (socket) => { if (socket.userData && socket.userData.userId) { - console.log(`[BC Socket.IO] Authenticated user ${socket.userData.username} (ID: ${socket.userData.userId}) connected with socket: ${socket.id}`); - loggedInUsers[socket.id] = socket.userData; // Сохраняем данные пользователя для этого сокета - // Запрашиваем состояние игры для подключившегося пользователя + console.log(`[BC Socket.IO] Authenticated user ${socket.userData.username} (ID: ${socket.userData.userId}) connected with socket: ${socket.id} to path ${socket.nsp.name}`); + loggedInUsers[socket.id] = socket.userData; if (gameManager && typeof gameManager.handleRequestGameState === 'function') { - // При подключении (или реконнекте с новым сокетом) пользователя, - // который уже был залогинен (токен валиден), пытаемся восстановить его игру. gameManager.handleRequestGameState(socket, socket.userData.userId); } else { console.error("[BC Socket.IO] CRITICAL: gameManager or handleRequestGameState not available on connect for authenticated user!"); } } else { - console.log(`[BC Socket.IO] Unauthenticated user connected with socket: ${socket.id}. No game state will be restored.`); - // Можно отправить клиенту сообщение, что он не аутентифицирован, если это необходимо - // socket.emit('authError', { message: 'Вы не аутентифицированы.' }); + console.log(`[BC Socket.IO] Unauthenticated user connected with socket: ${socket.id} to path ${socket.nsp.name}. No game state will be restored.`); } - socket.on('logout', () => { // Это событие инициируется клиентом перед фактическим удалением токена + // ... (остальные обработчики событий: logout, playerSurrender, createGame, и т.д. остаются как в вашей версии с логами) ... + // Копирую их из вашего предыдущего варианта для полноты: + socket.on('logout', () => { const username = socket.userData?.username || 'UnknownUser'; const userId = socket.userData?.userId; console.log(`[BC Socket.IO] 'logout' event from user ${username} (ID: ${userId}, Socket: ${socket.id})`); - // GameManager.handleDisconnect будет вызван автоматически при событии 'disconnect', - // которое произойдет после того, как клиент разорвет соединение или обновит токен. - // Если игрок нажал "выход", но еще в игре, клиент пошлет 'playerSurrender' ДО этого. if (loggedInUsers[socket.id]) { delete loggedInUsers[socket.id]; } - socket.userData = null; // Очищаем данные на сокете - // Клиент сам разорвет соединение и переподключится без токена, или с новым токеном. + socket.userData = null; console.log(`[BC Socket.IO] User ${username} (Socket: ${socket.id}) session data cleared on server due to 'logout' event.`); }); @@ -197,7 +174,7 @@ io.on('connection', (socket) => { return; } const identifier = socket.userData.userId; - const mode = data?.mode || 'ai'; // По умолчанию AI режим + const mode = data?.mode || 'ai'; const charKey = data?.characterKey; console.log(`[BC Socket.IO 'createGame'] Request from ${socket.userData.username} (ID: ${identifier}). Mode: ${mode}, Char: ${charKey}`); gameManager.createGame(socket, mode, charKey, identifier); @@ -228,15 +205,12 @@ io.on('connection', (socket) => { }); socket.on('requestPvPGameList', () => { - // Это событие может запрашивать любой подключенный сокет, аутентификация нестрогая, - // но список будет полезен только залогиненным. console.log(`[BC Socket.IO 'requestPvPGameList'] Request from socket ${socket.id} (User: ${socket.userData?.username || 'Unauth'}).`); const availableGames = gameManager.getAvailablePvPGamesListForClient(); socket.emit('availablePvPGamesList', availableGames); }); socket.on('requestGameState', () => { - // Это событие имеет смысл только для аутентифицированного пользователя if (!socket.userData?.userId) { console.warn(`[BC Socket.IO 'requestGameState'] Denied for unauthenticated socket ${socket.id}.`); socket.emit('gameNotFound', { message: 'Необходимо войти для восстановления игры.' }); @@ -263,18 +237,17 @@ io.on('connection', (socket) => { const username = socket.userData?.username || 'UnauthenticatedUser'; console.log(`[BC Socket.IO] User ${username} (ID: ${identifier || 'N/A'}, Socket: ${socket.id}) disconnected. Reason: ${reason}.`); if (identifier) { - // Уведомляем GameManager об отключении пользователя, чтобы он мог обработать логику игры - // (например, пометить игрока как временно отключенного, запустить таймер реконнекта) gameManager.handleDisconnect(socket.id, identifier); } if (loggedInUsers[socket.id]) { - delete loggedInUsers[socket.id]; // Удаляем из списка активных сокетов + delete loggedInUsers[socket.id]; } }); }); +// --- ЗАПУСК СЕРВЕРА --- const PORT = parseInt(process.env.BC_APP_PORT || '3200', 10); -const HOSTNAME = process.env.BC_APP_HOSTNAME || '127.0.0.1'; // '0.0.0.0' для прослушивания на всех интерфейсах +const HOSTNAME = process.env.BC_APP_HOSTNAME || '127.0.0.1'; if (isNaN(PORT)) { console.error(`[BC Server FATAL] Некорректное значение для BC_APP_PORT: "${process.env.BC_APP_PORT}". Ожидается число.`); @@ -291,19 +264,13 @@ server.listen(PORT, HOSTNAME, () => { console.log(`[BC Server] Server is listening on a specific interface: ${HOSTNAME}.`); } console.log(`[BC Server] Static files served from: ${publicPath}`); - console.log(`[BC Server] Socket.IO server using path: ${io.path()} and CORS origin: ${socketCorsOrigin === '*' ? "'*'" : socketCorsOrigin || 'Not set'}`); - console.log(`[BC Server] HTTP API CORS origin: ${clientOrigin === '*' ? "'*'" : clientOrigin || 'Not set'}`); }); -// Глобальные обработчики необработанных ошибок process.on('unhandledRejection', (reason, promise) => { console.error('[BC Server FATAL] Unhandled Rejection at:', promise, 'reason:', reason); - // В продакшене здесь может быть логика для более корректного завершения или перезапуска }); process.on('uncaughtException', (err) => { console.error('[BC Server FATAL] Uncaught Exception:', err); - // Важно: после uncaughtException приложение находится в непредсказуемом состоянии - // и должно быть перезапущено. process.exit(1); }); \ No newline at end of file