# Офлайн-доставка событий

В `gravity_sdk 0.24.0` очередь включена по умолчанию. SDK сохраняет запрос события на устройстве перед отправкой и повторяет доставку при временных ошибках. Это помогает сохранить действия при нестабильной сети и перезапуске приложения.

## Что сохраняется

| Запрос | Постоянная очередь |
|---|---|
| `triggerEvent()` / `triggerEventNoShow()` | Да: запрос `/event`, включая список событий |
| Результат встроенной in-app формы | Да: отправка результата через `/event` |
| `trackView()` / `trackViewNoShow()` | Нет |
| `getContentBy*`, inline, tooltip | Нет |
| Content/product engagement и tracking URL | Нет |

Запись соответствует **запросу**, а не одному событию: список `events` может включать несколько действий. Повторные попытки сетевых запросов и постоянная очередь — разные механизмы.

## Настройки

```dart
GravitySDK.instance.setOptions(
  offlineQueue: const OfflineQueueSettings(
    enabled: true,
    maxEntries: 500,
    maxAge: Duration(days: 7),
  ),
);
```

| Настройка | По умолчанию | Поведение |
|---|---|---|
| `enabled` | `true` | Разрешает добавление и отправку из очереди |
| `maxEntries` | `500` | При переполнении старейшие записи удаляются |
| `maxAge` | `7 дней` | Просроченные записи удаляются вместо отправки |

`maxEntries` должен быть положительным. Для отключения задайте `OfflineQueueSettings(enabled: false)`. Уже сохранённые записи остаются на диске, но не отправляются, пока очередь снова не включена. Отключение не равно очистке.

Если хотите изменить настройки до первой отправки старых записей, вызовите `setOptions()` перед `initialize()`. Не переключайте ключ/секцию для накопленных запросов одной и той же локальной установки: доставка и очистка очереди должны соответствовать выбранному окружению.

## Когда повторяется отправка

SDK пытается доставить очередь:

- при запуске после `initialize()`;
- при возвращении приложения на передний план;
- после успешного онлайн-запроса, который показывает, что сеть снова доступна;
- по таймеру повторных попыток;
- после включения очереди или ручного `flushQueue()`.

Очередь не устанавливает отдельный монитор подключения ОС. Если приложение уже следит за сетью, из его callback можно вызвать:

```dart
await GravitySDK.instance.flushQueue();
final int pending = await GravitySDK.instance.pendingDeliveries;
```

`flushQueue()` дожидается опустошения очереди либо остановки из-за ошибки; его завершение не гарантирует, что всё доставлено. При отключённой очереди это no-op. `pendingDeliveries` показывает число сохранённых ожидающих запросов, включая находящиеся в отправке, а не число отдельных событий.

Это не системная фоновая служба. Когда приложение не работает, SDK сам отправку не выполняет. Восстановление сети без пробуждения приложения не гарантирует немедленную доставку.

## Время и пользователь

Если `eventTime` задан, SDK сохраняет это время. Иначе фиксирует время вызова события. Повторная доставка не заменяет его временем отправки.

Для записи сохраняется исходная идентификация пользователя, чтобы событие предыдущего аккаунта не приписалось новому. Не сбрасывайте профиль ради повторной отправки и не пересоздавайте уже поставленное в очередь событие при каждом пробуждении приложения.

Результаты отложенной доставки не запускают запоздалый показ in-app. Очередь доставляет события; получение актуального контента для нового экрана — отдельный запрос.

## Очистка

```dart
await GravitySDK.instance.clearQueue();
```

Метод удаляет ожидающие запросы без восстановления; работает и при отключённой очереди. Вызывайте его только когда приложение действительно должно отказаться от доставки этих действий, например на общем устройстве по правилам удаления данных.

`resetUser()` сам очередь не очищает. Последовательность выхода с очисткой:

```dart
await GravitySDK.instance.clearQueue();
await GravitySDK.instance.resetUser();
```

На время операции остановите создание новых событий. Уже отправленный в сеть запрос не отменяется; ожидающий сессию запрос из очищаемой очереди не должен уйти позже. Если хранилище отказало в очистке, `clearQueue()` выбрасывает исключение — не считайте данные удалёнными.

## Ограничения и повторная доставка

Временные сетевые ошибки и ответы `408`/`429`/`5xx` допускают повторные попытки. Другие `4xx`, ошибки парсинга и отмена не исправляются обычным повтором. Серверные ошибки имеют ограниченное число попыток; постоянная недоставка не хранится бесконечно.

Если сервер обработал событие, но ответ потерялся, возможен дубль. SDK не обещает exactly-once. Для покупки используйте постоянный уникальный `uniqueTransactionId` заказа. Не генерируйте новый ID при повторной отправке.

Переполнение, истечение срока, очистка и ошибки локального хранилища могут привести к потере записи. Нулевой размер очереди сам по себе не доказывает успешную доставку всех действий. Проверяйте диагностику и события на стороне сервера.

## Проверка на устройстве

1. Отправьте покупку при недоступной сети с уникальным ID тестового заказа.
2. Проверьте, что число ожидающих запросов увеличилось.
3. Перезапустите приложение без сети: запись должна остаться, если не истёк срок хранения.
4. Восстановите соединение и вызовите `flushQueue()`.
5. Проверьте доставку с исходным временем и пользователем.
6. Повторите со сменой аккаунта; отдельно проверьте явную очистку.
7. Убедитесь, что старые in-app сообщения после доставки не появляются.

Для формы аналогично проверяется доставка ответа, но её tracking URL сами в постоянной очереди не сохраняются.
