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:
+103
-79
@@ -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.
Reference in New Issue
Block a user