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

5.5 KiB
Raw Blame History

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

{
  "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>|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 (минимум):

{
  "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.