sto-k-odnomu/src/services/socket.js
2026-01-11 10:11:59 +03:00

238 lines
6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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();