Files
DLE/backend/wsHub.js

312 lines
9.2 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] Новое подключение');
console.log('🔌 [WebSocket] IP клиента:', req.socket.remoteAddress);
console.log('🔌 [WebSocket] User-Agent:', req.headers['user-agent']);
console.log('🔌 [WebSocket] Origin:', req.headers.origin);
// Добавляем клиента в общий список
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);
}
if (data.type === 'ping') {
// Отправляем pong ответ
ws.send(JSON.stringify({
type: 'pong',
timestamp: data.timestamp
}));
}
} catch (error) {
console.error('❌ [WebSocket] Ошибка парсинга сообщения:', error);
}
});
ws.on('close', (code, reason) => {
console.log('🔌 [WebSocket] Соединение закрыто', { code, reason: reason.toString() });
// Удаляем клиента из всех списков
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.message);
});
});
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) {
console.log('🔔 [WebSocket] Отправляем уведомление об обновлении тегов');
const message = JSON.stringify({
type: 'tags-updated',
timestamp: Date.now()
});
// Отправляем всем подключенным клиентам
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
console.log('🔔 [WebSocket] Отправляем tags-updated клиенту');
client.send(message);
}
});
}
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)
};
}
// Функция для отправки уведомлений о статусе AI
function broadcastAIStatus(status) {
console.log('📢 [WebSocket] Отправка статуса AI всем клиентам');
for (const [userId, clients] of wsClients.entries()) {
for (const ws of clients) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({
type: 'ai-status',
status
}));
}
}
}
}
module.exports = {
initWSS,
broadcastContactsUpdate,
broadcastMessagesUpdate,
broadcastChatMessage,
broadcastConversationUpdate,
broadcastTableUpdate,
broadcastTableRelationsUpdate,
broadcastTagsUpdate,
broadcastAIStatus,
getConnectedUsers,
getStats
};