SHiNE-server/Dev_Docs/API/05_Technical_Requests_API.md

22 KiB
Raw Blame History

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

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

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

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

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

  • Ping нужен для регулярной проверки, что соединение всё ещё живо;
  • GetServerInfo нужен до авторизации и до работы с данными, чтобы клиент понял, что сервер доступен, и показал пользователю краткую карточку этого узла.
  • ListBlockchainHeads нужен для сервер-сервер сверки: партнёр получает список heads по всем цепочкам, сравнивает его со своим состоянием и затем добирает недостающие блоки по диапазону.
  • GetSyncUserProfile нужен для server-to-server режима, когда принимающий сервер хочет создать у себя локальные solana_users + blockchain_state без прямого обращения в Solana. Это используется как временный обход ограничений внешнего Solana RPC.
  • SendSignal нужен для доверенных межсессионных команд одного пользователя. Первое практическое применение — remote AddBlock via homeserver session, но формат задуман как общий transport на вырост.

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

1. Ping

Назначение

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

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

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

Запрос

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

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

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

{
  "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;
  • остальные поля читаются из настроек сервера;
  • если значение в конфиге не задано, сервер возвращает пустую строку.

Запрос

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

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

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

Назначение

Запрос краткой сводки по всем локальным блокчейнам сервера.

Нужен для межсерверной синхронизации. Партнёр может:

  • получить список всех блокчейнов;
  • сравнить lastBlockNumber и lastBlockHash со своими значениями;
  • понять, какие цепочки нужно догонять;
  • затем отдельно запросить недостающие блоки по диапазону.

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

Запрос

{
  "op": "ListBlockchainHeads",
  "requestId": "heads-001",
  "payload": {}
}

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

{
  "op": "ListBlockchainHeads",
  "requestId": "heads-001",
  "status": 200,
  "ok": true,
  "payload": {
    "blockchains": [
      {
        "blockchainName": "alice_main",
        "lastBlockNumber": 124,
        "lastBlockHash": "aabbccdd00112233445566778899aabbccddeeff00112233445566778899aabb",
        "fileSizeBytes": 58720
      }
    ]
  }
}

Поля ответа

  • blockchains — массив текущих heads всех цепочек сервера.
  • blockchainName — имя блокчейна.
  • lastBlockNumber — последний номер блока в этой цепочке.
  • lastBlockHash — последний хэш блока в HEX-формате 64 символа.
  • fileSizeBytes — текущий размер файла блокчейна в байтах.

Специфические коды ошибок ListBlockchainHeads

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

4. GetSyncUserProfile

Назначение

Запрос минимального профиля пользователя для межсерверной синхронизации.

Нужен в сценарии, когда сервер во время periodic sync увидел чужой блокчейн, которого у него локально ещё нет. Вместо обращения в Solana PDA он может запросить у партнёра:

  • login
  • blockchainName
  • solanaKey
  • blockchainKey
  • clientKey
  • blockchainSizeLimitBytes

После этого принимающий сервер может локально создать записи в solana_users и blockchain_state, а затем уже докачивать блоки через GetBlockchainBlock.

Этот запрос доступен без авторизации и предназначен именно для server-to-server sync.

Запрос

{
  "op": "GetSyncUserProfile",
  "requestId": "sync-user-001",
  "payload": {
    "login": "alice"
  }
}

Успешный ответ: пользователь не найден

{
  "op": "GetSyncUserProfile",
  "requestId": "sync-user-001",
  "status": 200,
  "ok": true,
  "payload": {
    "exists": false
  }
}

Успешный ответ: пользователь найден

{
  "op": "GetSyncUserProfile",
  "requestId": "sync-user-001",
  "status": 200,
  "ok": true,
  "payload": {
    "exists": true,
    "login": "alice",
    "blockchainName": "alice-001",
    "solanaKey": "BASE64_32",
    "blockchainKey": "BASE64_32",
    "clientKey": "BASE64_32",
    "blockchainSizeLimitBytes": 100000
  }
}

Поля ответа

  • exists — найден ли пользователь на сервере-партнёре.
  • login — канонический login из БД сервера-партнёра.
  • blockchainName — имя основной цепочки пользователя.
  • solanaKey — публичный ключ логина.
  • blockchainKey — публичный ключ блокчейна.
  • clientKey — публичный клиентский ключ, который в текущей модели используется при создании локальной записи.
  • blockchainSizeLimitBytes — лимит размера файла блокчейна, который будет записан в локальный blockchain_state.

Специфические коды ошибок GetSyncUserProfile

  • 400 / BAD_FIELDS — пустой или некорректный login.
  • 404 / BLOCKCHAIN_STATE_NOT_FOUND — пользователь найден, но на сервере-партнёре отсутствует blockchain_state для его цепочки.
  • При непредвиденной ошибке сервер вернёт общую ошибку из раздела 00, обычно 500 / INTERNAL_ERROR.

5. SendSignal

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

Назначение

Общий межсессионный технический сигнал.

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

Первый целевой сценарий:

  • remote AddBlock via homeserver session

То есть телефон без локального blockchain.key может:

  • подготовить только сырой payload операции без текущей вершины цепочки;
  • подписать сам SendSignal своим session key;
  • дополнительно подписать его client key, чтобы homeserver/ESP32 точно видел, что запрос пришёл от доверенного клиента этого же логина;
  • отправить запрос в выбранную homeserver-сессию;
  • получить от неё ответ после настоящего AddBlock, который homeserver соберёт и подпишет уже сама.

Режимы доставки

  • targetMode = "single_session" — доставка в одну конкретную targetSessionId.
  • targetMode = "all_sessions" — доставка во все активные сессии указанного логина.

Важное правило подписи

Сам SendSignal не подписывает поле data отдельной вложенной подписью. Вместо этого сервер проверяет подписи по общему preimage сигнала, в который входит:

  • fromLogin
  • fromSessionId
  • toLogin
  • targetMode
  • targetSessionId
  • signalType
  • signalRequestId
  • timeMs
  • sha256(data)

Поддерживаются две подписи:

  • sessionSignatureB64 — обязательная подпись текущей авторизованной session key;
  • clientSignatureB64 — необязательная подпись client key.

Для сценария remote AddBlock via homeserver текущая договорённость такая:

  • запрос должен идти только своему же логину;
  • запрос должен быть подписан и session key, и client key;
  • в будущем для отдельных wallet-сценариев clientSignatureB64 может быть пустой.

Запрос в одну сессию

{
  "op": "SendSignal",
  "requestId": "ws-req-001",
  "payload": {
    "toLogin": "alice",
    "targetMode": "single_session",
    "targetSessionId": "sess-hs-001",
    "signalType": "remote_addblock_request",
    "signalRequestId": "remote-addblock-001",
    "data": "{\"operation\":\"remote_addblock_request\",\"signalRequestId\":\"remote-addblock-001\",\"blockchainName\":\"alice_main\",\"blockBodyB64\":\"...\"}",
    "timeMs": 1774700000123,
    "sessionSignatureB64": "BASE64_64",
    "clientSignatureB64": "BASE64_64"
  }
}

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

{
  "op": "SendSignal",
  "requestId": "ws-req-001",
  "status": 200,
  "ok": true,
  "payload": {
    "deliveredCount": 1,
    "deliveredSessionIds": ["sess-hs-001"]
  }
}

Событие на принимающей стороне

{
  "op": "IncomingSignal",
  "eventId": "evt-001",
  "payload": {
    "fromLogin": "alice",
    "fromSessionId": "sess-phone-001",
    "toLogin": "alice",
    "targetMode": "single_session",
    "targetSessionId": "sess-hs-001",
    "signalType": "remote_addblock_request",
    "signalRequestId": "remote-addblock-001",
    "data": "{\"operation\":\"remote_addblock_request\",\"signalRequestId\":\"remote-addblock-001\",\"blockchainName\":\"alice_main\",\"blockBodyB64\":\"...\"}",
    "timeMs": 1774700000123,
    "sessionSignatureB64": "BASE64_64",
    "clientSignatureB64": "BASE64_64",
    "dataSha256B64": "BASE64_32"
  }
}

Специфика remote AddBlock

Для remote_addblock_request поле data теперь содержит:

  • blockchainName
  • blockBodyB64

Где blockBodyB64 — это не финальный блок и не почти готовый preimage, а компактный бинарный контейнер:

  • msgType (u16)
  • msgSubType (u16)
  • msgVersion (u16)
  • bodyBytes

После этого homeserver сама:

  • вызывает GetUser(login) и получает serverLastGlobalNumber/serverLastGlobalHash;
  • вычисляет новый blockNumber = last + 1;
  • подставляет актуальный prevBlockHash;
  • ставит текущее время;
  • досчитывает полный preimage;
  • подписывает его своим blockchain key;
  • и только потом делает настоящий AddBlock.

Специфические коды ошибок SendSignal

  • 422 / NOT_AUTHENTICATED — требуется авторизация.
  • 400 / BAD_FIELDS — не хватает обязательных полей или нарушено правило single_session/all_sessions.
  • 400 / BAD_TARGET_MODE — передан неизвестный targetMode.
  • 400 / TIME_SKEWtimeMs отличается от серверного более чем на 30 секунд.
  • 500 / NO_CLIENT_KEY — для текущего пользователя не найден client key.
  • 404 / USER_NOT_FOUND — логин адресата не найден.
  • 400 / BAD_DATA — сервер не смог обработать data.
  • 400 / BAD_SESSION_SIGNATURE — некорректная подпись session key.
  • 400 / BAD_CLIENT_SIGNATURE — некорректная подпись client key.
  • 404 / SESSION_NOT_FOUND — при single_session целевая сессия не найдена или не онлайн.
  • 404 / NO_TARGET_SESSIONS — при all_sessions у пользователя сейчас нет активных онлайн-сессий.
  • 404 / DELIVERY_FAILED — сервер не смог отправить событие ни в одну из целевых сессий.

6. GetCallIceConfig

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

Запрос

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

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

{
  "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 — требуется авторизация.

7. ClientErrorLog

Запрос

{
  "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\"}"
  }
}

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

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

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

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

8. ClientDebugLog

Запрос

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

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

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

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

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

9. CallDeliveryReport

Запрос

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

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

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

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

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

10. Короткое резюме

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