22 KiB
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 он может запросить у партнёра:
loginblockchainNamesolanaKeyblockchainKeyclientKeyblockchainSizeLimitBytes
После этого принимающий сервер может локально создать записи в 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 сигнала, в который входит:
fromLoginfromSessionIdtoLogintargetModetargetSessionIdsignalTypesignalRequestIdtimeMssha256(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 теперь содержит:
blockchainNameblockBodyB64
Где 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_SKEW—timeMsотличается от серверного более чем на 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используются для диагностики клиента и звонков.