Загрузка данных


Теперь картина складывается полностью. И это хорошая новость — ответственность между системами стала понятной.

### Что мы знаем

#### Клиент

* При открытии чата генерирует **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` на клиентскую сессию.