Загрузка данных
Теперь картина складывается полностью. И это хорошая новость — ответственность между системами стала понятной.
### Что мы знаем
#### Клиент
* При открытии чата генерирует **sessionId (clientId)**.
* Работает со своим идентификатором сессии.
#### Backend
* При создании/инициализации чата знает соответствие:
* `clientId (sessionId)` ↔ `visitorId (Naumen)`.
* При получении события от Naumen выполняет маппинг и уже понимает, к какой клиентской сессии относится событие.
#### Naumen
Отправляет событие
```json
{
"visitor_id": "2291483841",
"timeout": 10,
"text": "Оператор печатает..."
}
```
---
# Я бы предложила оформить спецификацию так
# SD. Отображение статуса «Оператор печатает»
## 1. Назначение
При получении события от Naumen мобильное приложение отображает пользователю индикатор
> Оператор печатает...
Функция не влияет на историю сообщений и отображается только во время набора текста оператором.
---
# 2. Архитектура взаимодействия
```text
Operator
│
▼
Naumen
│
operator-typing
(visitorId)
│
▼
Backend
│
mapping
visitorId → clientId(sessionId)
│
▼
WebSocket
│
▼
Mobile Client
│
Показать индикатор
```
---
# 3. Поток обработки
### 1.
Оператор начинает вводить сообщение.
↓
### 2.
Naumen публикует событие
```
operator-typing
```
↓
### 3.
Backend получает
```json
{
"visitor_id": "...",
"timeout": 10,
"text": "..."
}
```
↓
### 4.
Backend определяет
```
visitorId
↓
clientId(sessionId)
```
↓
### 5.
Backend отправляет событие соответствующему клиенту по WebSocket.
↓
### 6.
Мобильный клиент отображает
> Оператор печатает...
---
# 4. Контракт Naumen → Backend
Источник
```
STOMP
```
Destination
```
/event:server:operator-typing
```
Payload
| Поле | Тип | Обяз. | Описание |
| ---------- | ------- | ----- | --------------------------------- |
| visitor_id | String | Да | Идентификатор посетителя в Naumen |
| timeout | Integer | Да | Время отображения индикатора |
| text | String | Нет | Текст от Naumen |
Пример
```json
{
"visitor_id":"2291483841",
"timeout":10,
"text":"Оператор печатает..."
}
```
---
# 5. Контракт Backend → Mobile
Поскольку frontend не должен знать про `visitorId`, я бы рекомендовала сделать внутренний контракт уже с идентификатором клиента.
Например:
```json
{
"clientId":"<sessionId>",
"timeout":10
}
```
или
```json
{
"sessionId":"..."
}
```
Поле `text` можно вообще не передавать, так как фронт использует локализованный ресурс.
> **Если же архитектурно принято пробрасывать событие без изменений, это стоит явно указать.**
---
# 6. Логика Backend
Backend обязан:
* принять событие от Naumen;
* выполнить поиск по `visitorId`;
* определить соответствующий `clientId/sessionId`;
* отправить событие только владельцу данной сессии.
---
# 7. Логика Mobile
При получении события:
* показать индикатор;
* использовать локализованный текст
```
Оператор печатает...
```
* не использовать поле `text` из события;
* запустить таймер на `timeout`;
* при повторном событии сбросить таймер;
* при получении нового сообщения скрыть индикатор.
---
# 8. Правила отображения
Показывать если:
* открыт соответствующий чат;
* получено событие typing.
Не показывать:
* если чат закрыт;
* после истечения timeout;
* после получения сообщения оператора.
---
# 9. Edge Cases
| Сценарий | Поведение |
| --------------------- | -------------------------------------------------- |
| Повторное событие | Сбросить таймер |
| timeout истек | Скрыть индикатор |
| Получено сообщение | Скрыть индикатор |
| visitorId не найден | Backend не отправляет событие |
| WebSocket reconnect | Индикатор отображается только после нового события |
| Поле text отсутствует | Используется локализованный текст клиента |
---
# 10. Acceptance Criteria
### AC-1
При получении события
```
operator-typing
```
пользователь видит
> Оператор печатает...
---
### AC-2
Через timeout секунд без новых событий индикатор исчезает.
---
### AC-3
При получении сообщения оператора индикатор исчезает.
---
### AC-4
При повторном событии таймер отображения сбрасывается.
---
## Вопросы, которые я бы еще задала backend
Перед финализацией спецификации стоит уточнить несколько моментов:
1. **Контракт Backend → Mobile.** Будет ли backend пересылать событие как есть (`visitorId`, `timeout`, `text`) или преобразует его в клиентский контракт (`sessionId/clientId`, `timeout`)?
2. **Обработка отсутствия маппинга.** Что происходит, если для `visitorId` не найдено соответствие `clientId`? Событие игнорируется, логируется или возвращается ошибка?
3. **Семантика `timeout`.** Это рекомендуемое время отображения индикатора на клиенте или технический параметр Naumen?
4. **Повторные события.** Подтвердить, что при наборе текста Naumen отправляет несколько событий, а не только одно при начале печати.
---
На мой взгляд, в спецификации стоит **четко разделить два контракта**:
* внешний контракт **Naumen → Backend** (с `visitorId`);
* внутренний контракт **Backend → Mobile** (с `sessionId/clientId`, если backend действительно преобразует событие).
Такое разделение делает документ понятным и снимает вопросы о том, где именно происходит маппинг `visitorId` на клиентскую сессию.