# Миграция на единое WebSocket соединение ## Что изменилось ### До (старая архитектура - 2 WebSocket): ``` Пользователь → 2 WebSocket соединения: ├─ ws://host/ws/chats/ (глобальный - для списка чатов и бейджа) └─ ws://host/ws/chat/123/ (конкретный чат - только если открыт) ``` **Проблемы:** - Двойная подписка на группы `chat_{id}` - Дублирование событий - Больше нагрузка на сервер - Сложность управления ### После (новая архитектура - 1 WebSocket): ``` Пользователь → 1 WebSocket соединение: └─ ws://host/ws/ (универсальное для пользователя) ├─ Чаты: список + активный чат ├─ Уведомления (звонки, события, задачи) ├─ Бейджи в sidebar ├─ Онлайн-статус └─ Другие real-time события ``` **Преимущества:** - ✅ Одно соединение вместо двух - ✅ Нет дублирования событий - ✅ Меньше нагрузка на сервер (50% соединений) - ✅ Проще отладка - ✅ Быстрее переключение между чатами - ✅ Универсальное для всех типов обновлений - ✅ Масштабируемость (легко добавить новые типы событий) ## Изменения в файлах ### Backend #### 1. `communications/user_consumer.py` (НОВЫЙ) Универсальный Consumer для всех real-time операций пользователя. **Назначение:** - 🔵 **Чаты**: подписка на все чаты, отслеживание активного - 🔔 **Уведомления**: (будущее) звонки, события, задачи - 🔢 **Бейджи**: обновление счетчиков в sidebar - 🟢 **Статусы**: (будущее) онлайн/офлайн пользователей - 📅 **События**: (будущее) календарь, документы и др. **Ключевые особенности:** - Подписывается на все чаты пользователя при подключении - Отслеживает активный чат (`active_chat_id`) - Отправляет полные данные для активного чата - Отправляет компактные данные для обновления списка - Управляется через действия: `open_chat`, `close_chat` #### 2. `communications/routing.py` (ОБНОВЛЕН) ```python # Универсальный WebSocket endpoint re_path(r"^ws/$", UserConsumer.as_asgi(), name="ws_user") ``` ### Frontend #### 3. `static/js/components/userWebSocket.js` (НОВЫЙ) Универсальный клиентский модуль для WebSocket пользователя. **API:** ```javascript const api = { // Управление чатом openChat(chatId, loadHistory), closeChat(chatId), // Отправка сообщений sendMessage(content), // Реакции addReaction(messageId, emoji), removeReaction(messageId, emoji), // Редактирование editMessage(messageId, content), deleteMessage(messageId), // Отметка прочитанного markRead(chatId), // Голосование votePoll(pollId, optionIds), // Состояние getActiveChatId(), isConnected() }; ``` #### 4. `templates/base.html` (ОБНОВЛЕН) - Заменен `initChatWebSocket` на `initUnifiedChatWebSocket` - Удален отдельный глобальный WebSocket для списка чатов - Обновлен `initSidebarBadge` для работы с событиями из единого WS ## Протокол WebSocket ### Клиент → Сервер (действия) ```javascript // Открыть чат { action: "open_chat", chat_id: 123, load_history: true } // Закрыть чат { action: "close_chat", chat_id: 123 } // Отправить сообщение { action: "send_message", content: "Привет!" } // Добавить реакцию { action: "add_reaction", message_id: 456, emoji: "👍" } // Индикатор печати { action: "typing" } // Отметить прочитанным { action: "mark_read", chat_id: 123 } ``` ### Сервер → Клиент (события) ```javascript // Новое сообщение (полные данные для активного чата) { type: "new_message", chat_id: 123, message: { id, content, author_id, ... } } // Обновление списка (компактные данные для всех чатов) { type: "list_update", chat_id: 123, message: { id, content, author_id, created_ts } } // Реакция добавлена { type: "reaction_added", chat_id: 123, message_id: 456, emoji: "👍", user_id: 789 } // Пользователь печатает { type: "typing_start", chat_id: 123, user_id: 789, user_name: "Иван" } // Ping для keepalive { type: "ping", timestamp: "2025-12-01T12:00:00Z" } ``` ## Логика работы ### При загрузке страницы: 1. **Любая страница (авторизованный пользователь):** - Создается единое WebSocket соединение: `ws://host/ws/user/` - Сервер подписывает на все чаты пользователя - Начинается прослушивание событий 2. **Страница чата (/chats/123/):** - WebSocket уже открыт - Клиент отправляет `open_chat` с `chat_id: 123` - Сервер устанавливает `active_chat_id = 123` - Сервер отправляет начальную историю (если запрошена) - Клиент начинает получать полные данные сообщений 3. **Переход к другому чату:** - Клиент отправляет `close_chat` для текущего - Клиент отправляет `open_chat` для нового - WebSocket остается открытым! ### При получении нового сообщения: **На сервере (`UnifiedChatConsumer.chat_message`):** ```python chat_id = event['chat_id'] # Если это активный чат - полные данные if chat_id == self.active_chat_id: send_json({"type": "new_message", "message": full_data}) # Для списка - компактные данные send_json({"type": "list_update", "message": compact_data}) ``` **На клиенте:** ```javascript // new_message - добавляем в DOM активного чата handleNewMessage(data) { const msgEl = createMessageElement(data.message); scrollEl.appendChild(msgEl); } // list_update - обновляем карточку чата + бейдж handleListUpdate(data) { updateChatCard(data.chat_id, data.message); badgeManager.incrementChat(data.chat_id); } ``` ## Обратная совместимость Для плавной миграции: 1. **Старые endpoints закомментированы**, но не удалены 2. **API совместимо**: `window.chatWebSocketApi` и `window.chatWebSocket` работают 3. **События совместимы**: `chat:ws-ready`, `chat:list-update` и др. ## План полной миграции ### Этап 1: ✅ Создание (выполнено) - [x] Создан `UnifiedChatConsumer` - [x] Создан `unifiedChatWebSocket.js` - [x] Обновлен routing - [x] Обновлен base.html ### Этап 2: Тестирование - [ ] Проверить работу в разных браузерах - [ ] Проверить переключение между чатами - [ ] Проверить обновление бейджа - [ ] Проверить индикатор "печатает..." - [ ] Проверить реакции и редактирование ### Этап 3: Очистка - [ ] Удалить `ChatConsumer` и `ChatListConsumer` - [ ] Удалить `chatWebSocket.js` - [ ] Удалить старые endpoints из routing - [ ] Обновить документацию ## Тестирование ### Проверка соединения: ```javascript // В консоли браузера console.log(window.chatWebSocketApi); console.log(window.chatWebSocketApi.isConnected()); // true // Открыть чат window.chatWebSocketApi.openChat(123, true); // Отправить сообщение window.chatWebSocketApi.sendMessage("Тест"); ``` ### Проверка в Network DevTools: 1. Открыть DevTools → Network → WS 2. Найти соединение `ws/user/` 3. Проверить входящие/исходящие сообщения 4. Убедиться, что только одно WebSocket соединение активно ## Производительность ### До: ``` 1 пользователь = 2 WebSocket (если открыт чат) или 1 (если только список) 100 пользователей с открытыми чатами = 200 WebSocket соединений ``` ### После: ``` 1 пользователь = 1 WebSocket (всегда) 100 пользователей = 100 WebSocket соединений ``` **Экономия: 50% соединений!** ## Известные ограничения 1. **История сообщений**: Загружается только при `load_history: true` 2. **Переподключение**: Автоматическое переподключение до 5 попыток 3. **Keepalive**: Ping каждые 20 секунд (можно настроить) ## Troubleshooting ### Проблема: WebSocket не подключается **Решение:** ```javascript // Проверить URL console.log(location.host); // должен быть правильный хост // Проверить авторизацию console.log(window.currentUserId); // должен быть ID пользователя ``` ### Проблема: Сообщения не появляются **Решение:** ```javascript // Проверить активный чат console.log(window.chatWebSocketApi.getActiveChatId()); // должен быть chat_id // Переоткрыть чат const chatId = parseInt(document.getElementById('chatScroll').dataset.chatId); window.chatWebSocketApi.closeChat(chatId); window.chatWebSocketApi.openChat(chatId, true); ``` ### Проблема: Бейдж не обновляется **Решение:** Проверить, что события диспатчатся: ```javascript window.addEventListener('chat:list-update', (e) => { console.log('List update:', e.detail); }); ``` ## Дополнительные улучшения (будущее) 1. **Redis для typing status** - кэширование статуса печати 2. **Compression** - сжатие WebSocket сообщений 3. **Binary protocol** - для вложений 4. **Presence** - онлайн статус пользователей 5. **Read receipts** - уведомления о прочтении 6. **Message queue** - очередь для offline сообщений --- **Статус: READY FOR TESTING** **Дата: 1 декабря 2025 г.** **Автор: IgorNadein**