# API для разработчиков: Технические запросы Этот файл описывает технические WebSocket-запросы, которые нужны для служебной работы клиента с сервером. Часть операций доступна без авторизации, часть требует успешной авторизованной сессии. Сейчас здесь восемь методов: - `Ping` — keep-alive запрос для поддержания живого WebSocket-соединения; - `GetServerInfo` — запрос базовой публичной информации о сервере для выбора узла в децентрализованной сети; - `ListBlockchainHeads` — краткая сводка по всем локальным блокчейнам сервера для межсерверной синхронизации; - `GetSyncUserProfile` — межсерверный профиль пользователя для создания локальной цепочки без Solana RPC; - `GetCallIceConfig` — выдача STUN/TURN конфигурации для звонков; - `ClientErrorLog` — отправка клиентской ошибки в серверный лог; - `ClientDebugLog` — отправка клиентского debug-события в серверный буфер; - `CallDeliveryReport` — диагностический отчёт клиента о доставке/установке звонка. Логика раздела такая: - `Ping` нужен для регулярной проверки, что соединение всё ещё живо; - `GetServerInfo` нужен до авторизации и до работы с данными, чтобы клиент понял, что сервер доступен, и показал пользователю краткую карточку этого узла. - `ListBlockchainHeads` нужен для сервер-сервер сверки: партнёр получает список heads по всем цепочкам, сравнивает его со своим состоянием и затем добирает недостающие блоки по диапазону. - `GetSyncUserProfile` нужен для server-to-server режима, когда принимающий сервер хочет создать у себя локальные `solana_users + blockchain_state` без прямого обращения в Solana. Это используется как временный обход ограничений внешнего Solana RPC. Ниже сначала описаны назначение методов, затем точные форматы запросов и ответов. ## 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. `ListBlockchainHeads` ### Назначение Запрос краткой сводки по всем локальным блокчейнам сервера. Нужен для межсерверной синхронизации. Партнёр может: - получить список всех блокчейнов; - сравнить `lastBlockNumber` и `lastBlockHash` со своими значениями; - понять, какие цепочки нужно догонять; - затем отдельно запросить недостающие блоки по диапазону. Этот запрос доступен без авторизации. ### Запрос ```json { "op": "ListBlockchainHeads", "requestId": "heads-001", "payload": {} } ``` ### Успешный ответ ```json { "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. ### Запрос ```json { "op": "GetSyncUserProfile", "requestId": "sync-user-001", "payload": { "login": "alice" } } ``` ### Успешный ответ: пользователь не найден ```json { "op": "GetSyncUserProfile", "requestId": "sync-user-001", "status": 200, "ok": true, "payload": { "exists": false } } ``` ### Успешный ответ: пользователь найден ```json { "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. `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` — требуется авторизация. --- ## 6. `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` — обязательные поля ошибки не заполнены. --- ## 7. `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` не заполнено. --- ## 8. `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-слой и политика доступа.