Передача константы path глобально через шаблонизатор.

This commit is contained in:
PsiMagistr 2025-05-28 16:28:23 +03:00
parent f8b648659f
commit b78003b9c0
6 changed files with 281 additions and 41 deletions

174
package-lock.json generated
View File

@ -7,6 +7,7 @@
"dependencies": { "dependencies": {
"bcryptjs": "^3.0.2", "bcryptjs": "^3.0.2",
"dotenv": "^16.5.0", "dotenv": "^16.5.0",
"ejs": "^3.1.10",
"express": "^5.1.0", "express": "^5.1.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"mysql2": "^3.14.1", "mysql2": "^3.14.1",
@ -51,6 +52,27 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/async": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
"license": "MIT"
},
"node_modules/aws-ssl-profiles": { "node_modules/aws-ssl-profiles": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
@ -60,6 +82,12 @@
"node": ">= 6.0.0" "node": ">= 6.0.0"
} }
}, },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/base64id": { "node_modules/base64id": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
@ -98,6 +126,16 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/buffer-equal-constant-time": { "node_modules/buffer-equal-constant-time": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
@ -142,6 +180,46 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"license": "MIT"
},
"node_modules/content-disposition": { "node_modules/content-disposition": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
@ -269,6 +347,21 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/ejs": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
"license": "Apache-2.0",
"dependencies": {
"jake": "^10.8.5"
},
"bin": {
"ejs": "bin/cli.js"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/encodeurl": { "node_modules/encodeurl": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
@ -454,6 +547,36 @@
"url": "https://opencollective.com/express" "url": "https://opencollective.com/express"
} }
}, },
"node_modules/filelist": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
"integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
"license": "Apache-2.0",
"dependencies": {
"minimatch": "^5.0.1"
}
},
"node_modules/filelist/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/filelist/node_modules/minimatch": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/finalhandler": { "node_modules/finalhandler": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
@ -556,6 +679,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/has-symbols": { "node_modules/has-symbols": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
@ -635,6 +767,24 @@
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/jake": {
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz",
"integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==",
"license": "Apache-2.0",
"dependencies": {
"async": "^3.2.3",
"chalk": "^4.0.2",
"filelist": "^1.0.4",
"minimatch": "^3.1.2"
},
"bin": {
"jake": "bin/cli.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/jsonwebtoken": { "node_modules/jsonwebtoken": {
"version": "9.0.2", "version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
@ -801,6 +951,18 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@ -1287,6 +1449,18 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/toidentifier": { "node_modules/toidentifier": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",

View File

@ -2,6 +2,7 @@
"dependencies": { "dependencies": {
"bcryptjs": "^3.0.2", "bcryptjs": "^3.0.2",
"dotenv": "^16.5.0", "dotenv": "^16.5.0",
"ejs": "^3.1.10",
"express": "^5.1.0", "express": "^5.1.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"mysql2": "^3.14.1", "mysql2": "^3.14.1",

View File

@ -6,15 +6,11 @@ export function initAuth(dependencies) {
const { socket, clientState, ui } = dependencies; const { socket, clientState, ui } = dependencies;
const { loginForm, registerForm, logoutButton } = ui.elements; const { loginForm, registerForm, logoutButton } = ui.elements;
let APP_BASE_PATH = '';
console.log('[Auth.js DOM Check] loginForm in initAuth:', loginForm); // <--- ДОБАВЛЕНО console.log('[Auth.js DOM Check] loginForm in initAuth:', loginForm); // <--- ДОБАВЛЕНО
console.log('[Auth.js DOM Check] registerForm in initAuth:', registerForm); // <--- ДОБАВЛЕНО console.log('[Auth.js DOM Check] registerForm in initAuth:', registerForm); // <--- ДОБАВЛЕНО
console.log('[Auth.js DOM Check] logoutButton in initAuth:', logoutButton); // <--- ДОБАВЛЕНО console.log('[Auth.js DOM Check] logoutButton in initAuth:', logoutButton); // <--- ДОБАВЛЕНО
if (window.location.pathname.startsWith('/battleclub')) {
APP_BASE_PATH = '/battleclub'; const getApiUrl = (path) => `${window.location.origin}${base_path}${path}`;
}
const getApiUrl = (path) => `${window.location.origin}${APP_BASE_PATH}${path}`;
console.log('[Auth.js] API URLs will be relative to:', window.location.origin); // <--- ДОБАВЛЕНО console.log('[Auth.js] API URLs will be relative to:', window.location.origin); // <--- ДОБАВЛЕНО
const JWT_TOKEN_KEY = 'jwtToken'; const JWT_TOKEN_KEY = 'jwtToken';

View File

@ -98,6 +98,7 @@ document.addEventListener('DOMContentLoaded', () => {
console.log('[Main.js] Initializing Socket.IO client...'); console.log('[Main.js] Initializing Socket.IO client...');
const socket = io({ const socket = io({
path:base_path,
autoConnect: false, autoConnect: false,
auth: { token: localStorage.getItem('jwtToken') } // Передаем токен (может быть null, если был очищен) auth: { token: localStorage.getItem('jwtToken') } // Передаем токен (может быть null, если был очищен)
}); });

View File

@ -6,8 +6,10 @@ const express = require('express');
const http = require('http'); const http = require('http');
const { Server } = require('socket.io'); const { Server } = require('socket.io');
const path = require('path'); const path = require('path');
const APP_BASE_PATH = process.env.APP_BASE_PATH || "";
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const cors = require('cors'); const cors = require('cors');
// const cookieParser = require('cookie-parser'); // Раскомментируйте, если решите использовать куки для токена напрямую
const authService = require('./auth/authService'); const authService = require('./auth/authService');
const GameManager = require('./game/GameManager'); const GameManager = require('./game/GameManager');
@ -30,14 +32,58 @@ if (!clientOrigin && process.env.NODE_ENV !== 'development' && process.env.NODE_
app.use(cors({ app.use(cors({
origin: clientOrigin, origin: clientOrigin,
methods: ["GET", "POST"], methods: ["GET", "POST"],
credentials: true credentials: true // Важно для работы с куками, если они используются для токенов
})); }));
app.use(express.json()); app.use(express.json());
// app.use(cookieParser()); // Раскомментируйте, если JWT будет передаваться через httpOnly cookie
const publicPath = path.join(__dirname, '..', 'public'); const publicPath = path.join(__dirname, '..', 'public');
console.log(`[BC.JS CONFIG] Serving static files from: ${publicPath}`); console.log(`[BC.JS CONFIG] Serving static files from: ${publicPath}`);
app.use(express.static(publicPath)); app.use(express.static(publicPath));
// --- НАСТРОЙКА EJS ---
app.set('view engine', 'ejs');
// Указываем, где лежат шаблоны. Папка 'views' рядом с bc.js (т.е. server/views)
app.set('views', path.join(__dirname, 'views'));
console.log(`[BC.JS CONFIG] EJS view engine configured. Views directory: ${app.get('views')}`);
// --- HTTP МАРШРУТЫ ---
// Главная страница, рендеринг EJS шаблона
app.get('/', (req, res) => {
// Попытка извлечь токен из localStorage (недоступно на сервере напрямую)
// или из cookie (если настроено).
// Для EJS на сервере нам нужно определить состояние пользователя ДО рендеринга.
// Это обычно делается через проверку сессии или токена в cookie.
// Так как ваш клиент хранит токен в localStorage, при первом GET запросе
// на сервер токен не будет доступен в заголовках или куках автоматически.
//
// Вариант 1: Клиент делает AJAX-запрос для проверки токена после загрузки,
// а сервер отдает базовый HTML, который потом обновляется. (Текущий подход с main.js)
//
// Вариант 2: Сервер отдает базовый HTML, и клиент сам решает, что показывать,
// основываясь на токене в localStorage. (Текущий подход с main.js)
//
// Вариант 3: Передавать токен в cookie (httpOnly для безопасности),
// тогда сервер сможет его читать при GET запросе.
//
// Для простоты демонстрации EJS, предположим, что мы хотим передать
// некоторую базовую информацию, а клиентская логика main.js все равно отработает.
// Мы не можем здесь напрямую прочитать localStorage клиента.
res.render('index', { // Рендерим server/views/index.ejs
title: 'Battle Club RPG', // Передаем заголовок страницы
base_path:APP_BASE_PATH,
// Можно передать базовую структуру HTML, а main.js заполнит остальное
// Либо, если бы токен был в куках, мы могли бы здесь сделать:
// const userData = authService.verifyTokenFromCookie(req.cookies.jwtToken);
// isLoggedIn: !!userData, loggedInUsername: userData ? userData.username : '', ...
});
});
// --- HTTP МАРШРУТЫ АУТЕНТИФИКАЦИИ --- // --- HTTP МАРШРУТЫ АУТЕНТИФИКАЦИИ ---
app.post('/auth/register', async (req, res) => { app.post('/auth/register', async (req, res) => {
const { username, password } = req.body; const { username, password } = req.body;
@ -49,6 +95,8 @@ app.post('/auth/register', async (req, res) => {
const result = await authService.registerUser(username, password); const result = await authService.registerUser(username, password);
if (result.success) { if (result.success) {
console.log(`[BC HTTP /auth/register] Success for "${username}".`); console.log(`[BC HTTP /auth/register] Success for "${username}".`);
// Если вы используете куки для токена:
// res.cookie('jwtToken', result.token, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict' });
res.status(201).json(result); res.status(201).json(result);
} else { } else {
console.warn(`[BC HTTP /auth/register] Failed for "${username}": ${result.message}`); console.warn(`[BC HTTP /auth/register] Failed for "${username}": ${result.message}`);
@ -66,6 +114,8 @@ app.post('/auth/login', async (req, res) => {
const result = await authService.loginUser(username, password); const result = await authService.loginUser(username, password);
if (result.success) { if (result.success) {
console.log(`[BC HTTP /auth/login] Success for "${username}".`); console.log(`[BC HTTP /auth/login] Success for "${username}".`);
// Если вы используете куки для токена:
// res.cookie('jwtToken', result.token, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict' });
res.json(result); res.json(result);
} else { } else {
console.warn(`[BC HTTP /auth/login] Failed for "${username}": ${result.message}`); console.warn(`[BC HTTP /auth/login] Failed for "${username}": ${result.message}`);
@ -94,7 +144,7 @@ const io = new Server(server, {
console.log(`[BC.JS CONFIG] Socket.IO server configured with path: ${io.path()} and effective CORS origin: ${io.opts.cors.origin === '*' ? "'*'" : io.opts.cors.origin || 'NOT SET'}`); console.log(`[BC.JS CONFIG] Socket.IO server configured with path: ${io.path()} and effective CORS origin: ${io.opts.cors.origin === '*' ? "'*'" : io.opts.cors.origin || 'NOT SET'}`);
const gameManager = new GameManager(io); const gameManager = new GameManager(io);
const loggedInUsersBySocketId = {}; const loggedInUsersBySocketId = {}; // Этот объект используется только для логирования в bc.js, основная логика в GameManager
// --- MIDDLEWARE АУТЕНТИФИКАЦИИ SOCKET.IO --- // --- MIDDLEWARE АУТЕНТИФИКАЦИИ SOCKET.IO ---
io.use(async (socket, next) => { io.use(async (socket, next) => {
@ -113,10 +163,17 @@ io.use(async (socket, next) => {
return next(); return next();
} catch (err) { } catch (err) {
console.warn(`[BC Socket.IO Middleware] Socket ${socket.id} auth failed: Invalid token. Error: ${err.message}. Proceeding as unauthenticated.`); console.warn(`[BC Socket.IO Middleware] Socket ${socket.id} auth failed: Invalid token. Error: ${err.message}. Proceeding as unauthenticated.`);
// Не вызываем next(new Error(...)) чтобы не отключать сокет сразу,
// а позволить клиенту обработать это (например, показать экран логина).
// Однако, если бы мы хотели строго запретить неаутентифицированные сокеты:
// return next(new Error('Authentication error: Invalid token'));
} }
} else { } else {
console.log(`[BC Socket.IO Middleware] Socket ${socket.id} has no token. Proceeding as unauthenticated.`); console.log(`[BC Socket.IO Middleware] Socket ${socket.id} has no token. Proceeding as unauthenticated.`);
} }
// Если токена нет или он невалиден, все равно вызываем next() без ошибки,
// чтобы соединение установилось, но socket.userData не будет установлен.
// Логика на стороне сервера должна будет проверять наличие socket.userData.
next(); next();
}); });
@ -128,11 +185,8 @@ io.on('connection', (socket) => {
if (socket.userData && socket.userData.userId) { if (socket.userData && socket.userData.userId) {
console.log(`[BC Socket.IO Connection] Authenticated user ${socket.userData.username} (ID: ${socket.userData.userId}) connected. Socket: ${socket.id}, IP: ${clientIp}, Origin: ${originHeader}, Path: ${socketPath}`); console.log(`[BC Socket.IO Connection] Authenticated user ${socket.userData.username} (ID: ${socket.userData.userId}) connected. Socket: ${socket.id}, IP: ${clientIp}, Origin: ${originHeader}, Path: ${socketPath}`);
loggedInUsersBySocketId[socket.id] = socket.userData; loggedInUsersBySocketId[socket.id] = socket.userData; // Для логирования здесь
// --- НАЧАЛО ИЗМЕНЕНИЯ ---
// Отправляем текущий список доступных PvP игр этому конкретному сокету
// после успешной аутентификации.
if (gameManager && typeof gameManager.getAvailablePvPGamesListForClient === 'function') { if (gameManager && typeof gameManager.getAvailablePvPGamesListForClient === 'function') {
console.log(`[BC Socket.IO Connection] Sending initial available PvP games list to authenticated user ${socket.userData.username} (Socket: ${socket.id})`); console.log(`[BC Socket.IO Connection] Sending initial available PvP games list to authenticated user ${socket.userData.username} (Socket: ${socket.id})`);
const availableGames = gameManager.getAvailablePvPGamesListForClient(); const availableGames = gameManager.getAvailablePvPGamesListForClient();
@ -140,7 +194,6 @@ io.on('connection', (socket) => {
} else { } else {
console.error("[BC Socket.IO Connection] CRITICAL: gameManager or getAvailablePvPGamesListForClient not found for sending initial list!"); console.error("[BC Socket.IO Connection] CRITICAL: gameManager or getAvailablePvPGamesListForClient not found for sending initial list!");
} }
// --- КОНЕЦ ИЗМЕНЕНИЯ ---
if (gameManager && typeof gameManager.handleRequestGameState === 'function') { if (gameManager && typeof gameManager.handleRequestGameState === 'function') {
gameManager.handleRequestGameState(socket, socket.userData.userId); gameManager.handleRequestGameState(socket, socket.userData.userId);
@ -149,31 +202,36 @@ io.on('connection', (socket) => {
} }
} else { } else {
console.log(`[BC Socket.IO Connection] Unauthenticated user connected. Socket: ${socket.id}, IP: ${clientIp}, Origin: ${originHeader}, Path: ${socketPath}.`); console.log(`[BC Socket.IO Connection] Unauthenticated user connected. Socket: ${socket.id}, IP: ${clientIp}, Origin: ${originHeader}, Path: ${socketPath}.`);
// --- НАЧАЛО ИЗМЕНЕНИЯ (опционально, если неаутентифицированные тоже видят список) --- // Неаутентифицированные пользователи не должны иметь доступа к игровым функциям,
// Если неаутентифицированные пользователи тоже должны видеть список игр // но могут получать базовую информацию, если это предусмотрено.
/* // Например, список игр, если он публичный (в данном проекте он для залогиненных).
if (gameManager && typeof gameManager.getAvailablePvPGamesListForClient === 'function') { // socket.emit('authRequired', { message: 'Please login to access game features.' }); // Можно отправить такое сообщение
console.log(`[BC Socket.IO Connection] Sending initial available PvP games list to unauthenticated socket ${socket.id}`);
const availableGames = gameManager.getAvailablePvPGamesListForClient();
socket.emit('availablePvPGamesList', availableGames);
} else {
console.error("[BC Socket.IO Connection] CRITICAL: gameManager or getAvailablePvPGamesListForClient not found for sending initial list to unauth user!");
}
*/
// --- КОНЕЦ ИЗМЕНЕНИЯ ---
} }
socket.on('logout', () => { socket.on('logout', () => { // Это событие инициируется клиентом, когда он нажимает "Выйти"
const username = socket.userData?.username || 'UnknownUserOnLogout'; const username = socket.userData?.username || 'UnknownUserOnLogout';
const userId = socket.userData?.userId; const userId = socket.userData?.userId;
console.log(`[BC Socket.IO 'logout' event] User: ${username} (ID: ${userId || 'N/A'}, Socket: ${socket.id}).`); console.log(`[BC Socket.IO 'logout' event] User: ${username} (ID: ${userId || 'N/A'}, Socket: ${socket.id}). Performing server-side cleanup for logout.`);
// Здесь важно не просто удалить данные из loggedInUsersBySocketId (это локальный объект для логов),
// а также убедиться, что GameManager корректно обрабатывает выход игрока из игры, если он там был.
// GameManager.handleDisconnect должен вызываться автоматически при socket.disconnect() со стороны клиента.
// Дополнительно, если logout это не просто disconnect, а явное действие:
if (userId && gameManager) {
// Если игрок был в игре, GameManager.handleDisconnect должен был отработать при последующем socket.disconnect().
// Если нужно специфическое действие для logout перед disconnect:
// gameManager.handleExplicitLogout(userId, socket.id); // (потребовало бы добавить такой метод в GameManager)
}
if (loggedInUsersBySocketId[socket.id]) { if (loggedInUsersBySocketId[socket.id]) {
delete loggedInUsersBySocketId[socket.id]; delete loggedInUsersBySocketId[socket.id];
} }
socket.userData = null; socket.userData = null; // Очищаем данные пользователя на сокете
console.log(`[BC Socket.IO 'logout' event] Session data for socket ${socket.id} cleared on server.`); // Клиент сам вызовет socket.disconnect() и socket.connect() с новым (null) токеном.
console.log(`[BC Socket.IO 'logout' event] Session data for socket ${socket.id} cleared on server. Client is expected to disconnect and reconnect.`);
}); });
socket.on('playerSurrender', () => { socket.on('playerSurrender', () => {
if (!socket.userData?.userId) { if (!socket.userData?.userId) {
console.warn(`[BC Socket.IO 'playerSurrender'] Denied for unauthenticated socket ${socket.id}.`); console.warn(`[BC Socket.IO 'playerSurrender'] Denied for unauthenticated socket ${socket.id}.`);
@ -202,7 +260,7 @@ io.on('connection', (socket) => {
console.log(`[BC Socket.IO 'leaveAiGame'] Request from user ${username} (ID: ${identifier}, Socket: ${socket.id})`); console.log(`[BC Socket.IO 'leaveAiGame'] Request from user ${username} (ID: ${identifier}, Socket: ${socket.id})`);
if (gameManager && typeof gameManager.handleLeaveAiGame === 'function') { if (gameManager && typeof gameManager.handleLeaveAiGame === 'function') {
gameManager.handleLeaveAiGame(identifier); gameManager.handleLeaveAiGame(identifier, socket); // Передаем сокет, если он нужен для ответа
} else { } else {
console.error("[BC Socket.IO 'leaveAiGame'] CRITICAL: gameManager or handleLeaveAiGame method not found!"); console.error("[BC Socket.IO 'leaveAiGame'] CRITICAL: gameManager or handleLeaveAiGame method not found!");
socket.emit('gameError', { message: 'Ошибка сервера при выходе из AI игры.' }); socket.emit('gameError', { message: 'Ошибка сервера при выходе из AI игры.' });
@ -229,9 +287,10 @@ io.on('connection', (socket) => {
return; return;
} }
const gameId = data?.gameId; const gameId = data?.gameId;
const userId = socket.userData.userId; const userId = socket.userData.userId; // Используем userId из socket.userData
console.log(`[BC Socket.IO 'joinGame'] Request from ${socket.userData.username} (ID: ${userId}). GameID: ${gameId}`); const charKey = data?.characterKey; // Клиент может предлагать персонажа при присоединении
gameManager.joinGame(socket, gameId, userId); console.log(`[BC Socket.IO 'joinGame'] Request from ${socket.userData.username} (ID: ${userId}). GameID: ${gameId}, Char: ${charKey}`);
gameManager.joinGame(socket, gameId, userId, charKey);
}); });
socket.on('findRandomGame', (data) => { socket.on('findRandomGame', (data) => {
@ -247,19 +306,23 @@ io.on('connection', (socket) => {
}); });
socket.on('requestPvPGameList', () => { socket.on('requestPvPGameList', () => {
// Этот запрос может приходить и от неаутентифицированных, если дизайн это позволяет.
// В текущей логике GameManager, список игр формируется для залогиненных.
// Если не залогинен, можно отправить пустой список или специальное сообщение.
console.log(`[BC Socket.IO 'requestPvPGameList'] Request from socket ${socket.id} (User: ${socket.userData?.username || 'Unauth'}).`); console.log(`[BC Socket.IO 'requestPvPGameList'] Request from socket ${socket.id} (User: ${socket.userData?.username || 'Unauth'}).`);
if (gameManager && typeof gameManager.getAvailablePvPGamesListForClient === 'function') { if (gameManager && typeof gameManager.getAvailablePvPGamesListForClient === 'function') {
const availableGames = gameManager.getAvailablePvPGamesListForClient(); const availableGames = gameManager.getAvailablePvPGamesListForClient(); // GameManager сам решит, что вернуть
socket.emit('availablePvPGamesList', availableGames); socket.emit('availablePvPGamesList', availableGames);
} else { } else {
console.error("[BC Socket.IO 'requestPvPGameList'] CRITICAL: gameManager or getAvailablePvPGamesListForClient not found!"); console.error("[BC Socket.IO 'requestPvPGameList'] CRITICAL: gameManager or getAvailablePvPGamesListForClient not found!");
socket.emit('availablePvPGamesList', []); // Отправляем пустой список в случае ошибки socket.emit('availablePvPGamesList', []);
} }
}); });
socket.on('requestGameState', () => { socket.on('requestGameState', () => {
if (!socket.userData?.userId) { if (!socket.userData?.userId) {
console.warn(`[BC Socket.IO 'requestGameState'] Denied for unauthenticated socket ${socket.id}.`); console.warn(`[BC Socket.IO 'requestGameState'] Denied for unauthenticated socket ${socket.id}.`);
// Важно! Клиент main.js ожидает gameNotFound, чтобы показать экран логина
socket.emit('gameNotFound', { message: 'Необходимо войти для восстановления игры.' }); socket.emit('gameNotFound', { message: 'Необходимо войти для восстановления игры.' });
return; return;
} }
@ -280,16 +343,17 @@ io.on('connection', (socket) => {
}); });
socket.on('disconnect', (reason) => { socket.on('disconnect', (reason) => {
const identifier = socket.userData?.userId; const identifier = socket.userData?.userId; // Получаем из socket.userData, если был аутентифицирован
const username = socket.userData?.username || loggedInUsersBySocketId[socket.id]?.username || 'UnauthenticatedOrUnknown'; const username = socket.userData?.username || loggedInUsersBySocketId[socket.id]?.username || 'UnauthenticatedOrUnknown';
console.log(`[BC Socket.IO Disconnect] User ${username} (ID: ${identifier || 'N/A'}, Socket: ${socket.id}) disconnected. Reason: ${reason}.`); console.log(`[BC Socket.IO Disconnect] User ${username} (ID: ${identifier || 'N/A'}, Socket: ${socket.id}) disconnected. Reason: ${reason}.`);
if (identifier) { if (identifier && gameManager) { // Если пользователь был аутентифицирован
gameManager.handleDisconnect(socket.id, identifier); gameManager.handleDisconnect(socket.id, identifier);
} }
if (loggedInUsersBySocketId[socket.id]) { if (loggedInUsersBySocketId[socket.id]) { // Очистка из локального объекта для логов
delete loggedInUsersBySocketId[socket.id]; delete loggedInUsersBySocketId[socket.id];
} }
// socket.userData автоматически очищается при дисконнекте самого объекта сокета
}); });
}); });
@ -312,6 +376,7 @@ server.listen(PORT, HOSTNAME, () => {
console.log(`[BC Server Startup] Server is listening on a specific interface: ${HOSTNAME}.`); console.log(`[BC Server Startup] Server is listening on a specific interface: ${HOSTNAME}.`);
} }
console.log(`[BC Server Startup] Static files served from: ${publicPath}`); console.log(`[BC Server Startup] Static files served from: ${publicPath}`);
console.log(`[BC.JS Startup] EJS views directory: ${app.get('views')}`);
console.log(`[BC.JS Startup] Socket.IO server effective path: ${io.path()}`); console.log(`[BC.JS Startup] Socket.IO server effective path: ${io.path()}`);
console.log(`[BC.JS Startup] HTTP API effective CORS origin: ${clientOrigin === '*' ? "'*'" : clientOrigin || 'NOT SET'}`); console.log(`[BC.JS Startup] HTTP API effective CORS origin: ${clientOrigin === '*' ? "'*'" : clientOrigin || 'NOT SET'}`);
console.log(`[BC.JS Startup] Socket.IO effective CORS origin: ${io.opts.cors.origin === '*' ? "'*'" : io.opts.cors.origin || 'NOT SET'}`); console.log(`[BC.JS Startup] Socket.IO effective CORS origin: ${io.opts.cors.origin === '*' ? "'*'" : io.opts.cors.origin || 'NOT SET'}`);
@ -319,9 +384,10 @@ server.listen(PORT, HOSTNAME, () => {
process.on('unhandledRejection', (reason, promise) => { process.on('unhandledRejection', (reason, promise) => {
console.error('[BC Server FATAL UnhandledRejection] Reason:', reason, 'Promise:', promise); console.error('[BC Server FATAL UnhandledRejection] Reason:', reason, 'Promise:', promise);
// process.exit(1); // Можно раскомментировать для падения сервера при неперехваченных промисах
}); });
process.on('uncaughtException', (err) => { process.on('uncaughtException', (err) => {
console.error('[BC Server FATAL UncaughtException] Error:', err); console.error('[BC Server FATAL UncaughtException] Error:', err);
process.exit(1); process.exit(1); // Критические ошибки должны приводить к перезапуску через process manager
}); });

View File

@ -220,9 +220,11 @@
</div> </div>
</div> </div>
</div> <!-- Конец .game-wrapper --> </div> <!-- Конец .game-wrapper -->
<script>
const base_path = "<%=base_path%>"
</script>
<!-- Библиотека Socket.IO клиента --> <!-- Библиотека Socket.IO клиента -->
<script src="/socket.io/socket.io.js"></script> <script src="<%=base_path%>/socket.io/socket.io.js"></script>
<!-- Ваш скрипт для UI, который может создавать глобальные объекты или функции --> <!-- Ваш скрипт для UI, который может создавать глобальные объекты или функции -->
<!-- Он должен быть загружен до main.js, если main.js ожидает window.gameUI --> <!-- Он должен быть загружен до main.js, если main.js ожидает window.gameUI -->