const WebSocket = require('ws'); const redis = require('redis'); // Конфигурация const REDIS_HOST = process.env.REDIS_HOST || 'host.docker.internal'; const REDIS_PORT = process.env.REDIS_PORT || 6379; const REDIS_PASSWORD = process.env.REDIS_PASSWORD || 'CRM_Redis_Pass_2025_Secure!'; const WS_PORT = process.env.WS_PORT || 3000; const REDIS_CHANNEL = 'crm:file:events'; console.log('🚀 Starting CRM WebSocket Server...'); console.log(`📡 Redis: ${REDIS_HOST}:${REDIS_PORT}`); console.log(`🔌 WebSocket: 0.0.0.0:${WS_PORT}`); console.log(`📢 Channel: ${REDIS_CHANNEL}`); // Создаем WebSocket сервер const wss = new WebSocket.Server({ port: WS_PORT, perMessageDeflate: false }); // Подключаемся к Redis для Pub/Sub const subscriber = redis.createClient({ socket: { host: REDIS_HOST, port: REDIS_PORT }, password: REDIS_PASSWORD }); subscriber.on('error', (err) => { console.error('❌ Redis Subscriber Error:', err); }); subscriber.on('connect', () => { console.log('✅ Redis Subscriber connected'); }); // Подключаемся и подписываемся на канал (async () => { try { await subscriber.connect(); await subscriber.subscribe(REDIS_CHANNEL, (message) => { console.log(`📨 Received from Redis: ${message.substring(0, 100)}...`); // Отправляем всем WebSocket клиентам let sentCount = 0; wss.clients.forEach((client) => { if (client.readyState === WebSocket.OPEN) { client.send(message); sentCount++; } }); console.log(`📤 Sent to ${sentCount} WebSocket clients`); }); console.log(`✅ Subscribed to Redis channel: ${REDIS_CHANNEL}`); } catch (err) { console.error('❌ Failed to connect to Redis:', err); process.exit(1); } })(); // WebSocket сервер wss.on('connection', (ws, req) => { const clientIp = req.socket.remoteAddress; console.log(`🔗 New WebSocket connection from ${clientIp}`); console.log(`👥 Total clients: ${wss.clients.size}`); // Отправляем приветственное сообщение ws.send(JSON.stringify({ type: 'connected', data: { message: 'Connected to CRM WebSocket Server', channel: REDIS_CHANNEL, timestamp: Date.now() } })); // Heartbeat ws.isAlive = true; ws.on('pong', () => { ws.isAlive = true; }); // Обработка сообщений от клиента ws.on('message', (message) => { console.log(`📩 Message from client: ${message}`); try { const data = JSON.parse(message); // Обработка ping if (data.type === 'ping') { ws.send(JSON.stringify({ type: 'pong', timestamp: Date.now() })); } } catch (err) { console.error('❌ Invalid message format:', err); } }); // Обработка закрытия соединения ws.on('close', (code, reason) => { console.log(`🔌 WebSocket disconnected: ${code} - ${reason}`); console.log(`👥 Total clients: ${wss.clients.size}`); }); // Обработка ошибок ws.on('error', (err) => { console.error('❌ WebSocket error:', err); }); }); // Heartbeat для проверки живых соединений const heartbeat = setInterval(() => { wss.clients.forEach((ws) => { if (ws.isAlive === false) { console.log('💔 Terminating dead connection'); return ws.terminate(); } ws.isAlive = false; ws.ping(); }); }, 30000); // Каждые 30 секунд // Обработка завершения wss.on('close', () => { clearInterval(heartbeat); subscriber.quit(); console.log('🛑 WebSocket server stopped'); }); // Обработка сигналов завершения process.on('SIGTERM', () => { console.log('🛑 SIGTERM received, closing server...'); wss.close(() => { subscriber.quit(); process.exit(0); }); }); process.on('SIGINT', () => { console.log('🛑 SIGINT received, closing server...'); wss.close(() => { subscriber.quit(); process.exit(0); }); }); console.log('✅ WebSocket server started successfully!'); console.log(`🎯 Ready to receive events from Redis and broadcast to ${wss.clients.size} clients`);