From 4b3909c1a387793a65c45030165ecd5e204cde73 Mon Sep 17 00:00:00 2001 From: Georges-Leonard Prunet Date: Thu, 2 Apr 2026 12:52:06 +0200 Subject: [PATCH] auto log out + clean leave --- Transcendence/srcs/backend/services/socket.js | 80 ++++++++++--------- Transcendence/srcs/frontend/src/app.js | 48 +++++++++++ 2 files changed, 92 insertions(+), 36 deletions(-) diff --git a/Transcendence/srcs/backend/services/socket.js b/Transcendence/srcs/backend/services/socket.js index 85af39d..81053ab 100644 --- a/Transcendence/srcs/backend/services/socket.js +++ b/Transcendence/srcs/backend/services/socket.js @@ -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) { diff --git a/Transcendence/srcs/frontend/src/app.js b/Transcendence/srcs/frontend/src/app.js index 2620cb8..365fa0f 100644 --- a/Transcendence/srcs/frontend/src/app.js +++ b/Transcendence/srcs/frontend/src/app.js @@ -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 */