Update environment configuration and enhance control features in WebRTC
- Changed VITE_API_URL and VITE_WEBRTC_URL in .env to point to local IP addresses. - Added react-hot-toast for user notifications in the application. - Integrated toast notifications for control acquisition in SessionPage. - Enhanced PixelStreamingWrapper and SessionUsersPanel to manage control states for participants. - Implemented grant and revoke control functionalities in the WebRTC service, allowing dynamic control management among users. - Updated various components to reflect control states and improve user experience during sessions.
This commit is contained in:
+487
-48
@@ -61,10 +61,13 @@ interface User {
|
||||
socketId: string;
|
||||
isAudioEnabled?: boolean;
|
||||
isVideoEnabled?: boolean;
|
||||
hasControl?: boolean;
|
||||
}
|
||||
|
||||
const rooms = new Map<string, Room>();
|
||||
const users = new Map<string, User>();
|
||||
// Таймеры для автоматического завершения сессий (roomId -> NodeJS.Timeout)
|
||||
const sessionEndTimers = new Map<string, NodeJS.Timeout>();
|
||||
|
||||
// Вспомогательные функции
|
||||
function findUserBySocketId(socketId: string): User | undefined {
|
||||
@@ -81,6 +84,93 @@ function findSocketIdByUserId(userId: string): string | undefined {
|
||||
return user?.socketId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Отменить таймер автоматического завершения сессии для комнаты
|
||||
*/
|
||||
function cancelSessionEndTimer(roomId: string) {
|
||||
const timer = sessionEndTimers.get(roomId);
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
sessionEndTimers.delete(roomId);
|
||||
console.log(
|
||||
`[Session Auto-End] Cancelled auto-end timer for room ${roomId}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Запустить таймер автоматического завершения сессии через 3 минуты
|
||||
*/
|
||||
async function startSessionEndTimer(roomId: string) {
|
||||
// Отменяем существующий таймер, если есть
|
||||
cancelSessionEndTimer(roomId);
|
||||
|
||||
// Проверяем статус сессии - если уже завершена, не запускаем таймер
|
||||
try {
|
||||
const session = await serverSessionService.findById(roomId);
|
||||
if (session && (session.status === "ended" || session.status === "ending")) {
|
||||
console.log(
|
||||
`[Session Auto-End] Session ${roomId} is already ended or ending, skipping auto-end timer`
|
||||
);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[Session Auto-End] Error checking session status for ${roomId}:`,
|
||||
error instanceof Error ? error.message : error
|
||||
);
|
||||
// Продолжаем выполнение, если не удалось проверить статус
|
||||
}
|
||||
|
||||
const TIMEOUT_MS = 3 * 60 * 1000; // 3 минуты
|
||||
|
||||
console.log(
|
||||
`[Session Auto-End] Starting auto-end timer for room ${roomId} (will end in 3 minutes if no participants join)`
|
||||
);
|
||||
|
||||
const timer = setTimeout(async () => {
|
||||
try {
|
||||
// Проверяем, что комната все еще пустая
|
||||
const room = rooms.get(roomId);
|
||||
if (room && room.participants.size === 0) {
|
||||
console.log(
|
||||
`[Session Auto-End] Room ${roomId} has been empty for 3 minutes, ending session`
|
||||
);
|
||||
|
||||
// Завершаем сессию
|
||||
const session = await serverSessionService.findById(roomId);
|
||||
if (session && (session.status === "started" || session.status === "starting")) {
|
||||
await serverSessionService.end(roomId);
|
||||
console.log(
|
||||
`[Session Auto-End] Session ${roomId} has been ended automatically`
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
`[Session Auto-End] Session ${roomId} is already ended or ending, skipping`
|
||||
);
|
||||
}
|
||||
|
||||
// Удаляем комнату и таймер
|
||||
rooms.delete(roomId);
|
||||
sessionEndTimers.delete(roomId);
|
||||
} else {
|
||||
console.log(
|
||||
`[Session Auto-End] Room ${roomId} has participants now, cancelling auto-end`
|
||||
);
|
||||
sessionEndTimers.delete(roomId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[Session Auto-End] Error ending session ${roomId}:`,
|
||||
error instanceof Error ? error.message : error
|
||||
);
|
||||
sessionEndTimers.delete(roomId);
|
||||
}
|
||||
}, TIMEOUT_MS);
|
||||
|
||||
sessionEndTimers.set(roomId, timer);
|
||||
}
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
console.log(`[WebRTC] User connected: ${socket.id}`);
|
||||
|
||||
@@ -92,9 +182,32 @@ io.on("connection", (socket) => {
|
||||
`[WebRTC] User ${userId} (socket: ${socket.id}) joining room ${roomId}, audio: ${isAudioEnabled}, video: ${isVideoEnabled}`
|
||||
);
|
||||
|
||||
// Покинуть предыдущую комнату если была
|
||||
// Проверяем существующего пользователя
|
||||
const existingUser = users.get(userId);
|
||||
if (existingUser?.roomId) {
|
||||
|
||||
// Если пользователь уже в этой комнате с тем же socket.id - это дубликат запроса
|
||||
if (
|
||||
existingUser &&
|
||||
existingUser.roomId === roomId &&
|
||||
existingUser.socketId === socket.id
|
||||
) {
|
||||
console.log(
|
||||
`[WebRTC] User ${userId} is already in room ${roomId} with same socket, ignoring duplicate join`
|
||||
);
|
||||
// Но все равно отправляем состояние управления на случай если оно не было получено
|
||||
socket.emit("control-toggle", {
|
||||
userId,
|
||||
hasControl: existingUser.hasControl || false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Покинуть предыдущую комнату если была
|
||||
if (
|
||||
existingUser &&
|
||||
existingUser.roomId &&
|
||||
existingUser.roomId !== roomId
|
||||
) {
|
||||
console.log(
|
||||
`[WebRTC] User ${userId} leaving previous room ${existingUser.roomId}`
|
||||
);
|
||||
@@ -103,6 +216,19 @@ io.on("connection", (socket) => {
|
||||
if (prevRoom) {
|
||||
prevRoom.participants.delete(userId);
|
||||
socket.to(existingUser.roomId).emit("user-left", userId);
|
||||
|
||||
// Если предыдущая комната стала пустой, запускаем таймер завершения сессии
|
||||
if (prevRoom.participants.size === 0) {
|
||||
console.log(
|
||||
`[WebRTC] Previous room ${existingUser.roomId} is now empty, starting auto-end timer`
|
||||
);
|
||||
startSessionEndTimer(existingUser.roomId).catch((error) => {
|
||||
console.error(
|
||||
`[WebRTC] Error starting auto-end timer for room ${existingUser.roomId}:`,
|
||||
error
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,23 +245,88 @@ io.on("connection", (socket) => {
|
||||
}
|
||||
|
||||
const room = rooms.get(roomId)!;
|
||||
room.participants.add(userId);
|
||||
|
||||
// Сохранить пользователя с состоянием аудио/видео
|
||||
users.set(userId, {
|
||||
// Если пользователь уже в участниках комнаты (переподключение), удаляем его из старого socket
|
||||
if (room.participants.has(userId)) {
|
||||
console.log(
|
||||
`[WebRTC] User ${userId} is reconnecting to room ${roomId}, updating socket`
|
||||
);
|
||||
// Не возвращаемся - продолжаем обработку для обновления socketId и отправки состояния
|
||||
} else {
|
||||
room.participants.add(userId);
|
||||
// Отменяем таймер завершения сессии, так как появился участник
|
||||
cancelSessionEndTimer(roomId);
|
||||
}
|
||||
|
||||
// Проверяем, является ли пользователь владельцем сессии
|
||||
let hasControl = false;
|
||||
try {
|
||||
const session = await serverSessionService.findById(roomId);
|
||||
if (session) {
|
||||
// Владелец - это userId (для авторизованных) или guestId (для гостей)
|
||||
const isOwner = Boolean(
|
||||
(session.userId && session.userId === userId) ||
|
||||
(session.guestId && session.guestId === userId)
|
||||
);
|
||||
|
||||
if (isOwner) {
|
||||
// Проверяем, есть ли в комнате участники с управлением (кроме самого организатора)
|
||||
let hasControllerInRoom = false;
|
||||
for (const [uid, user] of users.entries()) {
|
||||
if (user.roomId === roomId && user.hasControl && uid !== userId) {
|
||||
hasControllerInRoom = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Если нет участников с управлением, передаем управление организатору
|
||||
if (!hasControllerInRoom) {
|
||||
hasControl = true;
|
||||
console.log(
|
||||
`[WebRTC] Owner ${userId} joining/reconnecting, no controller in room, granting control`
|
||||
);
|
||||
} else {
|
||||
// Если есть участник с управлением, сохраняем текущее состояние организатора
|
||||
// (если у организатора было управление, оно остается, если не было - остается без управления)
|
||||
hasControl = existingUser?.hasControl || false;
|
||||
console.log(
|
||||
`[WebRTC] Owner ${userId} joining/reconnecting, controller exists in room, keeping current state (hasControl=${hasControl})`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Для не-организаторов сохраняем существующее состояние управления
|
||||
hasControl = existingUser?.hasControl || false;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[WebRTC] Error checking session owner:`, error);
|
||||
}
|
||||
|
||||
// Сохранить/обновить пользователя с состоянием аудио/видео и управления
|
||||
const userData: User = {
|
||||
id: userId,
|
||||
roomId,
|
||||
socketId: socket.id,
|
||||
isAudioEnabled: isAudioEnabled !== false,
|
||||
isVideoEnabled: isVideoEnabled !== false,
|
||||
});
|
||||
hasControl: hasControl || false,
|
||||
};
|
||||
|
||||
// Если пользователь уже существует, обновляем его данные (особенно socketId при переподключении)
|
||||
if (existingUser) {
|
||||
Object.assign(existingUser, userData);
|
||||
} else {
|
||||
users.set(userId, userData);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[WebRTC] Room ${roomId} now has participants:`,
|
||||
Array.from(room.participants)
|
||||
);
|
||||
console.log(
|
||||
`[WebRTC] User ${userId} media state: audio=${isAudioEnabled !== false}, video=${isVideoEnabled !== false}`
|
||||
`[WebRTC] User ${userId} media state: audio=${
|
||||
isAudioEnabled !== false
|
||||
}, video=${isVideoEnabled !== false}`
|
||||
);
|
||||
|
||||
// Уведомить других участников
|
||||
@@ -162,12 +353,12 @@ io.on("connection", (socket) => {
|
||||
);
|
||||
socket.emit("room-participants", participants);
|
||||
|
||||
// Отправить состояние аудио/видео существующих участников новому пользователю
|
||||
// Отправить состояние аудио/видео и управления существующих участников новому пользователю
|
||||
participants.forEach((participantId) => {
|
||||
const participant = users.get(participantId);
|
||||
if (participant) {
|
||||
console.log(
|
||||
`[WebRTC] Sending ${participantId} media state to ${userId}: audio=${participant.isAudioEnabled}, video=${participant.isVideoEnabled}`
|
||||
`[WebRTC] Sending ${participantId} media state to ${userId}: audio=${participant.isAudioEnabled}, video=${participant.isVideoEnabled}, hasControl=${participant.hasControl}`
|
||||
);
|
||||
socket.emit("audio-toggle", {
|
||||
userId: participantId,
|
||||
@@ -177,8 +368,26 @@ io.on("connection", (socket) => {
|
||||
userId: participantId,
|
||||
isEnabled: participant.isVideoEnabled !== false,
|
||||
});
|
||||
socket.emit("control-toggle", {
|
||||
userId: participantId,
|
||||
hasControl: participant.hasControl || false,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Отправить состояние управления нового пользователя всем в комнате
|
||||
socket.to(roomId).emit("control-toggle", {
|
||||
userId,
|
||||
hasControl,
|
||||
});
|
||||
|
||||
// Отправить состояние управления самому новому пользователю
|
||||
socket.emit("control-toggle", {
|
||||
userId,
|
||||
hasControl,
|
||||
});
|
||||
|
||||
console.log(`[WebRTC] User ${userId} hasControl: ${hasControl}`);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -192,10 +401,17 @@ io.on("connection", (socket) => {
|
||||
room.participants.delete(userId);
|
||||
socket.to(roomId).emit("user-left", userId);
|
||||
|
||||
// Удалить пустую комнату
|
||||
// Если комната стала пустой, запускаем таймер завершения сессии
|
||||
if (room.participants.size === 0) {
|
||||
rooms.delete(roomId);
|
||||
console.log(`[WebRTC] Deleted empty room ${roomId}`);
|
||||
console.log(
|
||||
`[WebRTC] Room ${roomId} is now empty, starting auto-end timer`
|
||||
);
|
||||
startSessionEndTimer(roomId).catch((error) => {
|
||||
console.error(
|
||||
`[WebRTC] Error starting auto-end timer for room ${roomId}:`,
|
||||
error
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,14 +466,14 @@ io.on("connection", (socket) => {
|
||||
console.log(
|
||||
`[WebRTC] Audio toggle from ${userId} in room ${roomId}: ${isEnabled}`
|
||||
);
|
||||
|
||||
|
||||
// Обновляем сохраненное состояние пользователя
|
||||
const user = users.get(userId);
|
||||
if (user) {
|
||||
user.isAudioEnabled = isEnabled;
|
||||
console.log(`[WebRTC] Updated ${userId} audio state to ${isEnabled}`);
|
||||
}
|
||||
|
||||
|
||||
// Отправляем всем в комнате (кроме отправителя)
|
||||
socket.to(roomId).emit("audio-toggle", { userId, isEnabled });
|
||||
});
|
||||
@@ -266,14 +482,14 @@ io.on("connection", (socket) => {
|
||||
console.log(
|
||||
`[WebRTC] Video toggle from ${userId} in room ${roomId}: ${isEnabled}`
|
||||
);
|
||||
|
||||
|
||||
// Обновляем сохраненное состояние пользователя
|
||||
const user = users.get(userId);
|
||||
if (user) {
|
||||
user.isVideoEnabled = isEnabled;
|
||||
console.log(`[WebRTC] Updated ${userId} video state to ${isEnabled}`);
|
||||
}
|
||||
|
||||
|
||||
// Отправляем всем в комнате (кроме отправителя)
|
||||
socket.to(roomId).emit("video-toggle", { userId, isEnabled });
|
||||
});
|
||||
@@ -289,15 +505,17 @@ io.on("connection", (socket) => {
|
||||
console.log(
|
||||
`[WebRTC] Mute participant request: ${targetUserId} in room ${roomId}`
|
||||
);
|
||||
|
||||
|
||||
// Получаем информацию о пользователе, который отправил команду
|
||||
const requestingUser = findUserBySocketId(socket.id);
|
||||
if (!requestingUser) {
|
||||
console.warn(`[WebRTC] Unauthorized mute request from unknown socket ${socket.id}`);
|
||||
console.warn(
|
||||
`[WebRTC] Unauthorized mute request from unknown socket ${socket.id}`
|
||||
);
|
||||
socket.emit("error", { message: "Unauthorized: user not found" });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Проверяем, что пользователь находится в той же комнате
|
||||
if (requestingUser.roomId !== roomId) {
|
||||
console.warn(
|
||||
@@ -306,7 +524,7 @@ io.on("connection", (socket) => {
|
||||
socket.emit("error", { message: "Unauthorized: not in the same room" });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Проверяем, что пользователь является организатором сессии
|
||||
try {
|
||||
const session = await serverSessionService.findById(roomId);
|
||||
@@ -315,44 +533,51 @@ io.on("connection", (socket) => {
|
||||
socket.emit("error", { message: "Session not found" });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Проверяем, что запрашивающий пользователь - организатор
|
||||
// Организатор - это userId (для авторизованных) или guestId (для гостей)
|
||||
const isOrganizer =
|
||||
const isOrganizer =
|
||||
(session.userId && session.userId === requestingUser.id) ||
|
||||
(session.guestId && session.guestId === requestingUser.id);
|
||||
|
||||
|
||||
if (!isOrganizer) {
|
||||
console.warn(
|
||||
`[WebRTC] User ${requestingUser.id} is not the organizer of session ${roomId}`
|
||||
);
|
||||
socket.emit("error", { message: "Unauthorized: only organizer can mute participants" });
|
||||
socket.emit("error", {
|
||||
message: "Unauthorized: only organizer can mute participants",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[WebRTC] User ${requestingUser.id} is authorized as organizer`);
|
||||
|
||||
console.log(
|
||||
`[WebRTC] User ${requestingUser.id} is authorized as organizer`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(`[WebRTC] Error checking session organizer:`, error);
|
||||
socket.emit("error", { message: "Failed to verify permissions" });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Обновляем состояние участника
|
||||
const targetUser = users.get(targetUserId);
|
||||
if (targetUser) {
|
||||
targetUser.isAudioEnabled = false;
|
||||
console.log(`[WebRTC] Updated ${targetUserId} audio state to false`);
|
||||
}
|
||||
|
||||
|
||||
// Отправляем команду конкретному участнику
|
||||
const targetSocketId = findSocketIdByUserId(targetUserId);
|
||||
if (targetSocketId) {
|
||||
io.to(targetSocketId).emit("force-mute-audio");
|
||||
console.log(`[WebRTC] Sent force-mute-audio to ${targetUserId}`);
|
||||
}
|
||||
|
||||
|
||||
// Уведомляем всех в комнате об изменении состояния
|
||||
io.to(roomId).emit("audio-toggle", { userId: targetUserId, isEnabled: false });
|
||||
io.to(roomId).emit("audio-toggle", {
|
||||
userId: targetUserId,
|
||||
isEnabled: false,
|
||||
});
|
||||
});
|
||||
|
||||
// Обработка команды выключения камеры участника
|
||||
@@ -360,15 +585,17 @@ io.on("connection", (socket) => {
|
||||
console.log(
|
||||
`[WebRTC] Disable video request: ${targetUserId} in room ${roomId}`
|
||||
);
|
||||
|
||||
|
||||
// Получаем информацию о пользователе, который отправил команду
|
||||
const requestingUser = findUserBySocketId(socket.id);
|
||||
if (!requestingUser) {
|
||||
console.warn(`[WebRTC] Unauthorized disable video request from unknown socket ${socket.id}`);
|
||||
console.warn(
|
||||
`[WebRTC] Unauthorized disable video request from unknown socket ${socket.id}`
|
||||
);
|
||||
socket.emit("error", { message: "Unauthorized: user not found" });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Проверяем, что пользователь находится в той же комнате
|
||||
if (requestingUser.roomId !== roomId) {
|
||||
console.warn(
|
||||
@@ -377,7 +604,7 @@ io.on("connection", (socket) => {
|
||||
socket.emit("error", { message: "Unauthorized: not in the same room" });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Проверяем, что пользователь является организатором сессии
|
||||
try {
|
||||
const session = await serverSessionService.findById(roomId);
|
||||
@@ -386,44 +613,251 @@ io.on("connection", (socket) => {
|
||||
socket.emit("error", { message: "Session not found" });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Проверяем, что запрашивающий пользователь - организатор
|
||||
// Организатор - это userId (для авторизованных) или guestId (для гостей)
|
||||
const isOrganizer =
|
||||
const isOrganizer =
|
||||
(session.userId && session.userId === requestingUser.id) ||
|
||||
(session.guestId && session.guestId === requestingUser.id);
|
||||
|
||||
|
||||
if (!isOrganizer) {
|
||||
console.warn(
|
||||
`[WebRTC] User ${requestingUser.id} is not the organizer of session ${roomId}`
|
||||
);
|
||||
socket.emit("error", { message: "Unauthorized: only organizer can disable video" });
|
||||
socket.emit("error", {
|
||||
message: "Unauthorized: only organizer can disable video",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[WebRTC] User ${requestingUser.id} is authorized as organizer`);
|
||||
|
||||
console.log(
|
||||
`[WebRTC] User ${requestingUser.id} is authorized as organizer`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(`[WebRTC] Error checking session organizer:`, error);
|
||||
socket.emit("error", { message: "Failed to verify permissions" });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Обновляем состояние участника
|
||||
const targetUser = users.get(targetUserId);
|
||||
if (targetUser) {
|
||||
targetUser.isVideoEnabled = false;
|
||||
console.log(`[WebRTC] Updated ${targetUserId} video state to false`);
|
||||
}
|
||||
|
||||
|
||||
// Отправляем команду конкретному участнику
|
||||
const targetSocketId = findSocketIdByUserId(targetUserId);
|
||||
if (targetSocketId) {
|
||||
io.to(targetSocketId).emit("force-disable-video");
|
||||
console.log(`[WebRTC] Sent force-disable-video to ${targetUserId}`);
|
||||
}
|
||||
|
||||
|
||||
// Уведомляем всех в комнате об изменении состояния
|
||||
io.to(roomId).emit("video-toggle", { userId: targetUserId, isEnabled: false });
|
||||
io.to(roomId).emit("video-toggle", {
|
||||
userId: targetUserId,
|
||||
isEnabled: false,
|
||||
});
|
||||
});
|
||||
|
||||
// Обработка передачи управления PixelStreaming
|
||||
socket.on("grant-control", async ({ roomId, targetUserId }) => {
|
||||
console.log(
|
||||
`[WebRTC] Grant control request: ${targetUserId} in room ${roomId}`
|
||||
);
|
||||
|
||||
// Получаем информацию о пользователе, который отправил команду
|
||||
const requestingUser = findUserBySocketId(socket.id);
|
||||
if (!requestingUser) {
|
||||
console.warn(
|
||||
`[WebRTC] Unauthorized grant control request from unknown socket ${socket.id}`
|
||||
);
|
||||
socket.emit("error", { message: "Unauthorized: user not found" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем, что пользователь находится в той же комнате
|
||||
if (requestingUser.roomId !== roomId) {
|
||||
console.warn(
|
||||
`[WebRTC] User ${requestingUser.id} tried to grant control in room ${roomId}, but is in room ${requestingUser.roomId}`
|
||||
);
|
||||
socket.emit("error", { message: "Unauthorized: not in the same room" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем, что пользователь является владельцем сессии
|
||||
try {
|
||||
const session = await serverSessionService.findById(roomId);
|
||||
if (!session) {
|
||||
console.warn(`[WebRTC] Session ${roomId} not found`);
|
||||
socket.emit("error", { message: "Session not found" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем, что запрашивающий пользователь - владелец
|
||||
// Владелец - это userId (для авторизованных) или guestId (для гостей)
|
||||
const isOwner =
|
||||
(session.userId && session.userId === requestingUser.id) ||
|
||||
(session.guestId && session.guestId === requestingUser.id);
|
||||
|
||||
if (!isOwner) {
|
||||
console.warn(
|
||||
`[WebRTC] User ${requestingUser.id} is not the owner of session ${roomId}`
|
||||
);
|
||||
socket.emit("error", {
|
||||
message: "Unauthorized: only owner can grant control",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[WebRTC] User ${requestingUser.id} is authorized as owner`);
|
||||
} catch (error) {
|
||||
console.error(`[WebRTC] Error checking session owner:`, error);
|
||||
socket.emit("error", { message: "Failed to verify permissions" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем, что целевой пользователь существует и находится в комнате
|
||||
const targetUser = users.get(targetUserId);
|
||||
if (!targetUser || targetUser.roomId !== roomId) {
|
||||
console.warn(
|
||||
`[WebRTC] Target user ${targetUserId} not found or not in room ${roomId}`
|
||||
);
|
||||
socket.emit("error", { message: "Target user not found" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Находим текущего пользователя с управлением в этой комнате
|
||||
let currentController: User | undefined;
|
||||
for (const [userId, user] of users.entries()) {
|
||||
if (user.roomId === roomId && user.hasControl) {
|
||||
currentController = user;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Если есть текущий контроллер и это не целевой пользователь, отзываем управление
|
||||
if (currentController && currentController.id !== targetUserId) {
|
||||
currentController.hasControl = false;
|
||||
const currentControllerSocketId = findSocketIdByUserId(
|
||||
currentController.id
|
||||
);
|
||||
if (currentControllerSocketId) {
|
||||
io.to(currentControllerSocketId).emit("control-revoked");
|
||||
console.log(`[WebRTC] Revoked control from ${currentController.id}`);
|
||||
}
|
||||
// Уведомляем всех в комнате об отзыве управления
|
||||
io.to(roomId).emit("control-toggle", {
|
||||
userId: currentController.id,
|
||||
hasControl: false,
|
||||
});
|
||||
}
|
||||
|
||||
// Предоставляем управление целевому пользователю
|
||||
targetUser.hasControl = true;
|
||||
const targetSocketId = findSocketIdByUserId(targetUserId);
|
||||
if (targetSocketId) {
|
||||
io.to(targetSocketId).emit("control-granted");
|
||||
console.log(`[WebRTC] Granted control to ${targetUserId}`);
|
||||
}
|
||||
|
||||
// Уведомляем всех в комнате о предоставлении управления
|
||||
io.to(roomId).emit("control-toggle", {
|
||||
userId: targetUserId,
|
||||
hasControl: true,
|
||||
});
|
||||
});
|
||||
|
||||
// Обработка возврата управления владельцем
|
||||
socket.on("revoke-control", async ({ roomId }) => {
|
||||
console.log(`[WebRTC] Revoke control request in room ${roomId}`);
|
||||
|
||||
// Получаем информацию о пользователе, который отправил команду
|
||||
const requestingUser = findUserBySocketId(socket.id);
|
||||
if (!requestingUser) {
|
||||
console.warn(
|
||||
`[WebRTC] Unauthorized revoke control request from unknown socket ${socket.id}`
|
||||
);
|
||||
socket.emit("error", { message: "Unauthorized: user not found" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем, что пользователь находится в той же комнате
|
||||
if (requestingUser.roomId !== roomId) {
|
||||
console.warn(
|
||||
`[WebRTC] User ${requestingUser.id} tried to revoke control in room ${roomId}, but is in room ${requestingUser.roomId}`
|
||||
);
|
||||
socket.emit("error", { message: "Unauthorized: not in the same room" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем, что пользователь является владельцем сессии
|
||||
try {
|
||||
const session = await serverSessionService.findById(roomId);
|
||||
if (!session) {
|
||||
console.warn(`[WebRTC] Session ${roomId} not found`);
|
||||
socket.emit("error", { message: "Session not found" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем, что запрашивающий пользователь - владелец
|
||||
const isOwner =
|
||||
(session.userId && session.userId === requestingUser.id) ||
|
||||
(session.guestId && session.guestId === requestingUser.id);
|
||||
|
||||
if (!isOwner) {
|
||||
console.warn(
|
||||
`[WebRTC] User ${requestingUser.id} is not the owner of session ${roomId}`
|
||||
);
|
||||
socket.emit("error", {
|
||||
message: "Unauthorized: only owner can revoke control",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[WebRTC] User ${requestingUser.id} is authorized as owner`);
|
||||
} catch (error) {
|
||||
console.error(`[WebRTC] Error checking session owner:`, error);
|
||||
socket.emit("error", { message: "Failed to verify permissions" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Находим текущего пользователя с управлением в этой комнате
|
||||
let currentController: User | undefined;
|
||||
for (const [userId, user] of users.entries()) {
|
||||
if (user.roomId === roomId && user.hasControl) {
|
||||
currentController = user;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Если есть текущий контроллер, отзываем управление
|
||||
if (currentController) {
|
||||
currentController.hasControl = false;
|
||||
const currentControllerSocketId = findSocketIdByUserId(
|
||||
currentController.id
|
||||
);
|
||||
if (currentControllerSocketId) {
|
||||
io.to(currentControllerSocketId).emit("control-revoked");
|
||||
console.log(`[WebRTC] Revoked control from ${currentController.id}`);
|
||||
}
|
||||
// Уведомляем всех в комнате об отзыве управления
|
||||
io.to(roomId).emit("control-toggle", {
|
||||
userId: currentController.id,
|
||||
hasControl: false,
|
||||
});
|
||||
}
|
||||
|
||||
// Возвращаем управление владельцу
|
||||
requestingUser.hasControl = true;
|
||||
socket.emit("control-granted");
|
||||
console.log(`[WebRTC] Returned control to owner ${requestingUser.id}`);
|
||||
|
||||
// Уведомляем всех в комнате о возврате управления владельцу
|
||||
io.to(roomId).emit("control-toggle", {
|
||||
userId: requestingUser.id,
|
||||
hasControl: true,
|
||||
});
|
||||
});
|
||||
|
||||
// Обработка сообщений чата
|
||||
@@ -494,7 +928,7 @@ io.on("connection", (socket) => {
|
||||
const messageData = {
|
||||
sessionId: roomId,
|
||||
userId: userId || null, // userId для авторизованных пользователей
|
||||
guestId: userId ? null : (guestId || null), // guestId только если нет userId
|
||||
guestId: userId ? null : guestId || null, // guestId только если нет userId
|
||||
senderName: finalSenderName, // Имя отправителя
|
||||
content,
|
||||
type: "text" as const,
|
||||
@@ -561,12 +995,17 @@ io.on("connection", (socket) => {
|
||||
.to(disconnectedUser.roomId)
|
||||
.emit("user-left", disconnectedUser.id);
|
||||
|
||||
// Удалить пустую комнату
|
||||
// Если комната стала пустой, запускаем таймер завершения сессии
|
||||
if (room.participants.size === 0) {
|
||||
rooms.delete(disconnectedUser.roomId);
|
||||
console.log(
|
||||
`[WebRTC] Deleted empty room ${disconnectedUser.roomId}`
|
||||
`[WebRTC] Room ${disconnectedUser.roomId} is now empty, starting auto-end timer`
|
||||
);
|
||||
startSessionEndTimer(disconnectedUser.roomId).catch((error) => {
|
||||
console.error(
|
||||
`[WebRTC] Error starting auto-end timer for room ${disconnectedUser.roomId}:`,
|
||||
error
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user