auto log out + clean leave

This commit is contained in:
Georges-Leonard Prunet
2026-04-02 12:52:06 +02:00
parent 4fa835b62a
commit 4b3909c1a3
2 changed files with 92 additions and 36 deletions
+44 -36
View File
@@ -140,6 +140,47 @@ async function saveRoundPoints(currentScores, roundStartScores) {
}
}
function handlePlayerDeparture(io, roomId, username) {
const gameState = gameRooms.get(roomId);
if (!gameState || !gameState.isPlaying) return;
if (!Array.isArray(gameState.players)) return;
const leavingIndex = gameState.players.indexOf(username);
if (leavingIndex === -1) return;
const wasDrawer = gameState.drawer === username;
gameState.players = gameState.players.filter(p => p !== username);
if (gameState.scores) {
delete gameState.scores[username];
}
if (gameState.currentPlayerIndex >= leavingIndex) {
gameState.currentPlayerIndex = Math.max(0, gameState.currentPlayerIndex - 1);
}
if (gameState.currentPlayerIndex >= gameState.players.length) {
gameState.currentPlayerIndex = 0;
}
if (wasDrawer && gameState.players.length > 0) {
stopRoomTimer(roomId);
const newDrawer = gameState.players[gameState.currentPlayerIndex];
gameState.drawer = newDrawer;
gameState.currentWord = '';
gameState.revealedLetters = [];
gameState.revealedWord = [];
gameState.guessedLetters = [];
gameState.wrongGuesses = 0;
io.to(roomId).emit('game-drawer-changed', {
newDrawer: newDrawer,
reason: 'drawer_left',
message: `${username} (dessinateur) a quitte, ${newDrawer} devient le nouveau dessinateur`
});
startRoomTimer(io, roomId, 60);
}
}
function setupSocketIO(io)
{
ioInstance = io;
@@ -276,6 +317,7 @@ function setupSocketIO(io)
username: socket.user.username,
userId: socket.user.userId
});
handlePlayerDeparture(io, roomId, socket.user.username);
socket.leave(roomId);
console.log(`${socket.user.username} left ${roomId}`);
@@ -673,42 +715,7 @@ function setupSocketIO(io)
message: `${username} a quitté la partie`
});
const gameState = gameRooms.get(roomId);
if (gameState)
{
const wasDrawer = gameState.drawer === username;
gameState.players = gameState.players.filter(p => p !== username);
delete gameState.scores[username];
io.to(roomId).emit('scores-updated', gameState.scores);
// If the drawer left and there are still enough players, choose a new drawer
if (wasDrawer && gameState.players.length >= 1)
{
stopRoomTimer(roomId);
// Pick the next player as the new drawer
gameState.currentPlayerIndex = gameState.currentPlayerIndex % gameState.players.length;
const newDrawer = gameState.players[gameState.currentPlayerIndex];
gameState.drawer = newDrawer;
// Reset the word state for the new round
gameState.currentWord = '';
gameState.revealedLetters = [];
gameState.revealedWord = [];
gameState.guessedLetters = [];
gameState.wrongGuesses = 0;
console.log(`Drawer ${username} left, new drawer is ${newDrawer}`);
io.to(roomId).emit('game-drawer-changed', {
newDrawer: newDrawer,
reason: 'drawer_left',
message: `${username} (dessinateur) a quitté, ${newDrawer} devient le nouveau dessinateur`
});
startRoomTimer(io, roomId, 60);
}
}
handlePlayerDeparture(io, roomId, username);
await checkAndStopSinglePlayerGame(io, roomId, dbRoomId);
@@ -956,6 +963,7 @@ function setupSocketIO(io)
username: socket.user.username,
userId: socket.user.userId
});
handlePlayerDeparture(io, roomId, socket.user.username);
// Get updated player list and broadcast
if (dbRoomId) {
+48
View File
@@ -3,6 +3,8 @@
* Initializes windows and handles menu interactions
*/
import { windowRegistry } from './core/windows.js';
import { API, STORAGE_KEYS } from './core/config.js';
import { eventBus, Events } from './core/events.js';
import { LoginWindow } from './windows/login.js';
import { LogoutWindow } from './windows/logout.js';
import { GlobalChat } from './windows/global_chat.js';
@@ -17,6 +19,7 @@ import { StatsWindow } from './windows/stats.js';
*/
class App {
constructor() {
this.invalidateStaleToken();
this.initWindows();
this.initMenu();
this.initPage();
@@ -24,6 +27,51 @@ class App {
this.colorizeUI();
}
async invalidateStaleToken() {
const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
if (!token) return;
if (this.isJwtExpired(token)) {
localStorage.removeItem(STORAGE_KEYS.AUTH_TOKEN);
eventBus.emit(Events.USER_LOGGED_OUT);
return;
}
try {
const response = await fetch(API.STATS.ME, {
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.status === 401) {
localStorage.removeItem(STORAGE_KEYS.AUTH_TOKEN);
eventBus.emit(Events.USER_LOGGED_OUT);
setTimeout(() => window.location.reload(), 500);
}
} catch (error) {
console.warn('Token validation skipped:', error);
}
}
isJwtExpired(token) {
try {
const payload = this.decodeJwtPayload(token);
if (!payload || !payload.exp) return false;
const now = Math.floor(Date.now() / 1000);
return payload.exp <= now;
} catch (error) {
return false;
}
}
decodeJwtPayload(token) {
const parts = token.split('.');
if (parts.length < 2) return null;
const base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '=');
return JSON.parse(atob(padded));
}
/**
* Initializes all windows
*/