238 lines
6 KiB
JavaScript
238 lines
6 KiB
JavaScript
import { io } from 'socket.io-client';
|
||
|
||
const WS_URL = import.meta.env.VITE_WS_URL || 'http://localhost:3000';
|
||
|
||
class SocketService {
|
||
constructor() {
|
||
this.socket = null;
|
||
this.listeners = new Map();
|
||
}
|
||
|
||
connect() {
|
||
if (this.socket?.connected) {
|
||
return this.socket;
|
||
}
|
||
|
||
this.socket = io(WS_URL, {
|
||
withCredentials: true,
|
||
transports: ['websocket', 'polling'],
|
||
});
|
||
|
||
this.socket.on('connect', () => {
|
||
console.log('WebSocket connected:', this.socket.id);
|
||
});
|
||
|
||
this.socket.on('disconnect', () => {
|
||
console.log('WebSocket disconnected');
|
||
});
|
||
|
||
this.socket.on('error', (error) => {
|
||
console.error('WebSocket error:', error);
|
||
});
|
||
|
||
return this.socket;
|
||
}
|
||
|
||
disconnect() {
|
||
if (this.socket) {
|
||
this.socket.disconnect();
|
||
this.socket = null;
|
||
}
|
||
}
|
||
|
||
on(event, callback) {
|
||
if (!this.socket) {
|
||
this.connect();
|
||
}
|
||
this.socket.on(event, callback);
|
||
|
||
if (!this.listeners.has(event)) {
|
||
this.listeners.set(event, []);
|
||
}
|
||
this.listeners.get(event).push(callback);
|
||
}
|
||
|
||
off(event, callback) {
|
||
if (this.socket) {
|
||
this.socket.off(event, callback);
|
||
}
|
||
|
||
if (this.listeners.has(event)) {
|
||
const callbacks = this.listeners.get(event);
|
||
const index = callbacks.indexOf(callback);
|
||
if (index > -1) {
|
||
callbacks.splice(index, 1);
|
||
}
|
||
}
|
||
}
|
||
|
||
emit(event, data) {
|
||
if (!this.socket) {
|
||
this.connect();
|
||
}
|
||
this.socket.emit(event, data);
|
||
}
|
||
|
||
onReconnect(callback) {
|
||
if (!this.socket) {
|
||
this.connect();
|
||
}
|
||
// Слушаем событие 'connect' которое происходит при переподключении
|
||
this.socket.on('connect', callback);
|
||
}
|
||
|
||
offReconnect(callback) {
|
||
if (this.socket) {
|
||
this.socket.off('connect', callback);
|
||
}
|
||
}
|
||
|
||
// Game-specific methods
|
||
joinRoom(roomCode, userId) {
|
||
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 });
|
||
}
|
||
|
||
endGame(roomId, roomCode, userId) {
|
||
this.emit('endGame', { roomId, roomCode, userId });
|
||
}
|
||
|
||
// Note: Game actions now use 'playerAction' event with UUID
|
||
// Direct emit is preferred over these helper methods
|
||
// Example: socketService.emit('playerAction', { action: 'revealAnswer', ... })
|
||
|
||
updateRoomPack(roomId, roomCode, userId, questions) {
|
||
this.emit('updateRoomPack', { roomId, roomCode, userId, questions });
|
||
}
|
||
|
||
importQuestions(roomId, roomCode, userId, sourcePackId, questionIndices) {
|
||
this.emit('importQuestions', {
|
||
roomId,
|
||
roomCode,
|
||
userId,
|
||
sourcePackId,
|
||
questionIndices,
|
||
});
|
||
}
|
||
|
||
updatePlayerName(roomId, roomCode, userId, participantId, newName) {
|
||
this.emit('updatePlayerName', {
|
||
roomId,
|
||
roomCode,
|
||
userId,
|
||
participantId,
|
||
newName,
|
||
});
|
||
}
|
||
|
||
updatePlayerScore(roomId, roomCode, userId, participantId, newScore) {
|
||
this.emit('updatePlayerScore', {
|
||
roomId,
|
||
roomCode,
|
||
userId,
|
||
participantId,
|
||
newScore,
|
||
});
|
||
}
|
||
|
||
changeRoomTheme(roomId, roomCode, userId, themeId) {
|
||
this.emit('changeRoomTheme', {
|
||
roomId,
|
||
roomCode,
|
||
userId,
|
||
themeId,
|
||
});
|
||
}
|
||
|
||
changeParticipantRole(roomId, roomCode, userId, participantId, newRole) {
|
||
this.emit('changeParticipantRole', {
|
||
roomId,
|
||
roomCode,
|
||
userId,
|
||
participantId,
|
||
newRole,
|
||
});
|
||
}
|
||
|
||
toggleParticles(roomId, roomCode, userId, particlesEnabled) {
|
||
this.emit('toggleParticles', {
|
||
roomId,
|
||
roomCode,
|
||
userId,
|
||
particlesEnabled,
|
||
});
|
||
}
|
||
|
||
addPlayer(roomId, roomCode, userId, playerName, role = 'PLAYER') {
|
||
this.emit('addPlayer', {
|
||
roomId,
|
||
roomCode,
|
||
userId,
|
||
playerName,
|
||
role,
|
||
});
|
||
}
|
||
}
|
||
|
||
export default new SocketService();
|