main commit

This commit is contained in:
2025-10-04 11:55:55 +09:00
parent c8c3274527
commit 4ceccae6ce
678 changed files with 95975 additions and 185 deletions

539
backend/test_chunk.js Normal file
View File

@@ -0,0 +1,539 @@
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 {