diff --git a/src/router.js b/src/router.js index 9487bd2b3..f434d893f 100644 --- a/src/router.js +++ b/src/router.js @@ -71,6 +71,7 @@ const checkAndSubscribeToLobby = async (to) => { const getGameState = async (to) => { const gameStore = useGameStore(); + const authStore = useAuthStore(); const gameId = parseInt(to.params.gameId); gameStore.id = gameId; @@ -79,6 +80,12 @@ const getGameState = async (to) => { gameStateIndex = isValidGameStateIndex ? gameStateIndex : -1; try { const response = await gameStore.requestGameState(gameId, gameStateIndex, to); + // A 401 resolves without a game state; in that case mustReauthenticate + // is set and GameView shows the ReauthenticateDialog + const gamePlayers = response?.game?.players; + if (gamePlayers && !gamePlayers.some(({ username }) => username === authStore.username)) { + return { name: ROUTE_NAME_SPECTATE, params: { gameId } }; + } if (response?.victory?.gameOver && response.game.rematchGame) { await gameStore.requestGameState(response.game.rematchGame); return { name: to.name, params: { gameId: response.game.rematchGame } }; diff --git a/src/stores/game.js b/src/stores/game.js index 5f3694081..1a6ef097c 100644 --- a/src/stores/game.js +++ b/src/stores/game.js @@ -510,7 +510,8 @@ export const useGameStore = defineStore('game', () => { }); case 401: authStore.mustReauthenticate = true; - return resolve(jwres.body.message); + // Resolve without a game state; ReauthenticateDialog handles recovery + return resolve(null); default: return reject(jwres.body.message); } diff --git a/tests/e2e/specs/in-game/spectating/live-spectating.spec.js b/tests/e2e/specs/in-game/spectating/live-spectating.spec.js index e2dbf1f7a..5c1fe5374 100644 --- a/tests/e2e/specs/in-game/spectating/live-spectating.spec.js +++ b/tests/e2e/specs/in-game/spectating/live-spectating.spec.js @@ -51,6 +51,15 @@ describe('Spectating Games', () => { }); }); + it('Redirects a non-player to spectate when they navigate to the game route', () => { + cy.setupGameAsSpectator(); + cy.get('@gameId').then((gameId) => { + cy.visit(`/game/${gameId}`); + cy.url().should('include', `/spectate/${gameId}`); + cy.get('#deck').should('be.visible'); + }); + }); + it('Spectates a game', () => { cy.setupGameAsSpectator(); cy.loadGameFixture(0, {