io.on('connection', (socket) => { const userAgent = socket.handshake.headers['user-agent'] || ''; const isAndroidClient = userAgent.includes('okhttp'); const isMobileWeb = !isAndroidClient && /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent); logger.info(`New connection: ${socket.id}`, { address: socket.handshake.address, userAgent: userAgent, isAndroid: isAndroidClient, isMobileWeb: isMobileWeb }); if (isAndroidClient) { logger.info(`🤖 Android client connected: ${socket.id}`); // Логируем все события от Android клиента socket.onAny((eventName, ...args) => { logger.info(`📱 Android event: ${eventName}`, args[0]); }); // Отправляем приветственное сообщение Android клиенту socket.emit('server:hello', { message: 'Server ready for registration', expectedEvent: 'register:android' }); } else if (isMobileWeb) { logger.info(`📱 Mobile web client connected: ${socket.id}`); // Логируем события от мобильного веб-клиента socket.onAny((eventName, ...args) => { logger.info(`🌐 Mobile web event: ${eventName}`, args[0]); }); } // Регистрация Android клиента socket.on('register:android', (data) => { const { deviceId, deviceInfo } = data; // Регистрируем устройство через DeviceManager const device = deviceManager.registerDevice(deviceId, deviceInfo, socket); logger.info(`Android client registered: ${deviceId}`, deviceInfo); // Уведомляем всех операторов о новом устройстве const operatorSockets = Array.from(deviceManager.operators.values()) .filter(op => op.isConnected()) .map(op => op.socket); operatorSockets.forEach(opSocket => { opSocket.emit('device:connected', { deviceId, deviceInfo: device.getSummary() }); }); socket.emit('register:success', { deviceId }); }); // Регистрация мобильного веб-клиента socket.on('register:mobile_web', (data) => { const { deviceId, deviceInfo } = data; // Регистрируем мобильное веб-устройство const device = deviceManager.registerDevice(deviceId, { ...deviceInfo, platform: 'mobile_web', type: 'web_camera' }, socket); logger.info(`Mobile web client registered: ${deviceId}`, deviceInfo); // Уведомляем всех операторов о новом устройстве const operatorSockets = Array.from(deviceManager.operators.values()) .filter(op => op.isConnected()) .map(op => op.socket); operatorSockets.forEach(opSocket => { opSocket.emit('device:connected', { deviceId, deviceInfo: device.getSummary() }); }); socket.emit('register:success', { deviceId }); }); // Fallback: если Android отправляет register:operator вместо register:android socket.on('register:operator', (data) => { const userAgent = socket.handshake.headers['user-agent'] || ''; if (userAgent.includes('okhttp')) { logger.warn(`🚨 Android client sent wrong event! ${socket.id} sent 'register:operator' instead of 'register:android'`); socket.emit('register:error', { error: 'Android clients should use register:android event, not register:operator' }); return; } // Обычная обработка для реальных операторов const { operatorId, operatorInfo } = data; const finalOperatorId = operatorId || uuidv4(); // Регистрируем оператора через DeviceManager const operator = deviceManager.registerOperator(finalOperatorId, operatorInfo, socket); logger.info(`Operator registered: ${finalOperatorId}`); // Отправляем список доступных устройств const availableDevices = deviceManager.getAvailableDevicesForOperator(finalOperatorId); const devicesData = availableDevices.map(device => device.getSummary()); socket.emit('register:success', { operatorId: finalOperatorId, availableDevices: devicesData }); }); // Запрос на подключение к камере через ConnectionManager socket.on('camera:request', async (data) => { const { deviceId, cameraType = 'back' } = data; logger.info(`📷 Camera request received from operator socket ${socket.id}`); logger.info(`📷 Request data:`, { deviceId, cameraType }); // Получаем оператора из менеджера устройств const operator = Array.from(deviceManager.operators.values()) .find(op => op.socket === socket); if (!operator) { logger.error(`❌ Operator not found for socket ${socket.id}`); socket.emit('camera:error', { error: 'Operator not registered' }); return; } logger.info(`✅ Operator found: ${operator.operatorId}`); try { // Используем ConnectionManager для создания подключения const connection = await connectionManager.initiateConnection( operator.operatorId, deviceId, cameraType ); logger.info(`✅ Connection initiated: ${connection.connectionId}`); // Уведомляем оператора о создании подключения socket.emit('connection:initiated', { connectionId: connection.connectionId, sessionId: connection.sessionId, deviceId: deviceId, cameraType: cameraType, status: 'pending', createdAt: new Date().toISOString() }); } catch (error) { logger.error(`❌ Failed to initiate connection: ${error.message}`); socket.emit('camera:error', { error: error.message }); } }); // Ответ от Android клиента на запрос камеры через ConnectionManager socket.on('camera:response', async (data) => { const { sessionId, accepted, streamUrl, error } = data; logger.info(`📱 Camera response received from Android: sessionId=${sessionId}, accepted=${accepted}`); try { if (accepted) { // Принимаем подключение через ConnectionManager const connection = await connectionManager.acceptConnection(sessionId, { streamUrl }); logger.info(`✅ Connection accepted: ${connection.connectionId}`); // Получаем оператора для уведомления const operator = deviceManager.getOperator(connection.operatorId); if (operator && operator.isConnected()) { operator.socket.emit('connection:accepted', { connectionId: connection.connectionId, sessionId: sessionId, deviceId: connection.deviceId, cameraType: connection.cameraType, streamUrl: streamUrl, status: 'active' }); // Отправляем старое событие для обратной совместимости operator.socket.emit('camera:response', { success: true, sessionId: sessionId, session: { id: sessionId, deviceId: connection.deviceId, cameraType: connection.cameraType } }); } } else { // Отклоняем подключение через ConnectionManager await connectionManager.rejectConnection(sessionId, error); logger.info(`❌ Connection rejected: sessionId=${sessionId}, error=${error}`); // Находим подключение для получения информации об операторе const connection = connectionManager.getConnection(sessionId); if (connection) { const operator = deviceManager.getOperator(connection.operatorId); if (operator && operator.isConnected()) { operator.socket.emit('connection:rejected', { sessionId: sessionId, deviceId: connection.deviceId, cameraType: connection.cameraType, error: error }); // Отправляем старое событие для обратной совместимости operator.socket.emit('camera:response', { success: false, sessionId: sessionId, message: error }); } } } } catch (error) { logger.error(`❌ Failed to handle camera response: ${error.message}`); socket.emit('camera:error', { error: error.message }); } // Переключение камеры в активной сессии socket.on('camera:switch', (data) => { const { sessionId, cameraType } = data; const session = sessionManager.getSession(sessionId); if (!session) { socket.emit('camera:error', { error: 'Session not found' }); return; } // Получаем участников сессии const device = deviceManager.getDevice(session.deviceId); const operator = deviceManager.getOperator(session.operatorId); // Проверяем, что запрос идет от оператора этой сессии if (!operator || operator.socket !== socket) { socket.emit('camera:error', { error: 'Unauthorized to switch camera in this session' }); return; } // Проверяем, что сессия активна if (session.status !== 'active') { socket.emit('camera:error', { error: 'Session is not active' }); return; } logger.info(`Camera switch requested: session ${sessionId}, camera ${cameraType}`); // Отправляем запрос на переключение устройству if (device && device.isConnected()) { device.socket.emit('camera:switch', { sessionId: sessionId, cameraType: cameraType }); // Обновляем тип камеры в сессии session.cameraType = cameraType; } else { socket.emit('camera:error', { error: 'Device not connected' }); } }); // Завершение сессии по инициативе оператора socket.on('session:end', (data) => { const { sessionId } = data; const session = sessionManager.getSession(sessionId); if (!session) { socket.emit('session:error', { error: 'Session not found' }); return; } // Получаем участников сессии const device = deviceManager.getDevice(session.deviceId); const operator = deviceManager.getOperator(session.operatorId); // Проверяем, что запрос идет от оператора этой сессии if (!operator || operator.socket !== socket) { socket.emit('session:error', { error: 'Unauthorized to end this session' }); return; } logger.info(`Session ended by operator: ${sessionId}`); // Уведомляем устройство о завершении if (device && device.isConnected()) { device.socket.emit('camera:disconnect', { sessionId }); device.removeSession(sessionId); } // Уведомляем оператора о завершении operator.socket.emit('session:ended', { sessionId: sessionId, deviceId: session.deviceId, reason: 'Ended by operator' }); operator.removeSession(sessionId); // Закрываем сессию sessionManager.closeSession(sessionId); }); // WebRTC сигнализация socket.on('webrtc:offer', (data) => { const { sessionId, offer } = data; const session = sessionManager.getSession(sessionId); if (session) { // Определяем получателя (Android -> Operator или Operator -> Android) const device = deviceManager.getDevice(session.deviceId); const operator = deviceManager.getOperator(session.operatorId); // Если отправитель - устройство, то получатель - оператор if (device && device.socket === socket && operator && operator.isConnected()) { operator.socket.emit('webrtc:offer', { sessionId, offer }); session.updateWebRTCState('offer_sent'); } // Если отправитель - оператор, то получатель - устройство else if (operator && operator.socket === socket && device && device.isConnected()) { device.socket.emit('webrtc:offer', { sessionId, offer }); session.updateWebRTCState('offer_sent'); } } }); socket.on('webrtc:answer', (data) => { const { sessionId, answer } = data; const session = sessionManager.getSession(sessionId); if (session) { const device = deviceManager.getDevice(session.deviceId); const operator = deviceManager.getOperator(session.operatorId); // Если отправитель - устройство, то получатель - оператор if (device && device.socket === socket && operator && operator.isConnected()) { operator.socket.emit('webrtc:answer', { sessionId, answer }); session.updateWebRTCState('answer_sent'); } // Если отправитель - оператор, то получатель - устройство else if (operator && operator.socket === socket && device && device.isConnected()) { device.socket.emit('webrtc:answer', { sessionId, answer }); session.updateWebRTCState('answer_sent'); } } }); socket.on('webrtc:ice-candidate', (data) => { const { sessionId, candidate } = data; const session = sessionManager.getSession(sessionId); if (session) { const device = deviceManager.getDevice(session.deviceId); const operator = deviceManager.getOperator(session.operatorId); // Пересылаем ICE кандидата другой стороне if (device && device.socket === socket && operator && operator.isConnected()) { operator.socket.emit('webrtc:ice-candidate', { sessionId, candidate }); } else if (operator && operator.socket === socket && device && device.isConnected()) { device.socket.emit('webrtc:ice-candidate', { sessionId, candidate }); } } }); // Переключение типа камеры socket.on('camera:switch', (data) => { const { sessionId, cameraType } = data; const session = sessionManager.getSession(sessionId); if (session) { const device = deviceManager.getDevice(session.deviceId); const operator = deviceManager.getOperator(session.operatorId); // Проверяем права доступа if (operator && operator.socket === socket && device && device.isConnected()) { device.socket.emit('camera:switch', { sessionId, cameraType }); session.switchCamera(cameraType); logger.info(`Camera switch requested: ${sessionId} -> ${cameraType}`); } } }); // Завершение сессии socket.on('camera:disconnect', (data) => { const { sessionId } = data; const session = sessionManager.getSession(sessionId); if (session) { const device = deviceManager.getDevice(session.deviceId); const operator = deviceManager.getOperator(session.operatorId); // Уведомляем участников if (device && device.isConnected()) { device.socket.emit('camera:disconnect', { sessionId }); device.removeSession(sessionId); } if (operator && operator.isConnected()) { operator.socket.emit('camera:disconnected', { sessionId }); operator.socket.emit('session:ended', { sessionId: sessionId, deviceId: session.deviceId, reason: 'Device disconnected' }); operator.removeSession(sessionId); } sessionManager.closeSession(sessionId); logger.info(`Camera session ended: ${sessionId}`); } }); // Ping-Pong для проверки соединения socket.on('ping', (data, callback) => { if (callback) { callback({ timestamp: Date.now(), ...data }); } }); // Новые события для ConnectionManager // Завершение подключения от оператора socket.on('connection:terminate', async (data) => { const { connectionId } = data; logger.info(`🔚 Connection termination requested: ${connectionId}`); try { await connectionManager.terminateConnection(connectionId); socket.emit('connection:terminated', { connectionId: connectionId, timestamp: new Date().toISOString() }); logger.info(`✅ Connection terminated: ${connectionId}`); } catch (error) { logger.error(`❌ Failed to terminate connection: ${error.message}`); socket.emit('connection:error', { error: error.message }); } }); // Запрос статистики подключений socket.on('connection:status', (data, callback) => { const stats = connectionManager.getConnectionStats(); if (callback) { callback({ success: true, stats: stats, timestamp: new Date().toISOString() }); } else { socket.emit('connection:status_response', { stats: stats, timestamp: new Date().toISOString() }); } }); // Список активных подключений для оператора socket.on('connection:list', (data, callback) => { const operator = Array.from(deviceManager.operators.values()) .find(op => op.socket === socket); if (!operator) { const error = 'Operator not found'; if (callback) { callback({ success: false, error }); } else { socket.emit('connection:error', { error }); } return; } const connections = connectionManager.getOperatorConnections(operator.operatorId); if (callback) { callback({ success: true, connections: connections, timestamp: new Date().toISOString() }); } else { socket.emit('connection:list_response', { connections: connections, timestamp: new Date().toISOString() }); } }); // Обработка отключения с ConnectionManager socket.on('disconnect', (reason) => { logger.info(`Client disconnected: ${socket.id}, reason: ${reason}`); // Находим устройство или оператора по сокету const device = Array.from(deviceManager.devices.values()) .find(d => d.socket === socket); const operator = Array.from(deviceManager.operators.values()) .find(op => op.socket === socket); if (device) { // Очищаем подключения устройства через ConnectionManager connectionManager.cleanupDeviceConnections(device.deviceId); // Уведомляем операторов об отключении устройства const operators = deviceManager.getConnectedOperators(); operators.forEach(op => { op.socket.emit('device:disconnected', { deviceId: device.deviceId }); }); // Завершаем активные сессии устройства sessionManager.closeDeviceSessions(device.deviceId); deviceManager.disconnectDevice(device.deviceId); } if (operator) { // Очищаем подключения оператора через ConnectionManager connectionManager.cleanupOperatorConnections(operator.operatorId); // Завершаем активные сессии оператора sessionManager.closeOperatorSessions(operator.operatorId); deviceManager.disconnectOperator(operator.operatorId); } }); }); // Периодическая очистка старых сессий и устройств setInterval(() => { try {