Cockatrice/webclient/src/hooks/useGameGuard.ts
2025-08-08 15:33:52 -04:00

130 lines
No EOL
4.3 KiB
TypeScript

import { useSelector } from 'react-redux';
import { useRefreshGuard } from './useRefreshGuard';
import { useNavigationGuard } from './useNavigationGuard';
import { RoomsSelectors, ServerSelectors } from 'store';
import { StatusEnum } from 'types';
import { webClient } from 'websocket';
interface UseGameGuardReturn {
// Navigation guard (SPA only)
showNavigationModal: boolean;
setShowNavigationModal: (show: boolean) => void;
pendingLocation: string | null;
handleConfirmNavigation: () => void;
handleCancelNavigation: () => void;
navigationMessage: string;
// Refresh guard (browser refresh)
showRefreshModal: boolean;
setShowRefreshModal: (show: boolean) => void;
handleConfirmRefresh: () => void;
handleCancelRefresh: () => void;
refreshMessage: string;
// Guard state
isInActiveGame: boolean;
isConnected: boolean;
shouldGuard: boolean;
}
/**
* Combined hook that guards against page refresh (browser dialog) and SPA navigation (custom modal)
* when user is in an active game or has an active connection.
*
* - Browser refresh/navigation: Shows native browser dialog only
* - SPA navigation: Shows custom modal with detailed warning
*/
export const useGameGuard = (): UseGameGuardReturn => {
// Get connection and game state from Redux
const connectionStatus = useSelector(ServerSelectors.getState);
const joinedGameIds = useSelector(RoomsSelectors.getJoinedGameIds);
const joinedRoomIds = useSelector(RoomsSelectors.getJoinedRoomIds);
// Determine if user is in an active state that should be guarded
const isConnected = connectionStatus === StatusEnum.LOGGED_IN;
const isInActiveGame = Object.values(joinedGameIds).some(roomGames =>
Object.keys(roomGames).length > 0
);
const isInRoom = Object.keys(joinedRoomIds).length > 0;
// Guard conditions
const shouldGuard = isConnected && (isInActiveGame || isInRoom);
// Messages based on current state
const getMessage = (): string => {
if (isInActiveGame) {
return 'You are currently in an active game. Leaving will disconnect you from the game and may cause issues for other players.';
}
if (isInRoom) {
return 'You are currently connected to game rooms. Leaving will disconnect you from the server.';
}
return 'You are connected to the server. Leaving will end your session.';
};
const message = getMessage();
// Handle graceful disconnect before leaving
const handleGracefulDisconnect = async () => {
try {
// If in active games, try to leave them gracefully
if (isInActiveGame) {
console.log('Attempting graceful disconnect from active games...');
// Note: Specific game leave commands would be implemented here
// For now, we'll just disconnect the WebSocket
}
// Disconnect from server
webClient.disconnect();
} catch (error) {
console.error('Error during graceful disconnect:', error);
// Force disconnect even if graceful fails
webClient.disconnect();
}
};
// Refresh guard (browser page refresh/navigation only)
const refreshGuard = useRefreshGuard({
shouldGuard,
message,
onUnload: handleGracefulDisconnect
});
// Navigation guard (SPA navigation only)
const navigationGuard = useNavigationGuard({
shouldGuard,
message,
onBeforeNavigate: (targetPath) => {
console.log('Navigation blocked, target:', targetPath);
}
});
return {
// Navigation guard (SPA only)
showNavigationModal: navigationGuard.showModal,
setShowNavigationModal: navigationGuard.setShowModal,
pendingLocation: navigationGuard.pendingLocation,
handleConfirmNavigation: async () => {
await handleGracefulDisconnect();
navigationGuard.handleConfirmNavigation();
},
handleCancelNavigation: navigationGuard.handleCancelNavigation,
navigationMessage: navigationGuard.guardMessage,
// Refresh guard (browser refresh)
showRefreshModal: refreshGuard.showModal,
setShowRefreshModal: refreshGuard.setShowModal,
handleConfirmRefresh: async () => {
await handleGracefulDisconnect();
window.location.reload();
},
handleCancelRefresh: () => {
refreshGuard.setShowModal(false);
},
refreshMessage: refreshGuard.guardMessage,
// State
isInActiveGame,
isConnected,
shouldGuard
};
};