# Push-уведомления

Провайдеры с активным Premium могут рассылать push-уведомления своим подписчикам. Все сообщения проходят модерацию в INCY, кроме случая отправки на собственное устройство через [admin HWID](broken://pages/47e85f955b42ebe13c171895b9458c40537503a8).

***

## Жизненный цикл уведомления

{% stepper %}
{% step %}

### Создание

Провайдер жмёт «Отправить» в панели → запись кладётся в коллекцию `pendingNotifications` со статусом `pending`.
{% endstep %}

{% step %}

### Модерация

Бот INCY показывает заявку админу; тот нажимает «Одобрить» или «Отклонить с комментарием».
{% endstep %}

{% step %}

### Fan-out

При одобрении сервер выбирает устройства по `targetSegment`, раскладывает метаданные на их device-документы (для десктопного polling) и шлёт FCM-пуш на каждое устройство, у которого есть `fcmToken`.
{% endstep %}

{% step %}

### Статусы

Провайдер видит в панели статус и счётчики `deliveredCount` / `failedCount`.
{% endstep %}
{% endstepper %}

### Статусы записи

| Статус      | Значение                                                             |
| ----------- | -------------------------------------------------------------------- |
| `pending`   | Ждёт модерации                                                       |
| `approved`  | Одобрено модератором (или auto-approved); FCM-рассылка выполнена     |
| `rejected`  | Отклонено модератором. `moderatorComment` содержит причину           |
| `cancelled` | Провайдер отменил до одобрения. Рассылка не выполнялась              |
| `failed`    | Одобрено, но fan-out упал (нет верифицированных доменов / FCM error) |

***

## Отправка: поля уведомления

| Поле            | Тип       | Обязательное | Описание                                                     |
| --------------- | --------- | :----------: | ------------------------------------------------------------ |
| `title`         | `string`  |      да      | Заголовок, ≤ 100 символов                                    |
| `body`          | `string`  |      да      | Текст, ≤ 500 символов                                        |
| `image`         | `string?` |      нет     | URL большого изображения (отрисовывается в развёрнутом пуше) |
| `url`           | `string?` |      нет     | URL для кнопки / тапа по уведомлению                         |
| `urlButtonName` | `string?` |      нет     | Подпись кнопки. По умолчанию «Открыть»                       |
| `forceTimer`    | `number?` |      нет     | Enterprise-only: показать модальный диалог на N сек (1-10)   |
| `targetSegment` | `object`  |      да      | Параметры таргетинга (ниже)                                  |

### Таргетинг (`targetSegment`)

| Поле         | Тип         | Описание                                                                            |
| ------------ | ----------- | ----------------------------------------------------------------------------------- |
| `platform`   | `string`    | `all` \| `ios` \| `android` \| `linux` \| `windows` \| `macos`                      |
| `region`     | `string[]?` | Локаль устройства (например `["ru", "by"]`). Сравнение case-insensitive             |
| `domain`     | `string?`   | Конкретный домен подписки провайдера (если у провайдера их несколько)               |
| `activeDays` | `number?`   | Только устройствам, видимо активным за последние N дней                             |
| `appVersion` | `string?`   | Только конкретная версия приложения (например `"2.5.6"`)                            |
| `hwid`       | `string?`   | Конкретный HWID. Включает [admin-HWID auto-approve](#auto-approve-через-admin-hwid) |

Все фильтры объединяются логическим **И**. Пустые / неуказанные поля не ограничивают рассылку.

### Доставка

* **Android / iOS** получают пуш через FCM.
* **Desktop (Linux / Windows)** опрашивает собственный документ устройства в Firestore и забирает поля `pendingNotificationTitle`, `pendingNotificationBody`, `pendingNotificationAt`, `pendingNotificationUrl`, `pendingNotificationUrlButtonName`, `pendingNotificationForceTimer` — раздельно от FCM, поскольку десктоп без FCM.

### Ограничения

* Только для доменов с `isVerified: true` у провайдера.
* Только при активном Premium (`isPremium: true` в Firestore).
* `forceTimer` > 0 работает только на Enterprise-тарифе.

***

## Auto-approve через admin HWID

Если `targetSegment.hwid` совпадает с одним из `adminHwids` любого верифицированного домена провайдера — запись создаётся сразу со статусом `approved`, модерация пропускается, FCM уходит немедленно.

Подробности: [admin-hwids.md](broken://pages/47e85f955b42ebe13c171895b9458c40537503a8).

Это единственный путь, по которому провайдер может отправить уведомление в обход модерации.

***

## Отмена рассылки

Пока статус `pending`, провайдер может отменить уведомление в панели. В результате:

* Статус меняется на `cancelled`.
* Если модератор в этот момент решит «Одобрить» — бот получит ответ «Провайдер отменил рассылку» и FCM не выполнится.

После перевода в любой терминальный статус (`approved` / `rejected` / `cancelled` / `failed`) — операция необратима.

***

## Аудит

Каждая запись в `pendingNotifications` хранит:

| Поле                | Описание                                                     |
| ------------------- | ------------------------------------------------------------ |
| `providerId`        | UID провайдера                                               |
| `providerEmail`     | email провайдера на момент отправки                          |
| `createdAt`         | Когда провайдер нажал «Отправить»                            |
| `moderatedAt`       | Когда модератор решил / система auto-approved                |
| `moderatorComment`  | Текст отказа (для `rejected`)                                |
| `autoApproved`      | `true` если миновало модерацию                               |
| `autoApproveReason` | Причина, например `"adminHwid"`                              |
| `cancelledAt`       | Для `cancelled` — когда отменил провайдер                    |
| `cancelledBy`       | Источник отмены (сейчас только `"provider"`)                 |
| `deliveredCount`    | Число успешно отправленных пушей + desktop-polling устройств |
| `failedCount`       | FCM-ошибки                                                   |


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://incy.gitbook.io/docs/premium-api/push-uvedomleniya.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
