Refactor user and room management logic in WebRTC server

- Updated user management to use socketId as the key, allowing multiple socket connections for the same guestId.
- Enhanced room participant handling to ensure correct socketId usage and prevent duplicate joins.
- Improved session end timer logic with clearer conditions for session status checks.
- Refactored functions for finding users and socketIds to streamline user retrieval and enhance readability.
- Removed deprecated session-server.rar file.
This commit is contained in:
2026-02-20 13:40:57 +05:00
parent 0243284da0
commit 02af1d0923
2 changed files with 103 additions and 79 deletions
+103 -79
View File
@@ -54,7 +54,7 @@ httpServer.listen(process.env.SOCKET_PORT || 3001, () => {
interface Room {
id: string;
participants: Set<string>;
participants: Set<string>; // socketIds — один guestId может быть в нескольких комнатах (разные вкладки)
}
interface User {
@@ -67,23 +67,33 @@ interface User {
}
const rooms = new Map<string, Room>();
const users = new Map<string, User>();
const users = new Map<string, User>(); // ключ: socketId (один guestId = несколько сокетов в разных комнатах)
// Таймеры для автоматического завершения сессий (roomId -> NodeJS.Timeout)
const sessionEndTimers = new Map<string, NodeJS.Timeout>();
// Вспомогательные функции
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);
Binary file not shown.