✨ Features: - Migrated ALL files to new S3 structure (Projects, Contacts, Accounts, HelpDesk, Invoice, etc.) - Added Nextcloud folder buttons to ALL modules - Fixed Nextcloud editor integration - WebSocket server for real-time updates - Redis Pub/Sub integration - File path manager for organized storage - Redis caching for performance (Functions.php) 📁 New Structure: Documents/Project/ProjectName_ID/file_docID.ext Documents/Contacts/FirstName_LastName_ID/file_docID.ext Documents/Accounts/AccountName_ID/file_docID.ext 🔧 Technical: - FilePathManager for standardized paths - S3StorageService integration - WebSocket server (Node.js + Docker) - Redis cache for getBasicModuleInfo() - Predis library for Redis connectivity 📝 Scripts: - Migration scripts for all modules - Test pages for WebSocket/SSE/Polling - Documentation (MIGRATION_*.md, REDIS_*.md) 🎯 Result: 15,000+ files migrated successfully!
161 lines
4.7 KiB
JavaScript
161 lines
4.7 KiB
JavaScript
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`);
|
|
|
|
|
|
|
|
|