5.5 KiB
5.5 KiB
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:
adguardvpntransport:<client_id>system
- Первичный формат ответа:
HTTP 200 + ok/message(совместимо с текущим API-паттерном).
3) Модель данных
3.1 EgressIdentity
{
"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
{
"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
{
"scopes": ["adguardvpn", "transport:sg-realnetns"],
"force": false
}
3.4 EgressIdentityRefreshResponse
{
"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>|systemrefresh=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);forceoptional (trueигнорирует freshness TTL, но уважает single-flight lock).
Response:
200 + EgressIdentityRefreshResponse.
Ошибки запроса:
400bad 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.
- сразу рисовать cache (
6) SSE событие
Событие для подписчиков:
egress_identity_changed
Payload (минимум):
{
"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.