This commit is contained in:
Dmitry 2026-01-11 09:05:36 +03:00
parent 06e95fb432
commit 5390d5d675
4 changed files with 41 additions and 22 deletions

View file

@ -105,6 +105,11 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect, On
console.log(`🔌 Client ${client.id} joining WebSocket room ${payload.roomCode}, userId: ${payload.userId}`);
client.join(payload.roomCode);
// Логируем всех клиентов в комнате
const roomClients = this.server.sockets.adapter.rooms.get(payload.roomCode);
console.log(`👥 Clients in room ${payload.roomCode}: ${roomClients?.size || 0}`,
Array.from(roomClients || []));
// Получаем полное состояние для отправки присоединившемуся клиенту
const room = (await this.prisma.room.findUnique({
where: { code: payload.roomCode },
@ -574,7 +579,9 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect, On
})
};
const roomClients = this.server.sockets.adapter.rooms.get(roomCode);
console.log(`📡 Broadcasting gameStateUpdated to room ${roomCode} with ${room.participants.length} participants`);
console.log(`📡 Target clients: ${roomClients?.size || 0}`, Array.from(roomClients || []));
this.server.to(roomCode).emit('gameStateUpdated', fullState);
}

View file

@ -153,10 +153,6 @@ export class RoomsService {
// Отправляем событие roomUpdate всем клиентам в комнате
if (updatedRoom) {
// Небольшая задержка, чтобы дать время новому игроку присоединиться к WebSocket комнате
// WebSocket joinRoom может выполняться параллельно с REST API joinRoom
await new Promise(resolve => setTimeout(resolve, 50));
console.log(`📤 Broadcasting room update for ${updatedRoom.code} with ${updatedRoom.participants.length} participants`);
this.roomEventsService.emitRoomUpdate(updatedRoom.code, updatedRoom);
// Также отправляем gameStateUpdated через broadcastFullState
@ -248,9 +244,6 @@ export class RoomsService {
// Отправляем событие roomUpdate всем клиентам в комнате
if (updatedRoom) {
// Небольшая задержка, чтобы дать время новому игроку присоединиться к WebSocket комнате
await new Promise(resolve => setTimeout(resolve, 50));
this.roomEventsService.emitRoomUpdate(updatedRoom.code, updatedRoom);
// Также отправляем gameStateUpdated через broadcastFullState
await this.gameGateway.broadcastFullState(updatedRoom.code);

View file

@ -11,6 +11,12 @@ export const useRoom = (roomCode, onGameStarted = null, password = null) => {
const [participants, setParticipants] = useState([]);
const [requiresPassword, setRequiresPassword] = useState(false);
// Используем ref для onGameStarted, чтобы не пересоздавать обработчики при его изменении
const onGameStartedRef = useRef(onGameStarted);
useEffect(() => {
onGameStartedRef.current = onGameStarted;
}, [onGameStarted]);
// Обработчики событий вынесены наружу useEffect, чтобы они регистрировались только один раз
// и не зависели от изменений зависимостей
const handleRoomUpdate = useCallback((updatedRoom) => {
@ -22,11 +28,11 @@ export const useRoom = (roomCode, onGameStarted = null, password = null) => {
const handleGameStarted = useCallback((updatedRoom) => {
console.log('🎮 gameStarted received');
setRoom(updatedRoom);
// Вызываем callback для навигации на страницу игры
if (onGameStarted) {
onGameStarted(updatedRoom);
// Вызываем callback через ref
if (onGameStartedRef.current) {
onGameStartedRef.current(updatedRoom);
}
}, [onGameStarted]);
}, []); // Теперь НЕТ зависимости от onGameStarted!
const handleGameStateUpdated = useCallback((state) => {
console.log('🔄 gameStateUpdated received:', state.participants?.length, 'participants');
@ -57,11 +63,11 @@ export const useRoom = (roomCode, onGameStarted = null, password = null) => {
return updatedRoom;
});
// Если игра началась, вызываем callback
if (state.status === 'PLAYING' && onGameStarted) {
onGameStarted(state);
// Если игра началась, вызываем callback через ref
if (state.status === 'PLAYING' && onGameStartedRef.current) {
onGameStartedRef.current(state);
}
}, [onGameStarted]);
}, []); // Теперь НЕТ зависимости от onGameStarted!
const handleRoomPackUpdated = useCallback((updatedRoom) => {
setRoom(updatedRoom);
@ -140,7 +146,7 @@ export const useRoom = (roomCode, onGameStarted = null, password = null) => {
socketService.off('gameStateUpdated', handleGameStateUpdated);
socketService.off('roomPackUpdated', handleRoomPackUpdated);
};
}, [roomCode, password, user?.id, authLoading, handleRoomUpdate, handleGameStarted, handleGameStateUpdated, handleRoomPackUpdated]);
}, [roomCode, password, user?.id, authLoading]); // Убрали обработчики из зависимостей!
const createRoom = useCallback(async (hostId, questionPackId, settings = {}, hostName) => {
try {
@ -155,16 +161,24 @@ export const useRoom = (roomCode, onGameStarted = null, password = null) => {
const joinRoom = useCallback(async (roomId, userId, name, role = 'PLAYER') => {
try {
// ВАЖНО: Сначала подключаемся к WebSocket комнате
// Это гарантирует, что мы получим broadcast от backend после создания участника
if (roomCode) {
socketService.connect();
socketService.joinRoom(roomCode, userId);
// Даем время на установку WebSocket соединения
await new Promise(resolve => setTimeout(resolve, 100));
}
// Теперь вызываем REST API для создания участника
const response = await roomsApi.join(roomId, userId, name, role);
// После успешного присоединения запрашиваем полное состояние через WebSocket
// Это гарантирует получение актуального состояния от сервера (список игроков, тема, voiceMode)
// НЕ обновляем состояние локально - полагаемся только на серверные обновления для консистентности
// (дополнительная гарантия получения актуального состояния)
if (roomCode) {
// Небольшая задержка, чтобы убедиться, что WebSocket подключен
setTimeout(() => {
socketService.emit('requestFullState', { roomCode });
}, 100);
}, 50);
}
return response.data;

View file

@ -14,7 +14,7 @@ import GameManagementModal from '../components/GameManagementModal';
const RoomPage = () => {
const { roomCode } = useParams();
const navigate = useNavigate();
const { user } = useAuth();
const { user, loading: authLoading } = useAuth();
const { changeTheme } = useTheme();
// Храним предыдущий themeId комнаты для отслеживания изменений
@ -107,6 +107,11 @@ const RoomPage = () => {
// Единая логика присоединения к комнате
useEffect(() => {
const handleJoin = async () => {
// Ждем загрузки пользователя из куки
if (authLoading) {
return;
}
// Перенаправляем на главный экран, если нет пользователя
if (!user) {
alert('Пожалуйста, задайте имя на главном экране');
@ -156,7 +161,7 @@ const RoomPage = () => {
};
handleJoin();
}, [room, user, joined, participants, joinRoom, navigate, isRoleSelectionModalOpen]);
}, [room, user, joined, participants, joinRoom, navigate, isRoleSelectionModalOpen, authLoading]);
// Обработка выбора роли
// Присоединение разрешено независимо от статуса игры (WAITING, PLAYING, FINISHED)