diff --git a/bc.js b/bc.js index b28f6d7..c280e5e 100644 --- a/bc.js +++ b/bc.js @@ -1,7 +1,7 @@ // bc.js - Главный файл сервера Battle Club const express = require('express'); -const http = require('http'); +const http = require('http'); // Используем HTTP, так как SSL будет на Node.js прокси (server.js) const { Server } = require('socket.io'); const path = require('path'); @@ -16,10 +16,13 @@ const app = express(); const server = http.createServer(app); // Настройка Socket.IO -// cors options могут потребоваться, если клиент и сервер работают на разных портах/доменах const io = new Server(server, { cors: { - origin: "*", // Разрешить подключение с любого домена (для разработки). В продакшене лучше указать конкретный домен клиента. + origin: "https://pavel-chagovsky.com:3200", // Указываем точный origin, включая порт, откуда придет запрос К ПРОКСИ + // Если доступ будет с нескольких доменов или портов, можно использовать массив: + // origin: ["https://pavel-chagovsky.com:3200", "https://oleg-okhotnikov.ru:3200"], + // Или для разработки можно временно использовать "*", но это менее безопасно: + // origin: "*", methods: ["GET", "POST"] } }); @@ -31,198 +34,132 @@ app.use(express.static(path.join(__dirname, 'public'))); const gameManager = new GameManager(io); // Хранилище информации о залогиненных пользователях по socket.id -// В более сложном приложении здесь может быть Redis или другое внешнее хранилище сессий const loggedInUsers = {}; // { socket.id: { userId: ..., username: ... } } // Обработка подключений Socket.IO io.on('connection', (socket) => { - console.log(`[Socket.IO] Пользователь подключился: ${socket.id}`); + console.log(`[BC App HTTP] Socket.IO User connected: ${socket.id}`); - // Привязываем user data к сокету (пока пустые) - socket.userData = null; // { userId: ..., username: ... } + socket.userData = null; - // При подключении клиента, если он уже залогинен (например, по cookie/token, что здесь не реализовано, - // но может быть добавлено), нужно восстановить его user data и проверить, не в игре ли он. - // В текущей простой реализации, мы полагаемся на то, что клиент после коннекта сам отправит логин, - // если он был залогинен. Но если бы была проверка сессии, логика была бы тут. - // Добавляем вызов handleRequestGameState при коннекте, если есть user data (для примера, - // но для полной реализации нужны cookies/токены) - // if (socket.userData?.userId) { // Эта проверка сработает только после успешного логина в текущей сессии - // gameManager.handleRequestGameState(socket, socket.userData.userId); // Передаем объект socket - // } - - - // --- Обработчики событий Аутентификации --- socket.on('register', async (data) => { - console.log(`[Socket.IO] Register attempt for username: "${data?.username}" from ${socket.id}`); + console.log(`[BC App HTTP Socket.IO] Register attempt for username: "${data?.username}" from ${socket.id}`); const result = await auth.registerUser(data?.username, data?.password); if (result.success) { - console.log(`[Socket.IO] Registration successful for ${result.username} (${result.userId})`); + console.log(`[BC App HTTP Socket.IO] Registration successful for ${result.username} (${result.userId})`); } else { - console.warn(`[Socket.IO] Registration failed for "${data?.username}": ${result.message}`); + console.warn(`[BC App HTTP Socket.IO] Registration failed for "${data?.username}": ${result.message}`); } socket.emit('registerResponse', result); }); socket.on('login', async (data) => { - console.log(`[Socket.IO] Login attempt for username: "${data?.username}" from ${socket.id}`); + console.log(`[BC App HTTP Socket.IO] Login attempt for username: "${data?.username}" from ${socket.id}`); const result = await auth.loginUser(data?.username, data?.password); if (result.success) { - console.log(`[Socket.IO] Login successful for ${result.username} (${result.userId}). Assigning to socket ${socket.id}.`); - // Сохраняем информацию о пользователе в сессии сокета + console.log(`[BC App HTTP Socket.IO] Login successful for ${result.username} (${result.userId}). Assigning to socket ${socket.id}.`); socket.userData = { userId: result.userId, username: result.username }; loggedInUsers[socket.id] = socket.userData; - - // Проверяем, есть ли у пользователя активная игра при логине (если он был отключен) - // ИСПРАВЛЕНИЕ: Передаем объект socket gameManager.handleRequestGameState(socket, socket.userData.userId); - } else { - console.warn(`[Socket.IO] Login failed for "${data?.username}": ${result.message}`); - socket.userData = null; // Убеждаемся, что данные пользователя на сокете сброшены + console.warn(`[BC App HTTP Socket.IO] Login failed for "${data?.username}": ${result.message}`); + socket.userData = null; if (loggedInUsers[socket.id]) delete loggedInUsers[socket.id]; } socket.emit('loginResponse', result); }); socket.on('logout', () => { - console.log(`[Socket.IO] Logout for user ${socket.userData?.username || socket.id}`); - // Уведомляем gameManager о дисконнекте (для корректного выхода из игры, если в ней был) - // Game Manager сам очистит ссылку socketToGame[socket.id] при handleDisconnect - // ИСПРАВЛЕНИЕ: Передаем userId или socket.id в handleDisconnect + console.log(`[BC App HTTP Socket.IO] Logout for user ${socket.userData?.username || socket.id}`); gameManager.handleDisconnect(socket.id, socket.userData?.userId || socket.id); - - // Очищаем информацию о пользователе на сокете и в хранилище socket.userData = null; if (loggedInUsers[socket.id]) delete loggedInUsers[socket.id]; - - // Клиент должен сам переключиться на экран аутентификации }); - // --- Обработчики событий Управления Играми --- - socket.on('createGame', (data) => { - // Пользователь, даже не залогиненный, может создать AI игру (идентифицируется по socket.id) - // Для PvP игры нужна аутентификация (идентификация по userId) - const identifier = socket.userData?.userId || socket.id; // Используем userId для залогиненных, socket.id для гостей + const identifier = socket.userData?.userId || socket.id; const mode = data?.mode || 'ai'; - if (mode === 'pvp' && !socket.userData) { socket.emit('gameError', { message: 'Необходимо войти в систему для создания PvP игры.' }); return; } - - console.log(`[Socket.IO] Create Game request from ${socket.userData?.username || socket.id} (Identifier: ${identifier}). Mode: ${mode}, Character: ${data?.characterKey}`); - - const characterKey = data?.characterKey || 'elena'; // По умолчанию Елена - gameManager.createGame(socket, mode, characterKey, identifier); // Передаем идентификатор - + console.log(`[BC App HTTP Socket.IO] Create Game request from ${socket.userData?.username || socket.id} (Identifier: ${identifier}). Mode: ${mode}, Character: ${data?.characterKey}`); + const characterKey = data?.characterKey || 'elena'; + gameManager.createGame(socket, mode, characterKey, identifier); }); socket.on('joinGame', (data) => { - if (!socket.userData) { // Проверяем, залогинен ли пользователь + if (!socket.userData) { socket.emit('gameError', { message: 'Необходимо войти в систему для присоединения к игре.' }); return; } - console.log(`[Socket.IO] Join Game request from ${socket.userData.username} (${socket.id}). Game ID: ${data?.gameId}`); + console.log(`[BC App HTTP Socket.IO] Join Game request from ${socket.userData.username} (${socket.id}). Game ID: ${data?.gameId}`); const gameId = data?.gameId; - const identifier = socket.userData.userId; // Присоединиться может только залогиненный - + const identifier = socket.userData.userId; if (gameId) { - gameManager.joinGame(socket, gameId, identifier); // Передаем идентификатор + gameManager.joinGame(socket, gameId, identifier); } else { socket.emit('gameError', { message: 'Не указан ID игры для присоединения.' }); } }); socket.on('findRandomGame', (data) => { - if (!socket.userData) { // Проверяем, залогинен ли пользователь + if (!socket.userData) { socket.emit('gameError', { message: 'Необходимо войти в систему для поиска игры.' }); return; } - console.log(`[Socket.IO] Find Random Game request from ${socket.userData.username} (${socket.id}). Preferred Character: ${data?.characterKey}`); - const characterKey = data?.characterKey || 'elena'; // Предпочитаемый персонаж для создания, если не найдено - const identifier = socket.userData.userId; // Ищет и создает только залогиненный - - gameManager.findAndJoinRandomPvPGame(socket, characterKey, identifier); // Передаем идентификатор + console.log(`[BC App HTTP Socket.IO] Find Random Game request from ${socket.userData.username} (${socket.id}). Preferred Character: ${data?.characterKey}`); + const characterKey = data?.characterKey || 'elena'; + const identifier = socket.userData.userId; + gameManager.findAndJoinRandomPvPGame(socket, characterKey, identifier); }); socket.on('requestPvPGameList', () => { - // Список игр доступен всем, даже не залогиненным, но присоединиться можно только залогиненным - // if (!socket.userData) { - // socket.emit('gameError', { message: 'Необходимо войти в систему для просмотра игр.' }); - // return; - // } - console.log(`[Socket.IO] Request PvP Game List from ${socket.userData?.username || socket.id}`); + console.log(`[BC App HTTP Socket.IO] Request PvP Game List from ${socket.userData?.username || socket.id}`); const availableGames = gameManager.getAvailablePvPGamesListForClient(); socket.emit('availablePvPGamesList', availableGames); }); - // Обработчик для клиента, запрашивающего состояние игры (например, при переподключении) socket.on('requestGameState', () => { - // Запрашивать состояние игры может только залогиненный пользователь, т.к. только у них есть userId для идентификации if (!socket.userData) { - console.log(`[Socket.IO] Request Game State from unauthenticated socket ${socket.id}.`); + console.log(`[BC App HTTP Socket.IO] Request Game State from unauthenticated socket ${socket.id}.`); socket.emit('gameNotFound', { message: 'Необходимо войти для восстановления игры.' }); return; } - console.log(`[Socket.IO] Request Game State from ${socket.userData.username} (${socket.id}).`); - // ИСПРАВЛЕНИЕ: Передаем объект socket и identifier (userId) + console.log(`[BC App HTTP Socket.IO] Request Game State from ${socket.userData.username} (${socket.id}).`); gameManager.handleRequestGameState(socket, socket.userData.userId); }); - - // --- Обработчик события Игрового Действия --- socket.on('playerAction', (actionData) => { - // Действие в игре может совершить как залогиненный (PvP), так и не залогиненный (AI) игрок. - // Используем userId для залогиненных, socket.id для гостей. const identifier = socket.userData?.userId || socket.id; - - // Game Manager сам проверит, находится ли идентификатор в игре и его ли сейчас ход - // ИСПРАВЛЕНИЕ: Передаем идентификатор вместо socket.id - gameManager.handlePlayerAction(identifier, actionData); // Передаем идентификатор + gameManager.handlePlayerAction(identifier, actionData); }); - - // --- Обработчик отключения сокета --- socket.on('disconnect', (reason) => { - const identifier = socket.userData?.userId || socket.id; // Используем userId для залогиненных, socket.id для гостей - console.log(`[Socket.IO] Пользователь отключился: ${socket.id} (Причина: ${reason}). Identifier: ${identifier}`); - - // Уведомляем gameManager о дисконнекте, чтобы он обновил состояние игры. - // Передаем идентификатор пользователя. - gameManager.handleDisconnect(socket.id, identifier); // Передаем как socketId, так и identifier - - // Удаляем пользователя из списка залогиненных, если был там + const identifier = socket.userData?.userId || socket.id; + console.log(`[BC App HTTP Socket.IO] User disconnected: ${socket.id} (Причина: ${reason}). Identifier: ${identifier}`); + gameManager.handleDisconnect(socket.id, identifier); if (loggedInUsers[socket.id]) { delete loggedInUsers[socket.id]; } - // Если сокет не был залогинен, его identifier был socket.id. - // Связь userIdentifierToGameId будет очищена в gameManager.handleDisconnect, если игра пуста. }); - - // Опционально: отправка списка активных игр на сервере для отладки (по запросу с консоли или админки) - // global.getActiveGames = () => gameManager.getActiveGamesList(); - // console.log("Type getActiveGames() in server console to list games."); }); // Запуск HTTP сервера -const PORT = process.env.PORT || 3200; // Использовать порт из переменных окружения или 3000 по умолчанию -server.listen(PORT, () => { - console.log(`Server running on port ${PORT}`); +const PORT = process.env.BC_INTERNAL_PORT || 3200; // Внутренний порт для bc.js +const HOSTNAME = '127.0.0.1'; // Слушать ТОЛЬКО на localhost + +server.listen(PORT, HOSTNAME, () => { // Явно указываем HOSTNAME + console.log(`Battle Club HTTP Application Server running at http://${HOSTNAME}:${PORT}`); + console.log(`This server should only be accessed locally by the reverse proxy.`); console.log(`Serving static files from: ${path.join(__dirname, 'public')}`); - // console.log("Database connection pool created/checked (from db.js require)."); // db.js уже логирует }); // Обработка необработанных промис-ошибок process.on('unhandledRejection', (reason, promise) => { - console.error('[UNHANDLED REJECTION] Unhandled Rejection at:', promise, 'reason:', reason); - // Логировать ошибку, возможно, завершить процесс в продакшене + console.error('[BC App HTTP UNHANDLED REJECTION] Unhandled Rejection at:', promise, 'reason:', reason); }); process.on('uncaughtException', (err) => { - console.error('[UNCAUGHT EXCEPTION] Caught exception:', err); - // Логировать ошибку, выполнить очистку ресурсов, и завершить процесс - // В продакшене здесь может быть более сложная логика, например, graceful shutdown - // process.exit(1); // Аварийное завершение процесса - можно раскомментировать в продакшене + console.error('[BC App HTTP UNCAUGHT EXCEPTION] Caught exception:', err); }); \ No newline at end of file