# API для разработчиков: Технические запросы

Этот файл описывает технические WebSocket-запросы, которые нужны для служебной работы клиента с сервером. Часть операций доступна без авторизации, часть требует успешной авторизованной сессии.

Сейчас здесь шесть методов:

- `Ping` — keep-alive запрос для поддержания живого WebSocket-соединения;
- `GetServerInfo` — запрос базовой публичной информации о сервере для выбора узла в децентрализованной сети;
- `GetCallIceConfig` — выдача STUN/TURN конфигурации для звонков;
- `ClientErrorLog` — отправка клиентской ошибки в серверный лог;
- `ClientDebugLog` — отправка клиентского debug-события в серверный буфер;
- `CallDeliveryReport` — диагностический отчёт клиента о доставке/установке звонка.

Логика раздела такая:

- `Ping` нужен для регулярной проверки, что соединение всё ещё живо;
- `GetServerInfo` нужен до авторизации и до работы с данными, чтобы клиент понял, что сервер доступен, и показал пользователю краткую карточку этого узла.

Ниже сначала описаны назначение методов, затем точные форматы запросов и ответов.

## 1. `Ping`

### Назначение

Служебный keep-alive запрос.

Клиент может отправлять его периодически, чтобы:

- поддерживать активное WebSocket-соединение;
- понимать, что сервер отвечает;
- при необходимости получать текущее серверное время.

### Запрос

```json
{
  "op": "Ping",
  "requestId": "ping-001",
  "payload": {
    "ts": 1774700000123
  }
}
```

Поле `ts` в запросе необязательно для логики сервера. Сервер его не валидирует и не использует для принятия решения.

### Успешный ответ

```json
{
  "op": "Ping",
  "requestId": "ping-001",
  "status": 200,
  "ok": true,
  "payload": {
    "ts": 1774700000456
  }
}
```

### Специфические коды ошибок `Ping`

- У `Ping` нет специальных прикладных ошибок.
- Если произойдёт непредвиденная проблема, сервер вернёт общую ошибку из раздела `00`, обычно `500 / INTERNAL_ERROR`.

---

## 2. `GetServerInfo`

### Назначение

Запрос публичной информации о сервере.

Он нужен клиенту для выбора сервера в децентрализованной сети. По этому запросу клиент может:

- проверить, что сервер вообще доступен;
- показать URL и версию сервера;
- показать физический регион или адрес размещения;
- показать описание сервера;
- показать поле `origin` как комментарий о природе этого узла;
- показать дополнительную текстовую информацию.

Этот запрос доступен без авторизации.

### Источник данных

- `version` берётся из Gradle build и подставляется в `application.properties`;
- остальные поля читаются из настроек сервера;
- если значение в конфиге не задано, сервер возвращает пустую строку.

### Запрос

```json
{
  "op": "GetServerInfo",
  "requestId": "srv-001",
  "payload": {
  }
}
```

### Успешный ответ

```json
{
  "op": "GetServerInfo",
  "requestId": "srv-001",
  "status": 200,
  "ok": true,
  "payload": {
    "url": "wss://node.example.org/ws",
    "version": "1.0",
    "physicalRegion": "Грузия, Тбилиси",
    "description": "Public community SHiNE node",
    "origin": "Community-operated node",
    "extraInfo": "IPv4 + IPv6; test federation enabled"
  }
}
```

### Поля ответа

- `url` — публичный URL сервера.
- `version` — версия сервера из Gradle build.
- `physicalRegion` — физический регион или адрес размещения сервера.
- `description` — человекочитаемое описание сервера.
- `origin` — комментарий о том, какой это сервер.
- `extraInfo` — любая дополнительная информация о сервере.

### Специфические коды ошибок `GetServerInfo`

- У `GetServerInfo` нет специальных прикладных ошибок при штатной работе.
- Если произойдёт непредвиденная проблема, сервер вернёт общую ошибку из раздела `00`, обычно `500 / INTERNAL_ERROR`.

---

## 3. `GetCallIceConfig`

Доступно только после успешной авторизации.

### Запрос

```json
{
  "op": "GetCallIceConfig",
  "requestId": "ice-001",
  "payload": {
  }
}
```

### Успешный ответ

```json
{
  "op": "GetCallIceConfig",
  "requestId": "ice-001",
  "status": 200,
  "ok": true,
  "payload": {
    "stunUrls": ["stun:stun.example.org:3478"],
    "turnUrls": ["turn:turn.example.org:3478?transport=udp"],
    "turnUsername": "user",
    "turnPassword": "password",
    "turnServers": [
      {
        "id": "primary",
        "urls": ["turn:turn.example.org:3478?transport=udp"],
        "username": "user",
        "password": "password"
      }
    ],
    "turnEnabled": true,
    "generatedAtMs": 1774700000123,
    "expiresAtMs": 1774700300123,
    "ttlSec": 300
  }
}
```

### Специфические коды ошибок `GetCallIceConfig`

- `422 / NOT_AUTHENTICATED` — требуется авторизация.

---

## 4. `ClientErrorLog`

### Запрос

```json
{
  "op": "ClientErrorLog",
  "requestId": "err-001",
  "payload": {
    "kind": "global_error",
    "message": "TypeError: failed",
    "stack": "...",
    "sourceUrl": "https://shineup.me/app.js",
    "lineNumber": 10,
    "columnNumber": 20,
    "route": "#/channel-view/own-0",
    "href": "https://shineup.me/#/channel-view/own-0",
    "userAgent": "...",
    "clientTs": 1774700000123,
    "requestOp": "GetChannelMessages",
    "requestIdRef": "GetChannelMessages-123",
    "contextJson": "{\"screen\":\"channels\"}"
  }
}
```

### Успешный ответ

```json
{
  "op": "ClientErrorLog",
  "requestId": "err-001",
  "status": 200,
  "ok": true,
  "payload": {
    "serverTs": 1774700000456,
    "accepted": true
  }
}
```

### Специфические коды ошибок `ClientErrorLog`

- `400 / BAD_FIELDS` — обязательные поля ошибки не заполнены.

---

## 5. `ClientDebugLog`

### Запрос

```json
{
  "op": "ClientDebugLog",
  "requestId": "dbg-001",
  "payload": {
    "runId": "ui-run-1",
    "level": "info",
    "message": "opened channels tab",
    "details": "{\"route\":\"#/channels\"}"
  }
}
```

### Успешный ответ

```json
{
  "op": "ClientDebugLog",
  "requestId": "dbg-001",
  "status": 200,
  "ok": true,
  "payload": {
    "accepted": true,
    "serverTs": 1774700000456
  }
}
```

### Специфические коды ошибок `ClientDebugLog`

- `400 / BAD_FIELDS` — поле `message` не заполнено.

---

## 6. `CallDeliveryReport`

### Запрос

```json
{
  "op": "CallDeliveryReport",
  "requestId": "call-report-001",
  "payload": {
    "type": "outgoing_failed",
    "value": "{\"reason\":\"ice_failed\",\"callId\":\"call-1\"}"
  }
}
```

### Успешный ответ

```json
{
  "op": "CallDeliveryReport",
  "requestId": "call-report-001",
  "status": 200,
  "ok": true,
  "payload": {
    "serverTs": 1774700000456,
    "accepted": true
  }
}
```

### Специфические коды ошибок `CallDeliveryReport`

- `400 / BAD_FIELDS` — поле `type` не заполнено.

---

## 7. Короткое резюме

- `Ping` нужен для keep-alive и проверки, что WebSocket-соединение живо.
- `GetServerInfo` нужен для выбора сервера в сети и показа публичной информации об узле.
- `GetCallIceConfig` нужен для WebRTC-звонков и требует авторизации.
- `ClientErrorLog`, `ClientDebugLog`, `CallDeliveryReport` используются для диагностики клиента и звонков.


## 8. Прямое техническое сообщение в конкретную сессию

На текущий момент в публичном JSON API этого документа **нет отдельного RPC** для отправки произвольного технического сообщения в конкретную сессию пользователя (по `sessionId`).

Что уже есть в системе:

- сервер хранит `sessionId` активной сессии;
- есть `ListSessions`, чтобы клиент получил список sessionId своего пользователя;
- у сервера есть внутренний реестр активных WS-подключений по `sessionId`.

Чего не хватает для полноценной фичи «direct tech message by sessionId»:

1. отдельная API-операция (например, `SendSessionTechMessage`);
2. правило авторизации (кто имеет право писать в чужую/свою сессию);
3. унифицированный формат payload и события доставки;
4. коды ошибок (`SESSION_OFFLINE`, `SESSION_NOT_FOUND`, `FORBIDDEN` и т.п.).

Итог: как инфраструктурная база это почти готово, но нужен отдельный RPC-слой и политика доступа.
