main commit
This commit is contained in:
539
backend/test_chunk.js
Normal file
539
backend/test_chunk.js
Normal 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 {
|
||||
Reference in New Issue
Block a user