Files
DLE/backend/wsHub.js

312 lines
9.0 KiB
JavaScript
Raw 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.

/**
* Copyright (c) 2024-2025 Тарабанов Александр Викторович
* All rights reserved.
*
* This software is proprietary and confidential.
* Unauthorized copying, modification, or distribution is prohibited.
*
* For licensing inquiries: info@hb3-accelerator.com
* Website: https://hb3-accelerator.com
* GitHub: https://github.com/HB3-ACCELERATOR
*/
const WebSocket = require('ws');
let wss = null;
// Храним клиентов по userId для персонализированных уведомлений
const wsClients = new Map(); // userId -> Set of WebSocket connections
// Кэш для отслеживания изменений тегов
const tagsChangeCache = new Map();
const TAGS_CACHE_TTL = 5000; // 5 секунд
function initWSS(server) {
wss = new WebSocket.Server({ server, path: '/ws' });
wss.on('connection', (ws, req) => {
console.log('🔌 [WebSocket] Новое подключение');
// Добавляем клиента в общий список
if (!wsClients.has('anonymous')) {
wsClients.set('anonymous', new Set());
}
wsClients.get('anonymous').add(ws);
// Обработка сообщений от клиента
ws.on('message', (message) => {
try {
const data = JSON.parse(message);
console.log('📨 [WebSocket] Получено сообщение:', data);
if (data.type === 'auth' && data.userId) {
// Аутентификация пользователя
authenticateUser(ws, data.userId);
}
} catch (error) {
console.error('❌ [WebSocket] Ошибка парсинга сообщения:', error);
}
});
ws.on('close', () => {
console.log('🔌 [WebSocket] Соединение закрыто');
// Удаляем клиента из всех списков
for (const [userId, clients] of wsClients.entries()) {
clients.delete(ws);
if (clients.size === 0) {
wsClients.delete(userId);
}
}
});
ws.on('error', (error) => {
console.error('❌ [WebSocket] Ошибка соединения:', error);
});
});
console.log('🚀 [WebSocket] Сервер запущен на /ws');
}
function authenticateUser(ws, userId) {
console.log(`🔐 [WebSocket] Аутентификация пользователя ${userId}`);
// Удаляем из анонимных
if (wsClients.has('anonymous')) {
wsClients.get('anonymous').delete(ws);
}
// Добавляем в список пользователя
if (!wsClients.has(userId.toString())) {
wsClients.set(userId.toString(), new Set());
}
wsClients.get(userId.toString()).add(ws);
// Отправляем подтверждение
ws.send(JSON.stringify({
type: 'auth-success',
userId: userId
}));
}
function broadcastContactsUpdate() {
console.log('📢 [WebSocket] Отправка обновления контактов всем клиентам');
for (const [userId, clients] of wsClients.entries()) {
for (const ws of clients) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'contacts-updated' }));
}
}
}
}
function broadcastMessagesUpdate() {
console.log('📢 [WebSocket] Отправка обновления сообщений всем клиентам');
for (const [userId, clients] of wsClients.entries()) {
for (const ws of clients) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'messages-updated' }));
}
}
}
}
function broadcastChatMessage(message, targetUserId = null) {
console.log(`📢 [WebSocket] Отправка сообщения чата`, {
messageId: message.id,
targetUserId
});
if (targetUserId) {
// Отправляем конкретному пользователю
const userClients = wsClients.get(targetUserId.toString());
if (userClients) {
for (const ws of userClients) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({
type: 'chat-message',
message
}));
}
}
}
} else {
// Отправляем всем
for (const [userId, clients] of wsClients.entries()) {
for (const ws of clients) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({
type: 'chat-message',
message
}));
}
}
}
}
}
function broadcastConversationUpdate(conversationId, targetUserId = null) {
console.log(`📢 [WebSocket] Отправка обновления диалога`, {
conversationId,
targetUserId
});
const payload = {
type: 'conversation-updated',
conversationId
};
if (targetUserId) {
// Отправляем конкретному пользователю
const userClients = wsClients.get(targetUserId.toString());
if (userClients) {
for (const ws of userClients) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(payload));
}
}
}
} else {
// Отправляем всем
for (const [userId, clients] of wsClients.entries()) {
for (const ws of clients) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(payload));
}
}
}
}
}
function broadcastTableUpdate(tableId) {
console.log('📢 [WebSocket] Отправка обновления таблицы', tableId);
const payload = { type: 'table-updated', tableId };
for (const [userId, clients] of wsClients.entries()) {
for (const ws of clients) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(payload));
}
}
}
}
function broadcastTableRelationsUpdate(tableId, rowId, targetUserId = null) {
console.log(`📢 [WebSocket] Отправка обновления связей таблицы`, {
tableId,
rowId,
targetUserId
});
const payload = {
type: 'table-relations-updated',
tableId,
rowId
};
if (targetUserId) {
// Отправляем конкретному пользователю
const userClients = wsClients.get(targetUserId.toString());
if (userClients) {
for (const ws of userClients) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(payload));
}
}
}
} else {
// Отправляем всем
for (const [userId, clients] of wsClients.entries()) {
for (const ws of clients) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(payload));
}
}
}
}
}
function broadcastTagsUpdate(targetUserId = null) {
const now = Date.now();
const cacheKey = targetUserId || 'global';
// Проверяем, не отправляли ли мы недавно уведомление
const lastUpdate = tagsChangeCache.get(cacheKey);
if (lastUpdate && (now - lastUpdate) < TAGS_CACHE_TTL) {
console.log(`🏷️ [WebSocket] Пропускаем отправку уведомления о тегах (слишком часто)`, { targetUserId });
return;
}
// Обновляем кэш
tagsChangeCache.set(cacheKey, now);
console.log(`🏷️ [WebSocket] Отправка обновления тегов`, { targetUserId });
const payload = {
type: 'tags-updated',
timestamp: now
};
if (targetUserId) {
// Отправляем конкретному пользователю
const userClients = wsClients.get(targetUserId.toString());
if (userClients) {
for (const ws of userClients) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(payload));
}
}
}
} else {
// Отправляем всем
for (const [userId, clients] of wsClients.entries()) {
for (const ws of clients) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(payload));
}
}
}
}
}
function getConnectedUsers() {
const users = [];
for (const [userId, clients] of wsClients.entries()) {
if (userId !== 'anonymous' && clients.size > 0) {
users.push({
userId: parseInt(userId),
connections: clients.size
});
}
}
return users;
}
function getStats() {
let totalConnections = 0;
let anonymousConnections = 0;
for (const [userId, clients] of wsClients.entries()) {
if (userId === 'anonymous') {
anonymousConnections = clients.size;
}
totalConnections += clients.size;
}
return {
totalConnections,
anonymousConnections,
authenticatedUsers: getConnectedUsers(),
totalUsers: wsClients.size - (wsClients.has('anonymous') ? 1 : 0)
};
}
module.exports = {
initWSS,
broadcastContactsUpdate,
broadcastMessagesUpdate,
broadcastChatMessage,
broadcastConversationUpdate,
broadcastTableUpdate,
broadcastTableRelationsUpdate,
broadcastTagsUpdate,
getConnectedUsers,
getStats
};