fix
This commit is contained in:
parent
d349c5283e
commit
7ec72bc13e
5 changed files with 189 additions and 27 deletions
|
|
@ -110,6 +110,14 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect, On
|
|||
console.log(`👥 Clients in room ${payload.roomCode}: ${roomClients?.size || 0}`,
|
||||
Array.from(roomClients || []));
|
||||
|
||||
// ВАЖНО: Отправляем подтверждение подключения клиенту
|
||||
client.emit('roomJoined', {
|
||||
roomCode: payload.roomCode,
|
||||
success: true,
|
||||
clientsInRoom: roomClients?.size || 0
|
||||
});
|
||||
console.log(`✅ Sent roomJoined acknowledgement to client ${client.id}`);
|
||||
|
||||
// Получаем полное состояние для отправки присоединившемуся клиенту
|
||||
const room = (await this.prisma.room.findUnique({
|
||||
where: { code: payload.roomCode },
|
||||
|
|
@ -1035,4 +1043,42 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect, On
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeMessage('joinAsParticipant')
|
||||
async handleJoinAsParticipant(
|
||||
client: Socket,
|
||||
payload: {
|
||||
roomId: string;
|
||||
roomCode: string;
|
||||
userId: string;
|
||||
name: string;
|
||||
role: 'PLAYER' | 'SPECTATOR';
|
||||
}
|
||||
) {
|
||||
try {
|
||||
console.log(`🎭 Client ${client.id} joining as participant: ${payload.name} (${payload.role})`);
|
||||
|
||||
// Используем существующий сервис для создания участника
|
||||
const participant = await this.roomsService.joinRoom(
|
||||
payload.roomId,
|
||||
payload.userId,
|
||||
payload.name,
|
||||
payload.role
|
||||
);
|
||||
|
||||
console.log(`✅ Participant created: ${participant.id}, broadcasting to room ${payload.roomCode}`);
|
||||
|
||||
// broadcastFullState уже вызван внутри roomsService.joinRoom
|
||||
// Отправляем подтверждение клиенту
|
||||
client.emit('participantJoined', {
|
||||
success: true,
|
||||
participantId: participant.id
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('❌ Error joining as participant:', error);
|
||||
client.emit('error', {
|
||||
message: error.message || 'Failed to join as participant'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -161,28 +161,31 @@ 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));
|
||||
}
|
||||
console.log('🚀 Starting join process...');
|
||||
|
||||
// Теперь вызываем REST API для создания участника
|
||||
const response = await roomsApi.join(roomId, userId, name, role);
|
||||
// Шаг 1: Подключаемся к WebSocket комнате и ЖДЕМ подтверждения
|
||||
socketService.connect();
|
||||
console.log('⏳ Waiting for WebSocket room join confirmation...');
|
||||
const joinAck = await socketService.joinRoomWithAck(roomCode, userId);
|
||||
console.log('✅ WebSocket connected to room:', joinAck);
|
||||
|
||||
// После успешного присоединения запрашиваем полное состояние через WebSocket
|
||||
// (дополнительная гарантия получения актуального состояния)
|
||||
if (roomCode) {
|
||||
setTimeout(() => {
|
||||
socketService.emit('requestFullState', { roomCode });
|
||||
}, 50);
|
||||
}
|
||||
// Шаг 2: Теперь ГАРАНТИРОВАННО подключены, создаем участника через WebSocket
|
||||
console.log('⏳ Creating participant in database...');
|
||||
const participantAck = await socketService.joinAsParticipant(
|
||||
roomId,
|
||||
roomCode,
|
||||
userId,
|
||||
name,
|
||||
role
|
||||
);
|
||||
console.log('✅ Participant created:', participantAck);
|
||||
|
||||
return response.data;
|
||||
// broadcastFullState уже вызван backend, события gameStateUpdated придут автоматически
|
||||
// Все клиенты (включая хоста) получат обновление
|
||||
|
||||
return { success: true, participantId: participantAck.participantId };
|
||||
} catch (err) {
|
||||
console.error('❌ Join error:', err);
|
||||
setError(err.message);
|
||||
throw err;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { useRoom } from '../hooks/useRoom';
|
||||
import NameInputModal from '../components/NameInputModal';
|
||||
|
||||
const CreateRoom = () => {
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
const { user, loading: authLoading, loginAnonymous } = useAuth();
|
||||
const { createRoom, loading: roomLoading } = useRoom();
|
||||
|
||||
const [settings, setSettings] = useState({
|
||||
|
|
@ -13,11 +14,32 @@ const CreateRoom = () => {
|
|||
allowSpectators: true,
|
||||
password: '',
|
||||
});
|
||||
const [isNameModalOpen, setIsNameModalOpen] = useState(false);
|
||||
|
||||
// Проверка авторизации и показ модального окна для ввода имени
|
||||
// Только если загрузка завершена и пользователя нет
|
||||
useEffect(() => {
|
||||
if (!authLoading && !user) {
|
||||
setIsNameModalOpen(true);
|
||||
} else if (user) {
|
||||
setIsNameModalOpen(false);
|
||||
}
|
||||
}, [authLoading, user]);
|
||||
|
||||
// Обработка ввода имени и авторизация
|
||||
const handleNameSubmit = async (name) => {
|
||||
try {
|
||||
await loginAnonymous(name);
|
||||
setIsNameModalOpen(false);
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
alert('Ошибка при авторизации. Попробуйте еще раз.');
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreateRoom = async () => {
|
||||
if (!user) {
|
||||
alert('Пожалуйста, задайте имя на главном экране');
|
||||
navigate('/');
|
||||
setIsNameModalOpen(true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -92,7 +114,7 @@ const CreateRoom = () => {
|
|||
<div className="button-group">
|
||||
<button
|
||||
onClick={handleCreateRoom}
|
||||
disabled={roomLoading || !user}
|
||||
disabled={roomLoading}
|
||||
className="primary"
|
||||
>
|
||||
{roomLoading ? 'Создание...' : 'Создать комнату'}
|
||||
|
|
@ -100,6 +122,12 @@ const CreateRoom = () => {
|
|||
<button onClick={() => navigate('/')}>Назад</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NameInputModal
|
||||
isOpen={isNameModalOpen}
|
||||
onSubmit={handleNameSubmit}
|
||||
onCancel={() => navigate('/')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { questionsApi } from '../services/api';
|
|||
import QRCode from 'qrcode';
|
||||
import socketService from '../services/socket';
|
||||
import QRModal from '../components/QRModal';
|
||||
import NameInputModal from '../components/NameInputModal';
|
||||
import PasswordModal from '../components/PasswordModal';
|
||||
import RoleSelectionModal from '../components/RoleSelectionModal';
|
||||
import GameManagementModal from '../components/GameManagementModal';
|
||||
|
|
@ -14,7 +15,7 @@ import GameManagementModal from '../components/GameManagementModal';
|
|||
const RoomPage = () => {
|
||||
const { roomCode } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const { user, loading: authLoading } = useAuth();
|
||||
const { user, loading: authLoading, loginAnonymous } = useAuth();
|
||||
const { changeTheme } = useTheme();
|
||||
|
||||
// Храним предыдущий themeId комнаты для отслеживания изменений
|
||||
|
|
@ -40,6 +41,7 @@ const RoomPage = () => {
|
|||
const [qrCode, setQrCode] = useState('');
|
||||
const [joined, setJoined] = useState(false);
|
||||
const [isQRModalOpen, setIsQRModalOpen] = useState(false);
|
||||
const [isNameModalOpen, setIsNameModalOpen] = useState(false);
|
||||
const [isPasswordModalOpen, setIsPasswordModalOpen] = useState(false);
|
||||
const [isRoleSelectionModalOpen, setIsRoleSelectionModalOpen] = useState(false);
|
||||
const [isQuestionsModalOpen, setIsQuestionsModalOpen] = useState(false);
|
||||
|
|
@ -87,6 +89,16 @@ const RoomPage = () => {
|
|||
}
|
||||
}, [requiresPassword, isPasswordModalOpen, loading]);
|
||||
|
||||
// Проверка имени: показываем модальное окно для ввода имени
|
||||
// Только если загрузка завершена, пользователя нет, комната загружена и пароль не требуется
|
||||
useEffect(() => {
|
||||
if (!authLoading && !user && room && !loading && !requiresPassword) {
|
||||
setIsNameModalOpen(true);
|
||||
} else if (user) {
|
||||
setIsNameModalOpen(false);
|
||||
}
|
||||
}, [authLoading, user, room, loading, requiresPassword]);
|
||||
|
||||
// Обработка ввода пароля
|
||||
const handlePasswordSubmit = async (enteredPassword) => {
|
||||
try {
|
||||
|
|
@ -104,6 +116,17 @@ const RoomPage = () => {
|
|||
}
|
||||
};
|
||||
|
||||
// Обработка ввода имени и авторизация
|
||||
const handleNameSubmit = async (name) => {
|
||||
try {
|
||||
await loginAnonymous(name);
|
||||
setIsNameModalOpen(false);
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
alert('Ошибка при авторизации. Попробуйте еще раз.');
|
||||
}
|
||||
};
|
||||
|
||||
// Единая логика присоединения к комнате
|
||||
useEffect(() => {
|
||||
const handleJoin = async () => {
|
||||
|
|
@ -112,10 +135,8 @@ const RoomPage = () => {
|
|||
return;
|
||||
}
|
||||
|
||||
// Перенаправляем на главный экран, если нет пользователя
|
||||
// Если пользователя нет - модальное окно имени покажется через другой useEffect
|
||||
if (!user) {
|
||||
alert('Пожалуйста, задайте имя на главном экране');
|
||||
navigate('/');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -429,6 +450,12 @@ const RoomPage = () => {
|
|||
roomCode={roomCode}
|
||||
/>
|
||||
|
||||
<NameInputModal
|
||||
isOpen={isNameModalOpen}
|
||||
onSubmit={handleNameSubmit}
|
||||
onCancel={() => navigate('/')}
|
||||
/>
|
||||
|
||||
<PasswordModal
|
||||
isOpen={isPasswordModalOpen}
|
||||
onSubmit={handlePasswordSubmit}
|
||||
|
|
|
|||
|
|
@ -92,6 +92,64 @@ class SocketService {
|
|||
this.emit('joinRoom', { roomCode, userId });
|
||||
}
|
||||
|
||||
// Присоединение к WebSocket комнате с ожиданием подтверждения
|
||||
joinRoomWithAck(roomCode, userId) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('WebSocket join timeout - room not joined within 5 seconds'));
|
||||
}, 5000); // 5 секунд максимум
|
||||
|
||||
// Слушаем подтверждение один раз
|
||||
this.socket.once('roomJoined', (data) => {
|
||||
clearTimeout(timeout);
|
||||
console.log('✅ WebSocket room joined:', data);
|
||||
resolve(data);
|
||||
});
|
||||
|
||||
// Отправляем запрос на присоединение
|
||||
console.log(`📤 Requesting to join WebSocket room: ${roomCode}`);
|
||||
this.emit('joinRoom', { roomCode, userId });
|
||||
});
|
||||
}
|
||||
|
||||
// Присоединение как участник (создание записи в БД) через WebSocket
|
||||
joinAsParticipant(roomId, roomCode, userId, name, role) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('Participant join timeout - not created within 5 seconds'));
|
||||
}, 5000);
|
||||
|
||||
// Обработчик успешного присоединения
|
||||
const handleSuccess = (data) => {
|
||||
clearTimeout(timeout);
|
||||
this.socket.off('error', handleError); // Убираем обработчик ошибки
|
||||
console.log('✅ Participant joined successfully:', data);
|
||||
resolve(data);
|
||||
};
|
||||
|
||||
// Обработчик ошибки
|
||||
const handleError = (error) => {
|
||||
clearTimeout(timeout);
|
||||
this.socket.off('participantJoined', handleSuccess); // Убираем обработчик успеха
|
||||
console.error('❌ Participant join error:', error);
|
||||
reject(new Error(error.message || 'Failed to join as participant'));
|
||||
};
|
||||
|
||||
this.socket.once('participantJoined', handleSuccess);
|
||||
this.socket.once('error', handleError);
|
||||
|
||||
// Отправляем запрос
|
||||
console.log(`📤 Requesting to join as participant: ${name} (${role})`);
|
||||
this.emit('joinAsParticipant', {
|
||||
roomId,
|
||||
roomCode,
|
||||
userId,
|
||||
name,
|
||||
role
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
startGame(roomId, roomCode, userId) {
|
||||
this.emit('startGame', { roomId, roomCode, userId });
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue