Files
god_eye/backend/test_chunk.js
2025-10-04 11:55:55 +09:00

540 lines
20 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 {