# Premium API

### Обзор

Приложение при добавлении подписки извлекает домен из URL и запрашивает конфигурацию провайдера через зашифрованный API. Домен никогда не передаётся в открытом виде — используется SHA-256 хеш.

***

### API

#### Endpoint

```
GET /api/subscription/config?h=<sha256hex>&hwid=<sha256hex>
```

#### Запрос

| Параметр | Тип    | Обязательный | Описание                                                                    |
| -------- | ------ | ------------ | --------------------------------------------------------------------------- |
| `h`      | string | да           | SHA-256 хеш домена (64 hex-символа)                                         |
| `hwid`   | string | нет          | SHA-256 хеш HWID устройства (64 hex-символа). Для проверки лимита устройств |

Хеш вычисляется от домена в нижнем регистре:

```
SHA256("example.com") → "a379a6f6eeafb9a55e378c118034e2751e682fab9f2d30ab13d2125586ce1947"
```

#### Ответ

Сервер всегда возвращает зашифрованный ответ:

```json
{
    "encrypted": true,
    "iv": "<base64>",
    "data": "<base64>",
    "tag": "<base64>"
}
```

#### Поля ответа (после расшифровки)

| Поле                  | Тип     | Описание                                                   |
| --------------------- | ------- | ---------------------------------------------------------- |
| `success`             | boolean | Успешность запроса                                         |
| `domain`              | string  | Домен подписки                                             |
| `isPremium`           | boolean | Активен ли Premium у провайдера                            |
| `deviceLimitExceeded` | boolean | Лимит устройств провайдера превышен (см. Лимиты устройств) |
| `logoUrl`             | string? | URL логотипа провайдера                                    |
| `settings`            | object  | Настройки провайдера (см. ниже)                            |
| `theme`               | object  | Кастомная тема (см. ниже)                                  |

#### Rate Limit

30 запросов в минуту на IP-адрес. При превышении — ответ `429`.

***

### Шифрование

Ответы API зашифрованы алгоритмом AES-256-GCM. Ключ шифрования деривируется на основе домена — только клиент, знающий оригинальный домен и комбинацию, может расшифровать ответ.

Компоненты ответа:

| Поле   | Описание                                |
| ------ | --------------------------------------- |
| `iv`   | Initialization vector (base64, 12 байт) |
| `data` | Зашифрованные данные (base64)           |
| `tag`  | Authentication tag (base64, 16 байт)    |

> Детали деривации ключа и реализация шифрования являются внутренними и не публикуются.

***

### Структура конфигурации

#### Настройки провайдера (`settings`)

**Базовые**

| Поле                    | Тип     | Описание                                                       |
| ----------------------- | ------- | -------------------------------------------------------------- |
| `serverDescription`     | string  | Описание сервера (до 30 символов)                              |
| `alwaysHwidEnable`      | boolean | Принудительная отправка HWID (пользователь не может отключить) |
| `expiryNotifications`   | boolean | Локальные уведомления об истечении подписки                    |
| `showServerDescription` | boolean | Показывать описание серверов. По умолчанию `true`              |

**Domain fronting и фрагментация**

| Поле               | Тип     | Описание                                    |
| ------------------ | ------- | ------------------------------------------- |
| `resolveAddress`   | string  | IP для domain fronting                      |
| `hostHeader`       | string  | Host-заголовок для domain fronting          |
| `fragmentEnabled`  | boolean | TCP-фрагментация в hev-socks5-tunnel        |
| `fragmentLength`   | string  | Диапазон длины фрагментов (напр. `"10-30"`) |
| `fragmentInterval` | string  | Диапазон интервала (напр. `"20-40"` мс)     |
| `fragmentPackets`  | string  | Количество фрагментов (напр. `"5-10"`)      |

**DNS-резолв адреса сервера (DoH)**

| Поле                            | Тип     | Описание                                                              |
| ------------------------------- | ------- | --------------------------------------------------------------------- |
| `serverAddressResolveEnable`    | boolean | Предварительный резолв адреса сервера через DoH                       |
| `serverAddressResolveDnsDomain` | string  | URL DoH-сервера (напр. `https://common.dot.dns.yandex.net/dns-query`) |
| `serverAddressResolveDnsIp`     | string  | IP DoH-сервера (используется до резолва его домена)                   |

**Lite Mode**

Упрощённый интерфейс с ссылками на бот, канал, поддержку и (опционально) премиум. Подробнее про иконки — icon-presets.md.

| Поле             | Тип     | Описание                                                |
| ---------------- | ------- | ------------------------------------------------------- |
| `liteMode`       | boolean | Включить упрощённый режим                               |
| `liteBigButton`  | boolean | Большая кнопка подключения                              |
| `botUrl`         | string? | Ссылка на Telegram-бот                                  |
| `channelUrl`     | string? | Ссылка на Telegram-канал                                |
| `supportUrl`     | string? | Ссылка на поддержку                                     |
| `botIconKey`     | string? | Ключ иконки бота из пресет-набора (см. icon-presets.md) |
| `channelIconKey` | string? | Ключ иконки канала                                      |
| `supportIconKey` | string? | Ключ иконки поддержки                                   |

> `null` или неизвестный ключ → клиент использует дефолтную иконку (`send` для бота, `megaphone` для канала, `help` для поддержки).

**Админ-доступ (admin HWIDs)**

Устройства в списке `adminHwids` могут:

* Просматривать и редактировать конфиги серверов прямо в приложении.
* Отправлять провайдерские уведомления **без модерации** (auto-approve при совпадении с `targetSegment.hwid`).

Подробности — admin-hwids.md.

| Поле         | Тип        | Описание                              |
| ------------ | ---------- | ------------------------------------- |
| `adminHwids` | `string[]` | Список HWID устройств с админ-правами |

**Баннер подписки**

Красный/произвольного цвета баннер внутри карточки подписки. Используется для апсейла / предупреждений об истечении / анонсов.

| Поле                | Тип     | Описание                                                         |
| ------------------- | ------- | ---------------------------------------------------------------- |
| `bannerEnabled`     | boolean | Показывать баннер                                                |
| `bannerText`        | string? | Текст баннера (белым по `bannerBgColor`)                         |
| `bannerButtonText`  | string? | Текст кнопки (если пусто — кнопка скрыта)                        |
| `bannerButtonUrl`   | string? | URL кнопки                                                       |
| `bannerBgColor`     | string? | Цвет фона баннера (hex, напр. `"#E53E3E"`, по умолчанию красный) |
| `bannerButtonColor` | string? | Цвет кнопки (hex, по умолчанию зелёный)                          |

**Принудительные настройки**

Переопределяют выбор пользователя в настройках приложения, пока подписка активна.

| Поле                   | Тип       | Описание                                                                                            |
| ---------------------- | --------- | --------------------------------------------------------------------------------------------------- |
| `forceConnectionStyle` | `string?` | Стиль кнопки подключения: `"classic"` (крупная круглая кнопка) или `"compact"` (узкий toggle внизу) |

**Ping и сортировка**

Применяются при первом подключении подписки и переопределяют дефолтные настройки приложения.

| Поле                  | Тип      | Описание                                            |
| --------------------- | -------- | --------------------------------------------------- |
| `defaultPingProtocol` | string?  | Тип пинга: `tcp`, `proxy_head`, `proxy_get`, `icmp` |
| `defaultSortOrder`    | string?  | Сортировка серверов: `none`, `ping`, `name`         |
| `pingOnUpdate`        | boolean? | Авто-пинг всех серверов после обновления подписки   |

**Контент**

| Поле          | Тип     | Описание                                   |
| ------------- | ------- | ------------------------------------------ |
| `announceUrl` | string? | URL для объявлений провайдера в приложении |
| `webPageUrl`  | string? | Ссылка на веб-страницу провайдера          |

#### Кастомная тема (`theme`)

Расширенная палитра, применяется только когда пользователь видит premium-подписку. Поддерживает плоские цвета и 2-4-стоповые градиенты.

**Плоские цвета**

| Поле              | Тип     | Описание                                         |
| ----------------- | ------- | ------------------------------------------------ |
| `enabled`         | boolean | Кастомная тема включена                          |
| `darkMode`        | boolean | Тёмный режим                                     |
| `accent`          | string  | Основной акцентный цвет (hex, напр. `"#FF6B6B"`) |
| `accentSecondary` | string  | Вторичный акцентный цвет                         |
| `background`      | string  | Цвет фона                                        |
| `card`            | string  | Цвет карточек                                    |
| `surface`         | string  | Цвет поверхности                                 |
| `textPrimary`     | string  | Основной цвет текста                             |
| `textSecondary`   | string  | Вторичный цвет текста                            |

**Градиенты**

Если задан, перекрывает плоские `accent`/`accentSecondary` для акцентных элементов и `background` для фона.

| Поле                         | Тип             | Описание                                                             |
| ---------------------------- | --------------- | -------------------------------------------------------------------- |
| `accentGradient.stops`       | GradientStop\[] | 2-4 стопа (`{ color, position }`), `position` в диапазоне `0.0..1.0` |
| `accentGradient.angle`       | number          | Угол градиента в градусах `0..360`                                   |
| `backgroundGradient.enabled` | boolean         | Включить градиентный фон                                             |
| `backgroundGradient.stops`   | GradientStop\[] | 2-3 стопа                                                            |
| `backgroundGradient.angle`   | number          | Угол градиента                                                       |

Каждый стоп:

```json
{ "color": "#B8D94A", "position": 0.0 }
```

***

### Лимиты устройств

Premium-провайдеры имеют тарифы с ограничением по количеству устройств. Лимит хранится в поле `maxDevices` документа провайдера в Firestore.

#### Как работает

1. Клиент отправляет `hwid=SHA256(rawHwid)` в запросе конфигурации
2. Сервер проверяет, зарегистрировано ли устройство с таким `hwidHash` для доменов данного провайдера
3. Если устройство **уже зарегистрировано** — всегда получает premium (существующие устройства не блокируются)
4. Если устройство **не зарегистрировано** — сервер считает общее количество устройств провайдера:
   * Если `totalDevices < maxDevices` — premium выдаётся
   * Если `totalDevices >= maxDevices` — возвращается `isPremium: false` + `deviceLimitExceeded: true`

#### Поведение клиента при `deviceLimitExceeded: true`

* Premium-функции отключены (как при `isPremium: false`)
* Кастомная тема провайдера не применяется
* Premium-настройки (фрагментация, fronting и т.д.) не используются
* VPN продолжает работать в базовом режиме
* Устройство регистрируется в Firebase (но без premium)

#### Тарифы

| Лимит  | Описание                      |
| ------ | ----------------------------- |
| `1000` | Стандартный тариф             |
| `3000` | Средний тариф                 |
| `6000` | Максимальный тариф            |
| `null` | Безлимитный (нет ограничений) |

***

### Регистрация устройства

При добавлении подписки приложение регистрирует устройство для отслеживания активных подключений и push-уведомлений.

#### Данные регистрации

| Поле                     | Тип       | Описание                                          |
| ------------------------ | --------- | ------------------------------------------------- |
| `hwid`                   | string    | Аппаратный идентификатор устройства (подробнее)   |
| `hwidHash`               | string    | SHA-256 хеш HWID (для серверной проверки лимитов) |
| `uid`                    | string    | Firebase anonymous UID                            |
| `platform`               | string    | `"android"`, `"ios"`, `"linux"`, `"windows"`      |
| `appVersion`             | string    | Версия приложения                                 |
| `osVersion`              | string    | Версия ОС                                         |
| `locale`                 | string    | Язык устройства                                   |
| `subscriptionDomainHash` | string    | SHA-256 хеш домена подписки                       |
| `fcmToken`               | string    | Firebase Cloud Messaging токен                    |
| `lastActive`             | timestamp | Время последней активности                        |

> Поле `hwidHash` добавляется при регистрации и используется сервером для проверки лимита устройств без знания исходного HWID.

#### Синхронизация подписки

Сервер может установить флаг `subscriptionNeedsSync = true` на устройстве. При обнаружении этого флага приложение:

1. Читает поле `subscriptionNewDomain` из документа устройства
2. Обновляет URL подписки на новый домен
3. Сбрасывает флаг `subscriptionNeedsSync`

Это позволяет провайдеру мигрировать пользователей на новый домен при блокировке старого.

***

### Кеширование

* **In-memory кеш:** конфигурация хранится в памяти на время работы приложения
* **Disk кеш:** сохраняется в SecureStorage для offline-доступа
* **Fallback:** при ошибке `503` используется кешированная конфигурация
* **Очистка:** конфигурация удаляется из кеша, если домен больше не является premium

***

### Certificate Pinning

Запросы к Premium API на Android и Desktop защищены certificate pinning (SHA-256 пины TLS-сертификата). Это предотвращает MITM-атаки на канал связи с API.

***

#### Firestore индекс

Для корректной работы проверки лимитов необходим составной индекс:

* **Коллекция:** `devices`
* **Поля:** `hwidHash` (ASC), `subscriptionDomainHash` (ASC)\`


---

# 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.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.
