diff --git a/server/src/index.ts b/server/src/index.ts index 053353f..1a8eafc 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -54,7 +54,7 @@ httpServer.listen(process.env.SOCKET_PORT || 3001, () => { interface Room { id: string; - participants: Set; + participants: Set; // socketIds — один guestId может быть в нескольких комнатах (разные вкладки) } interface User { @@ -67,23 +67,33 @@ interface User { } const rooms = new Map(); -const users = new Map(); +const users = new Map(); // ключ: socketId (один guestId = несколько сокетов в разных комнатах) // Таймеры для автоматического завершения сессий (roomId -> NodeJS.Timeout) const sessionEndTimers = new Map(); // Вспомогательные функции function findUserBySocketId(socketId: string): User | undefined { - for (const [userId, user] of users.entries()) { - if (user.socketId === socketId) { - return user; - } + return users.get(socketId); +} + +function findSocketIdByUserId( + userId: string, + roomId: string +): string | undefined { + for (const [, user] of users.entries()) { + if (user.id === userId && user.roomId === roomId) return user.socketId; } return undefined; } -function findSocketIdByUserId(userId: string): string | undefined { - const user = users.get(userId); - return user?.socketId; +function getUserByUserIdInRoom( + userId: string, + roomId: string +): User | undefined { + for (const [, user] of users.entries()) { + if (user.id === userId && user.roomId === roomId) return user; + } + return undefined; } /** @@ -110,7 +120,10 @@ async function startSessionEndTimer(roomId: string) { // Проверяем статус сессии - если уже завершена, не запускаем таймер try { const session = await serverSessionService.findById(roomId); - if (session && (session.status === "ended" || session.status === "ending")) { + if ( + session && + (session.status === "ended" || session.status === "ending") + ) { console.log( `[Session Auto-End] Session ${roomId} is already ended or ending, skipping auto-end timer` ); @@ -141,7 +154,10 @@ async function startSessionEndTimer(roomId: string) { // Завершаем сессию const session = await serverSessionService.findById(roomId); - if (session && (session.status === "started" || session.status === "starting")) { + if ( + session && + (session.status === "started" || session.status === "starting") + ) { await serverSessionService.end(roomId); console.log( `[Session Auto-End] Session ${roomId} has been ended automatically` @@ -184,15 +200,11 @@ io.on("connection", (socket) => { `[WebRTC] User ${userId} (socket: ${socket.id}) joining room ${roomId}, audio: ${isAudioEnabled}, video: ${isVideoEnabled}` ); - // Проверяем существующего пользователя - const existingUser = users.get(userId); + // Проверяем существующего пользователя (по socketId — один гость может быть в нескольких комнатах) + const existingUser = users.get(socket.id); // Если пользователь уже в этой комнате с тем же socket.id - это дубликат запроса - if ( - existingUser && - existingUser.roomId === roomId && - existingUser.socketId === socket.id - ) { + if (existingUser && existingUser.roomId === roomId) { console.log( `[WebRTC] User ${userId} is already in room ${roomId} with same socket, ignoring duplicate join` ); @@ -204,19 +216,21 @@ io.on("connection", (socket) => { return; } - // Покинуть предыдущую комнату если была + // Покинуть предыдущую комнату только если это ТОТ ЖЕ сокет (переключение комнаты или переподключение). + // Если existingUser.socketId !== socket.id — это другая вкладка с тем же guestId; старую комнату не трогаем. if ( existingUser && existingUser.roomId && - existingUser.roomId !== roomId + existingUser.roomId !== roomId && + existingUser.socketId === socket.id ) { console.log( - `[WebRTC] User ${userId} leaving previous room ${existingUser.roomId}` + `[WebRTC] User ${userId} (same socket) leaving previous room ${existingUser.roomId}` ); socket.leave(existingUser.roomId); const prevRoom = rooms.get(existingUser.roomId); if (prevRoom) { - prevRoom.participants.delete(userId); + prevRoom.participants.delete(socket.id); socket.to(existingUser.roomId).emit("user-left", userId); // Если предыдущая комната стала пустой, запускаем таймер завершения сессии @@ -248,14 +262,13 @@ io.on("connection", (socket) => { const room = rooms.get(roomId)!; - // Если пользователь уже в участниках комнаты (переподключение), удаляем его из старого socket - if (room.participants.has(userId)) { + // Если этот сокет уже в участниках (дубликат join-room), обновляем состояние + if (room.participants.has(socket.id)) { console.log( - `[WebRTC] User ${userId} is reconnecting to room ${roomId}, updating socket` + `[WebRTC] Socket ${socket.id} is already in room ${roomId}, updating state` ); - // Не возвращаемся - продолжаем обработку для обновления socketId и отправки состояния } else { - room.participants.add(userId); + room.participants.add(socket.id); // Отменяем таймер завершения сессии, так как появился участник cancelSessionEndTimer(roomId); } @@ -274,8 +287,12 @@ io.on("connection", (socket) => { if (isOwner) { // Проверяем, есть ли в комнате участники с управлением (кроме самого организатора) let hasControllerInRoom = false; - for (const [uid, user] of users.entries()) { - if (user.roomId === roomId && user.hasControl && uid !== userId) { + for (const [, user] of users.entries()) { + if ( + user.roomId === roomId && + user.hasControl && + user.id !== userId + ) { hasControllerInRoom = true; break; } @@ -304,7 +321,7 @@ io.on("connection", (socket) => { console.error(`[WebRTC] Error checking session owner:`, error); } - // Сохранить/обновить пользователя с состоянием аудио/видео и управления + // Сохранить/обновить пользователя (ключ — socketId, один guestId может быть в нескольких комнатах) const userData: User = { id: userId, roomId, @@ -313,13 +330,7 @@ io.on("connection", (socket) => { isVideoEnabled: isVideoEnabled !== false, hasControl: hasControl || false, }; - - // Если пользователь уже существует, обновляем его данные (особенно socketId при переподключении) - if (existingUser) { - Object.assign(existingUser, userData); - } else { - users.set(userId, userData); - } + users.set(socket.id, userData); console.log( `[WebRTC] Room ${roomId} now has participants:`, @@ -345,37 +356,39 @@ io.on("connection", (socket) => { .to(roomId) .emit("video-toggle", { userId, isEnabled: isVideoEnabled !== false }); - // Отправить список участников новому пользователю - const participants = Array.from(room.participants).filter( - (id) => id !== userId - ); + // Отправить список участников (userIds) новому пользователю + const participantUserIds = Array.from(room.participants) + .filter((sid) => sid !== socket.id) + .map((sid) => users.get(sid)?.id) + .filter((id): id is string => !!id); console.log( `[WebRTC] Sending participant list to ${userId}:`, - participants + participantUserIds ); - socket.emit("room-participants", participants); + socket.emit("room-participants", participantUserIds); // Отправить состояние аудио/видео и управления существующих участников новому пользователю - participants.forEach((participantId) => { - const participant = users.get(participantId); + for (const sid of room.participants) { + if (sid === socket.id) continue; + const participant = users.get(sid); if (participant) { console.log( - `[WebRTC] Sending ${participantId} media state to ${userId}: audio=${participant.isAudioEnabled}, video=${participant.isVideoEnabled}, hasControl=${participant.hasControl}` + `[WebRTC] Sending ${participant.id} media state to ${userId}: audio=${participant.isAudioEnabled}, video=${participant.isVideoEnabled}, hasControl=${participant.hasControl}` ); socket.emit("audio-toggle", { - userId: participantId, + userId: participant.id, isEnabled: participant.isAudioEnabled !== false, }); socket.emit("video-toggle", { - userId: participantId, + userId: participant.id, isEnabled: participant.isVideoEnabled !== false, }); socket.emit("control-toggle", { - userId: participantId, + userId: participant.id, hasControl: participant.hasControl || false, }); } - }); + } // Отправить состояние управления нового пользователя всем в комнате socket.to(roomId).emit("control-toggle", { @@ -394,14 +407,18 @@ io.on("connection", (socket) => { ); // Покидание комнаты - socket.on("leave-room", ({ roomId, userId }) => { - console.log(`[WebRTC] User ${userId} leaving room ${roomId}`); + socket.on("leave-room", ({ roomId }) => { + const user = findUserBySocketId(socket.id); + const userId = user?.id; + console.log( + `[WebRTC] User ${userId} (socket ${socket.id}) leaving room ${roomId}` + ); socket.leave(roomId); const room = rooms.get(roomId); if (room) { - room.participants.delete(userId); - socket.to(roomId).emit("user-left", userId); + room.participants.delete(socket.id); + if (userId) socket.to(roomId).emit("user-left", userId); // Если комната стала пустой, запускаем таймер завершения сессии if (room.participants.size === 0) { @@ -417,7 +434,6 @@ io.on("connection", (socket) => { } } - const user = users.get(userId); if (user) { user.roomId = undefined; } @@ -426,8 +442,10 @@ io.on("connection", (socket) => { // WebRTC сигнализация socket.on("offer", ({ target, offer }) => { console.log(`[WebRTC] Offer from ${socket.id} to ${target}`); - const targetSocketId = findSocketIdByUserId(target); const senderUser = findUserBySocketId(socket.id); + const targetSocketId = senderUser?.roomId + ? findSocketIdByUserId(target, senderUser.roomId) + : undefined; if (targetSocketId && senderUser) { socket.to(targetSocketId).emit("offer", { @@ -439,8 +457,10 @@ io.on("connection", (socket) => { socket.on("answer", ({ target, answer }) => { console.log(`[WebRTC] Answer from ${socket.id} to ${target}`); - const targetSocketId = findSocketIdByUserId(target); const senderUser = findUserBySocketId(socket.id); + const targetSocketId = senderUser?.roomId + ? findSocketIdByUserId(target, senderUser.roomId) + : undefined; if (targetSocketId && senderUser) { socket.to(targetSocketId).emit("answer", { @@ -452,8 +472,10 @@ io.on("connection", (socket) => { socket.on("ice-candidate", ({ target, candidate }) => { console.log(`[WebRTC] ICE candidate from ${socket.id} to ${target}`); - const targetSocketId = findSocketIdByUserId(target); const senderUser = findUserBySocketId(socket.id); + const targetSocketId = senderUser?.roomId + ? findSocketIdByUserId(target, senderUser.roomId) + : undefined; if (targetSocketId && senderUser) { socket.to(targetSocketId).emit("ice-candidate", { @@ -469,11 +491,11 @@ io.on("connection", (socket) => { `[WebRTC] Audio toggle from ${userId} in room ${roomId}: ${isEnabled}` ); - // Обновляем сохраненное состояние пользователя - const user = users.get(userId); + // Обновляем сохраненное состояние пользователя (по socketId) + const user = findUserBySocketId(socket.id); if (user) { user.isAudioEnabled = isEnabled; - console.log(`[WebRTC] Updated ${userId} audio state to ${isEnabled}`); + console.log(`[WebRTC] Updated ${user.id} audio state to ${isEnabled}`); } // Отправляем всем в комнате (кроме отправителя) @@ -485,11 +507,11 @@ io.on("connection", (socket) => { `[WebRTC] Video toggle from ${userId} in room ${roomId}: ${isEnabled}` ); - // Обновляем сохраненное состояние пользователя - const user = users.get(userId); + // Обновляем сохраненное состояние пользователя (по socketId) + const user = findUserBySocketId(socket.id); if (user) { user.isVideoEnabled = isEnabled; - console.log(`[WebRTC] Updated ${userId} video state to ${isEnabled}`); + console.log(`[WebRTC] Updated ${user.id} video state to ${isEnabled}`); } // Отправляем всем в комнате (кроме отправителя) @@ -561,15 +583,15 @@ io.on("connection", (socket) => { return; } - // Обновляем состояние участника - const targetUser = users.get(targetUserId); + // Обновляем состояние участника (ищем по userId в комнате) + const targetUser = getUserByUserIdInRoom(targetUserId, roomId); if (targetUser) { targetUser.isAudioEnabled = false; console.log(`[WebRTC] Updated ${targetUserId} audio state to false`); } // Отправляем команду конкретному участнику - const targetSocketId = findSocketIdByUserId(targetUserId); + const targetSocketId = findSocketIdByUserId(targetUserId, roomId); if (targetSocketId) { io.to(targetSocketId).emit("force-mute-audio"); console.log(`[WebRTC] Sent force-mute-audio to ${targetUserId}`); @@ -641,15 +663,15 @@ io.on("connection", (socket) => { return; } - // Обновляем состояние участника - const targetUser = users.get(targetUserId); + // Обновляем состояние участника (ищем по userId в комнате) + const targetUser = getUserByUserIdInRoom(targetUserId, roomId); if (targetUser) { targetUser.isVideoEnabled = false; console.log(`[WebRTC] Updated ${targetUserId} video state to false`); } // Отправляем команду конкретному участнику - const targetSocketId = findSocketIdByUserId(targetUserId); + const targetSocketId = findSocketIdByUserId(targetUserId, roomId); if (targetSocketId) { io.to(targetSocketId).emit("force-disable-video"); console.log(`[WebRTC] Sent force-disable-video to ${targetUserId}`); @@ -720,8 +742,8 @@ io.on("connection", (socket) => { } // Проверяем, что целевой пользователь существует и находится в комнате - const targetUser = users.get(targetUserId); - if (!targetUser || targetUser.roomId !== roomId) { + const targetUser = getUserByUserIdInRoom(targetUserId, roomId); + if (!targetUser) { console.warn( `[WebRTC] Target user ${targetUserId} not found or not in room ${roomId}` ); @@ -731,7 +753,7 @@ io.on("connection", (socket) => { // Находим текущего пользователя с управлением в этой комнате let currentController: User | undefined; - for (const [userId, user] of users.entries()) { + for (const [, user] of users.entries()) { if (user.roomId === roomId && user.hasControl) { currentController = user; break; @@ -742,7 +764,8 @@ io.on("connection", (socket) => { if (currentController && currentController.id !== targetUserId) { currentController.hasControl = false; const currentControllerSocketId = findSocketIdByUserId( - currentController.id + currentController.id, + roomId ); if (currentControllerSocketId) { io.to(currentControllerSocketId).emit("control-revoked"); @@ -757,7 +780,7 @@ io.on("connection", (socket) => { // Предоставляем управление целевому пользователю targetUser.hasControl = true; - const targetSocketId = findSocketIdByUserId(targetUserId); + const targetSocketId = findSocketIdByUserId(targetUserId, roomId); if (targetSocketId) { io.to(targetSocketId).emit("control-granted"); console.log(`[WebRTC] Granted control to ${targetUserId}`); @@ -826,7 +849,7 @@ io.on("connection", (socket) => { // Находим текущего пользователя с управлением в этой комнате let currentController: User | undefined; - for (const [userId, user] of users.entries()) { + for (const [, user] of users.entries()) { if (user.roomId === roomId && user.hasControl) { currentController = user; break; @@ -837,7 +860,8 @@ io.on("connection", (socket) => { if (currentController) { currentController.hasControl = false; const currentControllerSocketId = findSocketIdByUserId( - currentController.id + currentController.id, + roomId ); if (currentControllerSocketId) { io.to(currentControllerSocketId).emit("control-revoked"); @@ -987,12 +1011,12 @@ io.on("connection", (socket) => { const disconnectedUser = findUserBySocketId(socket.id); if (disconnectedUser) { - users.delete(disconnectedUser.id); + users.delete(socket.id); if (disconnectedUser.roomId) { const room = rooms.get(disconnectedUser.roomId); if (room) { - room.participants.delete(disconnectedUser.id); + room.participants.delete(socket.id); socket .to(disconnectedUser.roomId) .emit("user-left", disconnectedUser.id); diff --git a/session-server/session-server.rar b/session-server/session-server.rar deleted file mode 100644 index cce0b86..0000000 Binary files a/session-server/session-server.rar and /dev/null differ