Files
elmprodvpn/docs/phase-e/E6_EGRESS_IDENTITY_API_CONTRACT.md

172 lines
5.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# E6 API-контракт: egress identity (IP + country)
Дата: 2026-03-10
Статус: draft (E6.1 freeze)
Владелец: Engineering
## 1) Цель
- Ввести единый backend-контракт для определения текущей egress-идентичности для любого движка.
- Избежать дублирования логики в desktop/web/mobile: UI только читает готовые поля из Go API.
- Дать унифицированные поля для показа `IP + страна` и построения флага в UI из `country_code`.
## 2) Область и ограничения
- Источник истины: только Go-ядро.
- UI не делает внешние IP/Geo запросы напрямую.
- Поддерживаемые scope:
- `adguardvpn`
- `transport:<client_id>`
- `system`
- Первичный формат ответа: `HTTP 200 + ok/message` (совместимо с текущим API-паттерном).
## 3) Модель данных
### 3.1 EgressIdentity
```json
{
"scope": "transport:sg-realnetns",
"source": "transport",
"source_id": "sg-realnetns",
"ip": "203.0.113.10",
"country_code": "SG",
"country_name": "Singapore",
"updated_at": "2026-03-10T07:20:00Z",
"stale": false,
"refresh_in_progress": false,
"last_error": "",
"next_retry_at": ""
}
```
Пояснения:
- `scope`: нормализованный ключ области.
- `source`: `adguardvpn|transport|system`.
- `source_id`: для `transport` это `client_id`, иначе пусто.
- `ip`: egress IP для выбранного scope.
- `country_code`: ISO-2 uppercase (например `US`, `SG`).
- `country_name`: человекочитаемое имя страны.
- `updated_at/stale/refresh_in_progress/last_error/next_retry_at`: SWR-метаданные.
### 3.2 EgressIdentityResponse
```json
{
"ok": true,
"message": "ok",
"item": {
"scope": "adguardvpn",
"source": "adguardvpn",
"source_id": "",
"ip": "198.51.100.5",
"country_code": "NL",
"country_name": "Netherlands",
"updated_at": "2026-03-10T07:20:00Z",
"stale": false,
"refresh_in_progress": false,
"last_error": "",
"next_retry_at": ""
}
}
```
### 3.3 EgressIdentityRefreshRequest
```json
{
"scopes": ["adguardvpn", "transport:sg-realnetns"],
"force": false
}
```
### 3.4 EgressIdentityRefreshResponse
```json
{
"ok": true,
"message": "refresh queued",
"count": 2,
"queued": 1,
"skipped": 1,
"items": [
{
"scope": "adguardvpn",
"status": "queued",
"queued": true,
"reason": ""
},
{
"scope": "transport:sg-realnetns",
"status": "skipped",
"queued": false,
"reason": "throttled or already fresh"
}
]
}
```
## 4) Endpoint-ы
### 4.1 `GET /api/v1/egress/identity`
Назначение:
- Получить текущий snapshot egress identity для одного scope.
Query:
- `scope` (required): `adguardvpn|transport:<id>|system`
- `refresh=1` (optional): best-effort trigger фонового refresh перед возвратом snapshot.
Response:
- `200 + EgressIdentityResponse`.
Ошибки запроса:
- `400` при невалидном `scope`.
### 4.2 `POST /api/v1/egress/identity/refresh`
Назначение:
- Поставить refresh в очередь для одного или нескольких scope без блокировки UI.
Request body:
- `scopes[]` optional (если пусто -> refresh всех известных scope);
- `force` optional (`true` игнорирует freshness TTL, но уважает single-flight lock).
Response:
- `200 + EgressIdentityRefreshResponse`.
Ошибки запроса:
- `400` bad json / invalid scope format.
## 5) Правила freshness/SWR
- Refresh делается в фоне, UI получает последний cache snapshot мгновенно.
- Для каждого scope:
- single-flight (не запускать параллельные одинаковые refresh);
- backoff при ошибках;
- `stale=true` если snapshot устарел или нет новых данных;
- `next_retry_at` выставляется при backoff.
- Рекомендуемая стратегия UI:
- сразу рисовать cache (`GET`),
- отдельным действием триггерить `POST .../refresh`,
- обновляться по SSE/ручному poll.
## 6) SSE событие
Событие для подписчиков:
- `egress_identity_changed`
Payload (минимум):
```json
{
"scope": "transport:sg-realnetns",
"ip": "203.0.113.10",
"country_code": "SG",
"country_name": "Singapore",
"updated_at": "2026-03-10T07:20:00Z",
"stale": false,
"last_error": ""
}
```
## 7) Отрисовка флага
- Backend возвращает только `country_code`.
- Флаг рендерится в UI из `country_code` (desktop/web/mobile одинаково).
- Если `country_code` пустой/невалидный: UI показывает `N/A` без флага.
## 8) Минимальные критерии готовности E6.1
- Документирован единый контракт `GET/POST` для egress identity.
- Зафиксированы scope и поля snapshot.
- Зафиксированы правила SWR и SSE-событие.
- Явно указано, что флаг строится в UI из `country_code`.