SHA256
Compare commits
4
Commits
| Author | SHA256 | Date | |
|---|---|---|---|
|
|
553a1f1e27 | ||
|
|
940dc855cf | ||
|
|
9e6ff07136 | ||
|
|
f095182673 |
-17
@@ -76,21 +76,10 @@ shine-solana/shine/scripts/**/*.env
|
|||||||
shine-solana/shine/scripts/**/TEMP_*.md
|
shine-solana/shine/scripts/**/TEMP_*.md
|
||||||
|
|
||||||
# Локальные артефакты и внешние материалы ESP32-подпроекта
|
# Локальные артефакты и внешние материалы ESP32-подпроекта
|
||||||
ESP32/esp32-config-tool/
|
|
||||||
ESP32/**/.git/
|
ESP32/**/.git/
|
||||||
ESP32/**/.idea/
|
ESP32/**/.idea/
|
||||||
ESP32-wallet/.idea/
|
|
||||||
ESP32/**/.arduino-build/
|
ESP32/**/.arduino-build/
|
||||||
ESP32/**/official-demo/
|
ESP32/**/official-demo/
|
||||||
!ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/
|
|
||||||
ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/**
|
|
||||||
!ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/
|
|
||||||
ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/**
|
|
||||||
!ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/Arduino-v3.3.5/
|
|
||||||
ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/Arduino-v3.3.5/**
|
|
||||||
!ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/Arduino-v3.3.5/libraries/
|
|
||||||
ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/Arduino-v3.3.5/libraries/**
|
|
||||||
!ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/Arduino-v3.3.5/libraries/lv_conf.h
|
|
||||||
ESP32/**/original-firmware/*.bin
|
ESP32/**/original-firmware/*.bin
|
||||||
ESP32/**/original-firmware/*.bin.sha256
|
ESP32/**/original-firmware/*.bin.sha256
|
||||||
ESP32/**/*.elf
|
ESP32/**/*.elf
|
||||||
@@ -104,9 +93,3 @@ ESP32/**/*.a
|
|||||||
# Полные серверные бэкапы (тяжёлые архивы, не коммитим)
|
# Полные серверные бэкапы (тяжёлые архивы, не коммитим)
|
||||||
server-backup/archive/**
|
server-backup/archive/**
|
||||||
!server-backup/archive/.gitkeep
|
!server-backup/archive/.gitkeep
|
||||||
|
|
||||||
# Локальная дев-обвязка Claude (дев-сервер shine-UI, сессии, планы) — не коммитим
|
|
||||||
.claude/
|
|
||||||
# Рабочие бэкапы/превью-ассеты UI — не для репозитория
|
|
||||||
*.bak.png
|
|
||||||
shine-UI/assets/navbar_preview.png
|
|
||||||
|
|||||||
Generated
+3
-1
@@ -2,5 +2,7 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="" vcs="Git" />
|
<mapping directory="" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/tools/understand-anything-lab/upstream" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -24,12 +24,12 @@
|
|||||||
- Подробные служебные правила Telegram-обработчика, его очередь, история, systemd-запуск и особенности ответов описывать в `SHiNE-agent-bot-coder/AGENT.md`.
|
- Подробные служебные правила Telegram-обработчика, его очередь, история, systemd-запуск и особенности ответов описывать в `SHiNE-agent-bot-coder/AGENT.md`.
|
||||||
- Если в сообщениях пользователя встречается «агент MD» или похожая формулировка про файл инструкций Codex, считать, что имеется в виду автоматически читаемый `AGENTS.md`.
|
- Если в сообщениях пользователя встречается «агент MD» или похожая формулировка про файл инструкций Codex, считать, что имеется в виду автоматически читаемый `AGENTS.md`.
|
||||||
|
|
||||||
## ESP32 UI homeserver
|
## ESP32 UI сабсервера
|
||||||
- Для UI-скетча устройства `ESP32-S3-Touch-AMOLED-2.16` документ-спецификация и Arduino-скетч должны всегда оставаться синхронными.
|
- Для UI-скетча устройства `ESP32-S3-Touch-AMOLED-2.16` документ-спецификация и Arduino-скетч должны всегда оставаться синхронными.
|
||||||
- Актуальный документ по экранной логике, состояниям, кнопкам, полям, статусам и ограничениям UI считать источником истины для скетча.
|
- Актуальный документ по экранной логике, состояниям, кнопкам, полям, статусам и ограничениям UI считать источником истины для скетча.
|
||||||
- При изменении документа обязательно в том же наборе изменений приводить в соответствие скетч.
|
- При изменении документа обязательно в том же наборе изменений приводить в соответствие скетч.
|
||||||
- При изменении скетча обязательно в том же наборе изменений обновлять документ, если поменялись экраны, тексты, переходы, статусы, кнопки, поля или поведение.
|
- При изменении скетча обязательно в том же наборе изменений обновлять документ, если поменялись экраны, тексты, переходы, статусы, кнопки, поля или поведение.
|
||||||
- Для нового ESP32 UI-прототипа homeserver использовать русский язык как основной и отдельно следить, чтобы текст реально отображался на устройстве, а не только логически присутствовал в коде.
|
- Для нового ESP32 UI-прототипа сабсервера использовать русский язык как основной и отдельно следить, чтобы текст реально отображался на устройстве, а не только логически присутствовал в коде.
|
||||||
|
|
||||||
## Solana-модуль
|
## Solana-модуль
|
||||||
- В проекте есть отдельный Solana/Anchor-модуль в папке `shine-solana/shine/`.
|
- В проекте есть отдельный Solana/Anchor-модуль в папке `shine-solana/shine/`.
|
||||||
@@ -83,26 +83,14 @@
|
|||||||
|
|
||||||
## Deploy
|
## Deploy
|
||||||
- Все документы и заметки по деплою хранить в папке `Dev_Docs/deploy/`.
|
- Все документы и заметки по деплою хранить в папке `Dev_Docs/deploy/`.
|
||||||
- Production-хост SHiNE: `player@shineup.me` (`185.229.109.118`).
|
- Базовый целевой хост для деплоя по умолчанию: `player@93.170.12.154` (`shineup.me`).
|
||||||
- Основной test-хост SHiNE: `player@193.8.215.70` (`test2.shineup.me`).
|
|
||||||
- Резервный test-хост SHiNE: `player@93.170.12.154` (`test.shineup.me`).
|
|
||||||
- Базовый путь на сервере для SHiNE: `/home/player` (проекты SHiNE размещать в `/home/player/SHiNE/...`).
|
- Базовый путь на сервере для SHiNE: `/home/player` (проекты SHiNE размещать в `/home/player/SHiNE/...`).
|
||||||
- По возможности все справки, комментарии и примечания в конфигах/документах писать на русском языке.
|
- По возможности все справки, комментарии и примечания в конфигах/документах писать на русском языке.
|
||||||
- Для операций `git push` при необходимости использовать токен из переменной окружения `$GITEA_TOKEN`.
|
- Для операций `git push` при необходимости использовать токен из переменной окружения `$GITEA_TOKEN`.
|
||||||
- Любые изменения и любой деплой на production `shineup.me` выполнять только после отдельного явного подтверждения пользователя.
|
- Для серверного деплоя использовать один gradle task: `./gradlew deployServer`.
|
||||||
- Если пользователь пишет просто `задеплой` без уточнения production/test, по умолчанию деплоить на `test2.shineup.me`.
|
- Для UI деплоя использовать один gradle task: `./gradlew deployUI`.
|
||||||
- Default server deploy: `./gradlew deployServer` или `./gradlew deployServerTest2`.
|
|
||||||
- Default UI deploy: `./gradlew deployUI` или `./gradlew deployUITest2`.
|
|
||||||
- Production server deploy: `./gradlew deployServerProduction`.
|
|
||||||
- Production UI deploy: `./gradlew deployUIProduction`.
|
|
||||||
- Резервный test deploy на `test.shineup.me`: `./gradlew deployServerTest` и `./gradlew deployUITest`, но пока их не использовать без отдельной причины.
|
|
||||||
- Для локального запуска использовать `./gradlew startLocal` (или `startLocalWithBuild`).
|
- Для локального запуска использовать `./gradlew startLocal` (или `startLocalWithBuild`).
|
||||||
- Сначала предлагать локальную проверку, а деплой на сервер выполнять по запросу пользователя.
|
- Сначала предлагать локальную проверку, а деплой на сервер выполнять по запросу пользователя.
|
||||||
- Для временной бесплатной загрузки аватаров в Arweave секретный JWK нельзя хранить в git и нельзя прописывать в репозиторный `application.properties`.
|
|
||||||
- Для продовой настройки тестового Arweave-кошелька JWK-файл нужно хранить только на сервере, например: `/home/player/SHiNE/secrets/test-free-avatar-wallet.json`.
|
|
||||||
- Для этой временной фичи на проде должны быть заданы параметры `test.freeAvatar.walletJwkPath` и `test.freeAvatar.walletAddress` через серверный override-конфиг/секреты на хосте.
|
|
||||||
- После изменения продовых значений `test.freeAvatar.*` нужно заново выполнить серверный деплой или перезапуск сервера, чтобы настройки были перечитаны приложением.
|
|
||||||
- При таких изменениях в git допускается коммитить только документацию и код чтения настроек, но не сам JWK, не содержимое секрета и не реальные приватные ключи.
|
|
||||||
|
|
||||||
## Логи звонков (установка соединения)
|
## Логи звонков (установка соединения)
|
||||||
- Специальный поток диагностики установки звонков идёт через `CallDeliveryReport` (клиент → сервер).
|
- Специальный поток диагностики установки звонков идёт через `CallDeliveryReport` (клиент → сервер).
|
||||||
|
|||||||
@@ -155,11 +155,11 @@
|
|||||||
|
|
||||||
- это обязательный шаг перед переходом от "собрали" к "доверяем".
|
- это обязательный шаг перед переходом от "собрали" к "доверяем".
|
||||||
|
|
||||||
### 3. Устройство на ESP32 как homeserver с ключами
|
### 3. Устройство на ESP32 как сабсервер с ключами
|
||||||
|
|
||||||
Что сделать:
|
Что сделать:
|
||||||
|
|
||||||
- дописать прошивку, чтобы устройство могло выступать homeserver с ключами;
|
- дописать прошивку, чтобы устройство могло выступать сабсервером с ключами;
|
||||||
- дать ему возможность регистрироваться и подключаться к серверу;
|
- дать ему возможность регистрироваться и подключаться к серверу;
|
||||||
- определить, какие операции устройство подписывает и где хранит ключевой материал.
|
- определить, какие операции устройство подписывает и где хранит ключевой материал.
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Этот файл описывает именно этапы авторизации клиента, то есть как создать новую сессию и как войти в уже существующую.
|
Этот файл описывает именно этапы авторизации клиента, то есть как создать новую сессию и как войти в уже существующую.
|
||||||
|
|
||||||
Здесь четыре базовых метода обычной авторизации:
|
Здесь четыре метода:
|
||||||
|
|
||||||
- `AuthChallenge`
|
- `AuthChallenge`
|
||||||
- `CreateAuthSession`
|
- `CreateAuthSession`
|
||||||
@@ -17,35 +17,8 @@
|
|||||||
- на втором шаге клиент присылает подписанный ответ;
|
- на втором шаге клиент присылает подписанный ответ;
|
||||||
- сервер сверяет актуальные публичные ключи и только потом проверяет подпись.
|
- сервер сверяет актуальные публичные ключи и только потом проверяет подпись.
|
||||||
|
|
||||||
Новые поля этого раздела:
|
|
||||||
|
|
||||||
- `sessionType` — числовой код типа сессии;
|
|
||||||
- `clientPlatform` — свободная строка платформы клиента.
|
|
||||||
|
|
||||||
Текущие поддерживаемые коды `sessionType`:
|
|
||||||
|
|
||||||
- `1` — обычный клиент;
|
|
||||||
- `50` — кошелёк;
|
|
||||||
- `100` — homeserver.
|
|
||||||
|
|
||||||
Правило проверки `sessionType`:
|
|
||||||
|
|
||||||
1. если в `Solana PDA` нет записи для `sessionKey`, сервер принимает `sessionType`, присланный клиентом;
|
|
||||||
2. если запись в `PDA` есть, `sessionType` в запросе должен совпадать с `session_type` из `PDA`;
|
|
||||||
3. при несовпадении сервер возвращает `460 / SESSION_TYPE_MISMATCH`.
|
|
||||||
|
|
||||||
Ниже в документе сначала описан сценарий, а потом зафиксированы точные форматы запросов и ответов.
|
Ниже в документе сначала описан сценарий, а потом зафиксированы точные форматы запросов и ответов.
|
||||||
|
|
||||||
Отдельно появился новый серверный сценарий pairing через доверенный homeserver/ESP. Он не заменяет обычный вход и описан в:
|
|
||||||
|
|
||||||
- `Dev_Docs/Протоколы/ESP_Pairing_и_режимы_подключения.md`
|
|
||||||
|
|
||||||
Кратко:
|
|
||||||
|
|
||||||
- `AuthChallenge/CreateAuthSession` и `SessionChallenge/SessionLogin` остаются каноническими потоками обычной авторизации;
|
|
||||||
- pairing через ESP идёт отдельными `op` и только подготавливает безопасное добавление новой сессии;
|
|
||||||
- решение об одобрении pairing принимает любая уже авторизованная доверенная сессия пользователя.
|
|
||||||
|
|
||||||
## 1. Поток авторизации
|
## 1. Поток авторизации
|
||||||
|
|
||||||
Поддерживаются два сценария:
|
Поддерживаются два сценария:
|
||||||
@@ -121,8 +94,6 @@ ed25519/BASE64_PUBLIC_KEY
|
|||||||
"authNonce": "nonce",
|
"authNonce": "nonce",
|
||||||
"deviceKey": "BASE64_DEVICE_PUBLIC_KEY",
|
"deviceKey": "BASE64_DEVICE_PUBLIC_KEY",
|
||||||
"signatureB64": "BASE64_SIGNATURE",
|
"signatureB64": "BASE64_SIGNATURE",
|
||||||
"sessionType": 1,
|
|
||||||
"clientPlatform": "Web",
|
|
||||||
"clientInfo": "Android 15; Pixel 9"
|
"clientInfo": "Android 15; Pixel 9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,8 +153,6 @@ AUTH_CREATE_SESSION:{login}:{sessionKey}:{storagePwd}:{timeMs}:{authNonce}
|
|||||||
- `400 / EMPTY_DEVICE_KEY` — в запросе не передан `deviceKey`.
|
- `400 / EMPTY_DEVICE_KEY` — в запросе не передан `deviceKey`.
|
||||||
- `422 / DEVICE_KEY_NOT_ACTUAL` — `deviceKey` не совпадает с актуальной версией на сервере.
|
- `422 / DEVICE_KEY_NOT_ACTUAL` — `deviceKey` не совпадает с актуальной версией на сервере.
|
||||||
- `422 / BAD_SIGNATURE` — подпись не прошла проверку.
|
- `422 / BAD_SIGNATURE` — подпись не прошла проверку.
|
||||||
- `460 / SESSION_TYPE_MISMATCH` — `sessionType` не совпадает с типом сессии, уже опубликованным для этого `sessionKey` в Solana PDA.
|
|
||||||
- `501 / SESSION_TYPE_PDA_CHECK_FAILED` — сервер не смог проверить `sessionType` по Solana PDA.
|
|
||||||
- `501 / DB_ERROR_SESSION_CREATE` — ошибка БД при создании записи активной сессии.
|
- `501 / DB_ERROR_SESSION_CREATE` — ошибка БД при создании записи активной сессии.
|
||||||
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
|
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
|
||||||
|
|
||||||
@@ -239,8 +208,6 @@ AUTH_CREATE_SESSION:{login}:{sessionKey}:{storagePwd}:{timeMs}:{authNonce}
|
|||||||
"sessionKey": "ed25519/BASE64_PUBLIC_KEY",
|
"sessionKey": "ed25519/BASE64_PUBLIC_KEY",
|
||||||
"timeMs": 1774600010456,
|
"timeMs": 1774600010456,
|
||||||
"signatureB64": "BASE64_SIGNATURE",
|
"signatureB64": "BASE64_SIGNATURE",
|
||||||
"sessionType": 1,
|
|
||||||
"clientPlatform": "Web",
|
|
||||||
"clientInfo": "Android 15; Pixel 9"
|
"clientInfo": "Android 15; Pixel 9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -291,40 +258,12 @@ SESSION_LOGIN:{sessionId}:{timeMs}:{nonce}
|
|||||||
- `422 / UNSUPPORTED_KEY_ALGORITHM` — префикс алгоритма в `sessionKey` не поддерживается текущим сервером.
|
- `422 / UNSUPPORTED_KEY_ALGORITHM` — префикс алгоритма в `sessionKey` не поддерживается текущим сервером.
|
||||||
- `400 / BAD_BASE64` — неверный Base64 в `sessionKey` или `signatureB64`.
|
- `400 / BAD_BASE64` — неверный Base64 в `sessionKey` или `signatureB64`.
|
||||||
- `422 / BAD_SIGNATURE` — подпись не прошла проверку.
|
- `422 / BAD_SIGNATURE` — подпись не прошла проверку.
|
||||||
- `460 / SESSION_TYPE_MISMATCH` — `sessionType` не совпадает с типом сессии, уже опубликованным для этого `sessionKey` в Solana PDA.
|
|
||||||
- `501 / SESSION_TYPE_PDA_CHECK_FAILED` — сервер не смог проверить `sessionType` по Solana PDA.
|
|
||||||
- `501 / DB_ERROR_USER_LOOKUP` — ошибка БД при чтении пользователя для этой сессии.
|
- `501 / DB_ERROR_USER_LOOKUP` — ошибка БД при чтении пользователя для этой сессии.
|
||||||
- `422 / USER_NOT_FOUND_FOR_SESSION` — пользователь, которому принадлежит сессия, не найден.
|
- `422 / USER_NOT_FOUND_FOR_SESSION` — пользователь, которому принадлежит сессия, не найден.
|
||||||
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
|
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. Pairing через homeserver/ESP
|
|
||||||
|
|
||||||
Новые `op`, относящиеся к этому сценарию:
|
|
||||||
|
|
||||||
- `GetTrustedDeviceLoginSettings`
|
|
||||||
- `UpsertTrustedDeviceLoginSettings`
|
|
||||||
- `StartTrustedDeviceLogin`
|
|
||||||
- `ListTrustedDeviceLoginRequests`
|
|
||||||
- `ApproveTrustedDeviceLogin`
|
|
||||||
- `RejectTrustedDeviceLogin`
|
|
||||||
- `CancelTrustedDeviceLogin`
|
|
||||||
- `GetTrustedDeviceLoginStatus`
|
|
||||||
|
|
||||||
В этом потоке:
|
|
||||||
|
|
||||||
- новое устройство не владеет `deviceKey` и не проходит обычный `CreateAuthSession`;
|
|
||||||
- пароль проверяется сервером только как фильтр;
|
|
||||||
- решение об одобрении принимает уже авторизованная доверенная сессия пользователя;
|
|
||||||
- сервер не расшифровывает `encryptedPayload` и не становится источником приватных ключей.
|
|
||||||
|
|
||||||
Точные форматы этих операций см. в `03_Session_Management_API.md` и в протокольном документе:
|
|
||||||
|
|
||||||
- `Dev_Docs/Протоколы/ESP_Pairing_и_режимы_подключения.md`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Пример ошибки
|
## 6. Пример ошибки
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -7,20 +7,6 @@
|
|||||||
- `ListSessions` — получить список активных сессий пользователя;
|
- `ListSessions` — получить список активных сессий пользователя;
|
||||||
- `CloseActiveSession` — закрыть одну из активных сессий.
|
- `CloseActiveSession` — закрыть одну из активных сессий.
|
||||||
|
|
||||||
Дополнительно в этом же слое управления сессиями появился сценарий pairing через доверенную уже авторизованную сессию пользователя:
|
|
||||||
|
|
||||||
- `GetTrustedDeviceLoginSettings`
|
|
||||||
- `UpsertTrustedDeviceLoginSettings`
|
|
||||||
- `ListTrustedDeviceLoginRequests`
|
|
||||||
- `ApproveTrustedDeviceLogin`
|
|
||||||
- `RejectTrustedDeviceLogin`
|
|
||||||
- `CancelTrustedDeviceLogin`
|
|
||||||
|
|
||||||
Анонимное новое устройство работает с двумя связанными операциями:
|
|
||||||
|
|
||||||
- `StartTrustedDeviceLogin`
|
|
||||||
- `GetTrustedDeviceLoginStatus`
|
|
||||||
|
|
||||||
Логика раздела такая:
|
Логика раздела такая:
|
||||||
|
|
||||||
- сначала пользователь проходит `SessionLogin`;
|
- сначала пользователь проходит `SessionLogin`;
|
||||||
@@ -56,9 +42,6 @@
|
|||||||
"sessions": [
|
"sessions": [
|
||||||
{
|
{
|
||||||
"sessionId": "sess_7c5e5c4b",
|
"sessionId": "sess_7c5e5c4b",
|
||||||
"sessionType": 1,
|
|
||||||
"clientPlatform": "Web",
|
|
||||||
"onlineOnThisServer": true,
|
|
||||||
"clientInfoFromClient": "Android 15; Pixel 9",
|
"clientInfoFromClient": "Android 15; Pixel 9",
|
||||||
"clientInfoFromRequest": "UA=Java-http-client/17.0.18; remote=127.0.0.1",
|
"clientInfoFromRequest": "UA=Java-http-client/17.0.18; remote=127.0.0.1",
|
||||||
"geo": "RU/Moscow",
|
"geo": "RU/Moscow",
|
||||||
@@ -75,20 +58,6 @@
|
|||||||
- `501 / DB_ERROR_LIST_SESSIONS` — ошибка БД при чтении списка активных сессий.
|
- `501 / DB_ERROR_LIST_SESSIONS` — ошибка БД при чтении списка активных сессий.
|
||||||
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
|
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
|
||||||
|
|
||||||
### Поля одной сессии в `ListSessions`
|
|
||||||
|
|
||||||
- `sessionId` — идентификатор активной сессии;
|
|
||||||
- `sessionType` — числовой код типа сессии:
|
|
||||||
- `1` — клиент;
|
|
||||||
- `50` — кошелёк;
|
|
||||||
- `100` — homeserver;
|
|
||||||
- `clientPlatform` — строка платформы, как её прислал клиент;
|
|
||||||
- `onlineOnThisServer` — `true`, если эта сессия сейчас держит живое WebSocket-подключение именно к данному серверу;
|
|
||||||
- `clientInfoFromClient` — краткая строка клиента;
|
|
||||||
- `clientInfoFromRequest` — строка, собранная сервером из запроса;
|
|
||||||
- `geo` — страна/город или fallback-строка;
|
|
||||||
- `lastAuthenticatedAtMs` — время последней успешной авторизации этой сессии.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. `CloseActiveSession`
|
## 2. `CloseActiveSession`
|
||||||
@@ -165,321 +134,3 @@ K9v3nQ4u8jYk0a2p7cD4mLx1zR0sT5wV6bN8eH3fQ1M
|
|||||||
|
|
||||||
Важно: это **не человеко-читаемое имя**, а непрозрачный идентификатор.
|
Важно: это **не человеко-читаемое имя**, а непрозрачный идентификатор.
|
||||||
Нужно передавать его как есть, без нормализации регистра и без URL-экранирования внутри JSON.
|
Нужно передавать его как есть, без нормализации регистра и без URL-экранирования внутри JSON.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. TrustedDeviceLogin через доверенную сессию
|
|
||||||
|
|
||||||
Этот блок относится к сценарию добавления новой сессии через доверенное устройство пользователя.
|
|
||||||
|
|
||||||
### 5.1. `GetTrustedDeviceLoginSettings`
|
|
||||||
|
|
||||||
Доступно для любой уже авторизованной доверенной сессии пользователя.
|
|
||||||
|
|
||||||
### Запрос
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"op": "GetTrustedDeviceLoginSettings",
|
|
||||||
"requestId": "trusted-login-get-001",
|
|
||||||
"payload": {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Успешный ответ
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"op": "GetTrustedDeviceLoginSettings",
|
|
||||||
"requestId": "trusted-login-get-001",
|
|
||||||
"status": 200,
|
|
||||||
"ok": true,
|
|
||||||
"payload": {
|
|
||||||
"enabled": true,
|
|
||||||
"hasPassword": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Если отдельной записи настроек на сервере ещё нет, сервер считает состояние по умолчанию таким:
|
|
||||||
|
|
||||||
- `enabled = true`
|
|
||||||
- `hasPassword = false`
|
|
||||||
|
|
||||||
### Ошибки
|
|
||||||
|
|
||||||
- `463 / PAIRING_REQUIRES_AUTH_SESSION` — операция вызвана без уже авторизованной доверенной сессии пользователя.
|
|
||||||
|
|
||||||
### 5.2. `UpsertTrustedDeviceLoginSettings`
|
|
||||||
|
|
||||||
Доступно для любой уже авторизованной доверенной сессии пользователя.
|
|
||||||
|
|
||||||
### Запрос
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"op": "UpsertTrustedDeviceLoginSettings",
|
|
||||||
"requestId": "esp-set-001",
|
|
||||||
"payload": {
|
|
||||||
"enabled": true,
|
|
||||||
"passwordHash": "sha256$0123abcd..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Если вход через доверенное устройство должен работать **без доп. пароля**, клиент включает его с пустым `passwordHash`.
|
|
||||||
|
|
||||||
Если `enabled = false`, сервер автоматически удаляет пароль и запрещает вход через другое устройство.
|
|
||||||
|
|
||||||
Формат непустого `passwordHash`:
|
|
||||||
|
|
||||||
```text
|
|
||||||
sha256$<hex( SHA-256("shine-pairing|" + lower(login.trim()) + "|" + password) )>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Успешный ответ
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"op": "UpsertTrustedDeviceLoginSettings",
|
|
||||||
"requestId": "esp-set-001",
|
|
||||||
"status": 200,
|
|
||||||
"ok": true,
|
|
||||||
"payload": {
|
|
||||||
"enabled": true,
|
|
||||||
"hasPassword": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ошибки
|
|
||||||
|
|
||||||
- `463 / PAIRING_REQUIRES_AUTH_SESSION` — операция вызвана без уже авторизованной доверенной сессии пользователя.
|
|
||||||
|
|
||||||
### 5.3. `StartTrustedDeviceLogin`
|
|
||||||
|
|
||||||
Эта операция доступна без уже существующей пользовательской сессии.
|
|
||||||
|
|
||||||
### Запрос
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"op": "StartTrustedDeviceLogin",
|
|
||||||
"requestId": "esp-start-001",
|
|
||||||
"payload": {
|
|
||||||
"login": "alice",
|
|
||||||
"passwordHash": "sha256$0123abcd...",
|
|
||||||
"requesterSessionKey": "ed25519/BASE64_PUBLIC_KEY",
|
|
||||||
"requesterSessionType": 1,
|
|
||||||
"requesterClientPlatform": "Android",
|
|
||||||
"payloadType": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Если на доверённом устройстве вход включён **без доп. пароля**, новое устройство может отправить пустой `passwordHash`.
|
|
||||||
|
|
||||||
Поле `trustedSessionOnline` показывает, что у пользователя сейчас есть хотя бы одна онлайн доверенная сессия, способная принять pairing-заявку.
|
|
||||||
Поле `shortCode` теперь содержит `10` цифр. В UI его рекомендуется показывать как `5` пар, например: `49 20 70 91 23`.
|
|
||||||
|
|
||||||
TTL заявки фиксирован на сервере и сейчас всегда равен `300` секундам.
|
|
||||||
|
|
||||||
### Успешный ответ
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"op": "StartTrustedDeviceLogin",
|
|
||||||
"requestId": "esp-start-001",
|
|
||||||
"status": 200,
|
|
||||||
"ok": true,
|
|
||||||
"payload": {
|
|
||||||
"pairingId": "base64url",
|
|
||||||
"state": "created",
|
|
||||||
"shortCode": "4920709123",
|
|
||||||
"fingerprintB58": "ASvYDPQidnAroKzQjtCjTuEQE8ckktV5nmmhYRhDzGaA",
|
|
||||||
"expiresAtMs": 1781441990538,
|
|
||||||
"trustedSessionOnline": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ошибки
|
|
||||||
|
|
||||||
- `400 / EMPTY_LOGIN`
|
|
||||||
- `400 / EMPTY_REQUESTER_SESSION_KEY`
|
|
||||||
- `400 / BAD_REQUESTER_SESSION_KEY`
|
|
||||||
- `400 / BAD_SESSION_TYPE`
|
|
||||||
- `400 / BAD_PAYLOAD_TYPE`
|
|
||||||
- `422 / PAIRING_NOT_AVAILABLE`
|
|
||||||
- `422 / PAIRING_PASSWORD_INVALID` — pairing-пароль не подходит. Та же ошибка возвращается и если новое устройство ввело пароль, а у пользователя режим pairing включён без пароля.
|
|
||||||
- `422 / PAIRING_NO_TRUSTED_SESSION_ONLINE` — сейчас нет ни одной онлайн доверённой сессии пользователя, поэтому код не создаётся.
|
|
||||||
- `429 / PAIRING_RATE_LIMITED`
|
|
||||||
|
|
||||||
### 5.4. `ListTrustedDeviceLoginRequests`
|
|
||||||
|
|
||||||
Доступно для любой уже авторизованной доверенной сессии пользователя.
|
|
||||||
Возвращает только реально активные pending-заявки со `state = created`. Уже `approved` и `rejected` заявки в этот список больше не попадают.
|
|
||||||
|
|
||||||
### Успешный ответ
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"op": "ListTrustedDeviceLoginRequests",
|
|
||||||
"requestId": "esp-list-001",
|
|
||||||
"status": 200,
|
|
||||||
"ok": true,
|
|
||||||
"payload": {
|
|
||||||
"requests": [
|
|
||||||
{
|
|
||||||
"pairingId": "base64url",
|
|
||||||
"state": "created",
|
|
||||||
"requesterSessionKey": "ed25519/BASE64_PUBLIC_KEY",
|
|
||||||
"requesterSessionType": 1,
|
|
||||||
"requesterClientPlatform": "Android",
|
|
||||||
"payloadType": 1,
|
|
||||||
"shortCode": "4920709123",
|
|
||||||
"fingerprintB58": "ASvYDPQidnAroKzQjtCjTuEQE8ckktV5nmmhYRhDzGaA",
|
|
||||||
"createdAtMs": 1781441810538,
|
|
||||||
"expiresAtMs": 1781441990538,
|
|
||||||
"deliveredToHomeserver": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ошибки
|
|
||||||
|
|
||||||
- `463 / PAIRING_REQUIRES_AUTH_SESSION`
|
|
||||||
|
|
||||||
### 5.5. `ApproveTrustedDeviceLogin`
|
|
||||||
|
|
||||||
Доступно для любой уже авторизованной доверенной сессии пользователя.
|
|
||||||
|
|
||||||
### Запрос
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"op": "ApproveTrustedDeviceLogin",
|
|
||||||
"requestId": "esp-approve-001",
|
|
||||||
"payload": {
|
|
||||||
"pairingId": "base64url",
|
|
||||||
"encryptedPayload": "BASE64_OR_OTHER_OPAQUE_PAYLOAD"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Успешный ответ
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"op": "ApproveTrustedDeviceLogin",
|
|
||||||
"requestId": "esp-approve-001",
|
|
||||||
"status": 200,
|
|
||||||
"ok": true,
|
|
||||||
"payload": {
|
|
||||||
"pairingId": "base64url",
|
|
||||||
"state": "approved"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ошибки
|
|
||||||
|
|
||||||
- `400 / EMPTY_PAIRING_ID`
|
|
||||||
- `400 / EMPTY_ENCRYPTED_PAYLOAD`
|
|
||||||
- `404 / PAIRING_NOT_FOUND`
|
|
||||||
- `422 / PAIRING_OF_ANOTHER_USER`
|
|
||||||
- `422 / PAIRING_NOT_PENDING`
|
|
||||||
- `422 / PAIRING_EXPIRED`
|
|
||||||
- `463 / PAIRING_REQUIRES_AUTH_SESSION`
|
|
||||||
|
|
||||||
### 5.6. `RejectTrustedDeviceLogin`
|
|
||||||
|
|
||||||
Доступно для любой уже авторизованной доверенной сессии пользователя. Похоже на approve, но переводит заявку в `state=rejected`.
|
|
||||||
|
|
||||||
### 5.7. `GetTrustedDeviceLoginStatus`
|
|
||||||
|
|
||||||
Операция для нового устройства.
|
|
||||||
|
|
||||||
### Запрос
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"op": "GetTrustedDeviceLoginStatus",
|
|
||||||
"requestId": "esp-status-001",
|
|
||||||
"payload": {
|
|
||||||
"pairingId": "base64url"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Успешный ответ после approve
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"op": "GetTrustedDeviceLoginStatus",
|
|
||||||
"requestId": "esp-status-001",
|
|
||||||
"status": 200,
|
|
||||||
"ok": true,
|
|
||||||
"payload": {
|
|
||||||
"pairingId": "base64url",
|
|
||||||
"state": "approved",
|
|
||||||
"shortCode": "4920709123",
|
|
||||||
"fingerprintB58": "ASvYDPQidnAroKzQjtCjTuEQE8ckktV5nmmhYRhDzGaA",
|
|
||||||
"payloadType": 1,
|
|
||||||
"encryptedPayload": "AQIDBA==",
|
|
||||||
"expiresAtMs": 1781441990538
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Возможные `state`
|
|
||||||
|
|
||||||
- `created`
|
|
||||||
- `approved`
|
|
||||||
- `rejected`
|
|
||||||
- `canceled`
|
|
||||||
- `expired`
|
|
||||||
|
|
||||||
### 5.8. `CancelTrustedDeviceLogin`
|
|
||||||
|
|
||||||
Операция для нового устройства, которое уже создало pairing-заявку и хочет принудительно снять ожидание до истечения TTL.
|
|
||||||
|
|
||||||
### Запрос
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"op": "CancelTrustedDeviceLogin",
|
|
||||||
"requestId": "esp-cancel-001",
|
|
||||||
"payload": {
|
|
||||||
"pairingId": "base64url",
|
|
||||||
"requesterSessionKey": "ed25519/BASE64_PUBLIC_KEY"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Успешный ответ
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"op": "CancelTrustedDeviceLogin",
|
|
||||||
"requestId": "esp-cancel-001",
|
|
||||||
"status": 200,
|
|
||||||
"ok": true,
|
|
||||||
"payload": {
|
|
||||||
"pairingId": "base64url",
|
|
||||||
"state": "canceled"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ошибки
|
|
||||||
|
|
||||||
- `400 / EMPTY_PAIRING_ID`
|
|
||||||
- `400 / EMPTY_REQUESTER_SESSION_KEY`
|
|
||||||
- `400 / BAD_REQUESTER_SESSION_KEY`
|
|
||||||
- `404 / PAIRING_NOT_FOUND`
|
|
||||||
- `422 / PAIRING_OF_ANOTHER_REQUESTER`
|
|
||||||
- `422 / PAIRING_NOT_PENDING`
|
|
||||||
|
|||||||
@@ -15,20 +15,10 @@
|
|||||||
| `AddUser` | `01_User_Registration_API.md` | отключено (`410 / ADD_USER_DISABLED`) |
|
| `AddUser` | `01_User_Registration_API.md` | отключено (`410 / ADD_USER_DISABLED`) |
|
||||||
| `GetUser` | `01_User_Registration_API.md` | чтение/проверка пользователя + server-состояние его блокчейна |
|
| `GetUser` | `01_User_Registration_API.md` | чтение/проверка пользователя + server-состояние его блокчейна |
|
||||||
| `SearchUsers` | `01_User_Registration_API.md` | поиск логинов по префиксу |
|
| `SearchUsers` | `01_User_Registration_API.md` | поиск логинов по префиксу |
|
||||||
| `TestGetFreeAvatarQuota` | `14_Test_Free_Avatar_Upload_API.md` | временный тестовый просмотр остатка бесплатных загрузок аватара |
|
|
||||||
| `TestUploadFreeAvatar` | `14_Test_Free_Avatar_Upload_API.md` | временная тестовая бесплатная загрузка маленького аватара в Arweave |
|
|
||||||
| `AuthChallenge` | `02_Authentication_API.md` | challenge для создания новой сессии |
|
| `AuthChallenge` | `02_Authentication_API.md` | challenge для создания новой сессии |
|
||||||
| `CreateAuthSession` | `02_Authentication_API.md` | создание новой авторизованной сессии |
|
| `CreateAuthSession` | `02_Authentication_API.md` | создание новой авторизованной сессии |
|
||||||
| `SessionChallenge` | `02_Authentication_API.md` | challenge для входа в существующую сессию |
|
| `SessionChallenge` | `02_Authentication_API.md` | challenge для входа в существующую сессию |
|
||||||
| `SessionLogin` | `02_Authentication_API.md` | вход в существующую сессию |
|
| `SessionLogin` | `02_Authentication_API.md` | вход в существующую сессию |
|
||||||
| `GetTrustedDeviceLoginSettings` | `03_Session_Management_API.md` | чтение текущего режима входа через доверенное устройство |
|
|
||||||
| `UpsertTrustedDeviceLoginSettings` | `03_Session_Management_API.md` | включение/обновление pairing-настроек доверенной сессией |
|
|
||||||
| `StartTrustedDeviceLogin` | `03_Session_Management_API.md` | создание pairing-заявки для нового устройства |
|
|
||||||
| `ListTrustedDeviceLoginRequests` | `03_Session_Management_API.md` | список активных pairing-заявок для доверенной сессии |
|
|
||||||
| `ApproveTrustedDeviceLogin` | `03_Session_Management_API.md` | подтверждение pairing-заявки доверенной сессией |
|
|
||||||
| `RejectTrustedDeviceLogin` | `03_Session_Management_API.md` | отклонение pairing-заявки доверенной сессией |
|
|
||||||
| `CancelTrustedDeviceLogin` | `03_Session_Management_API.md` | отмена pairing-заявки со стороны нового ожидающего устройства |
|
|
||||||
| `GetTrustedDeviceLoginStatus` | `03_Session_Management_API.md` | чтение статуса и результата pairing-заявки |
|
|
||||||
| `ListSessions` | `03_Session_Management_API.md` | список активных сессий |
|
| `ListSessions` | `03_Session_Management_API.md` | список активных сессий |
|
||||||
| `CloseActiveSession` | `03_Session_Management_API.md` | закрытие активной сессии |
|
| `CloseActiveSession` | `03_Session_Management_API.md` | закрытие активной сессии |
|
||||||
| `AddBlock` | `04_Add_Block_to_Blockchain_API.md` | добавление блока в блокчейн |
|
| `AddBlock` | `04_Add_Block_to_Blockchain_API.md` | добавление блока в блокчейн |
|
||||||
@@ -64,6 +54,5 @@
|
|||||||
## Важные замечания
|
## Важные замечания
|
||||||
|
|
||||||
- `ReceiveOutcomingMessage` сейчас зарегистрирован как алиас того же handler/request-класса, что и `SendMessagePair`.
|
- `ReceiveOutcomingMessage` сейчас зарегистрирован как алиас того же handler/request-класса, что и `SendMessagePair`.
|
||||||
- Отдельных HTTP endpoints для DM-файлов сейчас нет.
|
|
||||||
- Классы `Net_MarkChannelMessagesSeen_*` существуют в коде, но операция `MarkChannelMessagesSeen` не зарегистрирована в `JsonHandlerRegistry`, поэтому в публичный список API не входит.
|
- Классы `Net_MarkChannelMessagesSeen_*` существуют в коде, но операция `MarkChannelMessagesSeen` не зарегистрирована в `JsonHandlerRegistry`, поэтому в публичный список API не входит.
|
||||||
- HTTP debug endpoints из `src/main/java/server/debug/` не входят в этот индекс WebSocket `op`; они описаны отдельно в `13_HTTP_Debug_API.md`.
|
- HTTP debug endpoints из `src/main/java/server/debug/` не входят в этот индекс WebSocket `op`; они описаны отдельно в `13_HTTP_Debug_API.md`.
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
# API для разработчиков: DM, push и сигналы звонков
|
# API для разработчиков: DM, push и сигналы звонков
|
||||||
|
|
||||||
Документ описывает публичные операции, связанные с личными сообщениями, WebPush и сигналами звонков.
|
Документ описывает WebSocket-операции для подписанных личных сообщений, WebPush и realtime-сигналов звонков.
|
||||||
|
|
||||||
Подробная логика DM и бинарного формата:
|
Логика личных сообщений дополнительно описана в `Dev_Docs/Personal_Messages/README.md`; этот файл фиксирует именно публичные `op`, поля запросов и поля ответов.
|
||||||
|
|
||||||
- `Dev_Docs/Personal_Messages/README.md`
|
|
||||||
|
|
||||||
## 1. `UpsertPushToken`
|
## 1. `UpsertPushToken`
|
||||||
|
|
||||||
@@ -42,9 +40,11 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2. `SendTestWebPush`
|
## 2. `SendTestWebPush`
|
||||||
|
|
||||||
Требует авторизации.
|
Требует авторизации. Если `login` передан, он должен совпадать с логином текущей сессии.
|
||||||
|
|
||||||
### Запрос
|
### Запрос
|
||||||
|
|
||||||
@@ -61,18 +61,65 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 3. `SendMessagePair` и `ReceiveOutcomingMessage`
|
### Успешный ответ
|
||||||
|
|
||||||
`ReceiveOutcomingMessage` — алиас `SendMessagePair`.
|
```json
|
||||||
|
{
|
||||||
|
"op": "SendTestWebPush",
|
||||||
|
"requestId": "push-test-001",
|
||||||
|
"status": 200,
|
||||||
|
"ok": true,
|
||||||
|
"payload": {
|
||||||
|
"targetLogin": "alice",
|
||||||
|
"attemptedSessions": 1,
|
||||||
|
"sessionsWithPushConfig": 1,
|
||||||
|
"delivered": 1,
|
||||||
|
"failed": 0,
|
||||||
|
"sentAtMs": 1774700000123
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Назначение
|
---
|
||||||
|
|
||||||
Передаёт пару signed DM-блоков:
|
## 3. `SendDirectMessage`
|
||||||
|
|
||||||
- `incomingBlobB64` — блок `type=1` или `type=3`
|
Отправляет один подписанный DM-пакет.
|
||||||
- `outgoingBlobB64` — блок `type=2` или `type=4`
|
|
||||||
|
|
||||||
Для контентных сообщений `type=1/2` внутри base64 лежит бинарный формат `SHiNE_DM`.
|
### Запрос
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "SendDirectMessage",
|
||||||
|
"requestId": "dm-001",
|
||||||
|
"payload": {
|
||||||
|
"blobB64": "BASE64_SIGNED_DM_PACKET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Успешный ответ
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "SendDirectMessage",
|
||||||
|
"requestId": "dm-001",
|
||||||
|
"status": 200,
|
||||||
|
"ok": true,
|
||||||
|
"payload": {
|
||||||
|
"messageId": "dm-1",
|
||||||
|
"deliveredWsSessions": 1,
|
||||||
|
"deliveredWebPushSessions": 0,
|
||||||
|
"sessionNotFound": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. `SendMessagePair` и `ReceiveOutcomingMessage`
|
||||||
|
|
||||||
|
`ReceiveOutcomingMessage` сейчас является алиасом `SendMessagePair` и использует тот же request/handler.
|
||||||
|
|
||||||
### Запрос
|
### Запрос
|
||||||
|
|
||||||
@@ -96,31 +143,20 @@
|
|||||||
"status": 200,
|
"status": 200,
|
||||||
"ok": true,
|
"ok": true,
|
||||||
"payload": {
|
"payload": {
|
||||||
"baseKey": "from|to|time|nonce",
|
"baseKey": "base-key",
|
||||||
"incomingKey": "from|to|time|nonce|1",
|
"incomingKey": "incoming-key",
|
||||||
"outgoingKey": "from|to|time|nonce|2",
|
"outgoingKey": "outgoing-key",
|
||||||
"deliveredWsSessions": 1,
|
"deliveredWsSessions": 1,
|
||||||
"deliveredWebPushSessions": 0
|
"deliveredWebPushSessions": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Ошибки
|
---
|
||||||
|
|
||||||
- `400 / BAD_FIELDS` — пустой `incomingBlobB64` или `outgoingBlobB64`
|
## 5. `ReceiveIncomingMessage`
|
||||||
- `400 / BAD_BLOCK_FORMAT` — base64 или бинарный контейнер повреждён
|
|
||||||
- `400 / BAD_CONTENT_FORMAT` — для контентного сообщения пришёл не `SHiNE_DM`
|
|
||||||
- `400 / ATTACHMENTS_DISABLED` — в `SHiNE_DM` пришёл `attachmentsCount != 0`
|
|
||||||
- `404 / USER_NOT_FOUND` — один из логинов не найден
|
|
||||||
- `460 / BAD_SIGNATURE` — подпись блока не прошла проверку
|
|
||||||
|
|
||||||
## 4. `ReceiveIncomingMessage`
|
Принимает входящий подписанный DM-блок.
|
||||||
|
|
||||||
Принимает только один входящий signed DM-блок.
|
|
||||||
|
|
||||||
### Назначение
|
|
||||||
|
|
||||||
Используется там, где нужно принять только incoming-вариант сообщения.
|
|
||||||
|
|
||||||
### Запрос
|
### Запрос
|
||||||
|
|
||||||
@@ -134,9 +170,28 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 5. `AckSessionDelivery`
|
### Успешный ответ
|
||||||
|
|
||||||
Требует авторизации. Подтверждает доставку в текущую сессию.
|
```json
|
||||||
|
{
|
||||||
|
"op": "ReceiveIncomingMessage",
|
||||||
|
"requestId": "dm-in-001",
|
||||||
|
"status": 200,
|
||||||
|
"ok": true,
|
||||||
|
"payload": {
|
||||||
|
"messageKey": "incoming-key",
|
||||||
|
"baseKey": "base-key",
|
||||||
|
"deliveredWsSessions": 1,
|
||||||
|
"deliveredWebPushSessions": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. `AckSessionDelivery`
|
||||||
|
|
||||||
|
Требует авторизации. Подтверждает доставку сообщения в текущую сессию.
|
||||||
|
|
||||||
### Запрос
|
### Запрос
|
||||||
|
|
||||||
@@ -145,46 +200,107 @@
|
|||||||
"op": "AckSessionDelivery",
|
"op": "AckSessionDelivery",
|
||||||
"requestId": "ack-001",
|
"requestId": "ack-001",
|
||||||
"payload": {
|
"payload": {
|
||||||
"messageKey": "from|to|time|nonce|1"
|
"messageKey": "incoming-key"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 6. Событие `SignedMessageArrived`
|
### Успешный ответ
|
||||||
|
|
||||||
Сервер присылает его по WebSocket в активные сессии адресата.
|
|
||||||
|
|
||||||
### Payload события
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"messageKey": "from|to|time|nonce|1",
|
"op": "AckSessionDelivery",
|
||||||
"baseKey": "from|to|time|nonce",
|
"requestId": "ack-001",
|
||||||
"fromLogin": "alice",
|
"status": 200,
|
||||||
"toLogin": "bob",
|
"ok": true,
|
||||||
"targetLogin": "bob",
|
"payload": {
|
||||||
"messageType": 1,
|
"messageKey": "incoming-key"
|
||||||
"timeMs": 1774700000123,
|
}
|
||||||
"nonce": 123456789,
|
|
||||||
"blobB64": "BASE64_SIGNED_BLOCK",
|
|
||||||
"backlog": false
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Если это новая ревизия того же письма, `messageKey` остаётся тем же, а `revisionTimeMs` меняется внутри бинарного блока.
|
---
|
||||||
|
|
||||||
## 7. `CallInviteBroadcast`
|
## 7. `CallInviteBroadcast`
|
||||||
|
|
||||||
Требует авторизации. Шлёт приглашение к звонку в активные сессии `toLogin`.
|
Требует авторизации. Отправляет приглашение к звонку на активные сессии пользователя `toLogin`.
|
||||||
|
|
||||||
|
### Запрос
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "CallInviteBroadcast",
|
||||||
|
"requestId": "call-invite-001",
|
||||||
|
"payload": {
|
||||||
|
"toLogin": "bob",
|
||||||
|
"callId": "call-1",
|
||||||
|
"type": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Успешный ответ
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "CallInviteBroadcast",
|
||||||
|
"requestId": "call-invite-001",
|
||||||
|
"status": 200,
|
||||||
|
"ok": true,
|
||||||
|
"payload": {
|
||||||
|
"callId": "call-1",
|
||||||
|
"deliveredWsSessions": 1,
|
||||||
|
"deliveredFcmSessions": 0,
|
||||||
|
"deliveredWebPushSessions": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 8. `CallSignalToSession`
|
## 8. `CallSignalToSession`
|
||||||
|
|
||||||
Требует авторизации. Шлёт сигнал звонка в конкретную сессию.
|
Требует авторизации. Отправляет сигнал звонка в конкретную сессию получателя.
|
||||||
|
|
||||||
## 9. Замечания
|
### Запрос
|
||||||
|
|
||||||
- read-receipt `type=3/4` пока остаются в legacy-формате `SHiNE_dm2`
|
```json
|
||||||
- контентные DM `type=1/2` используют `SHiNE_DM`
|
{
|
||||||
- сервер хранит только последнюю версию контентного сообщения по `messageKey`
|
"op": "CallSignalToSession",
|
||||||
- удаление сообщения реализуется новой ревизией с пустым телом и `attachmentsCount = 0`
|
"requestId": "call-signal-001",
|
||||||
- HTTP endpoints для DM-файлов сейчас отсутствуют
|
"payload": {
|
||||||
|
"toLogin": "bob",
|
||||||
|
"targetSessionId": "SESSION_ID",
|
||||||
|
"callId": "call-1",
|
||||||
|
"type": 101,
|
||||||
|
"data": "{\"sdp\":\"...\"}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Успешный ответ
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "CallSignalToSession",
|
||||||
|
"requestId": "call-signal-001",
|
||||||
|
"status": 200,
|
||||||
|
"ok": true,
|
||||||
|
"payload": {
|
||||||
|
"delivered": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Если целевая сессия не найдена или доставка не удалась, сервер может вернуть `404`.
|
||||||
|
|
||||||
|
## Типовые ошибки
|
||||||
|
|
||||||
|
- `422 / NOT_AUTHENTICATED` — требуется авторизация.
|
||||||
|
- `400 / BAD_FIELDS` — не заполнены обязательные поля.
|
||||||
|
- `404 / USER_NOT_FOUND` — пользователь не найден.
|
||||||
|
- `404 / SESSION_NOT_FOUND` — сессия не найдена.
|
||||||
|
- `422 / BAD_SIGNATURE` — подпись DM не прошла проверку.
|
||||||
|
- `422 / BAD_DEVICE_KEY` — некорректный device key отправителя.
|
||||||
|
- `422 / BAD_TIME_WINDOW` — время подписанного сообщения вне допустимого окна.
|
||||||
|
- `422 / REPLAY` — повторное сообщение заблокировано.
|
||||||
|
|||||||
@@ -1,176 +0,0 @@
|
|||||||
# Временное Test API для бесплатной загрузки аватаров в Arweave
|
|
||||||
|
|
||||||
> Статус: **временное тестовое решение**.
|
|
||||||
> Все операции из этого файла начинаются с `Test...`, чтобы это было видно сразу и в коде, и в UI.
|
|
||||||
|
|
||||||
## Назначение
|
|
||||||
|
|
||||||
Этот временный API даёт пользователю ограниченную бесплатную загрузку маленьких аватаров в Arweave:
|
|
||||||
|
|
||||||
- загрузка идёт через **серверный Arweave-кошелёк**;
|
|
||||||
- лимит на пользователя: по умолчанию `3` загрузки за всё время;
|
|
||||||
- лимит хранится в SQLite-таблице `test_free_avatar_uploads`;
|
|
||||||
- если лимит исчерпан, сервер возвращает понятную ошибку;
|
|
||||||
- загружать можно только маленький итоговый файл аватара, по умолчанию до `128 KB`.
|
|
||||||
|
|
||||||
## Настройки сервера
|
|
||||||
|
|
||||||
В `application.properties`:
|
|
||||||
|
|
||||||
```properties
|
|
||||||
test.freeAvatar.enabled=true
|
|
||||||
test.freeAvatar.gateway=https://arweave.net
|
|
||||||
test.freeAvatar.limitPerUser=3
|
|
||||||
test.freeAvatar.maxBytes=131072
|
|
||||||
test.freeAvatar.walletAddress=
|
|
||||||
test.freeAvatar.walletJwkPath=
|
|
||||||
```
|
|
||||||
|
|
||||||
Пояснения:
|
|
||||||
|
|
||||||
- `test.freeAvatar.enabled` - включить или выключить временный API;
|
|
||||||
- `test.freeAvatar.gateway` - Arweave gateway для `price/tx/wallet`;
|
|
||||||
- `test.freeAvatar.limitPerUser` - пожизненный бесплатный лимит на пользователя;
|
|
||||||
- `test.freeAvatar.maxBytes` - максимальный размер итогового файла;
|
|
||||||
- `test.freeAvatar.walletAddress` - публичный адрес серверного Arweave-кошелька;
|
|
||||||
- `test.freeAvatar.walletJwkPath` - путь к приватному JWK-файлу серверного кошелька.
|
|
||||||
|
|
||||||
Важно:
|
|
||||||
|
|
||||||
- приватный JWK хранится вне кода;
|
|
||||||
- если `walletAddress` указан и не совпадает с адресом, вычисленным из JWK, сервер вернёт ошибку настройки.
|
|
||||||
|
|
||||||
## `TestGetFreeAvatarQuota`
|
|
||||||
|
|
||||||
Возвращает остаток бесплатных загрузок для текущего авторизованного пользователя.
|
|
||||||
|
|
||||||
### Запрос
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"op": "TestGetFreeAvatarQuota",
|
|
||||||
"requestId": "req-test-avatar-quota-1",
|
|
||||||
"payload": {}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Успешный ответ
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"op": "TestGetFreeAvatarQuota",
|
|
||||||
"requestId": "req-test-avatar-quota-1",
|
|
||||||
"status": 200,
|
|
||||||
"ok": true,
|
|
||||||
"payload": {
|
|
||||||
"enabled": true,
|
|
||||||
"limit": 3,
|
|
||||||
"usedCount": 1,
|
|
||||||
"remainingCount": 2,
|
|
||||||
"maxBytes": 131072
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Поля ответа
|
|
||||||
|
|
||||||
- `enabled` - временный API сейчас включён на сервере или нет;
|
|
||||||
- `limit` - полный лимит бесплатных загрузок;
|
|
||||||
- `usedCount` - сколько уже израсходовано;
|
|
||||||
- `remainingCount` - сколько ещё осталось;
|
|
||||||
- `maxBytes` - максимальный размер итогового файла.
|
|
||||||
|
|
||||||
### Ошибки
|
|
||||||
|
|
||||||
- `422 NOT_AUTHENTICATED` - требуется авторизация.
|
|
||||||
|
|
||||||
## `TestUploadFreeAvatar`
|
|
||||||
|
|
||||||
Временная бесплатная загрузка маленькой аватарки в Arweave через серверный кошелёк.
|
|
||||||
|
|
||||||
### Правила
|
|
||||||
|
|
||||||
- операция требует авторизованную сессию;
|
|
||||||
- сервер использует текущий login из сессии;
|
|
||||||
- сервер принимает только:
|
|
||||||
- `image/jpeg`
|
|
||||||
- `image/png`
|
|
||||||
- `image/webp`
|
|
||||||
- размер итогового файла должен быть не больше `maxBytes` из квоты;
|
|
||||||
- если пользователь уже сделал `limit` бесплатных загрузок, операция запрещена.
|
|
||||||
|
|
||||||
### Запрос
|
|
||||||
|
|
||||||
`fileBytesBase64` - это обычный Base64 байт итогового подготовленного файла.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"op": "TestUploadFreeAvatar",
|
|
||||||
"requestId": "req-test-avatar-upload-1",
|
|
||||||
"payload": {
|
|
||||||
"contentType": "image/webp",
|
|
||||||
"fileBytesBase64": "UklGRiQAAABXRUJQVlA4WAoAAAAQAAAA...",
|
|
||||||
"sha256Hex": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Успешный ответ
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"op": "TestUploadFreeAvatar",
|
|
||||||
"requestId": "req-test-avatar-upload-1",
|
|
||||||
"status": 200,
|
|
||||||
"ok": true,
|
|
||||||
"payload": {
|
|
||||||
"txId": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
|
||||||
"sha256Hex": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
|
||||||
"usedCount": 2,
|
|
||||||
"remainingCount": 1,
|
|
||||||
"limit": 3,
|
|
||||||
"gateway": "https://arweave.net"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Поля ответа
|
|
||||||
|
|
||||||
- `txId` - Arweave Transaction ID загруженного файла;
|
|
||||||
- `sha256Hex` - SHA-256 загруженного файла;
|
|
||||||
- `usedCount` - сколько бесплатных загрузок уже израсходовано после этой операции;
|
|
||||||
- `remainingCount` - сколько бесплатных загрузок осталось;
|
|
||||||
- `limit` - общий лимит;
|
|
||||||
- `gateway` - gateway, через который сервер отправлял транзакцию.
|
|
||||||
|
|
||||||
### Ошибки
|
|
||||||
|
|
||||||
- `422 NOT_AUTHENTICATED` - требуется авторизация;
|
|
||||||
- `400 BAD_FIELDS` - не переданы `contentType` или `fileBytesBase64`;
|
|
||||||
- `400 BAD_BASE64` - `fileBytesBase64` не декодируется;
|
|
||||||
- `400 BAD_AVATAR_FILE` - файл не проходит ограничения сервера;
|
|
||||||
- `400 FREE_AVATAR_LIMIT_EXHAUSTED` - бесплатный лимит аватарок исчерпан;
|
|
||||||
- `501 FREE_AVATAR_TEMP_DISABLED` - временная функция выключена или сервер не настроен;
|
|
||||||
- `500 INTERNAL_ERROR` - внутренняя ошибка сервера.
|
|
||||||
|
|
||||||
## Как это используется в UI
|
|
||||||
|
|
||||||
На экране редактирования профиля в мастере смены аватара есть временный сценарий:
|
|
||||||
|
|
||||||
- `Залить аватар бесплатно`
|
|
||||||
|
|
||||||
UI:
|
|
||||||
|
|
||||||
1. вызывает `TestGetFreeAvatarQuota`;
|
|
||||||
2. показывает остаток лимита;
|
|
||||||
3. локально подготавливает уменьшенный файл аватара;
|
|
||||||
4. проверяет, что итоговый файл не превышает `maxBytes`;
|
|
||||||
5. вызывает `TestUploadFreeAvatar`;
|
|
||||||
6. после получения `txId` обычным путём записывает `avatar.ar` в профиль через `AddBlock`.
|
|
||||||
|
|
||||||
## Почему решение временное
|
|
||||||
|
|
||||||
- используется общий серверный Arweave-кошелёк;
|
|
||||||
- лимит хранится отдельной технической таблицей;
|
|
||||||
- операции имеют префикс `Test...`;
|
|
||||||
- сценарий нужен как переходный бесплатный путь для маленьких аватаров.
|
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
- `medium/2026-05-25_1106_shine_balance_wallet.md` - кошелёк и пополнение баланса сияния через блокчейн.
|
- `medium/2026-05-25_1106_shine_balance_wallet.md` - кошелёк и пополнение баланса сияния через блокчейн.
|
||||||
- `medium/2026-05-26_0029_esp32s3_file_storage.md` - ESP32S3 как личное файловое хранилище SHiNE для файлов переписок и вложений.
|
- `medium/2026-05-26_0029_esp32s3_file_storage.md` - ESP32S3 как личное файловое хранилище SHiNE для файлов переписок и вложений.
|
||||||
- `medium/2026-06-03_подключение_других_устройств_через_qr.md` - довести подключение других устройств через QR: сейчас заготовка есть, но сценарий работает нестабильно и его нужно будет отдельно доделать.
|
- `medium/2026-06-03_подключение_других_устройств_через_qr.md` - довести подключение других устройств через QR: сейчас заготовка есть, но сценарий работает нестабильно и его нужно будет отдельно доделать.
|
||||||
- `medium/2026-06-02_сессионные_homeserver_в_pda.md` - несколько homeserver-ов пользователя как типизированные сессии в PDA с версией записи.
|
- `medium/2026-06-02_сессионные_саб_серверы_в_pda.md` - несколько саб-серверов пользователя как типизированные сессии в PDA с версией записи.
|
||||||
|
|
||||||
### DAO-запуск
|
### DAO-запуск
|
||||||
|
|
||||||
@@ -45,4 +45,4 @@
|
|||||||
|
|
||||||
### Дальнее будущее
|
### Дальнее будущее
|
||||||
|
|
||||||
- `far/2026-06-20_1639_homeserver_technical_commands_and_file_transfer.md` - технические команды для homeserver через SHiNE/WebRTC DataChannel и обмен файлами по чанкам с адресацией по `SHA-256`.
|
- Сейчас задач нет.
|
||||||
|
|||||||
-114
@@ -1,114 +0,0 @@
|
|||||||
# Homeserver: технические команды и передача файлов через SHiNE/WebRTC
|
|
||||||
|
|
||||||
## Зачем нужна фича
|
|
||||||
|
|
||||||
Идея на дальнее будущее: дать возможность обращаться к homeserver не только как к участнику сети SHiNE, но и как к удалённой технической точке управления.
|
|
||||||
|
|
||||||
Цели:
|
|
||||||
- отправлять на homeserver технические команды в текстовом виде;
|
|
||||||
- получать текстовый ответ на команду;
|
|
||||||
- при наличии WebRTC DataChannel передавать части файлов в обе стороны;
|
|
||||||
- хранить полученные файлы на SD-карте homeserver;
|
|
||||||
- использовать единый механизм доставки как через сервер SHiNE, так и напрямую через DataChannel.
|
|
||||||
|
|
||||||
## Горизонт
|
|
||||||
|
|
||||||
`far` - идея без ближайшего срока реализации. Сейчас приоритет ниже, чем запуск и стабилизация основного проекта.
|
|
||||||
|
|
||||||
## Что именно имеется в виду
|
|
||||||
|
|
||||||
### 1. Единая модель технической команды
|
|
||||||
|
|
||||||
Техническая команда должна иметь единый смысл независимо от транспорта доставки:
|
|
||||||
- через любой доступный сервер SHiNE;
|
|
||||||
- через уже установленный WebRTC DataChannel.
|
|
||||||
|
|
||||||
Если конкретный транспорт недоступен, ответ по нему может не прийти. Это считается нормальным поведением протокола.
|
|
||||||
|
|
||||||
### 2. Команда как короткоживущий подписанный сигнал
|
|
||||||
|
|
||||||
У команды должны быть:
|
|
||||||
- `commandId`;
|
|
||||||
- временная метка;
|
|
||||||
- TTL около 10 секунд;
|
|
||||||
- криптографическая подпись.
|
|
||||||
|
|
||||||
Смысл такой:
|
|
||||||
- если команда быстро дошла, homeserver подтверждает принятие;
|
|
||||||
- если не дошла вовремя, команда считается протухшей;
|
|
||||||
- отправитель может безопасно послать повтор;
|
|
||||||
- при повторе homeserver отвечает либо `команда принята`, либо `уже выполнено ранее`.
|
|
||||||
|
|
||||||
Это даёт дедупликацию и безопасный resend без повторного выполнения действия.
|
|
||||||
|
|
||||||
### 3. Текстовые технические команды
|
|
||||||
|
|
||||||
Базовый сценарий похож на короткий удалённый shell-протокол, но на уровне строго ограниченных команд:
|
|
||||||
- отправил строку-команду;
|
|
||||||
- получил строку-ответ.
|
|
||||||
|
|
||||||
Команды не обязаны исполнять произвольный shell. Предпочтительная модель - белый список операций с контролируемым форматом аргументов и ответа.
|
|
||||||
|
|
||||||
### 4. Передача файлов только при наличии DataChannel
|
|
||||||
|
|
||||||
Если между устройствами есть WebRTC DataChannel, через него можно передавать технические сообщения для файлового обмена.
|
|
||||||
|
|
||||||
Предварительная модель:
|
|
||||||
- имя файла = `SHA-256` содержимого;
|
|
||||||
- можно запросить диапазон байт `from..to`;
|
|
||||||
- можно отправить диапазон байт `from..to`;
|
|
||||||
- homeserver хранит полученные данные на SD-карте;
|
|
||||||
- если DataChannel нет, на запрос файловой передачи возвращается ответ в духе `не могу передать, нет data channel`.
|
|
||||||
|
|
||||||
Фактически файл-обмен должен быть частным случаем общего протокола технических команд.
|
|
||||||
|
|
||||||
### 5. Установка data-соединения по явной команде
|
|
||||||
|
|
||||||
Нужна техническая команда уровня:
|
|
||||||
- `установить data-соединение`.
|
|
||||||
|
|
||||||
Ответ:
|
|
||||||
- либо `да`, после чего запускается обычная процедура `offer/answer/ICE`;
|
|
||||||
- либо `нет` и причина отказа.
|
|
||||||
|
|
||||||
### 6. Доставка на пользовательские сессии
|
|
||||||
|
|
||||||
Логика должна быть совместима с общей моделью SHiNE, где технические сигналы можно отправлять на конкретные активные сессии пользователя.
|
|
||||||
|
|
||||||
Идея:
|
|
||||||
- на любую активную сессию пользователя можно посылать техническую команду;
|
|
||||||
- контакт пользователя может инициировать такую техническую коммуникацию так же, как он уже инициирует звонок или другой служебный сигнал.
|
|
||||||
|
|
||||||
## Что нужно будет сделать при возврате к задаче
|
|
||||||
|
|
||||||
- Спроектировать отдельный формат технических команд и ack-ответов.
|
|
||||||
- Решить, будет ли это новый тип служебных сообщений в существующем протоколе блокчейн/сигналинга или отдельная ветка поверх уже имеющихся transport-операций.
|
|
||||||
- Отдельно продумать авторизацию: кто именно из контактов и какие команды имеет право слать.
|
|
||||||
- Ограничить набор допустимых команд, чтобы не превратить механизм в небезопасный удалённый shell.
|
|
||||||
- Спроектировать протокол чанков файлов: размер чанка, нумерация, повторная отправка, контроль целостности, дозагрузка, завершение файла.
|
|
||||||
- Продумать хранение на SD-карте: временные файлы, сборка чанков, проверка итогового `SHA-256`, очистка мусора.
|
|
||||||
- Продумать поведение при отсутствии DataChannel, таймаутах и дублирующихся командах.
|
|
||||||
- Проверить, как это лучше встраивать в текущие клиентские сессии, звонки и homeserver-логику.
|
|
||||||
|
|
||||||
## Вопросы для будущего уточнения
|
|
||||||
|
|
||||||
- Это должен быть строго служебный протокол или пользователь сможет вызывать его и вручную из UI.
|
|
||||||
- Нужен ли доступ только к заранее разрешённым каталогам/файлам.
|
|
||||||
- Нужна ли двусторонняя синхронизация файлов или достаточно ручных команд `запросить кусок` / `отправить кусок`.
|
|
||||||
- Нужно ли разрешать передачу файлов через сервер SHiNE как fallback, или файл-обмен должен идти только через DataChannel.
|
|
||||||
- Какой максимальный размер файлов и допустимый объём хранения на SD-карте.
|
|
||||||
|
|
||||||
## Что уже сделано
|
|
||||||
|
|
||||||
Пока только зафиксирована идея и базовая концепция. Реализация не начиналась.
|
|
||||||
|
|
||||||
## Какие документы нужно будет обновить при реализации
|
|
||||||
|
|
||||||
- `Dev_Docs/Blockchain/README.md` и связанные файлы, если изменятся типы служебных сообщений или форматы блокчейн-команд.
|
|
||||||
- `Dev_Docs/API/` если изменится публичный серверный API или появятся новые операции.
|
|
||||||
- `Dev_Docs/Personal_Messages/README.md` если часть маршрутизации или подтверждений будет встроена в существующую логику доставки/сессий.
|
|
||||||
- Документацию по homeserver/ESP32, если появится пользовательская или сервисная файловая логика на устройстве.
|
|
||||||
|
|
||||||
## С какого места продолжать позже
|
|
||||||
|
|
||||||
Возвращаться к задаче только после стабилизации запуска проекта и базовых текущих функций. Начинать с проектирования протокола команд и матрицы прав доступа, а уже потом переходить к DataChannel-файлообмену.
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
# Дальнее будущее
|
# Дальнее будущее
|
||||||
|
|
||||||
|
Сейчас в этом горизонте нет активных идей.
|
||||||
|
|
||||||
Сюда переносить задачи, у которых нет понятного срока возврата и которые не нужно учитывать в ближайшем или среднесрочном планировании.
|
Сюда переносить задачи, у которых нет понятного срока возврата и которые не нужно учитывать в ближайшем или среднесрочном планировании.
|
||||||
|
|
||||||
## Идеи
|
|
||||||
|
|
||||||
- `2026-06-20_1639_homeserver_technical_commands_and_file_transfer.md` - технические команды для homeserver через сервер SHiNE или WebRTC DataChannel, а также файловый обмен чанками с хранением на SD-карте.
|
|
||||||
|
|||||||
+10
-10
@@ -1,4 +1,4 @@
|
|||||||
# Сессионные homeserver-ы в PDA пользователя
|
# Сессионные саб-серверы в PDA пользователя
|
||||||
|
|
||||||
- Статус:
|
- Статус:
|
||||||
`future`
|
`future`
|
||||||
@@ -10,15 +10,15 @@
|
|||||||
после завершения первого этапа по пользовательским сессиям
|
после завершения первого этапа по пользовательским сессиям
|
||||||
|
|
||||||
- Основание:
|
- Основание:
|
||||||
Идея зафиксирована после обсуждения архитектуры пользовательских сессий и внутренних homeserver-ов. Сейчас задача сознательно отложена: сначала нужно аккуратно ввести базовую модель сессий, а затем возвращаться к расширенной серверной роли.
|
Идея зафиксирована после обсуждения архитектуры пользовательских сессий и внутренних саб-серверов. Сейчас задача сознательно отложена: сначала нужно аккуратно ввести базовую модель сессий, а затем возвращаться к расширенной серверной роли.
|
||||||
|
|
||||||
## Зачем нужна фича
|
## Зачем нужна фича
|
||||||
|
|
||||||
У одного пользователя может быть несколько доверенных внутренних homeserver-ов, и каждый из них должен жить как отдельная пользовательская сессия, а не как отдельная особая сущность вне общей модели.
|
У одного пользователя может быть несколько доверенных внутренних саб-серверов, и каждый из них должен жить как отдельная пользовательская сессия, а не как отдельная особая сущность вне общей модели.
|
||||||
|
|
||||||
Это нужно, чтобы:
|
Это нужно, чтобы:
|
||||||
|
|
||||||
- хранить несколько homeserver-ов у одного пользователя одновременно;
|
- хранить несколько саб-серверов у одного пользователя одновременно;
|
||||||
- различать обычные клиентские сессии и серверные сессии по явному типу;
|
- различать обычные клиентские сессии и серверные сессии по явному типу;
|
||||||
- дать расширяемый формат записи с версией;
|
- дать расширяемый формат записи с версией;
|
||||||
- использовать единый подход для DM, звонков и внутренних команд между сессиями.
|
- использовать единый подход для DM, звонков и внутренних команд между сессиями.
|
||||||
@@ -35,18 +35,18 @@
|
|||||||
Предварительные значения:
|
Предварительные значения:
|
||||||
|
|
||||||
- тип `1` - обычная пользовательская сессия;
|
- тип `1` - обычная пользовательская сессия;
|
||||||
- тип `100` - homeserver пользователя;
|
- тип `100` - саб-сервер пользователя;
|
||||||
- версия `1` - первая рабочая версия формата записи сессии.
|
- версия `1` - первая рабочая версия формата записи сессии.
|
||||||
|
|
||||||
На текущем этапе под это уже зарезервирован отдельный блок `SessionsBlock` с `block_type = 55`, а `TrustedStateBlock` остаётся на `50`.
|
На текущем этапе под это уже зарезервирован отдельный блок `SessionsBlock` с `block_type = 55`, а `TrustedStateBlock` остаётся на `50`.
|
||||||
|
|
||||||
Важно: homeserver-ов у одного пользователя может быть несколько.
|
Важно: саб-серверов у одного пользователя может быть несколько.
|
||||||
|
|
||||||
## Архитектурный принцип
|
## Архитектурный принцип
|
||||||
|
|
||||||
Внутренний протокол взаимодействия должен оставаться транспортным.
|
Внутренний протокол взаимодействия должен оставаться транспортным.
|
||||||
|
|
||||||
То есть SHiNE-сервер не должен разбирать прикладной смысл внутренней нагрузки homeserver-а, а должен:
|
То есть SHiNE-сервер не должен разбирать прикладной смысл внутренней нагрузки саб-сервера, а должен:
|
||||||
|
|
||||||
- доставлять сообщения между сессиями;
|
- доставлять сообщения между сессиями;
|
||||||
- доставлять сигналы звонков между сессиями;
|
- доставлять сигналы звонков между сессиями;
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
- Вызов звонка уже рассылается по нескольким активным сессиям пользователя.
|
- Вызов звонка уже рассылается по нескольким активным сессиям пользователя.
|
||||||
- Сигналы звонка уже адресуются конкретной сессии, а stop-сигналы дублируются на остальные сессии того же пользователя.
|
- Сигналы звонка уже адресуются конкретной сессии, а stop-сигналы дублируются на остальные сессии того же пользователя.
|
||||||
|
|
||||||
Иными словами, текущая серверная логика ближе к модели "сервер доставляет между сессиями", чем к модели "сервер понимает внутренний протокол homeserver-а".
|
Иными словами, текущая серверная логика ближе к модели "сервер доставляет между сессиями", чем к модели "сервер понимает внутренний протокол саб-сервера".
|
||||||
|
|
||||||
## Что нужно сделать при возврате к задаче
|
## Что нужно сделать при возврате к задаче
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
- правила удаления и обновления записи;
|
- правила удаления и обновления записи;
|
||||||
- правила ротации `sessionPubKey`.
|
- правила ротации `sessionPubKey`.
|
||||||
6. Продумать, как UI и сервер будут отличать тип `1` и тип `100`.
|
6. Продумать, как UI и сервер будут отличать тип `1` и тип `100`.
|
||||||
7. Определить, какие внутренние сообщения homeserver-а останутся полностью прозрачными для SHiNE-сервера, а какие потребуют только технической маршрутизации.
|
7. Определить, какие внутренние сообщения саб-сервера останутся полностью прозрачными для SHiNE-сервера, а какие потребуют только технической маршрутизации.
|
||||||
8. Добавить API/операции чтения и обновления списка сессий, если для этого не хватит существующих механизмов.
|
8. Добавить API/операции чтения и обновления списка сессий, если для этого не хватит существующих механизмов.
|
||||||
9. После реализации обязательно обновить документацию.
|
9. После реализации обязательно обновить документацию.
|
||||||
|
|
||||||
@@ -101,5 +101,5 @@
|
|||||||
Продолжать после завершения первой части:
|
Продолжать после завершения первой части:
|
||||||
|
|
||||||
1. описать минимальный формат записи пользовательской сессии;
|
1. описать минимальный формат записи пользовательской сессии;
|
||||||
2. отдельно решить, живут ли homeserver-ы в том же списке, что и обычные сессии;
|
2. отдельно решить, живут ли саб-серверы в том же списке, что и обычные сессии;
|
||||||
3. затем уже проектировать операции регистрации, обновления и отключения таких сессий.
|
3. затем уже проектировать операции регистрации, обновления и отключения таких сессий.
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
# Деривация секрета и ключей SHiNE (формулы)
|
|
||||||
|
|
||||||
> **Статус: ИСТОЧНИК ИСТИНЫ (single source of truth) по конкретной деривации.**
|
|
||||||
> Этот файл описывает, как из пароля получается секрет и как из секрета выводятся
|
|
||||||
> все ключи (root, blockchain, device/Solana, homeserver) — формулами, байт-в-байт.
|
|
||||||
> Если в коде меняется деривация (формула секрета, параметры Argon2id, соль, формула
|
|
||||||
> ключа, разделитель `|`, набор/имена суффиксов, формат homeserver-ключа, связь
|
|
||||||
> dev-ключ ↔ Solana-адрес) — **в том же изменении обязательно править этот документ**.
|
|
||||||
> Роли и назначение ключей описаны отдельно в `Dev_Docs/Keys/README.md` (архитектура).
|
|
||||||
> Здесь — только механика. Документ намеренно краткий.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Секрет (masterSecret)
|
|
||||||
|
|
||||||
`masterSecret` — 32 байта. Два источника:
|
|
||||||
|
|
||||||
**А. Из пароля пользователя (основной путь, UI).**
|
|
||||||
|
|
||||||
```
|
|
||||||
login = trim(lowercase(login))
|
|
||||||
salt = SHA-256("shine-auth-v2|login=" + login + "|suffix=master.secret")[0..16) // первые 16 байт
|
|
||||||
material = utf8(login + "\n" + password)
|
|
||||||
masterSecret(32) = Argon2id(material, salt, t=2, m=65536 KiB, p=1, dkLen=32)
|
|
||||||
```
|
|
||||||
|
|
||||||
- Параметры Argon2id фиксированы: `t=2`, `m=65536` (64 МиБ), `p=1`, `dkLen=32`.
|
|
||||||
- Логин входит и в соль, и в начало `material` (склейка через `\n`).
|
|
||||||
- Пустой пароль **запрещён**: легаси-fallback без Argon2 удалён, `deriveMasterSecretFromPassword` бросает ошибку на пустом пароле, а форма регистрации в UI блокирует пустой пароль (`register-view.js`).
|
|
||||||
|
|
||||||
**Б. Случайный (прошивка ESP32, новый аккаунт без пароля).**
|
|
||||||
|
|
||||||
```
|
|
||||||
masterSecret(32) = 32 случайных байта (esp_random) // хранится на устройстве как base58
|
|
||||||
```
|
|
||||||
|
|
||||||
Дальше деривация ключей одинакова независимо от источника секрета.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Производные ключи
|
|
||||||
|
|
||||||
Все ключи выводятся из `masterSecret` по **одной формуле**, отличается только суффикс:
|
|
||||||
|
|
||||||
```
|
|
||||||
material = base64_std(masterSecret) + "|" + <суффикс>
|
|
||||||
seed(32) = SHA-256(material)
|
|
||||||
(pub, priv) = Ed25519_keypair_from_seed(seed)
|
|
||||||
```
|
|
||||||
|
|
||||||
- `base64_std` — стандартный base64 (не url-safe).
|
|
||||||
- Разделитель — символ `|`.
|
|
||||||
- Суффиксы значимы байт-в-байт (регистр и точки важны).
|
|
||||||
|
|
||||||
| Ключ | Суффикс | Назначение (кратко) |
|
|
||||||
|------|---------|---------------------|
|
|
||||||
| root | `root.key` | Личность. Подписывает unsigned-часть PDA-записи (`RootKeyBlock`). |
|
|
||||||
| blockchain | `bch.key` | Подписывает `LastBlockState` персонального блокчейна (`blockchain_public_key`). |
|
|
||||||
| device / **Solana** | `dev.key` | Ключ устройства = Solana-ключ. Fee payer и подпись Solana-транзакций; адрес кошелька = `base58(devicePub)`. См. §3. |
|
|
||||||
| homeserver | `homeserver.key:<имя>` | Ключ homeserver-устройства, по одному на каждый homeserver (различитель — имя). См. §4. |
|
|
||||||
|
|
||||||
Полные роли каждого ключа — в `Dev_Docs/Keys/README.md`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Solana-ключ
|
|
||||||
|
|
||||||
Отдельного «солана-ключа» нет. На Solana работают два ключа:
|
|
||||||
|
|
||||||
- **`dev.key` (device) — пополняемый кошелёк и fee payer.** Solana-адрес = `base58(devicePub)`.
|
|
||||||
Этим ключом оплачиваются и подписываются `create_user_pda` / `update_user_pda`.
|
|
||||||
Пополнять SOL нужно именно на этот адрес.
|
|
||||||
- **`root.key` — авторитет записи**, подписывает unsigned-часть PDA через Ed25519-инструкцию, но **не** является fee payer.
|
|
||||||
|
|
||||||
Соответствует формату PDA `shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md` §2.1
|
|
||||||
(«create/update оплачиваются с `device_key`», «root_key — не fee payer»).
|
|
||||||
|
|
||||||
Кратко про роли на Solana: `root.key` — это **главный (master) ключ**: им управляют PDA-записью
|
|
||||||
(`create/update`) и через это можно заменить все остальные ключи; `dev.key` — это **пополняемый
|
|
||||||
кошелёк и плательщик комиссий**. Полное описание ролей — `Dev_Docs/Keys/README.md`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Ключи homeserver
|
|
||||||
|
|
||||||
У пользователя может быть несколько homeserver-ов. Каждый имеет **своё имя** и **свой приватный ключ**,
|
|
||||||
выведенный из секрета по той же формуле с именованным суффиксом:
|
|
||||||
|
|
||||||
```
|
|
||||||
suffix = "homeserver.key:" + <имя homeserver> // имя по умолчанию: "homeserver1"
|
|
||||||
material = base64_std(masterSecret) + "|" + suffix
|
|
||||||
seed(32) = SHA-256(material)
|
|
||||||
(pub, priv) = Ed25519_keypair_from_seed(seed)
|
|
||||||
```
|
|
||||||
|
|
||||||
Пример для двух homeserver-ов:
|
|
||||||
|
|
||||||
```
|
|
||||||
homeserver.key:home-a -> ключ A
|
|
||||||
homeserver.key:home-b -> ключ B
|
|
||||||
```
|
|
||||||
|
|
||||||
Публичный ключ homeserver-а публикуется в `SessionsBlock` пользовательской PDA как
|
|
||||||
`session_pub_key` с `session_type = 100`, имя — в `session_name` (формат PDA §13).
|
|
||||||
|
|
||||||
> Это переименование прежней схемы `subserver.key:<имя>` → `homeserver.key:<имя>`.
|
|
||||||
> Термин «саб-сервер» по проекту заменяется на «homeserver».
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Где это в коде
|
|
||||||
|
|
||||||
### Деривация секрета и ключей (UI, каноническая)
|
|
||||||
- `shine-UI/js/services/crypto-utils.js`
|
|
||||||
- секрет из пароля: `makeArgon2Salt`, `deriveMasterSecretArgon2id`, `deriveMasterSecretFromPassword` (~129–218);
|
|
||||||
- ключ из секрета: `deriveEd25519FromMasterSecret` (~220).
|
|
||||||
- `shine-UI/js/services/auth-service.js` — набор root/bch/dev из `masterSecret` (~732–758).
|
|
||||||
- `shine-UI/server-ui/js/server-ui-shared.js` — те же root/bch/dev для серверного UI (~147–160).
|
|
||||||
|
|
||||||
### Solana-ключ / адрес кошелька (UI)
|
|
||||||
- `shine-UI/js/pages/registration-payment-view.js` — `deriveUserWalletAddress`: адрес = `base58(devicePub)` (~113).
|
|
||||||
- `shine-UI/js/pages/topup-view.js` — `deviceWalletAddressFromBundle`: тот же канонический адрес из `preGeneratedKeyBundle.devicePair`.
|
|
||||||
Прежний расходящийся путь `deriveWalletFromPassword` (прямой Argon2 по `dev.key`, мимо `masterSecret`) удалён.
|
|
||||||
|
|
||||||
### Деривация ключей (прошивка ESP32)
|
|
||||||
- `ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_main/shine_homeserver_main.ino`
|
|
||||||
- основной скетч ESP32-проекта `SHiNE`; `deriveKeysFromMasterSecret` (~782), `restoreDerivedKeysFromSecret` (~806), `deriveFreshSecretAndWallet` (~829);
|
|
||||||
- регистрация/подпись Solana: `registerHomeserverOnSolana` (~1182), `signMessageEd25519` (~1147).
|
|
||||||
- `ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_ui/shine_homeserver_ui.ino`
|
|
||||||
- старый тестовый вариант; оставлен как legacy-скетч для сравнения и диагностики.
|
|
||||||
|
|
||||||
### Формат PDA (куда попадают ключи)
|
|
||||||
- `shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md`
|
|
||||||
— `RootKeyBlock` §6, `DeviceKeyBlock` §7, `blockchain_public_key` §9, `SessionsBlock`/`session_type=100` §13, оплата §2.1.
|
|
||||||
|
|
||||||
### Сервер (тестовый seed)
|
|
||||||
- `SHiNE-server/src/test/java/test/it/cases/SeedDataPopulationHelper.java` `deriveKeysFromPassword` (~246) —
|
|
||||||
выводит ключи как `Ed25519(SHA-256(base64(SHA-256(password)) + suffix))`, **без** Argon2 и **без** разделителя `|`.
|
|
||||||
Это **не баг**, а точное повторение легаси-пути UI `derivePasswordSeed` (для пустого пароля), у которого тоже нет `|`.
|
|
||||||
С современным путём `masterSecret`-bundle (Argon2 + `base64(secret)|suffix`) он **не совпадает** by design.
|
|
||||||
Если потребуется, чтобы seed совпадал с реальными клиентами на Argon2 — нужно отдельно портировать
|
|
||||||
Argon2id+masterSecret в Java (на сервере Argon2 сейчас нет). Простое добавление `|` было бы **неверным**:
|
|
||||||
сломало бы совпадение с легаси-путём и всё равно не дало бы совпадения с Argon2-путём.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Правило синхронизации (обязательно)
|
|
||||||
|
|
||||||
1. Этот документ — источник истины по деривации секрета и ключей.
|
|
||||||
2. Любое изменение кода, затрагивающее формулу секрета, параметры Argon2id, соль, формулу ключа,
|
|
||||||
разделитель `|`, набор/имена суффиксов, формат homeserver-ключа или связь dev-ключ ↔ Solana-адрес —
|
|
||||||
**обязательно** отражать здесь в том же изменении.
|
|
||||||
3. Пункты, помеченные ⚠️, — это долг к устранению, а не норма.
|
|
||||||
4. Нельзя сознательно оставлять код и этот документ в рассинхроне без отдельной явной договорённости.
|
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
В SHiNE у пользователя есть несколько уровней ключей:
|
В SHiNE у пользователя есть несколько уровней ключей:
|
||||||
|
|
||||||
- `root key` - главный (master) ключ пользователя: тот, кто им владеет, управляет пользовательской PDA в Solana и может заменить все остальные ключи. Это не пополняемый кошелёк (комиссии платит `device key`).
|
- `root key` - главный корневой ключ пользователя, он же основной Solana-ключ.
|
||||||
- `blockchain key` - ключ записи в персональный SHiNE-блокчейн пользователя.
|
- `blockchain key` - ключ записи в персональный SHiNE-блокчейн пользователя.
|
||||||
- `device key` - общий ключ пользовательских устройств для повседневной работы, звонков, DM и мелких платежей.
|
- `device key` - общий ключ пользовательских устройств для повседневной работы, звонков, DM и мелких платежей.
|
||||||
- `session key` - ключ конкретной сессии или конкретного устройства для авторизации на сервере.
|
- `session key` - ключ конкретной сессии или конкретного устройства для авторизации на сервере.
|
||||||
@@ -28,9 +28,9 @@
|
|||||||
- управление остальными ключами;
|
- управление остальными ключами;
|
||||||
- подтверждение операций, которые должны иметь максимальный уровень доверия.
|
- подтверждение операций, которые должны иметь максимальный уровень доверия.
|
||||||
|
|
||||||
`root key` — это **главный (master) ключ** в следующем смысле: зная `root key`, можно управлять пользовательской PDA-записью в Solana (`create_user_pda` / `update_user_pda`) и тем самым **заменить все остальные ключи** пользователя (device, blockchain, homeserver). Поэтому компрометация `root key` равносильна компрометации всей личности пользователя.
|
В текущей модели `root key` совпадает по смыслу с главным Solana-ключом пользователя.
|
||||||
|
|
||||||
Важно не путать авторитет и кошелёк: `root key` — это авторитет над PDA-записью, а **SOL-комиссии за create/update платит `device key`** (он же fee payer и адрес для пополнения). Подробнее о том, какой ключ за что отвечает на Solana, — в `Dev_Docs/Keys/DERIVATION.md`, §3.
|
На `root key` могут храниться значимые средства, если пользователь сознательно выбирает такую модель. Для мелких текущих расходов предпочтительнее использовать `device key`.
|
||||||
|
|
||||||
## `blockchain key`
|
## `blockchain key`
|
||||||
|
|
||||||
@@ -158,7 +158,6 @@ Self-message - это сообщение пользователя самому
|
|||||||
|
|
||||||
## Связанные документы
|
## Связанные документы
|
||||||
|
|
||||||
- `Dev_Docs/Keys/DERIVATION.md` - **источник истины по конкретной деривации** секрета и ключей (формулы Argon2id, `base64|suffix→SHA-256→Ed25519`, суффиксы `root.key`/`bch.key`/`dev.key`/`homeserver.key:<имя>`, Solana-ключ, ссылки на код).
|
|
||||||
- `Dev_Docs/Personal_Messages/README.md` - текущая документация личных сообщений.
|
- `Dev_Docs/Personal_Messages/README.md` - текущая документация личных сообщений.
|
||||||
- `Dev_Docs/Blockchain/README.md` - точка входа по форматам SHiNE-блокчейна.
|
- `Dev_Docs/Blockchain/README.md` - точка входа по форматам SHiNE-блокчейна.
|
||||||
- `Dev_Docs/Solana_Architecture/README.md` - архитектура Solana-программ, PDA-счетов, DAO и движения средств.
|
- `Dev_Docs/Solana_Architecture/README.md` - архитектура Solana-программ, PDA-счетов, DAO и движения средств.
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Кошелёк: лимит/закрепление блокчейна Сияния
|
||||||
|
|
||||||
|
- статус: `pending`
|
||||||
|
|
||||||
|
## Кратко что сделано
|
||||||
|
|
||||||
|
- На экране `Кошелёк -> Блокчейн Сияния` добавлены 2 слоя данных:
|
||||||
|
- фактическое состояние цепочки на сервере (`кол-во блоков`, `размер`, `крайний блок`, `hash`, `размер крайнего блока`);
|
||||||
|
- закреплённое состояние в Solana PDA (`лимит`, `использовано`, `остаток`, `крайний блок`, `hash`).
|
||||||
|
- Добавлены действия:
|
||||||
|
- `Закрепить в Solana` — обновляет PDA до текущего состояния серверной цепочки;
|
||||||
|
- `Увеличить лимит` — увеличивает `paid_limit_bytes` в PDA с учётом цены из economy PDA.
|
||||||
|
- Если `rootKey`/`blockchainKey` не сохранены локально, экран запрашивает пароль, восстанавливает ключи через стандартную derivation-логику и предлагает сохранить их в зашифрованный контейнер.
|
||||||
|
|
||||||
|
## Что проверять вручную
|
||||||
|
|
||||||
|
1. Открыть `Кошелёк -> Блокчейн Сияния` под авторизованным пользователем.
|
||||||
|
2. Проверить, что в блоке "Фактическое состояние на сервере" отображаются:
|
||||||
|
- число блоков;
|
||||||
|
- размер цепочки;
|
||||||
|
- номер/хэш крайнего блока;
|
||||||
|
- размер крайнего блока.
|
||||||
|
3. Проверить, что в блоке "Закреплено в Solana" отображаются:
|
||||||
|
- лимит;
|
||||||
|
- израсходовано;
|
||||||
|
- остаток;
|
||||||
|
- номер/хэш крайнего закреплённого блока.
|
||||||
|
4. Нажать `Закрепить в Solana` и убедиться, что:
|
||||||
|
- приходит успешная транзакция;
|
||||||
|
- после обновления Solana-показатели подтягиваются до серверных (или максимально близко по актуальному состоянию).
|
||||||
|
5. Нажать `Увеличить лимит`, ввести значение кратное шагу, подтвердить списание и проверить:
|
||||||
|
- лимит увеличился;
|
||||||
|
- отображение цены/списания соответствует economy PDA.
|
||||||
|
6. Повторить пункты 4-5 в сценарии, когда `rootKey`/`blockchainKey` не сохранены, и проверить:
|
||||||
|
- появляется запрос пароля;
|
||||||
|
- после ввода пароля операции выполняются;
|
||||||
|
- предложение сохранить ключи показывается.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
|
||||||
|
- Экран корректно разделяет "фактическое состояние на сервере" и "закреплённое в Solana".
|
||||||
|
- Обе операции (`Закрепить в Solana`, `Увеличить лимит`) выполняются без ошибок при валидных данных.
|
||||||
|
- Восстановление ключей через пароль работает, а без нужных ключей операция не выполняется молча.
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Озвучивание ответов агента
|
||||||
|
|
||||||
|
## Что сделано
|
||||||
|
|
||||||
|
В локальный Telegram-бот-сервис агента-кодера добавлены персональные настройки озвучивания финальных ответов:
|
||||||
|
|
||||||
|
- `/voice_on` включает озвучивание для текущего Telegram-пользователя;
|
||||||
|
- `/voice_off` выключает озвучивание для текущего Telegram-пользователя;
|
||||||
|
- `/voice_status` показывает текущее состояние;
|
||||||
|
- если озвучивание включено, после текстового финального ответа сервис генерирует voice-файл через OpenAI TTS и отправляет его в Telegram;
|
||||||
|
- длинные ответы делятся на несколько фрагментов озвучки.
|
||||||
|
|
||||||
|
## Что проверять
|
||||||
|
|
||||||
|
1. Перезапустить `shine-agent-bot-coder`.
|
||||||
|
2. Отправить `/voice_status` и убедиться, что по умолчанию озвучивание выключено.
|
||||||
|
3. Отправить `/voice_on`.
|
||||||
|
4. Дать простую задачу агенту и проверить, что пришёл полный текстовый ответ и voice-файл с тем же ответом.
|
||||||
|
5. Отправить `/voice_off`.
|
||||||
|
6. Дать ещё одну простую задачу и проверить, что приходит только текст.
|
||||||
|
7. При возможности проверить второго whitelist-пользователя: его настройка должна быть независимой.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
|
||||||
|
Настройка хранится персонально по username и сохраняется после перезапуска сервиса. При включённой настройке Telegram получает текстовый ответ и дополнительное voice-сообщение с озвучкой. При выключенной настройке поведение остаётся прежним.
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
|
||||||
|
pending
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Голосовая адаптация ответов Telegram-бота
|
||||||
|
|
||||||
|
## Краткое описание
|
||||||
|
Добавлены персональные настройки голосовых ответов и адаптации текста перед озвучкой. Если голосовые ответы включены, сервис перед TTS может отдельно прогонять финальный текст через OpenAI-модель и отправлять более короткую голосовую версию в исходный чат, личный чат пользователя и общий чат `@shine_writing`, если эти чаты доступны и отличаются.
|
||||||
|
|
||||||
|
## Что проверить
|
||||||
|
- Команды `/voice_on`, `/voice_off`, `/voice_status` для конкретного пользователя.
|
||||||
|
- Команды `/voice_rewrite_on`, `/voice_rewrite_off`, `/voice_rewrite_status` для конкретного пользователя.
|
||||||
|
- Команда `/status` показывает очередь, голосовые ответы и адаптацию текста перед озвучкой.
|
||||||
|
- При включённых голосовых ответах после задачи приходит текстовый ответ и voice-ответ.
|
||||||
|
- При включённой адаптации voice-ответ короче и без длинных технических строк.
|
||||||
|
- При задаче из личного чата voice дополнительно появляется в общем чате `@shine_writing`.
|
||||||
|
- При задаче из общего чата voice дополнительно появляется в личном чате пользователя, если сервис уже знает его личный chat_id.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
Текстовый ответ остаётся полным. Голосовая версия приходит отдельно, звучит короче и естественнее, а персональные настройки одного пользователя не меняют поведение других пользователей.
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
pending
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Эксперимент Understand Anything
|
||||||
|
|
||||||
|
## Краткое описание
|
||||||
|
|
||||||
|
Добавлена изолированная лаборатория для проверки `Lum1104/Understand-Anything` без подключения к сборке, деплою и рабочему коду SHiNE.
|
||||||
|
|
||||||
|
## Что проверять
|
||||||
|
|
||||||
|
- Установить Node.js 22+ и pnpm 10+.
|
||||||
|
- Запустить `./tools/understand-anything-lab/install_codex_skills.sh`.
|
||||||
|
- Перезапустить Codex-сессию.
|
||||||
|
- Выполнить `/understand --language ru` в корне проекта.
|
||||||
|
- После генерации выполнить `/understand-dashboard` и проверить, что граф открывается и помогает ориентироваться по серверным, UI, Solana и агентским папкам.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
|
||||||
|
- В проекте появляется локальная папка `.understand-anything/` с графом знаний.
|
||||||
|
- Dashboard открывается и показывает интерактивный граф проекта.
|
||||||
|
- Основные процессы сборки и деплоя SHiNE не меняются.
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
|
||||||
|
`pending`
|
||||||
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Центр задач Telegram-агента
|
||||||
|
|
||||||
|
## Краткое описание
|
||||||
|
|
||||||
|
Добавлена первая версия центра задач и предложений внутри `SHiNE-agent-bot-coder`.
|
||||||
|
|
||||||
|
Бот хранит задачи и предложения в JSON-файле данных сервиса, умеет показывать список через `/tasks`, создавать задачи для игроков по фразе Айдара, принимать предложения игроков по префиксу `предложение:`, менять статусы и добавлять короткие напоминания после ответов.
|
||||||
|
|
||||||
|
## Что проверять
|
||||||
|
|
||||||
|
- Айдар пишет `/tasks` и видит текущий список задач и предложений без уже закрытого предложения от Димы.
|
||||||
|
- Айдар пишет `поставь задачу Милане: проверить описание SHiNE` и задача появляется в списке Миланы.
|
||||||
|
- Милана пишет `/tasks` и видит назначенную задачу.
|
||||||
|
- Игрок пишет `предложение: ...`, после чего предложение появляется у Айдара.
|
||||||
|
- Айдар меняет статус фразами вида `одобрить TC-XXXX`, `доработать TC-XXXX`, `закрыть TC-XXXX`, где `TC-XXXX` - ID существующей задачи или предложения.
|
||||||
|
- После обычного ответа бота Айдару или игроку появляется короткое напоминание, если у пользователя есть активные задачи.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
|
||||||
|
Задачи и предложения сохраняются между перезапусками сервиса, статусы меняются корректно, напоминания не мешают основному ответу Codex.
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
|
||||||
|
pending
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Рестарты и voice-настройки Telegram-агента
|
||||||
|
|
||||||
|
## Краткое описание
|
||||||
|
|
||||||
|
Добавлена первая версия безопасного рестарта Telegram-агента:
|
||||||
|
|
||||||
|
- `/restart` и `/restart_service` ставят отложенный рестарт после текущей задачи и до взятия следующей;
|
||||||
|
- `/restart_hard`, `/restart_now`, `/restart_force` выполняют жёсткий рестарт сразу;
|
||||||
|
- команды рестарта доступны только Айдару;
|
||||||
|
- voice-ответы включены по умолчанию для новых пользователей;
|
||||||
|
- адаптация текста перед озвучкой стала ближе к исходному ответу и не должна менять смысл;
|
||||||
|
- скрыты отдельные команды статуса voice-функций из справки, состояние показывается через `/status`.
|
||||||
|
|
||||||
|
## Что проверить
|
||||||
|
|
||||||
|
1. Отправить `/restart` во время активной задачи игрока или Айдара.
|
||||||
|
2. Убедиться, что активная задача завершается, после чего сервис перезапускается до следующей задачи.
|
||||||
|
3. Отправить `/restart_hard` и убедиться, что сервис перезапускается сразу.
|
||||||
|
4. Проверить, что игрок не может выполнить команды рестарта.
|
||||||
|
5. Проверить `/status`: он показывает очередь и состояния голосовых функций.
|
||||||
|
6. Проверить нового пользователя: voice-ответы должны быть включены по умолчанию.
|
||||||
|
7. Проверить текстовый запрос пользователя с включённым voice: после текстового ответа должен прийти voice-файл.
|
||||||
|
8. Проверить, что адаптированная озвучка не превращается в другой ответ, а только убирает длинные технические строки.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
|
||||||
|
Сервис можно обновлять без потери текущей задачи через отложенный рестарт. Жёсткий рестарт остаётся аварийной командой Айдара. Voice-ответы работают для текстовых и голосовых запросов, а голосовая версия остаётся близкой к текстовой.
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
|
||||||
|
pending
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Кнопки вкладки «Каналы»
|
||||||
|
|
||||||
|
## Что сделано
|
||||||
|
|
||||||
|
Доработана верхняя панель вкладки «Каналы»:
|
||||||
|
- при открытии нижней кнопкой «Каналы» показывается режим «Все каналы»;
|
||||||
|
- в режиме «Все каналы» справа доступны кнопка «Мои каналы» и иконка поиска канала;
|
||||||
|
- в режиме «Мои каналы» доступен переход обратно во «Все каналы», а справа показывается плюсик создания канала.
|
||||||
|
|
||||||
|
## Что проверить
|
||||||
|
|
||||||
|
1. Открыть вкладку «Каналы» через нижнюю навигацию.
|
||||||
|
2. Убедиться, что открыт режим «Все каналы», а плюсик создания канала не отображается.
|
||||||
|
3. Нажать иконку поиска в режиме «Все каналы».
|
||||||
|
4. Убедиться, что открывается текущий сценарий поиска каналов.
|
||||||
|
5. Нажать «Мои каналы».
|
||||||
|
6. Убедиться, что справа появился плюсик создания канала.
|
||||||
|
7. Нажать «Все каналы» или стрелку назад и проверить возврат к режиму «Все каналы».
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
|
||||||
|
Кнопки верхней панели соответствуют активному режиму: поиск в «Все каналы», создание только в «Мои каналы».
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
|
||||||
|
pending
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Длинные voice/audio в Telegram-боте агента
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
Бот теперь умеет обрабатывать длинные voice/audio аккуратнее: учитывает лимит Telegram Bot API на скачивание слишком больших файлов, поддерживает альтернативный `TELEGRAM_API_BASE_URL` для локального `telegram-bot-api`, локально пережимает длинное аудио через `ffmpeg`, режет на куски и отправляет их в OpenAI transcription последовательно.
|
||||||
|
- что именно проверять:
|
||||||
|
1. Короткий `voice` по-прежнему распознаётся без заметной задержки.
|
||||||
|
2. Длинный `audio/voice`, который помещается в скачивание Telegram, успешно пережимается, режется на части и даёт цельную расшифровку.
|
||||||
|
3. Очень большой файл через обычный `https://api.telegram.org` даёт понятное сообщение про лимит Telegram.
|
||||||
|
4. После переключения на локальный `telegram-bot-api` такой же большой файл начинает скачиваться и распознаваться.
|
||||||
|
- ожидаемый результат:
|
||||||
|
Бот не падает на длинных аудио, даёт либо расшифровку, либо понятное объяснение, какой именно лимит мешает и что нужно включить.
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Диагностика больших voice/audio в Telegram-боте
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
- Бот при большом voice/audio больше не отказывается заранее по метаданным Telegram. Теперь он сначала сообщает, что пробует скачать файл, затем отдельно сообщает об успешном скачивании и только после этого переходит к подготовке аудио и распознаванию через OpenAI.
|
||||||
|
- что именно проверять:
|
||||||
|
- Отправить в бота большой `voice` или `audio`, который раньше попадал под ранний отказ.
|
||||||
|
- Проверить, что сначала приходит сообщение о попытке скачать большой файл.
|
||||||
|
- Проверить два сценария:
|
||||||
|
- скачивание удалось: бот пишет об успешной загрузке и продолжает распознавание;
|
||||||
|
- скачивание не удалось: бот пишет именно о неудачном скачивании из Telegram, без ложной привязки к ошибке OpenAI.
|
||||||
|
- ожидаемый результат:
|
||||||
|
- Пользователь видит понятную поэтапную диагностику: попытка скачивания, результат скачивания и только потом следующий этап обработки.
|
||||||
|
- статус:
|
||||||
|
- pending
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Перенос server UI в shine-UI
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
Веб-панель управления серверной Solana PDA перенесена в `shine-UI/` как отдельные страницы.
|
||||||
|
Новая точка входа: `shine-UI/server-ui.html`.
|
||||||
|
Общая логика работы с PDA вынесена в единый модуль `shine-UI/js/services/shine-user-pda-service.js`.
|
||||||
|
|
||||||
|
- что именно проверять:
|
||||||
|
1. Открытие `shine-UI/server-ui.html` и переходы на страницы создания и обновления PDA.
|
||||||
|
2. Генерацию ключей из логина и пароля на странице создания.
|
||||||
|
3. Ручной ввод base58-ключей и регистрацию серверного PDA.
|
||||||
|
4. Загрузку существующей серверной PDA на странице обновления.
|
||||||
|
5. Обновление `server_address` и `sync_servers` только по `root + device` без blockchain-ключа.
|
||||||
|
6. Корректное чтение нового формата `ServerProfileBlock` через общий PDA-модуль.
|
||||||
|
7. То, что актуальной точкой входа остаётся `shine-UI/server-ui.html`.
|
||||||
|
|
||||||
|
- ожидаемый результат:
|
||||||
|
1. Новые страницы открываются без JS-ошибок.
|
||||||
|
2. Создание серверной PDA проходит через общий модуль и пишет актуальный формат.
|
||||||
|
3. Обновление серверной PDA переиспользует существующую подпись LastBlockState и не требует blockchain-ключ.
|
||||||
|
4. Клиентский UI не ломается после перевода общего PDA-слоя на новый формат.
|
||||||
|
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Кнопка настройки сервера и DEVNET topup
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
На экране `entry-settings-view` добавлена кнопка `Настроить свой сервер`, открывающая `server-ui.html` в новой вкладке.
|
||||||
|
На страницах серверного UI добавлена кнопка открытия `devnet-topup-view` в новой вкладке с автоматической передачей `wallet` из device-адреса.
|
||||||
|
|
||||||
|
- что именно проверять:
|
||||||
|
1. На странице настроек входа есть кнопка `Настроить свой сервер`.
|
||||||
|
2. Кнопка открывает `shine-UI/server-ui.html` в новой вкладке.
|
||||||
|
3. На страницах `create-server-pda.html` и `update-server-pda.html` есть кнопка `Открыть пополнение DEVNET`.
|
||||||
|
4. Если device public key заполнен, новая вкладка открывает `devnet-topup-view?wallet=...` с правильным адресом.
|
||||||
|
5. Если device-адрес не введён, серверный UI показывает понятную ошибку и не открывает пустую ссылку.
|
||||||
|
|
||||||
|
- ожидаемый результат:
|
||||||
|
1. Переход в серверный UI с клиентской страницы настроек работает.
|
||||||
|
2. Пополнение devnet из серверного UI открывается сразу на нужный адрес.
|
||||||
|
3. Основной клиентский UI и серверные страницы не получают JS-ошибок при загрузке.
|
||||||
|
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Фикс DEVNET topup и автоподстановки пароля
|
||||||
|
|
||||||
|
- статус: pending
|
||||||
|
- кратко: исправлена ширина экрана `devnet-topup-view` после успешного пополнения и отключена нежелательная автоподстановка пароля в server UI и на экранах входа/регистрации.
|
||||||
|
|
||||||
|
## Что проверять
|
||||||
|
- Открыть страницу пополнения DEVNET, выполнить пополнение и убедиться, что после появления `Signature` экран не расширяется по ширине.
|
||||||
|
- Проверить, что кнопки на странице пополнения остаются аккуратными и не разъезжаются.
|
||||||
|
- Открыть `server-ui/update-server-pda.html`, загрузить PDA и убедиться, что поле пароля остаётся пустым.
|
||||||
|
- Проверить обычные экраны входа и регистрации: поле пароля не должно самопроизвольно заполняться длинной строкой.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
- Длинная transaction signature переносится по строкам внутри прежней ширины экрана.
|
||||||
|
- Кнопки сохраняют компактный mobile-first layout.
|
||||||
|
- Поля пароля пустые, пока пользователь сам ничего не вводил.
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Диагностика ключей server PDA и баланс device
|
||||||
|
|
||||||
|
- статус: pending
|
||||||
|
- кратко: на странице обновления server PDA добавлена сверка ожидаемых ключей с уже загруженной PDA, предупреждение о неверном пароле, кнопка показа баланса device-аккаунта и уточнение, что create/update оплачиваются с deviceKey.
|
||||||
|
|
||||||
|
## Что проверять
|
||||||
|
- На `update-server-pda.html` загрузить существующую PDA и убедиться, что видны ожидаемые `root/blockchain/device` public key.
|
||||||
|
- Ввести правильный пароль и нажать `Сгенерировать`: должно появиться сообщение, что ключи совпадают.
|
||||||
|
- Ввести неверный пароль и нажать `Сгенерировать`: должно появиться сообщение, что ключи не совпали и пароль, вероятно, неверный.
|
||||||
|
- На `create-server-pda.html` и `update-server-pda.html` нажать `Показать / обновить баланс device` и убедиться, что баланс читается по текущему `devPub`.
|
||||||
|
- Повторить `update_user_pda` после увеличения `heap frame` и проверить, ушла ли ошибка `memory allocation failed`.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
- Пользователь видит, какие именно public key должны получиться для загруженной PDA.
|
||||||
|
- Ошибка неправильного пароля выявляется до отправки транзакции.
|
||||||
|
- Баланс device-кошелька читается прямо со страницы.
|
||||||
|
- Если проблема `OOM` была только в размере heap frame/compute budget клиента, `update_user_pda` начинает проходить.
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Lazy-import Solana PDA: актуальный формат
|
||||||
|
|
||||||
|
- Краткое описание:
|
||||||
|
Серверный Java lazy-import пользователя из `shine_users` обновлён под актуальный формат `user_pda`. Убран RPC-фильтр по размеру PDA, добавлен разбор нового `ServerProfileBlock` (`block_type = 30`) без сохранения server-only полей в `solana_users`.
|
||||||
|
- Что проверять:
|
||||||
|
1. Взять логин пользователя, который существует в Solana PDA, но отсутствует в локальной таблице `solana_users`.
|
||||||
|
2. Выполнить вход этим логином через сервер.
|
||||||
|
3. Убедиться, что lazy-import подтянул пользователя из Solana.
|
||||||
|
4. Убедиться, что запись в `solana_users` создана с полями `login`, `blockchain_name`, `solana_key`, `blockchain_key`, `device_key`.
|
||||||
|
5. Убедиться, что отсутствие/наличие server-полей в PDA не ломает импорт.
|
||||||
|
- Ожидаемый результат:
|
||||||
|
1. Пользователь успешно находится и импортируется из Solana PDA независимо от фактического размера PDA.
|
||||||
|
2. Новый `ServerProfileBlock` не ломает парсер.
|
||||||
|
3. В БД не появляются лишние server-only поля.
|
||||||
|
- Статус: `pending`
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Pure Rust `shine_users` и `shine_login_guard`
|
||||||
|
|
||||||
|
Статус: `pending`
|
||||||
|
|
||||||
|
## Что сделано
|
||||||
|
|
||||||
|
- `shine_login_guard` переписан без Anchor на чистый Rust/Solana SDK.
|
||||||
|
- `shine_users` переписан без Anchor на чистый Rust/Solana SDK.
|
||||||
|
- Для `shine_users` введён новый instruction ABI без Anchor discriminator'ов.
|
||||||
|
- Для `shine_users` используются новые seed'ы:
|
||||||
|
- `user_login=` для `user_pda`
|
||||||
|
- `shine_users_economy_config` для economy PDA
|
||||||
|
- Формат блоков PDA синхронизирован:
|
||||||
|
- `SessionsBlock = 50`
|
||||||
|
- `TrustedStateBlock = 70`
|
||||||
|
- UI JS-модуль и Java lazy-import обновлены под новые seeds/ABI/коды блоков.
|
||||||
|
|
||||||
|
## Что проверить руками
|
||||||
|
|
||||||
|
1. В обычном UI выполнить регистрацию нового пользователя в Solana.
|
||||||
|
2. Проверить, что после регистрации читается новая `user_pda`.
|
||||||
|
3. В server UI выполнить создание server PDA.
|
||||||
|
4. В server UI выполнить update server PDA.
|
||||||
|
5. Проверить, что после update растёт `record_number`.
|
||||||
|
6. Проверить, что lazy-import на сервере читает новый формат PDA без ошибок.
|
||||||
|
7. Проверить, что старые Anchor discriminator'ы больше нигде не требуются.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
|
||||||
|
- Регистрация и update работают на новых чисто-rust программах.
|
||||||
|
- UI не использует старый Anchor ABI.
|
||||||
|
- Серверный Java parser читает новый формат PDA.
|
||||||
|
- Ошибок `out of memory` и anchor-specific падений больше нет.
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# ESP32 Argon2/UI совместимость и экран результата
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
выравнивание derivation на `ESP32` с текущим `UI` по нормализации логина, совместимости `master secret`/`root.key`/`bch.key`/`dev.key`, а также правки экрана результата и progress bar.
|
||||||
|
|
||||||
|
- что именно проверять:
|
||||||
|
1. На `UI` и `ESP32` ввести один и тот же логин в разном регистре, например `Anya24`, и один и тот же непустой пароль.
|
||||||
|
2. Убедиться, что после нормализации логина на `ESP32` и `UI` получаются одинаковые:
|
||||||
|
`master secret`, `root`, `blockchain`, `device` в `Base58`.
|
||||||
|
3. Проверить режим пустого пароля:
|
||||||
|
`UI` и `ESP32` должны выдать одинаковые ключи в legacy-режиме.
|
||||||
|
4. Проверить, что пустой логин на `ESP32` не запускает расчёт и показывает сообщение об ошибке.
|
||||||
|
5. Проверить progress bar:
|
||||||
|
при непустом пароле полоса должна быть видна и двигаться.
|
||||||
|
6. Проверить экран результата:
|
||||||
|
сначала `Login`, затем `Password`, затем `Master secret` и ключи;
|
||||||
|
свайп вверх/вниз должен прокручивать длинный результат без артефактов.
|
||||||
|
|
||||||
|
- ожидаемый результат:
|
||||||
|
`ESP32` и `UI` считают одинаковый `master secret` и одинаковые ключи для одинаковых входных данных;
|
||||||
|
progress bar виден;
|
||||||
|
экран результата читаемый и корректно прокручивается.
|
||||||
|
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
## Краткое описание
|
||||||
|
В `SHiNE-agent-bot-coder` для личных чатов добавлен режим одного редактируемого статусного сообщения. Бот принимает запрос, обновляет это сообщение по этапам обработки и в конце превращает его в финальный текстовый ответ. При длинном ответе допускается ещё одно дополнительное текстовое сообщение с продолжением. Голосовой ответ остаётся отдельным сообщением.
|
||||||
|
|
||||||
|
## Что проверять
|
||||||
|
1. Отправить в личный чат короткий текстовый запрос и убедиться, что бот не шлёт цепочку промежуточных сообщений, а редактирует одно сообщение до финального ответа.
|
||||||
|
2. Отправить в личный чат `voice` или `audio` и убедиться, что в том же сообщении последовательно видны этапы распознавания и выполнения.
|
||||||
|
3. Проверить длинный ответ, который не помещается в один Telegram message: должно получиться не больше двух текстовых сообщений.
|
||||||
|
4. Проверить, что `voice`-ответ приходит отдельным новым сообщением после текстового.
|
||||||
|
5. Проверить, что в `@shine_writing` по-прежнему логируются только итоговые `вопрос -> ответ`, без промежуточных статусов.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
- В личке основная переписка стала чище: промежуточные этапы живут в одном редактируемом сообщении.
|
||||||
|
- При длинном ответе бот не разбрасывает ответ на много сообщений.
|
||||||
|
- Канал `@shine_writing` работает по старой схеме без лишнего шума.
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
`pending`
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
## Краткое описание
|
||||||
|
В локальный Telegram-бот `SHiNE-agent-bot-coder` добавлена команда `/settings`, которая сразу показывает текущие персональные настройки пользователя и список доступных команд для их изменения. В `/help` оставлена только ссылка на `/settings` без перечисления самих команд настроек. Также добавлен переключатель режима ответа в личке: один редактируемый статус или отдельные сообщения по этапам.
|
||||||
|
|
||||||
|
## Что проверять
|
||||||
|
1. Отправить `/help` и убедиться, что в справке есть `/settings`, но нет списка команд `/voice_*` и `/single_message_*`.
|
||||||
|
2. Отправить `/settings` и проверить, что бот показывает текущие значения:
|
||||||
|
- озвучивание финальных ответов;
|
||||||
|
- адаптацию текста перед озвучкой;
|
||||||
|
- режим одного редактируемого сообщения в личке.
|
||||||
|
3. По очереди переключить:
|
||||||
|
- `/voice_on` и `/voice_off`;
|
||||||
|
- `/voice_rewrite_on` и `/voice_rewrite_off`;
|
||||||
|
- `/single_message_on` и `/single_message_off`.
|
||||||
|
4. После каждого переключения снова вызвать `/settings` и убедиться, что статус изменился и сохранился.
|
||||||
|
5. При `/single_message_on` отправить обычный запрос в личку и проверить, что бот ведёт его через одно редактируемое сообщение.
|
||||||
|
6. При `/single_message_off` отправить обычный запрос в личку и проверить, что бот снова шлёт отдельные сообщения по этапам и отдельный финальный ответ.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
- `/settings` стал основной точкой входа для пользовательских настроек.
|
||||||
|
- `/help` стал короче и не дублирует список команд настроек.
|
||||||
|
- Режим ответа в личке реально переключается персонально для пользователя и сохраняется после перезапуска сервиса.
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
`pending`
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
# Shine Payments: e2e после переписи без Anchor и добавления Q3
|
||||||
|
|
||||||
|
## Краткое описание
|
||||||
|
|
||||||
|
Нужно вручную и через вспомогательные CLI-проверки подтвердить, что программа `shine_payments` после:
|
||||||
|
|
||||||
|
- переписи на чистый `solana_program`;
|
||||||
|
- отказа от `programs/common`;
|
||||||
|
- добавления очереди `Q3`;
|
||||||
|
- обновления HTML UI;
|
||||||
|
|
||||||
|
корректно работает на devnet с новым `program id`.
|
||||||
|
|
||||||
|
Отличие от финального боевого сценария:
|
||||||
|
|
||||||
|
- вместо DAO-механики используется обычный кошелёк `FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P`, которому даны права DAO на изменение коэффициента и выдачу лимитов менеджеру.
|
||||||
|
|
||||||
|
## Что именно проверять
|
||||||
|
|
||||||
|
### 1. Подготовка окружения
|
||||||
|
|
||||||
|
Проверить и зафиксировать:
|
||||||
|
|
||||||
|
- новый keypair программы `shine_payments`;
|
||||||
|
- новый `program id`;
|
||||||
|
- обновление `program id` в HTML UI и связанных настройках;
|
||||||
|
- наличие deploy authority, которой можно закрыть старый buffer/programdata, если это технически доступно;
|
||||||
|
- адреса тестовых кошельков:
|
||||||
|
- DAO/базовый кошелёк;
|
||||||
|
- менеджер;
|
||||||
|
- покупатель 1;
|
||||||
|
- покупатель 2;
|
||||||
|
- получатели выплат.
|
||||||
|
|
||||||
|
### 2. Очистка/смена старой программы
|
||||||
|
|
||||||
|
Проверить один из сценариев:
|
||||||
|
|
||||||
|
- если возможно, закрыть старый `program buffer/programdata` текущими ключами;
|
||||||
|
- если закрытие невозможно или нецелесообразно, зафиксировать это и продолжить с новым `program id`.
|
||||||
|
|
||||||
|
Отдельно проверить, что старые PDA предыдущей версии не используются новой программой.
|
||||||
|
|
||||||
|
### 3. Деплой и init новой программы
|
||||||
|
|
||||||
|
Проверить:
|
||||||
|
|
||||||
|
- `cargo build-sbf` проходит;
|
||||||
|
- новая программа деплоится на devnet;
|
||||||
|
- `init` выполняется один раз на пустых PDA;
|
||||||
|
- после `init` читаются:
|
||||||
|
- `config`;
|
||||||
|
- `coef_limit`;
|
||||||
|
- `queues`;
|
||||||
|
- `inflow_vault`.
|
||||||
|
|
||||||
|
Сразу после `init` запросить состояние очередей и зафиксировать, что:
|
||||||
|
|
||||||
|
- `Q1`, `Q2`, `Q3` пустые;
|
||||||
|
- `tickets_total = 0`;
|
||||||
|
- `tickets_paid = 0`;
|
||||||
|
- все суммы равны `0`.
|
||||||
|
|
||||||
|
### 4. Проверка покупки билета
|
||||||
|
|
||||||
|
На минимальных суммах проверить:
|
||||||
|
|
||||||
|
1. покупку через `buy_ticket_usd`;
|
||||||
|
2. покупку через `buy_ticket_sol`;
|
||||||
|
3. при необходимости ещё один вызов базового `buy_ticket`.
|
||||||
|
|
||||||
|
После каждой покупки:
|
||||||
|
|
||||||
|
- запросить состояние `Q1`;
|
||||||
|
- убедиться, что создался следующий ticket;
|
||||||
|
- проверить рост:
|
||||||
|
- `q1_tickets_total`;
|
||||||
|
- `q1_sum_total_usd_cents`;
|
||||||
|
- убедиться, что деньги покупки ушли в `dao_wallet`, а не в `inflow_vault`.
|
||||||
|
|
||||||
|
### 5. Проверка DAO-управления
|
||||||
|
|
||||||
|
Проверить:
|
||||||
|
|
||||||
|
1. изменение коэффициента через `update_coef_limit`;
|
||||||
|
2. повторный запрос `coef_limit` и подтверждение нового значения;
|
||||||
|
3. выдачу менеджеру прав через `grant_manager_limits`:
|
||||||
|
- отдельно под `Q1`;
|
||||||
|
- отдельно под `Q2`;
|
||||||
|
- отдельно под `Q3`.
|
||||||
|
|
||||||
|
После выдачи лимитов:
|
||||||
|
|
||||||
|
- считать `manager_allowance_pda`;
|
||||||
|
- убедиться, что лимиты записаны отдельно по трём очередям.
|
||||||
|
|
||||||
|
### 6. Проверка manager_add_ticket
|
||||||
|
|
||||||
|
На минимальных суммах создать менеджерские тикеты:
|
||||||
|
|
||||||
|
1. один ticket в `Q1`;
|
||||||
|
2. один ticket в `Q2`;
|
||||||
|
3. один ticket в `Q3`.
|
||||||
|
|
||||||
|
После каждого добавления:
|
||||||
|
|
||||||
|
- запросить состояние очередей;
|
||||||
|
- проверить рост счётчиков и сумм именно у нужной очереди;
|
||||||
|
- проверить уменьшение соответствующего manager allowance.
|
||||||
|
|
||||||
|
### 7. Проверка приоритета очередей
|
||||||
|
|
||||||
|
Подтвердить очередность `step_payout`:
|
||||||
|
|
||||||
|
1. сначала выплачивается `Q1`;
|
||||||
|
2. затем `Q2`;
|
||||||
|
3. затем `Q3`.
|
||||||
|
|
||||||
|
Для этого:
|
||||||
|
|
||||||
|
- между шагами регулярно читать `queues`;
|
||||||
|
- фиксировать, какой именно ticket был следующим к выплате;
|
||||||
|
- убедиться, что при наличии pending в `Q1` программа не уходит в `Q2` или `Q3`.
|
||||||
|
|
||||||
|
### 8. Проверка частичных выплат
|
||||||
|
|
||||||
|
Перед выплатами пополнять `inflow_vault` только минимально достаточными суммами.
|
||||||
|
|
||||||
|
Нужно проверить:
|
||||||
|
|
||||||
|
1. частичную серию выплат, когда часть тикетов ещё остаётся pending;
|
||||||
|
2. дополнительную покупку билета в промежутке между выплатами;
|
||||||
|
3. повторную проверку приоритета после появления нового билета в `Q1`.
|
||||||
|
|
||||||
|
После каждого `step_payout`:
|
||||||
|
|
||||||
|
- запрашивать состояние очередей;
|
||||||
|
- проверять:
|
||||||
|
- рост `tickets_paid`;
|
||||||
|
- рост `sum_paid_usd_cents`;
|
||||||
|
- `is_paid = true` у погашенного ticket;
|
||||||
|
- правильный DAO multiplier:
|
||||||
|
- `Q1 -> 1x`;
|
||||||
|
- `Q2 -> 2x`;
|
||||||
|
- `Q3 -> 3x`.
|
||||||
|
|
||||||
|
### 9. Проверка финального добора
|
||||||
|
|
||||||
|
После частичных выплат:
|
||||||
|
|
||||||
|
- купить ещё один билет;
|
||||||
|
- допополнить `inflow_vault`;
|
||||||
|
- выполнить оставшиеся `step_payout` до полного погашения всех трёх очередей.
|
||||||
|
|
||||||
|
В конце:
|
||||||
|
|
||||||
|
- все pending ticket должны отсутствовать;
|
||||||
|
- все суммы paid должны совпасть с total по каждой очереди;
|
||||||
|
- если вызвать `step_payout` на пустых очередях, доступный остаток `inflow_vault` должен уйти в `dao_wallet`.
|
||||||
|
|
||||||
|
### 10. Финальный возврат лампортов
|
||||||
|
|
||||||
|
После завершения теста вернуть все доступные остатки, которые можно вернуть текущими полномочиями, на базовый кошелёк:
|
||||||
|
|
||||||
|
- `FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P`
|
||||||
|
|
||||||
|
Отдельно зафиксировать:
|
||||||
|
|
||||||
|
- что именно удалось вернуть;
|
||||||
|
- что именно нельзя вернуть без специальной инструкции закрытия или без deploy authority.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
|
||||||
|
- `buy_ticket_usd` и `buy_ticket_sol` создают ticket без ошибок чтения state;
|
||||||
|
- `Q3` работает наравне с `Q2`, но с третьим приоритетом;
|
||||||
|
- DAO может менять коэффициент и выдавать лимиты;
|
||||||
|
- менеджер может создавать билеты во все три очереди;
|
||||||
|
- `step_payout` соблюдает порядок `Q1 -> Q2 -> Q3`;
|
||||||
|
- DAO-множитель на выплатах равен `1x/2x/3x` для `Q1/Q2/Q3`;
|
||||||
|
- HTML UI и on-chain программа используют один и тот же актуальный `program id`;
|
||||||
|
- остатки средств после теста по максимуму возвращены на базовый DAO-кошелёк.
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
|
||||||
|
- `done`
|
||||||
|
|
||||||
|
## Итог выполнения
|
||||||
|
|
||||||
|
- новый `shine_payments` задеплоен в devnet с `program id`:
|
||||||
|
- `c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW`
|
||||||
|
- старый `shine_payments`:
|
||||||
|
- `m48pWRGWrMj3TEHjuU4zsp5Gju4e7ZaPovk8RcVt7kR`
|
||||||
|
- закрыт, лампорты возвращены на базовый DAO-кошелёк
|
||||||
|
- HTML UI переведён на новый `program id`
|
||||||
|
- подтверждены:
|
||||||
|
- `init`
|
||||||
|
- `buy_ticket_usd`
|
||||||
|
- `buy_ticket_sol`
|
||||||
|
- `grant_manager_limits`
|
||||||
|
- `manager_add_ticket` для `Q1/Q2/Q3`
|
||||||
|
- `change_ticket_recipient`
|
||||||
|
- `update_coef_limit`
|
||||||
|
- `step_payout` по порядку `Q1 -> Q2 -> Q3`
|
||||||
|
- повторный возврат приоритета в `Q1` после новой покупки
|
||||||
|
- итоговые агрегаты очередей:
|
||||||
|
- `Q1 total=4, paid=4, sum_total=780, sum_paid=780`
|
||||||
|
- `Q2 total=1, paid=1, sum_total=60, sum_paid=60`
|
||||||
|
- `Q3 total=1, paid=1, sum_total=70, sum_paid=70`
|
||||||
|
- временные тестовые кошельки собраны обратно в базовый DAO-кошелёк
|
||||||
|
- в `inflow_vault` остался только rent-минимум PDA
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Клиентская Solana-регистрация после ухода от Anchor
|
||||||
|
|
||||||
|
## Краткое описание
|
||||||
|
|
||||||
|
Исправлен рассинхрон обычного клиентского UI с no-Anchor ABI программ:
|
||||||
|
|
||||||
|
- `shine_login_guard`
|
||||||
|
- `shine_users`
|
||||||
|
|
||||||
|
Исправлены клиентские вызовы:
|
||||||
|
|
||||||
|
1. Solana-предпроверка логина в обычном UI.
|
||||||
|
2. `init_users_economy_config` в обычном UI.
|
||||||
|
|
||||||
|
## Что проверять
|
||||||
|
|
||||||
|
1. На странице регистрации проверка свободного логина не выдаёт `InvalidInstructionData`.
|
||||||
|
2. Для свободного обычного логина отображается корректный статус без fallback-предупреждения про недоступную Solana-предпроверку.
|
||||||
|
3. Регистрация пользователя через обычный UI проходит до конца.
|
||||||
|
4. Страница `Solana: init регистрации` в обычном UI отправляет корректную транзакцию и не падает из-за старого Anchor discriminator.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
|
||||||
|
1. `shine_login_guard` принимает клиентский precheck.
|
||||||
|
2. `init_users_economy_config` из обычного UI совместим с текущей программой `shine_users`.
|
||||||
|
3. Обычный клиентский UI ведёт себя так же, как серверный UI, там где используется общий no-Anchor путь.
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
|
||||||
|
- `pending`
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# ESP32 UI-прототип сабсервера SHiNE
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
для `Waveshare ESP32-S3-Touch-AMOLED-2.16` добавлен новый интерактивный UI-скетч сабсервера `SHiNE` с хранением данных в `NVS`, настройками `Wi-Fi`, настройками серверов, кошельком, экраном `QR/URI`, живой Solana-регистрацией и экраном входящих запросов. Логика PIN в коде сохранена, но вход по PIN во временной сборке отключён, чтобы не блокировать проверку остальных экранов. В текущей версии `Wi-Fi` подключается реально, адреса `API/RPC/WS` проверяются реально, баланс кошелька читается из `Solana RPC`, а регистрация отправляет `create_user_pda` в `shine_users`.
|
||||||
|
|
||||||
|
- что именно проверять:
|
||||||
|
1. Прошить режим `subserver-ui` и дождаться открытия главного экрана без PIN.
|
||||||
|
2. Проверить, что текст в заголовках, кнопках и статусах отображается читаемо; в текущей временной сборке допускается ASCII-транслитерация русского текста.
|
||||||
|
3. Открыть `Настройки` и убедиться, что показывается пометка о временно отключённом входе по PIN.
|
||||||
|
4. Открыть `Подключение -> Wi-Fi`, ввести `SSID` и пароль, нажать `Проверить`, дождаться реального подключения, затем перезагрузить устройство и проверить, что значения сохранились.
|
||||||
|
5. Открыть `Подключение -> Серверы`, проверить или изменить `API/RPC/WS`, нажать `Проверить` и убедиться, что показываются реальные статусы доступности, затем перезагрузить устройство и проверить сохранение значений.
|
||||||
|
6. Открыть `Аккаунт`, ввести логин, имя сабсервера и нажать `Сгенерировать`; проверить, что появились секрет и адрес кошелька, а после перезагрузки они не исчезают.
|
||||||
|
7. Открыть `Кошелёк`, нажать `Проверить` и убедиться, что баланс реально читается из `Solana RPC`; затем открыть `QR и URI` и проверить, что QR-код отрисовывается и сканируется как `solana:`-ссылка.
|
||||||
|
8. При необходимости отдельно проверить тестовые кнопки `+/- SOL`: они меняют локальный баланс для UX-сценариев, но после следующей реальной RPC-проверки баланс должен вернуться к сетевому значению.
|
||||||
|
9. Вернуться на главный экран и проверить, что до выполнения всех условий кнопка регистрации недоступна, а после выполнения становится доступной.
|
||||||
|
10. Выполнить регистрацию и убедиться, что статус меняется на `Сабсервер активен`, онлайн-статус становится активным, а на экране появляются краткие отпечатки `PDA/TX`.
|
||||||
|
11. После регистрации проверить через `Solana`/UI проекта, что `user_pda` для этого логина реально создана и соответствует `device`-адресу устройства.
|
||||||
|
12. Открыть `Запросы`, поочерёдно открыть оба демонстрационных запроса и проверить, что кнопки `Разрешить` и `Отклонить` меняют их статус.
|
||||||
|
13. При необходимости открыть `Настройки -> Сменить PIN` и убедиться, что новый PIN сохраняется, хотя вход по PIN временно не используется на старте.
|
||||||
|
14. Выполнить `Полный сброс` и убедиться, что все поля, секрет, баланс, онлайн и регистрация очищаются.
|
||||||
|
|
||||||
|
- ожидаемый результат:
|
||||||
|
новый `ESP32`-скетч стабильно запускается, показывает читаемый интерфейс хотя бы в ASCII-транслитерации, сохраняет данные во внутренней памяти устройства, реально подключается к `Wi-Fi`, реально проверяет `API/RPC/WS`, реально читает баланс из `Solana RPC`, рисует рабочий `QR` для `solana:`-URI и позволяет вручную пройти полный сценарий on-chain регистрации сабсервера.
|
||||||
|
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# ESP32 авто-прошивка shine_subserver_ui
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
добавлен исполняемый скрипт `flash_shine_subserver_ui.sh`, который автоматически ищет USB-порт `ESP32` и запускает заливку прошивки `shine_subserver_ui` без ручного указания `PORT`.
|
||||||
|
- что именно проверять:
|
||||||
|
1. Подключить плату `ESP32` по USB.
|
||||||
|
2. Перейти в папку `ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/test-device/`.
|
||||||
|
3. Запустить `./flash_shine_subserver_ui.sh`.
|
||||||
|
4. Убедиться, что скрипт сам показывает найденный порт и успешно запускает compile/upload.
|
||||||
|
- ожидаемый результат:
|
||||||
|
скрипт без ручного ввода порта находит `ESP32`, печатает найденный `/dev/ttyACM*` или `/dev/ttyUSB*` и заливает `shine_subserver_ui`.
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# ESP32 тест рендера текста
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
добавлен отдельный диагностический скетч `text_render_test`, который показывает один экран с несколькими вариантами вывода текста: встроенный шрифт `Arduino_GFX`, `U8g2` ASCII, `U8g2` кириллица и кнопки с подписями. Скрипт нужен для изоляции проблемы, когда на экране видны только цветные кнопки и блоки, но не видно ни одной буквы.
|
||||||
|
- что именно проверять:
|
||||||
|
1. Прошить режим `text-test`.
|
||||||
|
2. Проверить, виден ли заголовок `TEXT TEST 123`.
|
||||||
|
3. Проверить, видны ли строки `A`, `B`, `C`, `D`.
|
||||||
|
4. Проверить, видны ли подписи на трёх нижних кнопках: `BTN 1`, `abc123`, `Русский`.
|
||||||
|
5. Сравнить, какой из способов вывода реально отображается, а какой нет.
|
||||||
|
- ожидаемый результат:
|
||||||
|
хотя бы один вариант вывода текста становится видим на экране, что позволяет локализовать проблему до конкретного шрифта или способа рендера.
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# ESP32 PIN-клавиатура: подписи кнопок
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
в UI-скетче `shine_subserver_ui` изменена отрисовка подписей кнопок. Вместо малого шрифта теперь используется более стабильный шрифт с явным центрированием текста внутри кнопок, чтобы на экране ввода PIN и других экранах не пропадали цифры и надписи.
|
||||||
|
- что именно проверять:
|
||||||
|
1. Включить устройство и дождаться экрана ввода PIN.
|
||||||
|
2. Убедиться, что на всех серых кнопках видны цифры `0-9`, `Отмена` и `OK`.
|
||||||
|
3. Открыть другие экраны с кнопками (`Главный экран`, `Wi-Fi`, `Серверы`, `Настройки`) и убедиться, что подписи отображаются и не уезжают за границы кнопок.
|
||||||
|
- ожидаемый результат:
|
||||||
|
подписи кнопок стабильно видны сразу после старта, текст визуально центрирован, пустых серых кнопок без цифр и названий нет.
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# ESP32 папка тестовых скетчей
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
добавлена отдельная папка `test_sketches/` с изолированными диагностическими скетчами для экрана `ESP32-S3-Touch-AMOLED-2.16`: тест рендера текста через `Arduino_GFX`, тест геометрии кнопок и минимальный тест `LVGL`.
|
||||||
|
- что именно проверять:
|
||||||
|
1. Запустить `./burn.sh gfx-text-test` и убедиться, что прошивается тест текста из новой папки.
|
||||||
|
2. Запустить `./burn.sh gfx-layout-test` и проверить нижние ряды кнопок.
|
||||||
|
3. Запустить `./burn.sh lvgl-basic-test` и проверить, что `LVGL` показывает текст и кнопки.
|
||||||
|
4. Убедиться, что новая папка не мешает сборке `subserver-ui`.
|
||||||
|
- ожидаемый результат:
|
||||||
|
тестовые скетчи лежат отдельно от основного UI, шьются отдельными режимами и позволяют быстро проверять разные гипотезы по экрану без правок в `shine_subserver_ui`.
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# ESP32 LVGL interaction test
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
добавлен отдельный скетч `lvgl_interaction_test` на `LVGL`: экран с 9 кнопками, touch-вводом и нижней статусной строкой. При нажатии на кнопку на экране и в `Serial` показывается, какая именно кнопка нажата и сколько нажатий уже было.
|
||||||
|
- что именно проверять:
|
||||||
|
1. Прошить режим `lvgl-interaction-test`.
|
||||||
|
2. Убедиться, что виден заголовок, подзаголовок, 9 кнопок и нижняя статусная панель.
|
||||||
|
3. Поочерёдно нажать разные кнопки.
|
||||||
|
4. Проверить, что нижняя строка меняется на `Pressed: <button> (#N)`.
|
||||||
|
5. Проверить, что touch устойчиво работает по всей сетке кнопок.
|
||||||
|
- ожидаемый результат:
|
||||||
|
`LVGL` стабильно рисует плотный экран с множеством кнопок, а нажатия корректно обрабатываются и визуально подтверждаются без глюков позиционирования.
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# ESP32 LVGL touch debug test
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
добавлен отдельный диагностический скетч `lvgl_touch_debug_test`, который одновременно показывает сырые координаты touch, маркер точки касания и одну большую кнопку `LVGL`. Он нужен, чтобы отделить проблему raw-touch от проблемы доставки событий в `LVGL`.
|
||||||
|
- что именно проверять:
|
||||||
|
1. Прошить режим `lvgl-touch-debug-test`.
|
||||||
|
2. Коснуться экрана в разных местах.
|
||||||
|
3. Проверить, меняется ли текст `RAW pressed` и координаты `x/y`.
|
||||||
|
4. Проверить, появляется ли розовый маркер точки касания.
|
||||||
|
5. Проверить, срабатывает ли большая кнопка `Tap Here` и меняется ли строка `LVGL button clicked`.
|
||||||
|
- ожидаемый результат:
|
||||||
|
становится ясно, работает ли сам touch-драйвер, правильно ли приходят координаты и доходит ли нажатие до кнопки `LVGL`.
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# ESP32 LVGL official based test
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
добавлен отдельный скетч `lvgl_official_based_test`, который строится на максимально близкой к официальному `05_LVGL_Widgets` инициализации `display + touch + LVGL`, но вместо официального demo рисует наш компактный экран с кнопками и статусом нажатия.
|
||||||
|
- что именно проверять:
|
||||||
|
1. Прошить режим `lvgl-official-based-test`.
|
||||||
|
2. Убедиться, что экран отображается без артефактов по краям.
|
||||||
|
3. Нажать разные кнопки и проверить, меняется ли нижняя строка `Pressed: ...`.
|
||||||
|
4. Проверить, идут ли координаты touch в `Serial`.
|
||||||
|
- ожидаемый результат:
|
||||||
|
если официальный каркас инициализации действительно является рабочей базой, то на этом тесте должны заработать и touch, и кнопки, и исчезнуть визуальные артефакты, которые были в наших самодельных `LVGL`-тестах.
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# LVGL Russian font test
|
||||||
|
|
||||||
|
- Краткое описание: тест кастомного `LVGL`-шрифта с кириллицей на базе рабочего `LVGL + subserver touch` контура.
|
||||||
|
- Что проверять:
|
||||||
|
- на экране видны русские заголовки и подписи без транслита;
|
||||||
|
- отображаются буквы `Ё/ё`;
|
||||||
|
- видны кнопки `Статус`, `Подключение`, `Кошелёк`, `Запросы`, `Настройки`, `Регистрация`, `Разрешить`, `Отклонить`, `Назад`;
|
||||||
|
- длинная кнопка `Проверка переноса русского текста` отображается читаемо;
|
||||||
|
- строка `Нажато:` меняется при клике;
|
||||||
|
- строка `Касание:` меняется при касании.
|
||||||
|
- Ожидаемый результат: кириллица стабильно отображается на `LVGL`-экране и не ломает touch.
|
||||||
|
- Статус: pending
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
# ESP32 nav minimal test
|
||||||
|
|
||||||
|
- Краткое описание: минимальный UI-прототип для сабсервера на базе `LVGL + subserver touch`, с Wi-Fi flow, серверными адресами и общим экраном редактирования текста.
|
||||||
|
- Что проверять:
|
||||||
|
- стартует экран `HOME`;
|
||||||
|
- на `HOME` видны реальное значение сабсервера или `subserver not set`, реальное значение логина или `login not set`, при отсутствии секрета строка `secret not set`, а также `STATUS`, верхний правый блок с процентом батареи, иконкой батареи и индикатором Wi-Fi, кнопка баланса, строка `SHiNE: ...`, кнопка `SETTINGS` уменьшенной ширины у правого края и нижняя подпись `SHiNE subserver (v.0.18)`;
|
||||||
|
- справа от строки логина виден индикатор статуса Solana-аккаунта:
|
||||||
|
- зелёный, если ключи совпали;
|
||||||
|
- красный, если mismatch;
|
||||||
|
- белый контур, если пользователь не найден;
|
||||||
|
- если статус не зелёный, рядом выводится краткое текстовое пояснение;
|
||||||
|
- строка Wi-Fi на `HOME` корректно показывает одно из состояний:
|
||||||
|
- `Wi-Fi (not configured) not configured`
|
||||||
|
- `Wi-Fi (<saved_ssid>) disconnected`
|
||||||
|
- `Wi-Fi (<current_ssid>) connected`
|
||||||
|
- строка `SHiNE:` корректно показывает одно из состояний:
|
||||||
|
- `connected`
|
||||||
|
- `account not configured`
|
||||||
|
- `unavailable`
|
||||||
|
- пока открыт `HOME`, статус сам обновляется без перехода на другие экраны;
|
||||||
|
- баланс обновляется кнопкой по нажатию;
|
||||||
|
- если логин зарегистрирован и секрет/сабсервер заданы, устройство:
|
||||||
|
- читает `user_pda` через Solana RPC;
|
||||||
|
- сверяет `root`, `blockchain`, `device` и `subserver` session type `100`;
|
||||||
|
- поднимает WebSocket-сессию с сервером SHiNE;
|
||||||
|
- шлёт `Ping` раз в минуту;
|
||||||
|
- кнопка `SETTINGS` открывает `SETTINGS_MENU`;
|
||||||
|
- свайп влево на `HOME` открывает `SETTINGS_MENU`;
|
||||||
|
- если пользователь не найден в Solana PDA, слева снизу появляется `REGISTER ACCOUNT`;
|
||||||
|
- `REGISTER ACCOUNT` открывает экран-заглушку;
|
||||||
|
- в `SETTINGS_MENU` сначала видны только `Wi-Fi` и `Server`;
|
||||||
|
- обе видимые карточки меню одного цвета;
|
||||||
|
- свайп вверх показывает `Server` и `Account`;
|
||||||
|
- свайп вниз возвращает `Wi-Fi` и `Server`;
|
||||||
|
- свайп вправо из `SETTINGS_MENU` возвращает на `HOME`;
|
||||||
|
- нажатие `Wi-Fi` открывает `WIFI_SCREEN`;
|
||||||
|
- `SELECT NETWORK` запускает скан;
|
||||||
|
- после скана показывается список доступных SSID;
|
||||||
|
- выбор SSID открывает общий экран редактирования текста для пароля;
|
||||||
|
- если для этого SSID пароль уже сохранялся раньше, он автоматически подставляется в редактор;
|
||||||
|
- если затем ввести пароль для другого SSID, пароль первой сети не теряется;
|
||||||
|
- одновременно хранится до `8` паролей для разных SSID;
|
||||||
|
- на этом экране видно старое значение, курсор стоит в конце;
|
||||||
|
- две верхние служебные строки над полем ввода отсутствуют;
|
||||||
|
- при вводе пароля Wi-Fi текст показывается открыто, без точек;
|
||||||
|
- большая клавиатура реально видна на экране и занимает большую часть высоты;
|
||||||
|
- буквы разбиты на 2 страницы;
|
||||||
|
- режим символов тоже разбит на 2 страницы;
|
||||||
|
- на правой странице кнопки стоят в ровных вертикальных колонках;
|
||||||
|
- свайп влево/вправо на экране ввода переключает страницы клавиатуры;
|
||||||
|
- при этом свайп страниц клавиатуры срабатывает только из нижней клавиатурной зоны, а не из верхней части экрана;
|
||||||
|
- при переключении `ABC/123` и `SHIFT` уже введённый текст не пропадает;
|
||||||
|
- при свайпе между левой и правой половиной клавиатуры уже введённый текст тоже не пропадает, в том числе для цифр, символов и заглавных букв;
|
||||||
|
- визуальный курсор в поле ввода не показывается;
|
||||||
|
- новые символы всегда дописываются только в конец строки;
|
||||||
|
- основные 3 ряда клавиш и нижний служебный ряд стали выше;
|
||||||
|
- внизу остаётся отдельная тёмная полоса с версией `SHiNE subserver (v.0.18)`, а рамка клавиатурного блока заканчивается выше неё;
|
||||||
|
- одно непрерывное касание вызывает не более одного действия кнопки;
|
||||||
|
- скольжение пальцем по клавиатуре не нажимает подряд несколько клавиш;
|
||||||
|
- медленный свайп по экрану не должен превращаться в случайное нажатие кнопки;
|
||||||
|
- `ABC/123`, `SHIFT`, `DEL`, `SAVE`, `CANCEL` работают;
|
||||||
|
- при успехе SSID и пароль сохраняются, а `HOME` показывает `Wi-Fi connected`;
|
||||||
|
- если после подключения ко второй сети снова выбрать первую, её старый пароль уже подставлен и достаточно нажать `SAVE`;
|
||||||
|
- при ошибке показывается `Connection failed`;
|
||||||
|
- `CLEAR SAVED WI-FI` очищает сохранённые настройки;
|
||||||
|
- если сеть была ранее успешно сохранена, после потери связи устройство автоматически пытается переподключиться;
|
||||||
|
- первые повторные попытки идут раз в `10` секунд, а после долгого отсутствия связи интервал увеличивается до `30` секунд;
|
||||||
|
- нажатие `Server` открывает `SERVER_SCREEN`;
|
||||||
|
- в `SERVER_SCREEN` видны и редактируются два значения:
|
||||||
|
- `https://api.devnet.solana.com`
|
||||||
|
- `https://shineup.me`
|
||||||
|
- нажатие `SOLANA RPC` открывает общий экран редактирования;
|
||||||
|
- нажатие `SHINE SERVER` открывает общий экран редактирования;
|
||||||
|
- после `SAVE` новые адреса сохраняются в NVS;
|
||||||
|
- нажатие `Account` открывает `ACCOUNT_SCREEN`;
|
||||||
|
- `ACCOUNT_SCREEN` показывает 3 кнопки:
|
||||||
|
- `Login (<value|not set>)`
|
||||||
|
- `Subserver (<value|not set>)`
|
||||||
|
- `Secret (<*****|not set>)`
|
||||||
|
- `Login` открывает общий экран редактирования и сохраняется в NVS;
|
||||||
|
- `Subserver` открывает промежуточный экран с `USE SUBSERVER1` и `EDIT MANUALLY`;
|
||||||
|
- `USE SUBSERVER1` возвращает стандартное значение `subserver1`;
|
||||||
|
- `EDIT MANUALLY` открывает общий экран редактирования и сохраняет значение в NVS;
|
||||||
|
- `Secret` теперь открывает меню секрета с показом секрета, ручным вводом и генерацией;
|
||||||
|
- в `SHOW SECRET` показывается прокручиваемый список всех ключей:
|
||||||
|
- `Secret (base58)`
|
||||||
|
- `Root key (base58)`
|
||||||
|
- `Root key priv (base58)`
|
||||||
|
- `Blockchain key (base58)`
|
||||||
|
- `Blockchain key priv (base58)`
|
||||||
|
- `Device key (base58)`
|
||||||
|
- `Device key priv (base58)`
|
||||||
|
- `Subserver key (base58)`
|
||||||
|
- `Subserver key priv (base58)`
|
||||||
|
- значения ключей показываются полными строками увеличенным шрифтом;
|
||||||
|
- при смене `login` сохранённый секрет сбрасывается в `not set`;
|
||||||
|
- во время генерации секрета есть `CANCEL` и подтверждение остановки;
|
||||||
|
- при отмене генерации старый секрет, если он был, не должен теряться;
|
||||||
|
- свайп вправо из внутренних экранов возвращает в `SETTINGS_MENU`;
|
||||||
|
- свайп вправо из `ACCOUNT_SUBSERVER_SCREEN` и `ACCOUNT_SECRET_SCREEN` возвращает в `ACCOUNT_SCREEN`;
|
||||||
|
- если во время реального свайпа палец проходит по кнопке, это не должно открывать кнопку как обычный `click`.
|
||||||
|
- Ожидаемый результат: новый скетч даёт чистый навигационный каркас и уже умеет настраивать Wi-Fi и серверные адреса на самой ESP32.
|
||||||
|
- Дополнительно ожидается: `HOME` уже показывает реальный Solana/WS-статус сабсервера, а отсутствие пользователя в Solana заметно сразу без перехода в настройки.
|
||||||
|
- Статус: pending
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Deeplink ссылки профиля и связей
|
||||||
|
|
||||||
|
- краткое описание:
|
||||||
|
Исправлена загрузка UI по прямым ссылкам вида `https://shineup.me/shine.<login>` и `https://shineup.me/shine.<login>/links` через добавление корневого `<base href="/">` в основной `index.html`.
|
||||||
|
- что проверять:
|
||||||
|
1. Открыть прямую ссылку на профиль в новой вкладке: `https://shineup.me/shine.<login>`.
|
||||||
|
2. Открыть прямую ссылку на связи в новой вкладке: `https://shineup.me/shine.<login>/links`.
|
||||||
|
3. Повторить оба сценария в состоянии гостя.
|
||||||
|
4. Повторить оба сценария в состоянии, когда в браузере залогинен другой пользователь.
|
||||||
|
- ожидаемый результат:
|
||||||
|
1. Страница загружается напрямую без поломки ассетов и без ухода на неверный экран.
|
||||||
|
2. Открывается профиль/связи именно пользователя из URL.
|
||||||
|
3. Для гостя экран открывается в read-only режиме.
|
||||||
|
4. Для залогиненного другого пользователя URL не подменяется на текущую сессию.
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# Быстрое скрытие экрана звонка и остановка гудков при отклонении
|
|
||||||
|
|
||||||
- краткое описание фичи:
|
|
||||||
- Исправлена нижняя подпись вкладки личных сообщений на `личные`.
|
|
||||||
- Исправлена логика звонка, когда одна из входящих сессий отклоняет вызов до принятия: исходящая сессия теперь должна прекращать гудки даже если ранее `RINGING` пришёл от другой входящей сессии.
|
|
||||||
- На устройстве, где пользователь нажимает отмену/сброс звонка, экран вызова теперь скрывается сразу локально, без ожидания сетевого ответа.
|
|
||||||
|
|
||||||
- что именно проверять:
|
|
||||||
- В нижней панели с 5 кнопками подпись первой кнопки должна отображаться как `личные`.
|
|
||||||
- Сценарий: один пользователь звонит, у второго входящий вызов приходит на несколько устройств; на любом одном входящем устройстве нажать `Сбросить`.
|
|
||||||
- Проверить, что у звонящего сразу прекращаются гудки и экран вызова корректно завершается.
|
|
||||||
- Проверить, что на устройстве, где нажали `Сбросить` или `Положить трубку`, overlay звонка исчезает сразу, без заметной задержки.
|
|
||||||
- Проверить, что после принятия звонка на одном устройстве поздние отмены с других устройств не ломают уже выбранную пару соединения.
|
|
||||||
|
|
||||||
- ожидаемый результат:
|
|
||||||
- Подпись в нижней панели корректная.
|
|
||||||
- При отклонении входящего звонка любым устройством звонящего не оставляет в состоянии бесконечных гудков.
|
|
||||||
- Локальный экран звонка скрывается мгновенно после нажатия кнопки отмены.
|
|
||||||
- Уже зафиксированный сценарий соединения после `ACCEPT` не сбивается другими сессиями.
|
|
||||||
|
|
||||||
- статус:
|
|
||||||
- `pending`
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
# Отключение устаревшего TURN-узла `45.136.124.227`
|
|
||||||
|
|
||||||
- краткое описание:
|
|
||||||
- из конфигурации звонков убран устаревший TURN-узел `45.136.124.227:3478`;
|
|
||||||
- основным и единственным выдаваемым TURN-узлом оставлен `93.170.12.154:3478`.
|
|
||||||
- что проверять:
|
|
||||||
- сделать несколько тестовых звонков между разными устройствами/сетями;
|
|
||||||
- убедиться, что звонок доходит до стадии соединения и появляется звук;
|
|
||||||
- убедиться, что в логах `CallDeliveryReport` больше не фигурирует `45.136.124.227`.
|
|
||||||
- ожидаемый результат:
|
|
||||||
- клиентам больше не выдаётся устаревший TURN-адрес;
|
|
||||||
- звонки не заваливаются из-за попыток использовать отключённый TURN-узел.
|
|
||||||
- статус:
|
|
||||||
- pending
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
# Фикс самообрыва звонка из-за `stop_call` push своей же сессии
|
|
||||||
|
|
||||||
- краткое описание:
|
|
||||||
- исправлена ситуация, когда активный звонок мог оборваться сразу после соединения;
|
|
||||||
- причина была в том, что `stop_call` push, предназначенный для других сессий того же пользователя, обрабатывался и в исходной сессии.
|
|
||||||
- что проверять:
|
|
||||||
- открыть несколько вкладок/устройств одного пользователя;
|
|
||||||
- принять звонок на одной сессии;
|
|
||||||
- убедиться, что активная сессия не обрывает звонок сразу после соединения;
|
|
||||||
- убедиться, что лишние сессии при этом закрывают свой локальный экран звонка.
|
|
||||||
- ожидаемый результат:
|
|
||||||
- звонок не завершается сразу после `call_connected`;
|
|
||||||
- `accepted_on_other_device` и связанные `stop_call` события больше не убивают исходную активную сессию.
|
|
||||||
- статус:
|
|
||||||
- pending
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
# Фикс привязки call push к целевой sessionId
|
|
||||||
|
|
||||||
- краткое описание:
|
|
||||||
- push-события `incoming_call` и `stop_call` теперь помечаются целевой `sessionId`;
|
|
||||||
- UI и service worker обрабатывают call push только для своей целевой сессии;
|
|
||||||
- `stop_call` для лишних сессий закрывает локальный экран тихо, без обратных сигналов и без лишних тех-сообщений.
|
|
||||||
- что проверять:
|
|
||||||
- держать несколько сессий одного пользователя в одном браузере/на одном origin;
|
|
||||||
- позвонить этому пользователю и убедиться, что входящий экран закрывается корректно только на целевых сессиях;
|
|
||||||
- после `ACCEPT` одной сессии остальные должны тихо убрать экран вызова и не ломать выбранную пару;
|
|
||||||
- после отмены входящей сессией исходящая сессия должна централизованно завершить сценарий.
|
|
||||||
- ожидаемый результат:
|
|
||||||
- push одного session endpoint больше не влияет на чужие сессии этого же origin;
|
|
||||||
- исчезают ложные `stop_call_push:accepted_on_other_device` и `terminal_call_signal_150` на неправильных сессиях.
|
|
||||||
- статус:
|
|
||||||
- pending
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# ESP32 homeserver: заявки на подключение устройств
|
|
||||||
|
|
||||||
- краткое описание фичи:
|
|
||||||
- на ESP32 homeserver в `SETTINGS` добавлен первый пункт `Device requests`, который появляется только после авторизации homeserver в SHiNE;
|
|
||||||
- экран показывает список активных pairing-заявок, позволяет открыть каждую заявку и подтвердить или отклонить её;
|
|
||||||
- формат кода подключения изменён на `10` цифр и показывается как `5` пар.
|
|
||||||
|
|
||||||
- что проверять:
|
|
||||||
- на обычном клиенте и в wallet-plugin код отображается как `XX XX XX XX XX`;
|
|
||||||
- на доверенном веб-клиенте экран `Подключить по коду` показывает все активные заявки без поля ручного ввода;
|
|
||||||
- на ESP32 после успешной homeserver-авторизации в `SETTINGS` появляется пункт `Device requests` первым;
|
|
||||||
- `REFRESH` реально загружает активные заявки;
|
|
||||||
- на экране видно две плитки, список листается вертикально;
|
|
||||||
- client-session заявка после `YES` подключается с передачей только `device key`;
|
|
||||||
- wallet-session заявка после `YES` подключается без передачи ключей, через выпуск отдельной wallet-session;
|
|
||||||
- `NO` отклоняет заявку и она исчезает из списка активных.
|
|
||||||
|
|
||||||
- ожидаемый результат:
|
|
||||||
- все три клиента используют единый формат кода;
|
|
||||||
- активные заявки видны без ручного ввода кода;
|
|
||||||
- ESP32 может одобрять и отклонять живые pairing-заявки пользователя.
|
|
||||||
|
|
||||||
- статус:
|
|
||||||
- pending
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# Тестовые deploy-контуры `test2.shineup.me` и `test.shineup.me`
|
|
||||||
|
|
||||||
- краткое описание:
|
|
||||||
- default deploy-задачи `deployServer` и `deployUI` переведены на основной тестовый сервер `test2.shineup.me`;
|
|
||||||
- production-задачи вынесены в `deployServerProduction` и `deployUIProduction`;
|
|
||||||
- `test.shineup.me` оставлен как резервный тестовый сервер без обычного deploy по умолчанию.
|
|
||||||
|
|
||||||
- что проверять:
|
|
||||||
- `./gradlew deployServer` и `./gradlew deployUI` действительно направлены на `test2.shineup.me`;
|
|
||||||
- `./gradlew deployServerProduction` и `./gradlew deployUIProduction` больше не используются как default;
|
|
||||||
- `https://test2.shineup.me` открывает UI;
|
|
||||||
- `wss://test2.shineup.me/ws` отвечает;
|
|
||||||
- на `test2.shineup.me` после deploy есть копия продовой `shine.sqlite` и `.bch`.
|
|
||||||
|
|
||||||
- ожидаемый результат:
|
|
||||||
- default deploy идёт только на `test2.shineup.me`;
|
|
||||||
- production `shineup.me` меняется только после отдельного подтверждения;
|
|
||||||
- `test.shineup.me` остаётся резервным тестовым сервером;
|
|
||||||
- тестовый deploy не гоняет удалённые тесты и не создаёт пустую БД.
|
|
||||||
|
|
||||||
- статус:
|
|
||||||
- pending
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# Регистрация: FAQ и режим пароля из 12 слов
|
|
||||||
|
|
||||||
- краткое описание:
|
|
||||||
- на экране регистрации добавлен блок частых вопросов с переходом на отдельный экран справки;
|
|
||||||
- добавлен альтернативный режим ввода пароля через 12 полей-слов в кошелёчном формате, которые склеиваются в одну строку без изменения API;
|
|
||||||
- такой же режим добавлен и на экран входа по логину и паролю.
|
|
||||||
|
|
||||||
- что проверять:
|
|
||||||
- на стартовом экране открыть `Зарегистрироваться`;
|
|
||||||
- убедиться, что внизу экрана есть кнопки FAQ;
|
|
||||||
- открыть несколько вопросов и проверить возврат обратно на регистрацию;
|
|
||||||
- включить галочку `Представить пароль в виде 12 слов`;
|
|
||||||
- убедиться, что появляется сетка с нумерованными полями в 3 колонки;
|
|
||||||
- ввести часть слов, перейти дальше и проверить, что шаг подтверждения и генерация ключей работают;
|
|
||||||
- выключить галочку и проверить, что пароль остаётся собранным в одном поле;
|
|
||||||
- открыть экран входа по паролю и повторить те же проверки для режима `12 слов`;
|
|
||||||
- пройти регистрацию до шага оплаты без ошибок интерфейса.
|
|
||||||
|
|
||||||
- ожидаемый результат:
|
|
||||||
- FAQ открывается отдельным экраном и содержит понятные ответы;
|
|
||||||
- режим `12 слов` не ломает регистрацию и вход и даёт тот же поток, что и обычный пароль;
|
|
||||||
- пароль не отправляется в новом формате, а продолжает использоваться как одна строка.
|
|
||||||
|
|
||||||
- статус:
|
|
||||||
- pending
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# Временная бесплатная загрузка аватара в Arweave
|
|
||||||
|
|
||||||
- краткое описание фичи:
|
|
||||||
Добавлены два временных `Test...` API для бесплатной загрузки маленьких аватаров в Arweave через серверный кошелёк с лимитом `3` загрузки на пользователя. В UI мастера смены аватара добавлен пункт `Залить аватар бесплатно`.
|
|
||||||
|
|
||||||
- что именно проверять:
|
|
||||||
1. Пользователь с активной сессией открывает редактирование профиля.
|
|
||||||
2. По нажатию на аватар открывается мастер `Сменить аватар`.
|
|
||||||
3. В мастере есть пункт `Залить аватар бесплатно`.
|
|
||||||
4. До первой загрузки UI показывает остаток `3 из 3`.
|
|
||||||
5. Маленький JPEG/PNG/WebP после уменьшения до файла <= `128 KB` успешно уходит через `TestUploadFreeAvatar`.
|
|
||||||
6. После загрузки приходит `txId`, и аватар сохраняется в профиль как `avatar.ar`.
|
|
||||||
7. Остаток уменьшается: `2`, `1`, `0`.
|
|
||||||
8. На четвёртой попытке сервер отвечает понятной ошибкой про исчерпанный бесплатный лимит.
|
|
||||||
9. Если итоговый уменьшенный файл всё ещё > `128 KB`, UI не отправляет его и показывает понятную ошибку.
|
|
||||||
10. Если серверный Arweave JWK/path не настроен, UI получает понятную ошибку временной функции.
|
|
||||||
|
|
||||||
- ожидаемый результат:
|
|
||||||
- первые 3 маленькие аватарки загружаются через серверный Arweave-кошелёк;
|
|
||||||
- после каждой успешной загрузки `ava` в профиле указывает на новый `txId`;
|
|
||||||
- после исчерпания лимита дальнейшая бесплатная загрузка блокируется без записи в профиль;
|
|
||||||
- обычная загрузка через свой Arweave-кошелёк продолжает работать отдельно.
|
|
||||||
|
|
||||||
- статус:
|
|
||||||
pending
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# Исправление chatId личных сообщений через lowercase
|
|
||||||
|
|
||||||
- краткое описание фичи:
|
|
||||||
- В клиентском UI SHiNE для личных сообщений технический `chatId` теперь канонизируется через `trim().toLowerCase()` при приёме DM, открытии чата и восстановлении сообщений из IndexedDB.
|
|
||||||
- Цель: исключить рассинхрон, когда unread-индикатор есть, а входящие сообщения конкретного собеседника не видны из-за разного регистра логина.
|
|
||||||
|
|
||||||
- что именно проверять:
|
|
||||||
- Отправить личные сообщения между двумя пользователями, у одного из которых логин отображается с заглавными буквами.
|
|
||||||
- Убедиться, что входящие сообщения показываются внутри открытого чата, а не только в общем unread-индикаторе.
|
|
||||||
- Перезагрузить страницу и проверить, что история чата после гидрации из IndexedDB остаётся в одном диалоге.
|
|
||||||
- Проверить, что переход в чат из списка диалогов и из графа связей открывает тот же диалог без дублирования.
|
|
||||||
|
|
||||||
- ожидаемый результат:
|
|
||||||
- Все сообщения одного собеседника попадают в один и тот же DM-чат независимо от регистра логина.
|
|
||||||
- Общий unread, список диалогов и содержимое открытого чата совпадают между собой.
|
|
||||||
- После перезагрузки UI не появляется отдельный дубль диалога с тем же логином в другом регистре.
|
|
||||||
|
|
||||||
- статус:
|
|
||||||
- pending
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# AGENTS
|
|
||||||
|
|
||||||
## Документация DM в этой папке
|
|
||||||
|
|
||||||
- Основной актуальный документ по личным сообщениям:
|
|
||||||
- `README.md`
|
|
||||||
- Его считать единственным источником истины по текущей реализованной логике DM.
|
|
||||||
|
|
||||||
## Черновик будущих вложений
|
|
||||||
|
|
||||||
- Файл `Черновик_будущих_DM_вложений.md` не является актуальной спецификацией.
|
|
||||||
- В нём описан только ранний черновик того, как когда-то планировались:
|
|
||||||
- формат вложений в DM;
|
|
||||||
- внешние и внутренние поля вложения;
|
|
||||||
- предполагаемая механика загрузки файлов.
|
|
||||||
- Эта схема не была реализована в таком виде и может существенно измениться в будущем.
|
|
||||||
- Любые решения по текущему коду, протоколу и UI нельзя принимать по этому черновику.
|
|
||||||
- Если есть расхождение между `README.md` и черновиком вложений, верным всегда считается `README.md`.
|
|
||||||
@@ -1,203 +1,269 @@
|
|||||||
# Личные сообщения (DM)
|
# Личные сообщения (DM): как это устроено
|
||||||
|
|
||||||
## Текущее состояние
|
## Коротко (для быстрого понимания)
|
||||||
|
|
||||||
Сейчас в проекте реализованы:
|
Личные сообщения в SHiNE сейчас работают как пара **подписанных клиентом блоков** в формате `SHiNE_dm2`:
|
||||||
|
|
||||||
- новый формат контентных личных сообщений `SHiNE_DM`;
|
- тип `1` — входящее сообщение для собеседника;
|
||||||
- ревизии сообщений через `revisionTimeMs`;
|
- тип `2` — исходящая копия того же сообщения для автора.
|
||||||
- редактирование сообщения через повторную отправку той же логической пары;
|
|
||||||
- удаление сообщения через пустую ревизию;
|
|
||||||
- `upsert` последней версии сообщения на сервере.
|
|
||||||
|
|
||||||
Сейчас в проекте **не реализованы**:
|
Оба блока отправляются вместе одной операцией (`SendMessagePair` / `ReceiveOutcomingMessage`) и либо сохраняются оба, либо не сохраняются вовсе.
|
||||||
|
Дальше сервер доставляет их по активным сессиям целевого логина событием `SignedMessageArrived`, а клиент подтверждает доставку на конкретную сессию через `AckSessionDelivery`.
|
||||||
|
|
||||||
- вложения в DM;
|
Подтверждение прочтения также идёт парой блоков:
|
||||||
- upload/download файлов для DM;
|
|
||||||
- UI-кнопка прикрепления файла;
|
|
||||||
- серверное хранение файловых связей для DM.
|
|
||||||
|
|
||||||
Черновик будущих вложений вынесен отдельно:
|
- тип `3` — «прочитано» для исходящего сообщения автора;
|
||||||
|
- тип `4` — зеркальная копия для второй стороны.
|
||||||
|
|
||||||
- `Dev_Docs/Personal_Messages/Черновик_будущих_DM_вложений.md`
|
UI чата строится на этих типах: текстовые сообщения (1/2), read-receipt (3/4), непрочитанные, галочки и история.
|
||||||
|
|
||||||
## Общая схема
|
---
|
||||||
|
|
||||||
Личное сообщение по-прежнему отправляется парой signed-блоков:
|
## Подробно
|
||||||
|
|
||||||
- `type=1` — входящий блок для получателя;
|
## 1) Общая схема потока
|
||||||
- `type=2` — исходящая копия для отправителя.
|
|
||||||
|
|
||||||
Read-receipt пока остаются в legacy-формате:
|
1. Клиент формирует текст сообщения и строит **2 подписанных блока** (`type=1` и `type=2`) с одинаковыми `fromLogin/toLogin/timeMs/nonce`.
|
||||||
|
2. Клиент отправляет оба блока в одном RPC: `SendMessagePair` (алиас: `ReceiveOutcomingMessage`).
|
||||||
|
3. Сервер:
|
||||||
|
- парсит оба блока;
|
||||||
|
- валидирует пару;
|
||||||
|
- проверяет существование `from/to` пользователей и подписи;
|
||||||
|
- атомарно сохраняет пару в `signed_messages_v2`.
|
||||||
|
4. Сервер доставляет блоки в активные сессии целевого логина событием `SignedMessageArrived`.
|
||||||
|
5. Клиент, получив событие, кладёт сообщение в локальный чат и отправляет `AckSessionDelivery(messageKey)`.
|
||||||
|
6. При открытии чата клиент отправляет read-receipt (пара `type=3/4`) для непрочитанных входящих.
|
||||||
|
|
||||||
- `type=3` — входящее подтверждение прочтения;
|
## 2) Формат signed DM-блока (`SHiNE_dm2`)
|
||||||
- `type=4` — исходящая копия подтверждения.
|
|
||||||
|
|
||||||
Ключи сообщения:
|
Префикс: `SHiNE_dm2` (ASCII).
|
||||||
|
|
||||||
- `baseKey = fromLogin|toLogin|timeMs|nonce`
|
Далее поля (big-endian):
|
||||||
|
|
||||||
|
1. `toLoginLen` (`u8`) + `toLogin` (ASCII, 1..60);
|
||||||
|
2. `fromLoginLen` (`u8`) + `fromLogin` (ASCII, 1..60);
|
||||||
|
3. `timeMs` (`u64`);
|
||||||
|
4. `nonce` (`u32`);
|
||||||
|
5. `messageType` (`u16`);
|
||||||
|
6. `payloadLen` (`u16`);
|
||||||
|
7. `payloadBytes` (`1..4096`);
|
||||||
|
8. `signature` (`64 bytes`, Ed25519).
|
||||||
|
|
||||||
|
Ограничения:
|
||||||
|
|
||||||
|
- полный пакет: до `8192` байт;
|
||||||
|
- `messageType` сейчас допустим только `1..4`.
|
||||||
|
|
||||||
|
## 3) Типы DM-сообщений
|
||||||
|
|
||||||
|
- `1` (`TYPE_INCOMING_TEXT`) — входящий текст для получателя.
|
||||||
|
- `2` (`TYPE_OUTGOING_COPY`) — исходящая копия в истории автора.
|
||||||
|
- `3` (`TYPE_READ_INCOMING`) — read-receipt (входящий тип для пары квитанции).
|
||||||
|
- `4` (`TYPE_READ_OUTGOING_COPY`) — зеркальная копия read-receipt.
|
||||||
|
|
||||||
|
Правило пары:
|
||||||
|
|
||||||
|
- первый блок должен быть нечётным (`1` или `3`);
|
||||||
|
- второй должен быть ровно `+1` (`2` или `4`);
|
||||||
|
- ключевые поля пары совпадают: `toLogin/fromLogin/timeMs/nonce`.
|
||||||
|
|
||||||
|
## 4) Ключи сообщений
|
||||||
|
|
||||||
|
- `baseKey = from|to|timeMs|nonce`
|
||||||
- `messageKey = baseKey|messageType`
|
- `messageKey = baseKey|messageType`
|
||||||
|
|
||||||
Логический идентификатор письма задаётся парой:
|
Эти ключи используются:
|
||||||
|
|
||||||
- `timeMs`
|
- для дедупликации;
|
||||||
- `nonce`
|
- для связи read-receipt с исходным сообщением;
|
||||||
|
- для ACK доставки по сессии.
|
||||||
|
|
||||||
Эти поля не меняются при редактировании или удалении. Меняется только:
|
## 5) RPC и события
|
||||||
|
|
||||||
- `revisionTimeMs`
|
## `SendMessagePair` (алиас `ReceiveOutcomingMessage`)
|
||||||
- содержимое `encryptedBody`
|
|
||||||
|
|
||||||
Сервер хранит только последнюю версию записи для каждого `messageKey`.
|
Запрос:
|
||||||
|
|
||||||
## Формат контентного DM: `SHiNE_DM`
|
```json
|
||||||
|
{
|
||||||
|
"op": "SendMessagePair",
|
||||||
|
"requestId": "req-1",
|
||||||
|
"payload": {
|
||||||
|
"incomingBlobB64": "<base64 signed block type 1 or 3>",
|
||||||
|
"outgoingBlobB64": "<base64 signed block type 2 or 4>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Префикс бинарного блока:
|
Успешный ответ:
|
||||||
|
|
||||||
- `SHiNE_DM`
|
```json
|
||||||
|
{
|
||||||
|
"op": "SendMessagePair",
|
||||||
|
"requestId": "req-1",
|
||||||
|
"status": 200,
|
||||||
|
"ok": true,
|
||||||
|
"payload": {
|
||||||
|
"baseKey": "from|to|time|nonce",
|
||||||
|
"incomingKey": "from|to|time|nonce|1",
|
||||||
|
"outgoingKey": "from|to|time|nonce|2",
|
||||||
|
"deliveredWsSessions": 2,
|
||||||
|
"deliveredWebPushSessions": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Поля идут в big-endian порядке:
|
## `SignedMessageArrived` (server event)
|
||||||
|
|
||||||
1. `formatVersionMajor` (`u8`) = `1`
|
Событие в сессию получателя содержит:
|
||||||
2. `formatVersionMinor` (`u8`) = `0`
|
|
||||||
3. `toLoginLen` (`u8`) + `toLogin` (ASCII, `1..60`)
|
|
||||||
4. `fromLoginLen` (`u8`) + `fromLogin` (ASCII, `1..60`)
|
|
||||||
5. `timeMs` (`u64`)
|
|
||||||
6. `nonce` (`u32`)
|
|
||||||
7. `messageType` (`u16`) — только `1` или `2`
|
|
||||||
8. `revisionTimeMs` (`u64`)
|
|
||||||
9. `attachmentsCount` (`u8`)
|
|
||||||
10. `encryptedBodyLen` (`u32`)
|
|
||||||
11. `encryptedBody` (`bytes`)
|
|
||||||
12. `signature` (`64 bytes`, Ed25519)
|
|
||||||
|
|
||||||
### Ограничения
|
- `messageKey`, `baseKey`;
|
||||||
|
- `fromLogin`, `toLogin`, `targetLogin`;
|
||||||
|
- `messageType`, `timeMs`, `nonce`;
|
||||||
|
- `blobB64`;
|
||||||
|
- `backlog` (признак догрузки из очереди).
|
||||||
|
|
||||||
- `attachmentsCount` сейчас всегда должен быть `0`
|
## `AckSessionDelivery`
|
||||||
- `encryptedBodyLen` сейчас ограничен сервером до `16384` байт
|
|
||||||
- `revisionTimeMs` не может быть отрицательным
|
|
||||||
|
|
||||||
Если приходит `attachmentsCount != 0`, сервер отклоняет такой DM как:
|
Запрос:
|
||||||
|
|
||||||
- `ATTACHMENTS_DISABLED`
|
```json
|
||||||
|
{
|
||||||
|
"op": "AckSessionDelivery",
|
||||||
|
"requestId": "ack-1",
|
||||||
|
"payload": {
|
||||||
|
"messageKey": "from|to|time|nonce|1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Legacy read-receipt: `SHiNE_dm2`
|
Ответ: `status=200`, echo `messageKey`.
|
||||||
|
|
||||||
Подтверждения прочтения `type=3/4` пока используют старый контейнер `SHiNE_dm2`:
|
## 6) Хранение на сервере (SQLite)
|
||||||
|
|
||||||
1. `toLoginLen` (`u8`) + `toLogin`
|
Основные таблицы:
|
||||||
2. `fromLoginLen` (`u8`) + `fromLogin`
|
|
||||||
3. `timeMs` (`u64`)
|
|
||||||
4. `nonce` (`u32`)
|
|
||||||
5. `messageType` (`u16`) — `3` или `4`
|
|
||||||
6. `payloadLen` (`u16`)
|
|
||||||
7. `payloadBytes`
|
|
||||||
8. `signature`
|
|
||||||
|
|
||||||
## Редактирование
|
1. `signed_messages_v2` — сами DM-блоки типов `1/2/3/4`:
|
||||||
|
- `message_key` (PK),
|
||||||
|
- `base_key`,
|
||||||
|
- `target_login`,
|
||||||
|
- `from_login`, `to_login`,
|
||||||
|
- `time_ms`, `nonce`, `message_type`,
|
||||||
|
- `raw_block`,
|
||||||
|
- `source_api`, `origin_session_id`,
|
||||||
|
- `receipt_ref_base_key`, `receipt_ref_type`.
|
||||||
|
2. `signed_message_session_delivery` — доставка по сессиям:
|
||||||
|
- составной PK `(message_key, session_id)`,
|
||||||
|
- `delivered` (0/1),
|
||||||
|
- `delivered_at_ms`, `created_at_ms`.
|
||||||
|
|
||||||
Редактирование делается новой отправкой той же логической пары сообщения:
|
Примечание: историческая таблица `signed_direct_messages_history` в БД присутствует как legacy-слой, но текущий рабочий поток DM v2 опирается на `signed_messages_v2` + `signed_message_session_delivery`.
|
||||||
|
|
||||||
- `timeMs` и `nonce` остаются теми же;
|
## 7) Доставка и backlog
|
||||||
- `messageType` остаётся `1/2`;
|
|
||||||
- `revisionTimeMs` становится больше;
|
|
||||||
- `encryptedBody` содержит новую версию текста.
|
|
||||||
|
|
||||||
Если на сервер приходит более старая ревизия, она игнорируется.
|
- При сохранении пары сервер пытается сразу доставить в онлайн-сессии.
|
||||||
|
- Для офлайн/недоступных сессий остаётся pending-запись доставки в таблице `signed_message_session_delivery`.
|
||||||
|
- При подключении сессии сервер автоматически вызывает `dispatchPendingForSession`:
|
||||||
|
- для новой сессии регистрирует все существующие сообщения адресата как «недоставленные»;
|
||||||
|
- отправляет **все** pending через WebSocket событием `SignedMessageArrived(backlog=true)`;
|
||||||
|
- лимита на количество сообщений нет — передаётся вся история без ограничений.
|
||||||
|
- Клиент дедублирует входящие через `knownMessageKeys`: если `messageKey` уже есть локально — игнорирует.
|
||||||
|
- После получения клиент отправляет `AckSessionDelivery`, чтобы отметить `delivered=1` в таблице доставки.
|
||||||
|
|
||||||
Если приходит та же ревизия и тот же бинарный блок, сервер тоже её не применяет повторно.
|
## 8) Read-receipt логика
|
||||||
|
|
||||||
## Удаление
|
Когда клиент открывает чат:
|
||||||
|
|
||||||
Удаление личного сообщения делается как новая ревизия того же сообщения:
|
1. ищет входящие `messageType=1` без `readReceiptSent`;
|
||||||
|
2. для каждого отправляет read-receipt как пару `type=3/4`;
|
||||||
|
3. после успешной отправки помечает `readReceiptSent`.
|
||||||
|
|
||||||
- `timeMs` и `nonce` остаются прежними;
|
Сервер для read-receipt хранит ссылку на исходное сообщение:
|
||||||
- `revisionTimeMs` увеличивается;
|
|
||||||
- `attachmentsCount = 0`;
|
|
||||||
- `encryptedBodyLen = 0`;
|
|
||||||
- `encryptedBody` пустой.
|
|
||||||
|
|
||||||
В UI такое сообщение не показывается.
|
- `receipt_ref_base_key`;
|
||||||
|
- `receipt_ref_type`.
|
||||||
|
|
||||||
На сервере это не отдельный тип сообщения, а просто последняя пустая ревизия того же `messageKey`.
|
Есть уникальность, чтобы не плодить дубликаты receipt на один и тот же `baseKey` для одного `target_login`.
|
||||||
|
|
||||||
## Поведение сервера
|
## 9) Логика UI-клиента
|
||||||
|
|
||||||
Для контентных DM сервер:
|
### Хранилище сообщений
|
||||||
|
|
||||||
1. принимает пару signed-блоков `type=1/2`;
|
- In-memory: `state.chats[chatId]` — массив сообщений по каждому диалогу.
|
||||||
2. валидирует формат, подпись и совпадение ключевых полей пары;
|
- Персистентно: IndexedDB база `shine-ui-messages-v1`, object store `messages`, ключ `messageKey`.
|
||||||
3. проверяет, что для обеих сторон пары совпадают:
|
- `chatId` для `type=1` — `fromLogin`, для `type=2` — `toLogin`.
|
||||||
- `fromLogin`
|
|
||||||
- `toLogin`
|
|
||||||
- `timeMs`
|
|
||||||
- `nonce`
|
|
||||||
- `revisionTimeMs`
|
|
||||||
- `encryptedBody`
|
|
||||||
4. делает `upsert` последней версии в `signed_messages_v2`;
|
|
||||||
5. сбрасывает pending-доставку по сессиям для новой ревизии;
|
|
||||||
6. рассылает актуальную версию адресатам через `SignedMessageArrived`.
|
|
||||||
|
|
||||||
История старых ревизий сейчас не хранится отдельно: в таблице остаётся только последняя версия по каждому `messageKey`.
|
### Жизненный цикл при старте/подключении
|
||||||
|
|
||||||
## Хранение в БД
|
1. `hydrateMessagesFromStore()` — читает все сообщения из IndexedDB в `state.chats` (до WebSocket-соединения).
|
||||||
|
2. После установки WebSocket-сессии сервер присылает backlog (`SignedMessageArrived(backlog=true)`) для всех недоставленных сообщений.
|
||||||
|
3. Клиент дедублирует через `knownMessageKeys` — уже имеющиеся в IndexedDB игнорируются.
|
||||||
|
4. Новые сообщения в реальном времени приходят теми же WebSocket-событиями.
|
||||||
|
|
||||||
Основная таблица:
|
### Очистка при выходе и смене пользователя
|
||||||
|
|
||||||
- `signed_messages_v2`
|
- При любом логауте (`terminateCurrentSession`) IndexedDB с сообщениями **удаляется полностью**.
|
||||||
|
- При входе нового пользователя через QR — IndexedDB удаляется явно до вызова `terminateCurrentSession`.
|
||||||
|
- При входе нового пользователя через логин/пароль — IndexedDB удаляется в `registration-keys-view.js` прямо перед `authorizeSession()`.
|
||||||
|
- Это гарантирует: при любом способе входа старые сообщения предыдущего пользователя не попадут к следующему.
|
||||||
|
|
||||||
Для контентных DM в ней используются:
|
### UI-поведение
|
||||||
|
|
||||||
- `message_key`
|
- непрочитанные считаются по `from='in' && unread=true`;
|
||||||
- `base_key`
|
- доставка/прочтение исходящих:
|
||||||
- `target_login`
|
- `firstTick` — сообщение принято сервером,
|
||||||
- `from_login`
|
- `secondTick` — пришло подтверждение прочтения;
|
||||||
- `to_login`
|
- при открытии диалога UI автопрокручивает ленту в самый низ;
|
||||||
- `time_ms`
|
- после отправки нового сообщения UI сразу прокручивает ленту вниз.
|
||||||
- `nonce`
|
|
||||||
- `message_type`
|
|
||||||
- `revision_time_ms`
|
|
||||||
- `raw_block`
|
|
||||||
- `created_at_ms`
|
|
||||||
|
|
||||||
Отдельных таблиц файлов для DM сейчас нет.
|
## 10) Синхронизация личных сообщений между серверами
|
||||||
|
|
||||||
## События и доставка
|
Когда пользователи зарегистрированы на разных серверах SHiNE, серверы должны синхронизировать DM между собой.
|
||||||
|
|
||||||
Запрос на отправку по WebSocket остаётся прежним:
|
### Общий принцип
|
||||||
|
|
||||||
- `SendMessagePair`
|
- Сервер A получает DM-блок, адресованный пользователю на сервере B.
|
||||||
- `ReceiveOutcomingMessage` как алиас
|
- Сервер A пересылает этот блок серверу B (межсерверный relay).
|
||||||
|
- Сервер B сохраняет блок и доставляет его в активные сессии получателя.
|
||||||
|
- Серверы, между которыми идёт синхронизация, задаются списком `sync_servers` в PDA пользователя-сервера.
|
||||||
|
|
||||||
Клиент отправляет:
|
### Что синхронизируется
|
||||||
|
|
||||||
- `incomingBlobB64`
|
- Все DM-блоки типов `1/2` (текстовые сообщения) и `3/4` (read-receipt).
|
||||||
- `outgoingBlobB64`
|
- Синхронизация двусторонняя: оба сервера должны уметь принимать и пересылать блоки.
|
||||||
|
|
||||||
Событие в активные сессии:
|
### Идемпотентность
|
||||||
|
|
||||||
- `SignedMessageArrived`
|
- Блоки имеют уникальный `message_key` (`from|to|timeMs|nonce|type`).
|
||||||
|
- Повторная доставка одного и того же блока безопасна — дедупликация происходит по `message_key`.
|
||||||
|
|
||||||
Если пришла новая ревизия того же сообщения, `messageKey` остаётся прежним, а внутри `blobB64` будет более новый `revisionTimeMs`.
|
### Статус реализации
|
||||||
|
|
||||||
Подтверждение доставки в сессию:
|
Межсерверная синхронизация DM **пока не реализована**. Текущая версия работает только в рамках одного сервера. Это задача для следующего этапа.
|
||||||
|
|
||||||
- `AckSessionDelivery`
|
---
|
||||||
|
|
||||||
## Правила UI
|
## 11) Инварианты (обязательно соблюдать при доработках)
|
||||||
|
|
||||||
UI сейчас работает так:
|
1. Пара блоков (1/2 или 3/4) должна оставаться атомарной.
|
||||||
|
2. `messageKey`/`baseKey` формат должен быть совместим с текущей логикой дедупликации и receipt.
|
||||||
|
3. Доставка должна оставаться **по сессиям** с явным `AckSessionDelivery`.
|
||||||
|
4. Read-receipt не должен отправляться многократно на один и тот же `baseKey`.
|
||||||
|
5. Любые изменения DM-логики в коде должны сразу отражаться в этом документе.
|
||||||
|
|
||||||
- показывает только текст `encryptedBody`;
|
## 12) Ключевые файлы реализации
|
||||||
- умеет обновлять уже существующее сообщение по тому же `messageKey`;
|
|
||||||
- не показывает удалённые сообщения;
|
|
||||||
- позволяет владельцу сообщения вызвать меню `Скопировать как текст / Прочесть / Изменить / Удалить`;
|
|
||||||
- при редактировании показывает над полем ввода полоску `Редактируем сообщение: ...` с кнопкой отмены;
|
|
||||||
- после редактирования показывает под временем отдельную строку `изменено: <дата время>`;
|
|
||||||
- не показывает и не принимает вложения.
|
|
||||||
|
|
||||||
## Что обязательно помнить
|
- UI:
|
||||||
|
- `shine-UI/js/services/auth-service.js`
|
||||||
- вложения в DM сейчас отключены на уровне протокола и UI;
|
- `shine-UI/js/app.js`
|
||||||
- любые старые описания `/f/...`, `/upload` и файловых таблиц для DM больше не актуальны;
|
- `shine-UI/js/state.js`
|
||||||
- если позже вложения вернутся, их формат и серверная логика могут быть другими.
|
- `shine-UI/js/pages/chat-view.js`
|
||||||
|
- Сервер:
|
||||||
|
- `shine-server-net-protocol/.../messages/SignedMessageBlock.java`
|
||||||
|
- `shine-server-net-protocol/.../messages/SignedMessagesCore.java`
|
||||||
|
- `shine-server-net-protocol/.../messages/Net_SendMessagePair_Handler.java`
|
||||||
|
- `shine-server-net-protocol/.../messages/SignedMessagesRealtime.java`
|
||||||
|
- `shine-server-net-protocol/.../messages/Net_AckSessionDelivery_Handler.java`
|
||||||
|
- БД:
|
||||||
|
- `shine-server-db/src/main/java/shine/db/DatabaseInitializer.java`
|
||||||
|
- `shine-server-db/src/main/java/shine/db/dao/SignedMessagesV2DAO.java`
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
# Черновик будущих вложений в DM
|
|
||||||
|
|
||||||
## Важно
|
|
||||||
|
|
||||||
Этот документ описывает только ранний черновик идеи.
|
|
||||||
|
|
||||||
Сейчас в проекте **нет** поддержки вложений в личных сообщениях:
|
|
||||||
|
|
||||||
- в реализованном формате `SHiNE_DM` поле `attachmentsCount` пока всегда должно быть `0`;
|
|
||||||
- UI не показывает кнопку прикрепления файлов;
|
|
||||||
- сервер не принимает upload файлов для DM;
|
|
||||||
- сервер не раздаёт специальные DM-файлы по отдельным endpoints;
|
|
||||||
- сервер не хранит отдельные файловые связи для личных сообщений.
|
|
||||||
|
|
||||||
Этот документ нужен только для того, чтобы рядом с актуальной документацией было явно видно:
|
|
||||||
|
|
||||||
- какие идеи обсуждались;
|
|
||||||
- что это **не реализовано**;
|
|
||||||
- что формат, хранение и способ загрузки потом могут сильно измениться.
|
|
||||||
|
|
||||||
## Что обсуждалось
|
|
||||||
|
|
||||||
Рассматривался такой общий подход:
|
|
||||||
|
|
||||||
- у контентного DM есть внешний список вложений;
|
|
||||||
- во внешнем формате лежат только технические данные;
|
|
||||||
- человекочитаемые данные о файле живут внутри зашифрованного тела сообщения;
|
|
||||||
- один и тот же blob-файл теоретически мог бы переиспользоваться в нескольких сообщениях.
|
|
||||||
|
|
||||||
Черновой вариант внешнего списка:
|
|
||||||
|
|
||||||
- `attachmentsCount`
|
|
||||||
- далее для каждого вложения:
|
|
||||||
- `encFileHashSHA256` (`32 bytes`)
|
|
||||||
- `encFileSize` (`u64`)
|
|
||||||
|
|
||||||
Черновой вариант внутреннего маркера в тексте:
|
|
||||||
|
|
||||||
```text
|
|
||||||
<<file:file-format(1.0):type|fileName|origSize|origHashB64u|encHashB64u|encSize|keyB64u|nonceB64u>>
|
|
||||||
```
|
|
||||||
|
|
||||||
Где обсуждались поля:
|
|
||||||
|
|
||||||
- `type`
|
|
||||||
- `fileName`
|
|
||||||
- `origSize`
|
|
||||||
- `origHashB64u`
|
|
||||||
- `encHashB64u`
|
|
||||||
- `encSize`
|
|
||||||
- `keyB64u`
|
|
||||||
- `nonceB64u`
|
|
||||||
|
|
||||||
## Что может измениться
|
|
||||||
|
|
||||||
В будущем могут измениться любые части идеи:
|
|
||||||
|
|
||||||
- сам бинарный формат;
|
|
||||||
- способ привязки файлов к сообщению;
|
|
||||||
- момент загрузки файла относительно отправки сообщения;
|
|
||||||
- серверное хранение blob-файлов;
|
|
||||||
- права доступа к скачиванию;
|
|
||||||
- способ рендера вложения в UI.
|
|
||||||
|
|
||||||
Именно поэтому этот файл не надо воспринимать как актуальную спецификацию.
|
|
||||||
|
|
||||||
## Источник истины на сейчас
|
|
||||||
|
|
||||||
Актуальное состояние личных сообщений описано только в:
|
|
||||||
|
|
||||||
- `Dev_Docs/Personal_Messages/README.md`
|
|
||||||
|
|
||||||
Если между этим черновиком и основным README есть расхождение, верным считается `README.md`.
|
|
||||||
@@ -90,7 +90,7 @@ UserPdaRecordV1
|
|||||||
| `3` | `BlockchainRegistryBlock` | Один или несколько блокчейнов пользователя. |
|
| `3` | `BlockchainRegistryBlock` | Один или несколько блокчейнов пользователя. |
|
||||||
| `30` | `ServerProfileBlock` | Серверные данные пользователя. |
|
| `30` | `ServerProfileBlock` | Серверные данные пользователя. |
|
||||||
| `40` | `AccessServersBlock` | Серверы доступа/relay. |
|
| `40` | `AccessServersBlock` | Серверы доступа/relay. |
|
||||||
| `50` | `SessionsBlock` | Опубликованные пользовательские сессии и homeserver-ы. |
|
| `50` | `SessionsBlock` | Опубликованные пользовательские сессии и саб-серверы. |
|
||||||
| `70` | `TrustedStateBlock` | Счетчик trusted-связей. |
|
| `70` | `TrustedStateBlock` | Счетчик trusted-связей. |
|
||||||
| `255` | `ReservedBlock` | Зарезервировано, пока не используется. |
|
| `255` | `ReservedBlock` | Зарезервировано, пока не используется. |
|
||||||
|
|
||||||
@@ -309,7 +309,7 @@ SessionRecord
|
|||||||
| Значение | Смысл |
|
| Значение | Смысл |
|
||||||
|----------|-------|
|
|----------|-------|
|
||||||
| `1` | Обычная пользовательская сессия. |
|
| `1` | Обычная пользовательская сессия. |
|
||||||
| `100` | Homeserver пользователя. |
|
| `100` | Саб-сервер пользователя. |
|
||||||
|
|
||||||
Правила:
|
Правила:
|
||||||
|
|
||||||
|
|||||||
@@ -1,177 +0,0 @@
|
|||||||
# Аудит безопасности Solana-программ SHiNE — выпуск 2 (11.06.2026)
|
|
||||||
|
|
||||||
Повторный независимый аудит после исправления всех 4 находок первого отчёта
|
|
||||||
(`Solana-audit-by-Claude-File5-9июня2026.md`). Код перечитан целиком:
|
|
||||||
|
|
||||||
- `shine_login_guard` (183 строки) — stateless-классификатор логинов;
|
|
||||||
- `shine_users` (1069 строк) — реестр пользователей, PDA-записи, подписи, экономика лимитов;
|
|
||||||
- `shine_payments` (1381 строка) — очереди тикетов, выплаты из вольта, оракул Pyth.
|
|
||||||
|
|
||||||
Перебраны классы атак: подмена аккаунтов/PDA, авторизация и подписи, арифметика и
|
|
||||||
переполнения, валидация оракула, экономика, реентранси, griefing/DoS, **алиасинг
|
|
||||||
аккаунтов (передача одного аккаунта в несколько слотов инструкции)**.
|
|
||||||
|
|
||||||
## Статус прошлых находок (все закрыты)
|
|
||||||
|
|
||||||
- 🔴 Critical #1 (economy-config PDA в `shine_users`) — закрыто: `validate_users_economy_config_pda` проверяет и адрес, и `owner == program_id`, и вызывается перед чтением и в create, и в update.
|
|
||||||
- 🔴 Critical #2 (singleton-PDA в `shine_payments`) — закрыто: `validate_singleton_state_pda` проверяет точный адрес + `owner == id()` во всех инструкциях (`update_coef_limit`, `grant_manager_limits`, `buy_ticket*`, `manager_add_ticket`, `step_payout`, `change_ticket_recipient`).
|
|
||||||
- 🟠 Medium (валидация Pyth) — закрыто: пин адреса аккаунта `PYTH_SOL_USD_ACCOUNT`, проверка `owner == pyth_receiver`, разбор официальным `PriceUpdateV2`, `get_price_no_older_than` с проверкой `feed_id`, проверка возраста и доверительного интервала (`ORACLE_MAX_CONFIDENCE_PPM`).
|
|
||||||
- 🟡 Low (griefing на предсказуемых адресах) — закрыто: `create_pda_account` в обеих программах переведён на «создание поверх предзаполненного» (allocate + assign + добор ренты).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔴 HIGH (НОВОЕ) — `shine_payments`: тикет с `recipient_wallet == inflow_vault` навсегда замораживает все выплаты — ✅ ИСПРАВЛЕНО (11.06.2026)
|
|
||||||
|
|
||||||
Закрыто: равенство `recipient == inflow_vault` запрещено во всех точках задания
|
|
||||||
получателя — `buy_ticket_by_purchase_usd` (через `config.inflow_vault`),
|
|
||||||
`process_manager_add_ticket` и `process_change_ticket_recipient` (через
|
|
||||||
`find_single_pda(INFLOW_VAULT_SEED)`). Дополнительно в `transfer_from_vault` добавлена
|
|
||||||
защита по умолчанию `require!(vault.key != recipient.key)`. Документация —
|
|
||||||
`doc/programs/shine_payments.md` §10.1. Историческое описание находки ниже.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Где
|
|
||||||
`transfer_from_vault` (строки 1258–1268) переводит лампорты из вольта прямой
|
|
||||||
манипуляцией балансами (вольт — PDA без приватного ключа, обычный system-перевод
|
|
||||||
невозможен):
|
|
||||||
|
|
||||||
```rust
|
|
||||||
fn transfer_from_vault(vault: &AccountInfo, recipient: &AccountInfo, amount: u64) -> ProgramResult {
|
|
||||||
if amount == 0 { return Ok(()); }
|
|
||||||
let mut vault_lamports = vault.try_borrow_mut_lamports()?; // займ #1
|
|
||||||
let mut recipient_lamports = recipient.try_borrow_mut_lamports()?; // займ #2
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
В `step_payout` (строка 849) получатель — это `ticket.recipient_wallet`:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
transfer_from_vault(inflow_vault_pda, ticket_recipient_wallet, ticket_lamports)?;
|
|
||||||
```
|
|
||||||
|
|
||||||
А `recipient_wallet` нигде не валидируется при создании тикета:
|
|
||||||
`buy_ticket*` (строки 696/711/725 → 1031), `manager_add_ticket` (строка 765),
|
|
||||||
`change_ticket_recipient` (строка 900) — берут его «как есть» из аргументов.
|
|
||||||
|
|
||||||
### Суть атаки (алиасинг аккаунта)
|
|
||||||
В Solana, если один и тот же аккаунт передан в инструкцию в нескольких слотах,
|
|
||||||
рантайм отдаёт для всех слотов **один и тот же** `RefCell` (механизм дублей).
|
|
||||||
Поэтому если `ticket.recipient_wallet` равен адресу `inflow_vault` PDA, то в
|
|
||||||
`step_payout` аккаунт вольта попадает и в слот `inflow_vault_pda`, и в слот
|
|
||||||
`ticket_recipient_wallet`. Тогда внутри `transfer_from_vault`:
|
|
||||||
|
|
||||||
- `vault.try_borrow_mut_lamports()` — берёт mutable-займ (успех);
|
|
||||||
- `recipient.try_borrow_mut_lamports()` — это **тот же** аккаунт → второй
|
|
||||||
mutable-займ → `Err(AccountBorrowFailed)` → `?` возвращает ошибку → инструкция
|
|
||||||
падает.
|
|
||||||
|
|
||||||
### Почему это «заморозка всего», а не один тикет
|
|
||||||
Выплаты идут строго по возрастанию индекса. `step_payout` всегда обслуживает
|
|
||||||
сначала очередь Q1 (если в ней есть pending), затем Q2, затем Q3, и в каждой —
|
|
||||||
ровно «следующий неоплаченный» тикет (`paid + 1`). Тикет с `recipient == vault`:
|
|
||||||
|
|
||||||
- не может быть оплачен (`step_payout` всегда падает на нём);
|
|
||||||
- не может быть пропущен (нет механизма «skip»);
|
|
||||||
- блокирует все тикеты после него в своей очереди;
|
|
||||||
- если он в Q1 — блокирует обслуживание Q2 и Q3 (до них очередь не доходит);
|
|
||||||
- лампорты вольта (накопленные регистрационные комиссии) перестают выплачиваться
|
|
||||||
и не уходят в DAO (слив в DAO происходит только когда `pending == 0` по всем
|
|
||||||
очередям, а это состояние недостижимо).
|
|
||||||
|
|
||||||
### Эксплуатация (тривиальная, перестановочная)
|
|
||||||
Q1 — публичная очередь (`buy_ticket` доступен любому). Атакующий покупает **один**
|
|
||||||
дешёвый тикет Q1, указав `recipient_wallet = <адрес inflow_vault PDA>`. Адрес вольта
|
|
||||||
детерминирован и публичен (`find_single_pda(INFLOW_VAULT_SEED)`). С этого момента вся
|
|
||||||
подсистема выплат и средства вольта заморожены за стоимость одного тикета + ренты.
|
|
||||||
|
|
||||||
Дополнительно: даже при защите на этапе покупки остаётся вектор через
|
|
||||||
`change_ticket_recipient` (строка 900) — владелец любого своего неоплаченного тикета
|
|
||||||
может выставить `new_recipient_wallet = vault` позже.
|
|
||||||
|
|
||||||
### Класс и серьёзность
|
|
||||||
Класс: «account aliasing / duplicate-account mutable borrow» + отсутствие
|
|
||||||
валидации адреса получателя. Прямой кражи средств нет, но это перманентный
|
|
||||||
отказ в обслуживании (availability) с блокировкой средств вольта, триггер —
|
|
||||||
копеечный и доступен анонимно. Оценка: **HIGH**.
|
|
||||||
|
|
||||||
### Рекомендуемый фикс
|
|
||||||
Запретить `recipient`, равный адресу вольта, во всех точках, где он задаётся, чтобы
|
|
||||||
тикет с таким получателем вообще не мог появиться:
|
|
||||||
|
|
||||||
1. в `buy_ticket_by_purchase_usd` — `require!(recipient_wallet != config.inflow_vault, …)`
|
|
||||||
(config уже прочитан);
|
|
||||||
2. в `process_manager_add_ticket` — сверять с `find_single_pda(INFLOW_VAULT_SEED).0`;
|
|
||||||
3. в `process_change_ticket_recipient` — то же для `new_recipient_wallet`.
|
|
||||||
|
|
||||||
Дополнительно (defense-in-depth) — в `transfer_from_vault` явно
|
|
||||||
`require!(vault.key != recipient.key, …)` с понятной ошибкой, чтобы любой будущий
|
|
||||||
вызов был защищён от алиасинга. Этого `require` недостаточно как единственной меры
|
|
||||||
(тикет всё равно застрял бы), поэтому основная защита — на входе.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🟡 LOW / INFO — наблюдения без прямой эксплуатации
|
|
||||||
|
|
||||||
### L1. `change_ticket_recipient` и `buy_ticket` не проверяют получателя на «опасные» адреса
|
|
||||||
Связано с HIGH выше; после фикса основной проблемы стоит заодно зафиксировать
|
|
||||||
правило «получатель не должен совпадать с системными PDA программы».
|
|
||||||
|
|
||||||
### L2. Гонка за логином (first-come) в `shine_users`
|
|
||||||
Адрес `user_pda` выводится из логина. После закрытия griefing-подсева остаётся
|
|
||||||
обычное состязание: увидев в мемпуле регистрацию `alice`, атакующий может
|
|
||||||
зарегистрировать `alice` со своим `root_key` первым. On-chain это решается только
|
|
||||||
commit-reveal; для текущей модели — приемлемый риск, отметить как известный.
|
|
||||||
|
|
||||||
### L3. `step_payout` без slippage-параметра
|
|
||||||
Выплата считается по текущей цене оракула без верхней границы лампортов. Цена
|
|
||||||
ограничена возрастом (120с) и доверительным интервалом (10%), аккаунт оракула
|
|
||||||
запинен — манипуляция маловероятна, но при резком движении цены SOL объём выплаты
|
|
||||||
в лампортах плавает. Риск низкий; при желании добавить верхнюю границу на шаг.
|
|
||||||
|
|
||||||
### L4. Экономическая устойчивость вольта (дизайн, не баг)
|
|
||||||
Деньги за покупку тикетов (`buy_ticket`) уходят на `dao_wallet`, а выплаты в
|
|
||||||
`step_payout` идут из `inflow_vault`, который наполняется **регистрационными
|
|
||||||
комиссиями** `shine_users`. Если поток регистраций меньше обязательств по выплатам,
|
|
||||||
вольт истощается и выплаты останавливаются (без потери средств, но с остановкой
|
|
||||||
сервиса). Это свойство экономической модели — стоит явно держать в уме и
|
|
||||||
мониторить баланс вольта/обязательств.
|
|
||||||
|
|
||||||
### L5. Заполнение Q1 до лимита как мягкий DoS
|
|
||||||
`buy_ticket` блокируется при `q1_sum_total >= limit_usd_cents`. Атакующий может
|
|
||||||
наполнить Q1 своими тикетами и приостановить покупки. Дорого (тратит SOL в DAO и
|
|
||||||
ренту) и его же тикеты потом оплачиваются из вольта, поэтому это скорее
|
|
||||||
экономический, а не дешёвый griefing. Риск низкий.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Проверено и подтверждено как корректное
|
|
||||||
|
|
||||||
- **Подмена singleton-PDA** невозможна: везде сверяется точный адрес и владелец.
|
|
||||||
- **Авторизация**: `update_coef_limit`/`grant_manager_limits` требуют `signer == config.dao_wallet`; `manager_add_ticket` — `signer == allowance.manager_wallet`; `change_ticket_recipient` — `signer == ticket.recipient_wallet`; обновление economy-config — `signer == DAO_AUTHORITY`.
|
|
||||||
- **Ed25519 в `shine_users`**: строгие относительные индексы (−1/−2), `num_signatures == 1`, все три `ix_index == u16::MAX` (данные внутри самой ed25519-инструкции), сверка pubkey/signature/message по хэшу. Подмена и указание на чужую инструкцию исключены.
|
|
||||||
- **Цепочка версий записи** (`version == record_number+1`, `prev_hash == hash(old)`) — корректная защита от replay; сигнатура записи завязана на `root_key`, а не на плательщика.
|
|
||||||
- **Монотонность** `used_bytes`/`last_block_number` и `used_bytes <= paid_limit_bytes`.
|
|
||||||
- **Арифметика**: повсеместные `checked_*`, `overflow-checks = true`, расчёты оракула в `u128` с `u64::try_from` на сужении.
|
|
||||||
- **Оракул Pyth**: пин аккаунта + owner + feed_id + возраст + confidence через официальный SDK.
|
|
||||||
- **Рент-экземпт вольта** сохраняется: `available_vault_lamports` вычитает `minimum_balance`, а суммарная проверка `available >= needed` гарантирует, что после выплат вольт не опустится ниже ренты.
|
|
||||||
- **Двойная оплата тикета** исключена: `is_paid` + инкремент `*_tickets_paid`, следующий шаг адресует следующий индекс.
|
|
||||||
- **Реентранси отсутствует**: CPI только в System Program (transfer/allocate/assign) и в stateless `shine_login_guard` (с проверкой возвращённого `program_id`); обратных вызовов в наши программы нет.
|
|
||||||
- **create_pda_account (новый)**: устойчив к подсеву лампортов; атакующий не может ни выделить данные, ни сменить владельца PDA (нет ключа/seeds), поэтому ветка allocate+assign безопасна.
|
|
||||||
- **shine_login_guard**: stateless, без аккаунтов и средств; DFS-классификация ограничена (`MAX_WORDS_PER_LOGIN = 3`, длина ≤ 20) — без compute-DoS.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Приоритет действий
|
|
||||||
|
|
||||||
1. **HIGH** — запретить `recipient == inflow_vault` в `buy_ticket*`, `manager_add_ticket`,
|
|
||||||
`change_ticket_recipient`; добавить `require!(vault.key != recipient.key)` в
|
|
||||||
`transfer_from_vault` как защиту по умолчанию. Закрыть до mainnet.
|
|
||||||
2. **LOW** — зафиксировать правило «получатель ≠ системные PDA» (L1), оценить
|
|
||||||
добавление верхней границы выплаты на шаг (L3).
|
|
||||||
3. **INFO** — формально задокументировать экономику вольта (L4) и known-issue
|
|
||||||
гонки за логином (L5/L2).
|
|
||||||
|
|
||||||
Изменений в код в рамках этого аудита не вносил — это анализ. Готов подготовить патч
|
|
||||||
по пункту 1, если подтвердите.
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
# Аудит безопасности Solana-программ SHiNE — выпуск 3 (12.06.2026)
|
|
||||||
|
|
||||||
Тематический аудит с фокусом на **полноту проверок входных аккаунтов**
|
|
||||||
(signer / owner / каноничный PDA-адрес / system-program / sysvar инструкций /
|
|
||||||
аккаунт оракула) — отвечает на вопрос «точно ли хватает всех проверок входных
|
|
||||||
аккаунтов». Код перечитан целиком после исправлений аудита №2
|
|
||||||
(`Solana-audit-2-by-Claude-11июня2026.md`):
|
|
||||||
|
|
||||||
- `shine_login_guard` (183 строки) — stateless-классификатор логинов, аккаунтами не пользуется;
|
|
||||||
- `shine_users` (1068 строк) — реестр пользователей, PDA-записи, ed25519-подписи, экономика лимитов;
|
|
||||||
- `shine_payments` (1398 строк) — очереди тикетов, выплаты из вольта, оракул Pyth.
|
|
||||||
|
|
||||||
Это ручная (не-Anchor `#[derive(Accounts)]`) реализация на `solana_program`, поэтому
|
|
||||||
каждая проверка аккаунта выполняется явно в коде handler-а. Перебраны: подмена
|
|
||||||
аккаунтов/PDA, подмена владельца, bump-seed атаки, отсутствие signer/authority,
|
|
||||||
подмена system-program и sysvar, подмена аккаунта оракула, неинициализированные/
|
|
||||||
повторно инициализируемые PDA, «лишние» аккаунты.
|
|
||||||
|
|
||||||
## Итоговый вердикт
|
|
||||||
|
|
||||||
**Проверок входных аккаунтов достаточно во всех трёх программах.** По каждому
|
|
||||||
handler присутствуют все требуемые классы проверок; грубых дыр (подмена PDA на
|
|
||||||
чужой аккаунт, отсутствие owner/signer-проверки, использование пользовательского
|
|
||||||
bump, подмена аккаунта оракула) не найдено. Все Critical/HIGH из аудитов №1 и №2
|
|
||||||
закрыты и в этом проходе подтверждены в коде. Новых эксплуатируемых пробелов в
|
|
||||||
валидации аккаунтов нет; есть несколько LOW/INFO-замечаний «by design».
|
|
||||||
|
|
||||||
## Статус прошлых находок (подтверждено в коде на 12.06.2026)
|
|
||||||
|
|
||||||
- 🔴 Critical #1 (economy-config PDA, `shine_users`) — закрыто: `validate_users_economy_config_pda` (адрес + `owner == program_id`) вызывается и в create, и в update перед чтением.
|
|
||||||
- 🔴 Critical #2 (singleton-PDA, `shine_payments`) — закрыто: `validate_singleton_state_pda` (адрес + `owner == id()`) во всех инструкциях.
|
|
||||||
- 🟠 Medium (валидация Pyth) — закрыто: пин адреса `PYTH_SOL_USD_ACCOUNT`, `owner == pyth_receiver`, `PriceUpdateV2`, `feed_id`, возраст, доверительный интервал.
|
|
||||||
- 🟡 Low (griefing на предсказуемых адресах) — закрыто: `create_pda_account` создаёт «поверх предзаполненного» в обеих программах.
|
|
||||||
- 🔴 HIGH аудита №2 (`recipient_wallet == inflow_vault` замораживает выплаты) — закрыто: запрет `recipient == inflow_vault` в `buy_ticket_by_purchase_usd` (стр. 1026), `process_manager_add_ticket` (стр. 747), `process_change_ticket_recipient` (стр. 878) + защита по умолчанию `require!(vault.key != recipient.key)` в `transfer_from_vault` (стр. 1278).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Матрица проверок входных аккаунтов
|
|
||||||
|
|
||||||
### shine_users
|
|
||||||
|
|
||||||
| Инструкция | signer | owner PDA | адрес/seed PDA | system | sysvar / подпись | прочее |
|
|
||||||
|---|---|---|---|---|---|---|
|
|
||||||
| `init_users_economy_config` | ✓ | `owner == system` + `data_is_empty` (анти-reinit) | деривация + сверка | ✓ | — | значения из `settings`, не из ввода |
|
|
||||||
| `update_users_economy_config` | ✓ + `signer == DAO_AUTHORITY` | `owner == program_id` | деривация + сверка | — | — | `lamports_per_limit_step > 0` |
|
|
||||||
| `create_user_pda` | ✓ + `signer == device_key` | user_pda `owner == system` + empty; econ_config `owner == program_id` | user_pda, econ_config, inflow_vault, login_guard — все сверены | ✓ | ed25519 (record sig idx −2, last_block idx −1) | `inflow_vault` сверен с PDA `shine_payments`; login_guard сверен дважды |
|
|
||||||
| `update_user_pda` | ✓ + `signer == device_key` | user_pda `owner == program_id`; econ_config `owner == program_id` | деривация + сверка | ✓ | ed25519 + `version == old+1` + `prev_hash == hash(old)` | immutable-поля сверены с прежней записью |
|
|
||||||
|
|
||||||
### shine_payments
|
|
||||||
|
|
||||||
| Инструкция | signer | owner / валидация PDA | адрес PDA | system | прочее |
|
|
||||||
|---|---|---|---|---|---|
|
|
||||||
| `init` | ✓ payer | все 4 PDA `is_uninitialized` | деривация + сверка | ✓ | `dao_wallet` из `settings`, нет лишних аккаунтов |
|
|
||||||
| `update_coef_limit` | ✓ + `signer == config.dao_wallet` | config/coef `owner == id()` | деривация + сверка | — | границы coef/limit/reward; нет лишних аккаунтов |
|
|
||||||
| `grant_manager_limits` | ✓ + `signer == config.dao_wallet` | config `owner == id()`; allowance create/read | allowance из `manager_wallet` | ✓ | `state.manager_wallet == args.manager_wallet` |
|
|
||||||
| `buy_ticket` / `_usd` / `_sol` | ✓ | config/coef/queues `owner == id()` | ticket деривация + сверка + `is_uninitialized` | ✓ | oracle (key+owner+возраст+confidence), `dao_wallet == config.dao_wallet`, `recipient != inflow_vault`, slippage |
|
|
||||||
| `manager_add_ticket` | ✓ | allowance/queues `owner == id()` | allowance из `signer`; ticket деривация + сверка + uninit | ✓ | `allowance.manager_wallet == signer`, `queue_id ∈ {1,2,3}`, `recipient != inflow_vault` |
|
|
||||||
| `step_payout` | ✓ | все singleton-PDA `owner == id()` | ticket деривация + сверка | — | `dao_wallet == config.dao_wallet`, `inflow == config.inflow_vault`, ticket `queue/index/!is_paid/recipient`, oracle |
|
|
||||||
| `change_ticket_recipient` | ✓ + `signer == ticket.recipient_wallet` | queues + ticket `owner == id()` (через `read_state`) | ticket деривация из своих `queue_id/index` + сверка | — | `!is_paid`, запрет менять «следующий к выплате», `recipient != inflow_vault` |
|
|
||||||
|
|
||||||
### shine_login_guard
|
|
||||||
|
|
||||||
Аккаунты не используются (`_accounts`); программа stateless, средствами не владеет.
|
|
||||||
Защита со стороны вызова реализована в `shine_users`: сверяется и адрес вызываемой
|
|
||||||
программы (`login_guard_program.key == SHINE_LOGIN_GUARD_PROGRAM_ID`), и `program_id`
|
|
||||||
в `get_return_data`. Подмена/подделка ответа исключены. Отдельных проверок входных
|
|
||||||
аккаунтов внутри программы не требуется.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🟡 LOW / INFO — наблюдения без прямой эксплуатации
|
|
||||||
|
|
||||||
### L1. Permissionless `init` в обеих программах
|
|
||||||
`shine_payments::init` и `shine_users::init_users_economy_config` может вызвать кто
|
|
||||||
угодно первым. Практического эксплойта нет: все значения (включая `dao_wallet` и
|
|
||||||
`DAO_AUTHORITY`) берутся из констант `settings`, а не из ввода, повторная
|
|
||||||
инициализация заблокирована проверками `is_uninitialized` / `data_is_empty`. Риск
|
|
||||||
низкий; при желании привязать init к ожидаемому деплой-кошельку. Совпадает с моделью
|
|
||||||
«первый init = деплой».
|
|
||||||
|
|
||||||
### L2. В `shine_users` нет явной проверки «лишних аккаунтов» — ✅ ИСПРАВЛЕНО (12.06.2026)
|
|
||||||
`shine_payments` в каждом handler делает `require!(account_iter.next().is_none())`.
|
|
||||||
В `shine_users` такой проверки не было — лишние аккаунты в конце списка просто
|
|
||||||
игнорировались (читается строго нужное количество через `next_account_info`). Это
|
|
||||||
безвредно (на безопасность не влияло), но для симметрии и явности добавлено.
|
|
||||||
Класс: гигиена, не уязвимость.
|
|
||||||
|
|
||||||
Закрыто: во все 4 инструкции `shine_users` (`init_users_economy_config`,
|
|
||||||
`update_users_economy_config`, `create_user_pda`, `update_user_pda`) после чтения
|
|
||||||
фиксированного набора аккаунтов добавлено `require!(it.next().is_none(),
|
|
||||||
ShineUsersError::InvalidInstruction)`. Документация — `doc/programs/shine_users.md` §3.4.
|
|
||||||
|
|
||||||
### L3. Гонка за логином (first-come) в `shine_users` — known issue
|
|
||||||
Адрес `user_pda` детерминирован из логина; после закрытия griefing-подсева остаётся
|
|
||||||
обычное состязание за регистрацию (front-run в мемпуле). On-chain решается только
|
|
||||||
commit-reveal; для текущей модели — приемлемый риск, ранее зафиксирован в аудите №2
|
|
||||||
(L2). К проверкам аккаунтов не относится.
|
|
||||||
|
|
||||||
### L4. Экономическая устойчивость вольта (дизайн, не баг)
|
|
||||||
Деньги за покупку тикетов уходят на `dao_wallet`, а выплаты `step_payout` идут из
|
|
||||||
`inflow_vault`, наполняемого регистрационными комиссиями `shine_users` (коэффициент
|
|
||||||
по умолчанию `START_COEF_PPM = 5x`). При недостаточном притоке регистраций вольт
|
|
||||||
истощается и выплаты останавливаются (без потери средств). Это свойство
|
|
||||||
экономической модели «очередь/билеты», а не дефект валидации аккаунтов — отмечено
|
|
||||||
для полноты (ранее L4 в аудите №2). Мониторить баланс вольта vs обязательств.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Проверено и подтверждено как корректное (по входным аккаунтам)
|
|
||||||
|
|
||||||
- **Подмена PDA** невозможна нигде: всюду пара «деривация `find_program_address` + сверка полного адреса». Пользовательский bump не принимается, `create_program_address` с внешним bump не используется — bump-seed атаки исключены.
|
|
||||||
- **Проверка владельца** при каждом чтении PDA: `read_state` и `validate_singleton_state_pda` (`shine_payments`) требуют `owner == id()`; `validate_users_economy_config_pda` и проверка `user_pda.owner == program_id` (`shine_users`) — перед десериализацией данных.
|
|
||||||
- **Создаваемые PDA**: проверка `is_uninitialized` / `owner == system && data_is_empty` исключает повторную инициализацию и перезапись чужого аккаунта.
|
|
||||||
- **signer / authority**: все handler начинают с обязательного `is_signer`; привилегированные операции дополнительно сверяют ключ с авторитетом (`config.dao_wallet`, `DAO_AUTHORITY`, `allowance.manager_wallet`, `ticket.recipient_wallet`, `device_key`).
|
|
||||||
- **system-program** сверяется с `system_program::ID` там, где идёт создание аккаунта/перевод; **sysvar инструкций** сверяется с `sysvar::instructions::id()` перед ed25519-интроспекцией.
|
|
||||||
- **Аккаунт оракула**: пин адреса `PYTH_SOL_USD_ACCOUNT` + `owner == pyth_receiver` + `feed_id` + возраст (120 с) + доверительный интервал (10%).
|
|
||||||
- **Ed25519 в `shine_users`**: относительные индексы −1/−2, `num_signatures == 1`, все три `ix_index == u16::MAX` (offset-данные внутри самой ed25519-инструкции), сверка `program_id == ed25519_program` и pubkey/signature/message по хэшу — указать на чужую инструкцию нельзя.
|
|
||||||
- **Алиасинг аккаунтов**: `recipient != inflow_vault` запрещён на входе во всех точках задания получателя + `vault.key != recipient.key` в `transfer_from_vault`.
|
|
||||||
- **`inflow_vault` в `shine_users`** сверяется с PDA, выведенным из `SHINE_PAYMENTS_PROGRAM_ID` и `SHINE_PAYMENTS_INFLOW_VAULT_SEED` — комиссия не может уйти на чужой адрес.
|
|
||||||
- **Реентранси** отсутствует: CPI только в System Program и в stateless `shine_login_guard` (с проверкой возвращённого `program_id`); обратных вызовов в наши программы нет.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Приоритет действий
|
|
||||||
|
|
||||||
1. **LOW** — ✅ выполнено 12.06.2026: добавлено `require!(it.next().is_none(), …)` во
|
|
||||||
все инструкции `shine_users` для симметрии с `shine_payments` (L2).
|
|
||||||
2. **INFO** — зафиксировать в эксплуатационной документации known-issue гонки за
|
|
||||||
логином (L3) и экономику вольта (L4); рассмотреть привязку `init` к ожидаемому
|
|
||||||
деплой-кошельку (L1).
|
|
||||||
|
|
||||||
Критичных и высоких находок по полноте проверок входных аккаунтов в этом проходе
|
|
||||||
нет. Единственная LOW-правка (L2) применена в рамках этого же изменения; код
|
|
||||||
`shine_users` собирается успешно (`cargo build -p shine_users`).
|
|
||||||
@@ -73,19 +73,7 @@ read_sol_usd_price / parse_pyth_price_update_v2 (строки 1038–1075):
|
|||||||
Проверка возраста цены (ORACLE_MAX_AGE_SECS = 120) есть и сделана корректно. Рекомендация: проверять владельца аккаунта, сверять feed_id с константой и валидировать verification_level == Full (или парсить через официальный pyth_solana_receiver_sdk, который уже завендорен в .vendor/).
|
Проверка возраста цены (ORACLE_MAX_AGE_SECS = 120) есть и сделана корректно. Рекомендация: проверять владельца аккаунта, сверять feed_id с константой и валидировать verification_level == Full (или парсить через официальный pyth_solana_receiver_sdk, который уже завендорен в .vendor/).
|
||||||
|
|
||||||
---
|
---
|
||||||
🟡 LOW — DoS через предсказуемые адреса тикетов — ✅ ИСПРАВЛЕНО (11.06.2026)
|
🟡 LOW — DoS через предсказуемые адреса тикетов
|
||||||
|
|
||||||
Закрыто: `create_pda_account` в `shine_payments` и `shine_users` переведён на паттерн
|
|
||||||
«создание поверх предзаполненного» (allocate + assign + добор ренты вместо строгого
|
|
||||||
`system_instruction::create_account`). «Подсев» лампортов на заранее известный адрес
|
|
||||||
тикета или пользовательской записи больше не блокирует создание PDA. Проверка
|
|
||||||
`is_uninitialized_account` в payments перестала зависеть от нулевого баланса. Тот же фикс
|
|
||||||
закрывает аналогичный сквоттинг логинов в `shine_users` (адрес выводится из логина).
|
|
||||||
Подробности — в `doc/programs/shine_payments.md` §3.4 и `doc/programs/shine_users.md` §3.3.
|
|
||||||
|
|
||||||
Историческое описание находки ниже.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
is_uninitialized_account (строка 1195) считает аккаунт неинициализированным только если lamports() == 0. Адреса тикетов детерминированы (queue_seed + index), а индекс последователен и предсказуем. Любой может заранее перевести немного лампортов на адрес следующего тикета — тогда create_pda_account упадёт (PdaAlreadyExists / ошибка create_account), заблокировав покупку/добавление тикета. Это griefing-DoS, не кража. Митигировать можно паттерном «create поверх предзаполненного» (allocate + assign + добор ренты) вместо system_instruction::create_account.
|
is_uninitialized_account (строка 1195) считает аккаунт неинициализированным только если lamports() == 0. Адреса тикетов детерминированы (queue_seed + index), а индекс последователен и предсказуем. Любой может заранее перевести немного лампортов на адрес следующего тикета — тогда create_pda_account упадёт (PdaAlreadyExists / ошибка create_account), заблокировав покупку/добавление тикета. Это griefing-DoS, не кража. Митигировать можно паттерном «create поверх предзаполненного» (allocate + assign + добор ренты) вместо system_instruction::create_account.
|
||||||
|
|
||||||
|
|||||||
@@ -13,56 +13,12 @@
|
|||||||
- актуальный IP должен браться через DNS-резолв на момент подключения;
|
- актуальный IP должен браться через DNS-резолв на момент подключения;
|
||||||
- ручное дублирование IP в документации и deploy-скриптах не поддерживать.
|
- ручное дублирование IP в документации и deploy-скриптах не поддерживать.
|
||||||
|
|
||||||
## Контуры деплоя
|
|
||||||
|
|
||||||
- Production:
|
|
||||||
- SSH: `player@shineup.me`
|
|
||||||
- Домен: `shineup.me`
|
|
||||||
- IP: `185.229.109.118`
|
|
||||||
- Main test:
|
|
||||||
- SSH: `player@193.8.215.70`
|
|
||||||
- Домен: `test2.shineup.me`
|
|
||||||
- IP: `193.8.215.70`
|
|
||||||
- Reserve test:
|
|
||||||
- SSH: `player@93.170.12.154`
|
|
||||||
- Домен: `test.shineup.me`
|
|
||||||
- IP: `93.170.12.154`
|
|
||||||
|
|
||||||
## Локальные команды
|
## Локальные команды
|
||||||
|
|
||||||
- Default server deploy: `./gradlew deployServer` или `./gradlew deployServerTest2`
|
- Деплой сервера: `./gradlew deployServer`
|
||||||
- Default UI deploy: `./gradlew deployUI` или `./gradlew deployUITest2`
|
- Деплой UI: `./gradlew deployUI`
|
||||||
- Production server deploy: `./gradlew deployServerProduction`
|
|
||||||
- Production UI deploy: `./gradlew deployUIProduction`
|
|
||||||
- Reserve test server deploy: `./gradlew deployServerTest`
|
|
||||||
- Reserve test UI deploy: `./gradlew deployUITest`
|
|
||||||
- Локальный запуск: `./gradlew startLocal`
|
- Локальный запуск: `./gradlew startLocal`
|
||||||
|
|
||||||
## Политика подтверждений
|
|
||||||
|
|
||||||
- `shineup.me` — production.
|
|
||||||
- Любые изменения на `shineup.me`, включая deploy сервера, deploy UI, конфиги, перезапуски и миграции, делать только после отдельного явного подтверждения пользователя.
|
|
||||||
- Если пользователь пишет просто `задеплой` без уточнения, по умолчанию это означает deploy на `test2.shineup.me`.
|
|
||||||
|
|
||||||
## Main test deploy (`test2.shineup.me`)
|
|
||||||
|
|
||||||
- Это основной сервер для тестов.
|
|
||||||
- `deployServer` и `deployUI` по умолчанию направлены именно сюда.
|
|
||||||
- Серверный deploy не запускает JUnit/IT-тесты на удалённом сервере.
|
|
||||||
- `deployServer` / `deployServerTest2` делают:
|
|
||||||
- сборку fat-jar локально;
|
|
||||||
- синхронизацию `data/` и `shine.sqlite` с production `shineup.me`;
|
|
||||||
- перенос `application.properties` с production с поправкой `server.ui.indexPath` на `/home/player/SHiNE/shine-ui/index.html`;
|
|
||||||
- установку `systemd` unit на `193.8.215.70`;
|
|
||||||
- перезапуск `shine-server.service`;
|
|
||||||
- установку/проверку Caddy для `test2.shineup.me`.
|
|
||||||
- `deployUI` / `deployUITest2` публикуют UI в `/home/player/SHiNE/shine-ui` на `193.8.215.70`.
|
|
||||||
|
|
||||||
## Reserve test deploy (`test.shineup.me`)
|
|
||||||
|
|
||||||
- `test.shineup.me` пока не использовать для обычного deploy.
|
|
||||||
- Задачи `deployServerTest` и `deployUITest` считаются резервными и требуют отдельной причины.
|
|
||||||
|
|
||||||
## UI-деплой и Caddy (обязательно)
|
## UI-деплой и Caddy (обязательно)
|
||||||
|
|
||||||
- Целевая директория UI-деплоя: `/home/player/SHiNE/shine-ui`.
|
- Целевая директория UI-деплоя: `/home/player/SHiNE/shine-ui`.
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
# Сервер `193.8.215.70` — основной test (`test2.shineup.me`)
|
|
||||||
|
|
||||||
- Пользователь: `player`
|
|
||||||
- Домен: `test2.shineup.me`
|
|
||||||
- Каталог SHiNE: `/home/player/SHiNE`
|
|
||||||
- UI публикация для Caddy: `/home/player/SHiNE/shine-ui`
|
|
||||||
- Сервер: `/home/player/SHiNE/shine-server/shine-server.jar`
|
|
||||||
- Данные: `/home/player/SHiNE/shine-server/data/`
|
|
||||||
- `shine.sqlite`
|
|
||||||
- `*.bch`
|
|
||||||
- Логи сервера: `/home/player/SHiNE/shine-server/logs/app.log`
|
|
||||||
|
|
||||||
## Сервисы
|
|
||||||
|
|
||||||
- `shine-server.service` (systemd)
|
|
||||||
- `caddy.service` (systemd)
|
|
||||||
|
|
||||||
## Статус
|
|
||||||
|
|
||||||
- Это основной сервер для тестов SHiNE.
|
|
||||||
- Default deploy по умолчанию должен идти сюда.
|
|
||||||
- Источник данных для тестовой БД: production `shineup.me`.
|
|
||||||
|
|
||||||
## Caddy
|
|
||||||
|
|
||||||
- Конфиг: `/etc/caddy/Caddyfile`
|
|
||||||
- Сайты:
|
|
||||||
- `test2.shineup.me`
|
|
||||||
- `agent.shiningpeople.ru`
|
|
||||||
- Для `test2.shineup.me`:
|
|
||||||
- `root * /home/player/SHiNE/shine-ui`
|
|
||||||
- `try_files {path} /index.html`
|
|
||||||
- `reverse_proxy /ws* -> 127.0.0.1:7070`
|
|
||||||
|
|
||||||
## Deploy
|
|
||||||
|
|
||||||
- Default server deploy:
|
|
||||||
- `./gradlew deployServer`
|
|
||||||
- `./gradlew deployServerTest2`
|
|
||||||
- Default UI deploy:
|
|
||||||
- `./gradlew deployUI`
|
|
||||||
- `./gradlew deployUITest2`
|
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
# Сервер `93.170.12.154` — test.shineup.me
|
# Сервер `93.170.12.154` — резервный
|
||||||
|
|
||||||
- Пользователь: `player`
|
- Пользователь: `player`
|
||||||
- Каталог SHiNE: `/home/player/SHiNE`
|
- Каталог SHiNE: `/home/player/SHiNE`
|
||||||
- Домен: `test.shineup.me`
|
- UI исходник (после rsync): `/home/player/SHiNE/SHiNE-UI`
|
||||||
- UI публикация для Caddy: `/home/player/SHiNE/shine-ui`
|
- UI публикация для Caddy: `/var/www/shine-ui`
|
||||||
- Сервер: `/home/player/SHiNE/shine-server/shine-server.jar`
|
- Сервер: `/home/player/SHiNE/SHiNE-server/shine-server.jar`
|
||||||
- Данные: `/home/player/SHiNE/shine-server/data/`
|
- Данные: `/home/player/SHiNE/SHiNE-server/data/`
|
||||||
- `shine.sqlite`
|
- `shine.sqlite`
|
||||||
- `*.bch`
|
- `*.bch`
|
||||||
- Логи сервера: `/home/player/SHiNE/shine-server/logs/app.log`
|
- Логи сервера: `/home/player/SHiNE/SHiNE-server/logs/app.log`
|
||||||
|
|
||||||
## Сервисы
|
## Сервисы
|
||||||
|
|
||||||
@@ -17,10 +17,8 @@
|
|||||||
|
|
||||||
## Статус
|
## Статус
|
||||||
|
|
||||||
- Резервный тестовый сервер для SHiNE.
|
- Резервный сервер для SHiNE.
|
||||||
- Источник данных для тестовой БД: production `shineup.me`.
|
- Основной прод-сервер: `shineup.me` (подключение через `player@shineup.me`, IP определяется через DNS).
|
||||||
- Пока не использовать для обычного deploy.
|
|
||||||
- Основной прод-сервер: `shineup.me` (`185.229.109.118`).
|
|
||||||
|
|
||||||
## Caddy
|
## Caddy
|
||||||
|
|
||||||
@@ -28,12 +26,4 @@
|
|||||||
- Настройки:
|
- Настройки:
|
||||||
- `no-store/no-cache` заголовки;
|
- `no-store/no-cache` заголовки;
|
||||||
- `try_files {path} /index.html` (SPA fallback);
|
- `try_files {path} /index.html` (SPA fallback);
|
||||||
- `reverse_proxy /ws* -> 127.0.0.1:7070`;
|
- `reverse_proxy /ws* -> 127.0.0.1:7070`.
|
||||||
- целевой сайт: `test.shineup.me`.
|
|
||||||
|
|
||||||
## Deploy
|
|
||||||
|
|
||||||
- Резервные задачи:
|
|
||||||
- `./gradlew deployServerTest`
|
|
||||||
- `./gradlew deployUITest`
|
|
||||||
- Эти задачи пока не использовать без отдельной причины.
|
|
||||||
|
|||||||
@@ -33,15 +33,3 @@
|
|||||||
- `https://test-solana-tickets.shineup.me`
|
- `https://test-solana-tickets.shineup.me`
|
||||||
- `https://test-solana-tickets.shiningpeople.ru`
|
- `https://test-solana-tickets.shiningpeople.ru`
|
||||||
- Для всех deploy-скриптов и инструкций использовать именно `player@shineup.me`, без жёсткой фиксации IP.
|
- Для всех deploy-скриптов и инструкций использовать именно `player@shineup.me`, без жёсткой фиксации IP.
|
||||||
|
|
||||||
## Правило изменений
|
|
||||||
|
|
||||||
- `shineup.me` — production.
|
|
||||||
- Любые изменения на этом сервере делать только после отдельного явного подтверждения пользователя.
|
|
||||||
|
|
||||||
## Deploy
|
|
||||||
|
|
||||||
- Production deploy-задачи:
|
|
||||||
- `./gradlew deployServerProduction`
|
|
||||||
- `./gradlew deployUIProduction`
|
|
||||||
- Default deploy-задачи `./gradlew deployServer` и `./gradlew deployUI` сюда больше не относятся.
|
|
||||||
|
|||||||
@@ -1,112 +0,0 @@
|
|||||||
# ESP Pairing и режимы подключения
|
|
||||||
|
|
||||||
Этот документ фиксирует текущие и новые режимы входа/подключения в SHiNE без клиентской UI-реализации. Он нужен как отдельная точка входа по сценариям подключения, чтобы не смешивать обычную авторизацию и серверный pairing через доверенное уже авторизованное устройство пользователя.
|
|
||||||
|
|
||||||
## 1. Текущие режимы
|
|
||||||
|
|
||||||
### 1. Создание новой сессии через `deviceKey`
|
|
||||||
|
|
||||||
Поток:
|
|
||||||
|
|
||||||
`AuthChallenge -> CreateAuthSession`
|
|
||||||
|
|
||||||
Смысл:
|
|
||||||
|
|
||||||
- новое устройство уже владеет приватным `deviceKey`;
|
|
||||||
- сервер проверяет подпись `deviceKey`;
|
|
||||||
- создаётся обычная активная сессия пользователя;
|
|
||||||
- этот поток остаётся без изменений.
|
|
||||||
|
|
||||||
### 2. Повторный вход в существующую сессию через `sessionKey`
|
|
||||||
|
|
||||||
Поток:
|
|
||||||
|
|
||||||
`SessionChallenge -> SessionLogin`
|
|
||||||
|
|
||||||
Смысл:
|
|
||||||
|
|
||||||
- устройство уже владеет приватным `sessionKey`;
|
|
||||||
- сервер проверяет подпись `sessionKey`;
|
|
||||||
- соединение снова входит в существующую сессию;
|
|
||||||
- этот поток тоже остаётся без изменений.
|
|
||||||
|
|
||||||
## 2. Новый режим: добавление сессии через доверенное устройство пользователя
|
|
||||||
|
|
||||||
Новый поток не заменяет обычный логин, а живёт рядом с ним.
|
|
||||||
|
|
||||||
Цель:
|
|
||||||
|
|
||||||
- новое устройство знает `login`, а `pairing password` используется только если он включён на доверённом устройстве;
|
|
||||||
- сервер использует пароль только как фильтр от мусора;
|
|
||||||
- реальное доверие даёт любая уже онлайн доверенная сессия пользователя;
|
|
||||||
- сервер не выдаёт приватные ключи сам от себя.
|
|
||||||
|
|
||||||
Поток версии `v1`:
|
|
||||||
|
|
||||||
1. Любая доверенная сессия пользователя создаёт на сервере pairing-настройку:
|
|
||||||
`UpsertEspPairingSettings`
|
|
||||||
2. Новое устройство создаёт pending-заявку:
|
|
||||||
`StartEspPairing`
|
|
||||||
3. Онлайн доверенная сессия видит список активных заявок:
|
|
||||||
`ListEspPairingRequests`
|
|
||||||
4. Доверенная сессия либо подтверждает заявку:
|
|
||||||
`ApproveEspPairing`
|
|
||||||
5. Либо отклоняет:
|
|
||||||
`RejectEspPairing`
|
|
||||||
6. Новое устройство читает результат:
|
|
||||||
`GetEspPairingStatus`
|
|
||||||
|
|
||||||
## 3. Что именно делает сервер
|
|
||||||
|
|
||||||
- хранит включённость pairing и optional `passwordHash` в формате `sha256$<hex>`;
|
|
||||||
- хранит pairing-заявки всех статусов, но в список активных для доверённого устройства отдаёт только pending `created`;
|
|
||||||
- рассчитывает короткий код `shortCode` из `10` цифр;
|
|
||||||
- рассчитывает длинный `fingerprintB58` из `SHA-256` заявки;
|
|
||||||
- уведомляет онлайн доверенные сессии событием `IncomingEspPairingRequest`, если такие сессии подключены;
|
|
||||||
- хранит переданный `encryptedPayload` как непрозрачную строку и не анализирует его содержимое.
|
|
||||||
|
|
||||||
## 4. Чего сервер в этой версии не делает
|
|
||||||
|
|
||||||
- не передаёт приватный `deviceKey`;
|
|
||||||
- не расшифровывает `encryptedPayload`;
|
|
||||||
- не проверяет криптографию содержимого payload;
|
|
||||||
- не делает клиентский UI;
|
|
||||||
- не навязывает конкретную схему `Ed25519 -> X25519` в коде сервера.
|
|
||||||
|
|
||||||
Это намеренно: серверная версия `v1` подготавливает безопасный каркас маршрутизации и состояния, а настоящая E2E-логика упаковки ключей будет жить на клиентах и ESP-устройствах.
|
|
||||||
|
|
||||||
## 5. Роли и ограничения
|
|
||||||
|
|
||||||
- любая уже авторизованная доверенная сессия пользователя может вызывать:
|
|
||||||
- `UpsertEspPairingSettings`
|
|
||||||
- `ListEspPairingRequests`
|
|
||||||
- `ApproveEspPairing`
|
|
||||||
- `RejectEspPairing`
|
|
||||||
- новое устройство может вызвать `StartEspPairing` и `GetEspPairingStatus` без уже существующей авторизованной сессии;
|
|
||||||
- `payloadType` поддерживается в вариантах:
|
|
||||||
- `1` — минимальный пакет
|
|
||||||
- `2` — расширенный пакет
|
|
||||||
- `3` — полный пакет
|
|
||||||
|
|
||||||
Сервер не интерпретирует эти три типа глубже, а только фиксирует их в состоянии заявки.
|
|
||||||
|
|
||||||
## 6. Статусы pairing-заявки
|
|
||||||
|
|
||||||
- `created` — заявка создана и ждёт решения доверенной сессии;
|
|
||||||
- `approved` — доверенная сессия подтвердила и приложила `encryptedPayload`;
|
|
||||||
- `rejected` — доверенная сессия отклонила заявку;
|
|
||||||
- `expired` — TTL заявки истёк до подтверждения.
|
|
||||||
|
|
||||||
## 7. Практический смысл
|
|
||||||
|
|
||||||
Эта схема даёт нужное разделение доверия:
|
|
||||||
|
|
||||||
- пароль на сервере, если он включён, только отсеивает лишних;
|
|
||||||
- онлайн доверенная сессия решает, добавлять ли новую сессию;
|
|
||||||
- сервер остаётся маршрутизатором и хранилищем состояния, а не владельцем секретов.
|
|
||||||
|
|
||||||
Текущий формат pairing-пароля:
|
|
||||||
|
|
||||||
```text
|
|
||||||
sha256$<hex( SHA-256("shine-pairing|" + lower(login.trim()) + "|" + password) )>
|
|
||||||
```
|
|
||||||
@@ -34,7 +34,7 @@ ls -l /dev/ttyACM0
|
|||||||
|
|
||||||
- `official-demo/` — официальный repo Waveshare (примеры+библиотеки)
|
- `official-demo/` — официальный repo Waveshare (примеры+библиотеки)
|
||||||
- `original-firmware/` — backup/restore заводской прошивки
|
- `original-firmware/` — backup/restore заводской прошивки
|
||||||
- `main-device/` — прошивки и `burn.sh`
|
- `test-device/` — прошивки и `burn.sh`
|
||||||
- `reference/` — заметки и ссылки
|
- `reference/` — заметки и ссылки
|
||||||
|
|
||||||
## 4) Бэкап перед любыми экспериментами
|
## 4) Бэкап перед любыми экспериментами
|
||||||
@@ -59,7 +59,7 @@ cd ESP32-S3-Touch-AMOLED-2.16/original-firmware
|
|||||||
Главный скрипт:
|
Главный скрипт:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ESP32-S3-Touch-AMOLED-2.16/main-device
|
cd ESP32-S3-Touch-AMOLED-2.16/test-device
|
||||||
./burn.sh <mode>
|
./burn.sh <mode>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ ls -l /dev/ttyACM0
|
|||||||
|
|
||||||
- `official-demo/` — официальный repo Waveshare (примеры+библиотеки)
|
- `official-demo/` — официальный repo Waveshare (примеры+библиотеки)
|
||||||
- `original-firmware/` — backup/restore заводской прошивки
|
- `original-firmware/` — backup/restore заводской прошивки
|
||||||
- `main-device/` — прошивки и `burn.sh`
|
- `test-device/` — прошивки и `burn.sh`
|
||||||
- `reference/` — заметки и ссылки
|
- `reference/` — заметки и ссылки
|
||||||
|
|
||||||
## 4) Бэкап перед любыми экспериментами
|
## 4) Бэкап перед любыми экспериментами
|
||||||
@@ -59,7 +59,7 @@ cd ESP32-S3-Touch-AMOLED-2.16/original-firmware
|
|||||||
Главный скрипт:
|
Главный скрипт:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ESP32-S3-Touch-AMOLED-2.16/main-device
|
cd ESP32-S3-Touch-AMOLED-2.16/test-device
|
||||||
./burn.sh <mode>
|
./burn.sh <mode>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,8 @@
|
|||||||
|
|
||||||
- `official-demo/` — официальный репозиторий примеров Waveshare
|
- `official-demo/` — официальный репозиторий примеров Waveshare
|
||||||
- `original-firmware/` — резервная копия заводской прошивки
|
- `original-firmware/` — резервная копия заводской прошивки
|
||||||
- `main-device/` — скрипты быстрой проверки устройства и основной скетч `shine_homeserver_main/`
|
- `test-device/` — скрипты быстрой проверки устройства
|
||||||
- `reference/` — локальные заметки по документации и железу
|
- `reference/` — локальные заметки по документации и железу
|
||||||
- `main-device/shine_homeserver_main/` — основной рабочий скетч ESP32-проекта `SHiNE`
|
|
||||||
|
|
||||||
Примечание по git:
|
Примечание по git:
|
||||||
|
|
||||||
@@ -21,8 +20,6 @@
|
|||||||
1. Сделать backup текущей прошивки:
|
1. Сделать backup текущей прошивки:
|
||||||
- `cd original-firmware && ./backup_factory.sh`
|
- `cd original-firmware && ./backup_factory.sh`
|
||||||
2. Залить тест экрана/тача:
|
2. Залить тест экрана/тача:
|
||||||
- `cd ../main-device && ./burn.sh widgets`
|
- `cd ../test-device && ./burn.sh widgets`
|
||||||
3. Залить тест динамика:
|
3. Залить тест динамика:
|
||||||
- `cd ../main-device && ./burn.sh audio`
|
- `cd ../test-device && ./burn.sh audio`
|
||||||
4. Залить основной UI:
|
|
||||||
- `cd ../main-device && ./burn.sh shine-homeserver-main`
|
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
# SHiNE Homeserver UI Main
|
|
||||||
|
|
||||||
Это основной рабочий скетч ESP32-проекта `SHiNE`.
|
|
||||||
|
|
||||||
Текущая каноническая точка запуска:
|
|
||||||
|
|
||||||
- `./burn.sh shine-homeserver-main`
|
|
||||||
- `./burn.sh homeserver-ui`
|
|
||||||
|
|
||||||
Историческое имя этого скетча:
|
|
||||||
|
|
||||||
- `lvgl-nav-minimal-test`
|
|
||||||
|
|
||||||
Прежние тестовые варианты для этой платы остаются в `main-device/test_sketches/` и должны восприниматься как старые диагностические сборки, а не как основной UI.
|
|
||||||
-6196
File diff suppressed because it is too large
Load Diff
@@ -1,6 +0,0 @@
|
|||||||
# SHiNE Homeserver UI Legacy
|
|
||||||
|
|
||||||
Это старый тестовый вариант UI для ESP32-платы `Waveshare ESP32-S3-Touch-AMOLED-2.16`.
|
|
||||||
|
|
||||||
Не использовать как основной скетч проекта.
|
|
||||||
Основной рабочий скетч сейчас лежит в `../shine_homeserver_main/`.
|
|
||||||
-784
@@ -1,784 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file lv_conf.h
|
|
||||||
* Configuration file for v8.4.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copy this file as `lv_conf.h`
|
|
||||||
* 1. simply next to the `lvgl` folder
|
|
||||||
* 2. or any other places and
|
|
||||||
* - define `LV_CONF_INCLUDE_SIMPLE`
|
|
||||||
* - add the path as include path
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* clang-format off */
|
|
||||||
#if 1 /*Set it to "1" to enable content*/
|
|
||||||
|
|
||||||
#ifndef LV_CONF_H
|
|
||||||
#define LV_CONF_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
/*====================
|
|
||||||
COLOR SETTINGS
|
|
||||||
*====================*/
|
|
||||||
|
|
||||||
/*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)*/
|
|
||||||
#define LV_COLOR_DEPTH 16
|
|
||||||
|
|
||||||
/*Swap the 2 bytes of RGB565 color. Useful if the display has an 8-bit interface (e.g. SPI)*/
|
|
||||||
#define LV_COLOR_16_SWAP 0
|
|
||||||
|
|
||||||
/*Enable features to draw on transparent background.
|
|
||||||
*It's required if opa, and transform_* style properties are used.
|
|
||||||
*Can be also used if the UI is above another layer, e.g. an OSD menu or video player.*/
|
|
||||||
#define LV_COLOR_SCREEN_TRANSP 0
|
|
||||||
|
|
||||||
/* Adjust color mix functions rounding. GPUs might calculate color mix (blending) differently.
|
|
||||||
* 0: round down, 64: round up from x.75, 128: round up from half, 192: round up from x.25, 254: round up */
|
|
||||||
#define LV_COLOR_MIX_ROUND_OFS 0
|
|
||||||
|
|
||||||
/*Images pixels with this color will not be drawn if they are chroma keyed)*/
|
|
||||||
#define LV_COLOR_CHROMA_KEY lv_color_hex(0x00ff00) /*pure green*/
|
|
||||||
|
|
||||||
/*=========================
|
|
||||||
MEMORY SETTINGS
|
|
||||||
*=========================*/
|
|
||||||
|
|
||||||
/*1: use custom malloc/free, 0: use the built-in `lv_mem_alloc()` and `lv_mem_free()`*/
|
|
||||||
#define LV_MEM_CUSTOM 0
|
|
||||||
#if LV_MEM_CUSTOM == 0
|
|
||||||
/*Size of the memory available for `lv_mem_alloc()` in bytes (>= 2kB)*/
|
|
||||||
#define LV_MEM_SIZE (48U * 1024U) /*[bytes]*/
|
|
||||||
|
|
||||||
/*Set an address for the memory pool instead of allocating it as a normal array. Can be in external SRAM too.*/
|
|
||||||
#define LV_MEM_ADR 0 /*0: unused*/
|
|
||||||
/*Instead of an address give a memory allocator that will be called to get a memory pool for LVGL. E.g. my_malloc*/
|
|
||||||
#if LV_MEM_ADR == 0
|
|
||||||
#undef LV_MEM_POOL_INCLUDE
|
|
||||||
#undef LV_MEM_POOL_ALLOC
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#else /*LV_MEM_CUSTOM*/
|
|
||||||
#define LV_MEM_CUSTOM_INCLUDE <stdlib.h> /*Header for the dynamic memory function*/
|
|
||||||
#define LV_MEM_CUSTOM_ALLOC malloc
|
|
||||||
#define LV_MEM_CUSTOM_FREE free
|
|
||||||
#define LV_MEM_CUSTOM_REALLOC realloc
|
|
||||||
#endif /*LV_MEM_CUSTOM*/
|
|
||||||
|
|
||||||
/*Number of the intermediate memory buffer used during rendering and other internal processing mechanisms.
|
|
||||||
*You will see an error log message if there wasn't enough buffers. */
|
|
||||||
#define LV_MEM_BUF_MAX_NUM 16
|
|
||||||
|
|
||||||
/*Use the standard `memcpy` and `memset` instead of LVGL's own functions. (Might or might not be faster).*/
|
|
||||||
#define LV_MEMCPY_MEMSET_STD 0
|
|
||||||
|
|
||||||
/*====================
|
|
||||||
HAL SETTINGS
|
|
||||||
*====================*/
|
|
||||||
|
|
||||||
/*Default display refresh period. LVG will redraw changed areas with this period time*/
|
|
||||||
#define LV_DISP_DEF_REFR_PERIOD 10 /*[ms]*/
|
|
||||||
|
|
||||||
/*Input device read period in milliseconds*/
|
|
||||||
#define LV_INDEV_DEF_READ_PERIOD 10 /*[ms]*/
|
|
||||||
|
|
||||||
/*Use a custom tick source that tells the elapsed time in milliseconds.
|
|
||||||
*It removes the need to manually update the tick with `lv_tick_inc()`)*/
|
|
||||||
#define LV_TICK_CUSTOM 0
|
|
||||||
#if LV_TICK_CUSTOM
|
|
||||||
#define LV_TICK_CUSTOM_INCLUDE "Arduino.h" /*Header for the system time function*/
|
|
||||||
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis()) /*Expression evaluating to current system time in ms*/
|
|
||||||
/*If using lvgl as ESP32 component*/
|
|
||||||
// #define LV_TICK_CUSTOM_INCLUDE "esp_timer.h"
|
|
||||||
// #define LV_TICK_CUSTOM_SYS_TIME_EXPR ((esp_timer_get_time() / 1000LL))
|
|
||||||
#endif /*LV_TICK_CUSTOM*/
|
|
||||||
|
|
||||||
/*Default Dot Per Inch. Used to initialize default sizes such as widgets sized, style paddings.
|
|
||||||
*(Not so important, you can adjust it to modify default sizes and spaces)*/
|
|
||||||
#define LV_DPI_DEF 130 /*[px/inch]*/
|
|
||||||
|
|
||||||
/*=======================
|
|
||||||
* FEATURE CONFIGURATION
|
|
||||||
*=======================*/
|
|
||||||
|
|
||||||
/*-------------
|
|
||||||
* Drawing
|
|
||||||
*-----------*/
|
|
||||||
|
|
||||||
/*Enable complex draw engine.
|
|
||||||
*Required to draw shadow, gradient, rounded corners, circles, arc, skew lines, image transformations or any masks*/
|
|
||||||
#define LV_DRAW_COMPLEX 1
|
|
||||||
#if LV_DRAW_COMPLEX != 0
|
|
||||||
|
|
||||||
/*Allow buffering some shadow calculation.
|
|
||||||
*LV_SHADOW_CACHE_SIZE is the max. shadow size to buffer, where shadow size is `shadow_width + radius`
|
|
||||||
*Caching has LV_SHADOW_CACHE_SIZE^2 RAM cost*/
|
|
||||||
#define LV_SHADOW_CACHE_SIZE 0
|
|
||||||
|
|
||||||
/* Set number of maximally cached circle data.
|
|
||||||
* The circumference of 1/4 circle are saved for anti-aliasing
|
|
||||||
* radius * 4 bytes are used per circle (the most often used radiuses are saved)
|
|
||||||
* 0: to disable caching */
|
|
||||||
#define LV_CIRCLE_CACHE_SIZE 4
|
|
||||||
#endif /*LV_DRAW_COMPLEX*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* "Simple layers" are used when a widget has `style_opa < 255` to buffer the widget into a layer
|
|
||||||
* and blend it as an image with the given opacity.
|
|
||||||
* Note that `bg_opa`, `text_opa` etc don't require buffering into layer)
|
|
||||||
* The widget can be buffered in smaller chunks to avoid using large buffers.
|
|
||||||
*
|
|
||||||
* - LV_LAYER_SIMPLE_BUF_SIZE: [bytes] the optimal target buffer size. LVGL will try to allocate it
|
|
||||||
* - LV_LAYER_SIMPLE_FALLBACK_BUF_SIZE: [bytes] used if `LV_LAYER_SIMPLE_BUF_SIZE` couldn't be allocated.
|
|
||||||
*
|
|
||||||
* Both buffer sizes are in bytes.
|
|
||||||
* "Transformed layers" (where transform_angle/zoom properties are used) use larger buffers
|
|
||||||
* and can't be drawn in chunks. So these settings affects only widgets with opacity.
|
|
||||||
*/
|
|
||||||
#define LV_LAYER_SIMPLE_BUF_SIZE (24 * 1024)
|
|
||||||
#define LV_LAYER_SIMPLE_FALLBACK_BUF_SIZE (3 * 1024)
|
|
||||||
|
|
||||||
/*Default image cache size. Image caching keeps the images opened.
|
|
||||||
*If only the built-in image formats are used there is no real advantage of caching. (I.e. if no new image decoder is added)
|
|
||||||
*With complex image decoders (e.g. PNG or JPG) caching can save the continuous open/decode of images.
|
|
||||||
*However the opened images might consume additional RAM.
|
|
||||||
*0: to disable caching*/
|
|
||||||
#define LV_IMG_CACHE_DEF_SIZE 0
|
|
||||||
|
|
||||||
/*Number of stops allowed per gradient. Increase this to allow more stops.
|
|
||||||
*This adds (sizeof(lv_color_t) + 1) bytes per additional stop*/
|
|
||||||
#define LV_GRADIENT_MAX_STOPS 2
|
|
||||||
|
|
||||||
/*Default gradient buffer size.
|
|
||||||
*When LVGL calculates the gradient "maps" it can save them into a cache to avoid calculating them again.
|
|
||||||
*LV_GRAD_CACHE_DEF_SIZE sets the size of this cache in bytes.
|
|
||||||
*If the cache is too small the map will be allocated only while it's required for the drawing.
|
|
||||||
*0 mean no caching.*/
|
|
||||||
#define LV_GRAD_CACHE_DEF_SIZE 0
|
|
||||||
|
|
||||||
/*Allow dithering the gradients (to achieve visual smooth color gradients on limited color depth display)
|
|
||||||
*LV_DITHER_GRADIENT implies allocating one or two more lines of the object's rendering surface
|
|
||||||
*The increase in memory consumption is (32 bits * object width) plus 24 bits * object width if using error diffusion */
|
|
||||||
#define LV_DITHER_GRADIENT 0
|
|
||||||
#if LV_DITHER_GRADIENT
|
|
||||||
/*Add support for error diffusion dithering.
|
|
||||||
*Error diffusion dithering gets a much better visual result, but implies more CPU consumption and memory when drawing.
|
|
||||||
*The increase in memory consumption is (24 bits * object's width)*/
|
|
||||||
#define LV_DITHER_ERROR_DIFFUSION 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*Maximum buffer size to allocate for rotation.
|
|
||||||
*Only used if software rotation is enabled in the display driver.*/
|
|
||||||
#define LV_DISP_ROT_MAX_BUF (10*1024)
|
|
||||||
|
|
||||||
/*-------------
|
|
||||||
* GPU
|
|
||||||
*-----------*/
|
|
||||||
|
|
||||||
/*Use Arm's 2D acceleration library Arm-2D */
|
|
||||||
#define LV_USE_GPU_ARM2D 0
|
|
||||||
|
|
||||||
/*Use STM32's DMA2D (aka Chrom Art) GPU*/
|
|
||||||
#define LV_USE_GPU_STM32_DMA2D 0
|
|
||||||
#if LV_USE_GPU_STM32_DMA2D
|
|
||||||
/*Must be defined to include path of CMSIS header of target processor
|
|
||||||
e.g. "stm32f7xx.h" or "stm32f4xx.h"*/
|
|
||||||
#define LV_GPU_DMA2D_CMSIS_INCLUDE
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*Enable RA6M3 G2D GPU*/
|
|
||||||
#define LV_USE_GPU_RA6M3_G2D 0
|
|
||||||
#if LV_USE_GPU_RA6M3_G2D
|
|
||||||
/*include path of target processor
|
|
||||||
e.g. "hal_data.h"*/
|
|
||||||
#define LV_GPU_RA6M3_G2D_INCLUDE "hal_data.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*Use SWM341's DMA2D GPU*/
|
|
||||||
#define LV_USE_GPU_SWM341_DMA2D 0
|
|
||||||
#if LV_USE_GPU_SWM341_DMA2D
|
|
||||||
#define LV_GPU_SWM341_DMA2D_INCLUDE "SWM341.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*Use NXP's PXP GPU iMX RTxxx platforms*/
|
|
||||||
#define LV_USE_GPU_NXP_PXP 0
|
|
||||||
#if LV_USE_GPU_NXP_PXP
|
|
||||||
/*1: Add default bare metal and FreeRTOS interrupt handling routines for PXP (lv_gpu_nxp_pxp_osa.c)
|
|
||||||
* and call lv_gpu_nxp_pxp_init() automatically during lv_init(). Note that symbol SDK_OS_FREE_RTOS
|
|
||||||
* has to be defined in order to use FreeRTOS OSA, otherwise bare-metal implementation is selected.
|
|
||||||
*0: lv_gpu_nxp_pxp_init() has to be called manually before lv_init()
|
|
||||||
*/
|
|
||||||
#define LV_USE_GPU_NXP_PXP_AUTO_INIT 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*Use NXP's VG-Lite GPU iMX RTxxx platforms*/
|
|
||||||
#define LV_USE_GPU_NXP_VG_LITE 0
|
|
||||||
|
|
||||||
/*Use SDL renderer API*/
|
|
||||||
#define LV_USE_GPU_SDL 0
|
|
||||||
#if LV_USE_GPU_SDL
|
|
||||||
#define LV_GPU_SDL_INCLUDE_PATH <SDL2/SDL.h>
|
|
||||||
/*Texture cache size, 8MB by default*/
|
|
||||||
#define LV_GPU_SDL_LRU_SIZE (1024 * 1024 * 8)
|
|
||||||
/*Custom blend mode for mask drawing, disable if you need to link with older SDL2 lib*/
|
|
||||||
#define LV_GPU_SDL_CUSTOM_BLEND_MODE (SDL_VERSION_ATLEAST(2, 0, 6))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*-------------
|
|
||||||
* Logging
|
|
||||||
*-----------*/
|
|
||||||
|
|
||||||
/*Enable the log module*/
|
|
||||||
#define LV_USE_LOG 0
|
|
||||||
#if LV_USE_LOG
|
|
||||||
|
|
||||||
/*How important log should be added:
|
|
||||||
*LV_LOG_LEVEL_TRACE A lot of logs to give detailed information
|
|
||||||
*LV_LOG_LEVEL_INFO Log important events
|
|
||||||
*LV_LOG_LEVEL_WARN Log if something unwanted happened but didn't cause a problem
|
|
||||||
*LV_LOG_LEVEL_ERROR Only critical issue, when the system may fail
|
|
||||||
*LV_LOG_LEVEL_USER Only logs added by the user
|
|
||||||
*LV_LOG_LEVEL_NONE Do not log anything*/
|
|
||||||
#define LV_LOG_LEVEL LV_LOG_LEVEL_WARN
|
|
||||||
|
|
||||||
/*1: Print the log with 'printf';
|
|
||||||
*0: User need to register a callback with `lv_log_register_print_cb()`*/
|
|
||||||
#define LV_LOG_PRINTF 0
|
|
||||||
|
|
||||||
/*Enable/disable LV_LOG_TRACE in modules that produces a huge number of logs*/
|
|
||||||
#define LV_LOG_TRACE_MEM 1
|
|
||||||
#define LV_LOG_TRACE_TIMER 1
|
|
||||||
#define LV_LOG_TRACE_INDEV 1
|
|
||||||
#define LV_LOG_TRACE_DISP_REFR 1
|
|
||||||
#define LV_LOG_TRACE_EVENT 1
|
|
||||||
#define LV_LOG_TRACE_OBJ_CREATE 1
|
|
||||||
#define LV_LOG_TRACE_LAYOUT 1
|
|
||||||
#define LV_LOG_TRACE_ANIM 1
|
|
||||||
|
|
||||||
#endif /*LV_USE_LOG*/
|
|
||||||
|
|
||||||
/*-------------
|
|
||||||
* Asserts
|
|
||||||
*-----------*/
|
|
||||||
|
|
||||||
/*Enable asserts if an operation is failed or an invalid data is found.
|
|
||||||
*If LV_USE_LOG is enabled an error message will be printed on failure*/
|
|
||||||
#define LV_USE_ASSERT_NULL 1 /*Check if the parameter is NULL. (Very fast, recommended)*/
|
|
||||||
#define LV_USE_ASSERT_MALLOC 1 /*Checks is the memory is successfully allocated or no. (Very fast, recommended)*/
|
|
||||||
#define LV_USE_ASSERT_STYLE 0 /*Check if the styles are properly initialized. (Very fast, recommended)*/
|
|
||||||
#define LV_USE_ASSERT_MEM_INTEGRITY 0 /*Check the integrity of `lv_mem` after critical operations. (Slow)*/
|
|
||||||
#define LV_USE_ASSERT_OBJ 0 /*Check the object's type and existence (e.g. not deleted). (Slow)*/
|
|
||||||
|
|
||||||
/*Add a custom handler when assert happens e.g. to restart the MCU*/
|
|
||||||
#define LV_ASSERT_HANDLER_INCLUDE <stdint.h>
|
|
||||||
#define LV_ASSERT_HANDLER while(1); /*Halt by default*/
|
|
||||||
|
|
||||||
/*-------------
|
|
||||||
* Others
|
|
||||||
*-----------*/
|
|
||||||
|
|
||||||
/*1: Show CPU usage and FPS count*/
|
|
||||||
#define LV_USE_PERF_MONITOR 0
|
|
||||||
#if LV_USE_PERF_MONITOR
|
|
||||||
#define LV_USE_PERF_MONITOR_POS LV_ALIGN_BOTTOM_RIGHT
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*1: Show the used memory and the memory fragmentation
|
|
||||||
* Requires LV_MEM_CUSTOM = 0*/
|
|
||||||
#define LV_USE_MEM_MONITOR 0
|
|
||||||
#if LV_USE_MEM_MONITOR
|
|
||||||
#define LV_USE_MEM_MONITOR_POS LV_ALIGN_BOTTOM_LEFT
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*1: Draw random colored rectangles over the redrawn areas*/
|
|
||||||
#define LV_USE_REFR_DEBUG 0
|
|
||||||
|
|
||||||
/*Change the built in (v)snprintf functions*/
|
|
||||||
#define LV_SPRINTF_CUSTOM 0
|
|
||||||
#if LV_SPRINTF_CUSTOM
|
|
||||||
#define LV_SPRINTF_INCLUDE <stdio.h>
|
|
||||||
#define lv_snprintf snprintf
|
|
||||||
#define lv_vsnprintf vsnprintf
|
|
||||||
#else /*LV_SPRINTF_CUSTOM*/
|
|
||||||
#define LV_SPRINTF_USE_FLOAT 0
|
|
||||||
#endif /*LV_SPRINTF_CUSTOM*/
|
|
||||||
|
|
||||||
#define LV_USE_USER_DATA 1
|
|
||||||
|
|
||||||
/*Garbage Collector settings
|
|
||||||
*Used if lvgl is bound to higher level language and the memory is managed by that language*/
|
|
||||||
#define LV_ENABLE_GC 0
|
|
||||||
#if LV_ENABLE_GC != 0
|
|
||||||
#define LV_GC_INCLUDE "gc.h" /*Include Garbage Collector related things*/
|
|
||||||
#endif /*LV_ENABLE_GC*/
|
|
||||||
|
|
||||||
/*=====================
|
|
||||||
* COMPILER SETTINGS
|
|
||||||
*====================*/
|
|
||||||
|
|
||||||
/*For big endian systems set to 1*/
|
|
||||||
#define LV_BIG_ENDIAN_SYSTEM 0
|
|
||||||
|
|
||||||
/*Define a custom attribute to `lv_tick_inc` function*/
|
|
||||||
#define LV_ATTRIBUTE_TICK_INC
|
|
||||||
|
|
||||||
/*Define a custom attribute to `lv_timer_handler` function*/
|
|
||||||
#define LV_ATTRIBUTE_TIMER_HANDLER
|
|
||||||
|
|
||||||
/*Define a custom attribute to `lv_disp_flush_ready` function*/
|
|
||||||
#define LV_ATTRIBUTE_FLUSH_READY
|
|
||||||
|
|
||||||
/*Required alignment size for buffers*/
|
|
||||||
#define LV_ATTRIBUTE_MEM_ALIGN_SIZE 1
|
|
||||||
|
|
||||||
/*Will be added where memories needs to be aligned (with -Os data might not be aligned to boundary by default).
|
|
||||||
* E.g. __attribute__((aligned(4)))*/
|
|
||||||
#define LV_ATTRIBUTE_MEM_ALIGN
|
|
||||||
|
|
||||||
/*Attribute to mark large constant arrays for example font's bitmaps*/
|
|
||||||
#define LV_ATTRIBUTE_LARGE_CONST
|
|
||||||
|
|
||||||
/*Compiler prefix for a big array declaration in RAM*/
|
|
||||||
#define LV_ATTRIBUTE_LARGE_RAM_ARRAY
|
|
||||||
|
|
||||||
/*Place performance critical functions into a faster memory (e.g RAM)*/
|
|
||||||
#define LV_ATTRIBUTE_FAST_MEM
|
|
||||||
|
|
||||||
/*Prefix variables that are used in GPU accelerated operations, often these need to be placed in RAM sections that are DMA accessible*/
|
|
||||||
#define LV_ATTRIBUTE_DMA
|
|
||||||
|
|
||||||
/*Export integer constant to binding. This macro is used with constants in the form of LV_<CONST> that
|
|
||||||
*should also appear on LVGL binding API such as Micropython.*/
|
|
||||||
#define LV_EXPORT_CONST_INT(int_value) struct _silence_gcc_warning /*The default value just prevents GCC warning*/
|
|
||||||
|
|
||||||
/*Extend the default -32k..32k coordinate range to -4M..4M by using int32_t for coordinates instead of int16_t*/
|
|
||||||
#define LV_USE_LARGE_COORD 0
|
|
||||||
|
|
||||||
/*==================
|
|
||||||
* FONT USAGE
|
|
||||||
*===================*/
|
|
||||||
|
|
||||||
/*Montserrat fonts with ASCII range and some symbols using bpp = 4
|
|
||||||
*https://fonts.google.com/specimen/Montserrat*/
|
|
||||||
#define LV_FONT_MONTSERRAT_8 1
|
|
||||||
#define LV_FONT_MONTSERRAT_10 1
|
|
||||||
#define LV_FONT_MONTSERRAT_12 1
|
|
||||||
#define LV_FONT_MONTSERRAT_14 1
|
|
||||||
#define LV_FONT_MONTSERRAT_16 1
|
|
||||||
#define LV_FONT_MONTSERRAT_18 1
|
|
||||||
#define LV_FONT_MONTSERRAT_20 1
|
|
||||||
#define LV_FONT_MONTSERRAT_22 1
|
|
||||||
#define LV_FONT_MONTSERRAT_24 1
|
|
||||||
#define LV_FONT_MONTSERRAT_26 1
|
|
||||||
#define LV_FONT_MONTSERRAT_28 1
|
|
||||||
#define LV_FONT_MONTSERRAT_30 1
|
|
||||||
#define LV_FONT_MONTSERRAT_32 1
|
|
||||||
#define LV_FONT_MONTSERRAT_34 1
|
|
||||||
#define LV_FONT_MONTSERRAT_36 1
|
|
||||||
#define LV_FONT_MONTSERRAT_38 1
|
|
||||||
#define LV_FONT_MONTSERRAT_40 1
|
|
||||||
#define LV_FONT_MONTSERRAT_42 1
|
|
||||||
#define LV_FONT_MONTSERRAT_44 1
|
|
||||||
#define LV_FONT_MONTSERRAT_46 1
|
|
||||||
#define LV_FONT_MONTSERRAT_48 1
|
|
||||||
|
|
||||||
/*Demonstrate special features*/
|
|
||||||
#define LV_FONT_MONTSERRAT_12_SUBPX 0
|
|
||||||
#define LV_FONT_MONTSERRAT_28_COMPRESSED 0 /*bpp = 3*/
|
|
||||||
#define LV_FONT_DEJAVU_16_PERSIAN_HEBREW 0 /*Hebrew, Arabic, Persian letters and all their forms*/
|
|
||||||
#define LV_FONT_SIMSUN_16_CJK 0 /*1000 most common CJK radicals*/
|
|
||||||
|
|
||||||
/*Pixel perfect monospace fonts*/
|
|
||||||
#define LV_FONT_UNSCII_8 0
|
|
||||||
#define LV_FONT_UNSCII_16 0
|
|
||||||
|
|
||||||
/*Optionally declare custom fonts here.
|
|
||||||
*You can use these fonts as default font too and they will be available globally.
|
|
||||||
*E.g. #define LV_FONT_CUSTOM_DECLARE LV_FONT_DECLARE(my_font_1) LV_FONT_DECLARE(my_font_2)*/
|
|
||||||
#define LV_FONT_CUSTOM_DECLARE
|
|
||||||
|
|
||||||
/*Always set a default font*/
|
|
||||||
#define LV_FONT_DEFAULT &lv_font_montserrat_14
|
|
||||||
|
|
||||||
/*Enable handling large font and/or fonts with a lot of characters.
|
|
||||||
*The limit depends on the font size, font face and bpp.
|
|
||||||
*Compiler error will be triggered if a font needs it.*/
|
|
||||||
#define LV_FONT_FMT_TXT_LARGE 0
|
|
||||||
|
|
||||||
/*Enables/disables support for compressed fonts.*/
|
|
||||||
#define LV_USE_FONT_COMPRESSED 0
|
|
||||||
|
|
||||||
/*Enable subpixel rendering*/
|
|
||||||
#define LV_USE_FONT_SUBPX 0
|
|
||||||
#if LV_USE_FONT_SUBPX
|
|
||||||
/*Set the pixel order of the display. Physical order of RGB channels. Doesn't matter with "normal" fonts.*/
|
|
||||||
#define LV_FONT_SUBPX_BGR 0 /*0: RGB; 1:BGR order*/
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*Enable drawing placeholders when glyph dsc is not found*/
|
|
||||||
#define LV_USE_FONT_PLACEHOLDER 1
|
|
||||||
|
|
||||||
/*=================
|
|
||||||
* TEXT SETTINGS
|
|
||||||
*=================*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Select a character encoding for strings.
|
|
||||||
* Your IDE or editor should have the same character encoding
|
|
||||||
* - LV_TXT_ENC_UTF8
|
|
||||||
* - LV_TXT_ENC_ASCII
|
|
||||||
*/
|
|
||||||
#define LV_TXT_ENC LV_TXT_ENC_UTF8
|
|
||||||
|
|
||||||
/*Can break (wrap) texts on these chars*/
|
|
||||||
#define LV_TXT_BREAK_CHARS " ,.;:-_"
|
|
||||||
|
|
||||||
/*If a word is at least this long, will break wherever "prettiest"
|
|
||||||
*To disable, set to a value <= 0*/
|
|
||||||
#define LV_TXT_LINE_BREAK_LONG_LEN 0
|
|
||||||
|
|
||||||
/*Minimum number of characters in a long word to put on a line before a break.
|
|
||||||
*Depends on LV_TXT_LINE_BREAK_LONG_LEN.*/
|
|
||||||
#define LV_TXT_LINE_BREAK_LONG_PRE_MIN_LEN 3
|
|
||||||
|
|
||||||
/*Minimum number of characters in a long word to put on a line after a break.
|
|
||||||
*Depends on LV_TXT_LINE_BREAK_LONG_LEN.*/
|
|
||||||
#define LV_TXT_LINE_BREAK_LONG_POST_MIN_LEN 3
|
|
||||||
|
|
||||||
/*The control character to use for signalling text recoloring.*/
|
|
||||||
#define LV_TXT_COLOR_CMD "#"
|
|
||||||
|
|
||||||
/*Support bidirectional texts. Allows mixing Left-to-Right and Right-to-Left texts.
|
|
||||||
*The direction will be processed according to the Unicode Bidirectional Algorithm:
|
|
||||||
*https://www.w3.org/International/articles/inline-bidi-markup/uba-basics*/
|
|
||||||
#define LV_USE_BIDI 0
|
|
||||||
#if LV_USE_BIDI
|
|
||||||
/*Set the default direction. Supported values:
|
|
||||||
*`LV_BASE_DIR_LTR` Left-to-Right
|
|
||||||
*`LV_BASE_DIR_RTL` Right-to-Left
|
|
||||||
*`LV_BASE_DIR_AUTO` detect texts base direction*/
|
|
||||||
#define LV_BIDI_BASE_DIR_DEF LV_BASE_DIR_AUTO
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*Enable Arabic/Persian processing
|
|
||||||
*In these languages characters should be replaced with an other form based on their position in the text*/
|
|
||||||
#define LV_USE_ARABIC_PERSIAN_CHARS 0
|
|
||||||
|
|
||||||
/*==================
|
|
||||||
* WIDGET USAGE
|
|
||||||
*================*/
|
|
||||||
|
|
||||||
/*Documentation of the widgets: https://docs.lvgl.io/latest/en/html/widgets/index.html*/
|
|
||||||
|
|
||||||
#define LV_USE_ARC 1
|
|
||||||
|
|
||||||
#define LV_USE_BAR 1
|
|
||||||
|
|
||||||
#define LV_USE_BTN 1
|
|
||||||
|
|
||||||
#define LV_USE_BTNMATRIX 1
|
|
||||||
|
|
||||||
#define LV_USE_CANVAS 1
|
|
||||||
|
|
||||||
#define LV_USE_CHECKBOX 1
|
|
||||||
|
|
||||||
#define LV_USE_DROPDOWN 1 /*Requires: lv_label*/
|
|
||||||
|
|
||||||
#define LV_USE_IMG 1 /*Requires: lv_label*/
|
|
||||||
|
|
||||||
#define LV_USE_LABEL 1
|
|
||||||
#if LV_USE_LABEL
|
|
||||||
#define LV_LABEL_TEXT_SELECTION 1 /*Enable selecting text of the label*/
|
|
||||||
#define LV_LABEL_LONG_TXT_HINT 1 /*Store some extra info in labels to speed up drawing of very long texts*/
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define LV_USE_LINE 1
|
|
||||||
|
|
||||||
#define LV_USE_ROLLER 1 /*Requires: lv_label*/
|
|
||||||
#if LV_USE_ROLLER
|
|
||||||
#define LV_ROLLER_INF_PAGES 7 /*Number of extra "pages" when the roller is infinite*/
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define LV_USE_SLIDER 1 /*Requires: lv_bar*/
|
|
||||||
|
|
||||||
#define LV_USE_SWITCH 1
|
|
||||||
|
|
||||||
#define LV_USE_TEXTAREA 1 /*Requires: lv_label*/
|
|
||||||
#if LV_USE_TEXTAREA != 0
|
|
||||||
#define LV_TEXTAREA_DEF_PWD_SHOW_TIME 1500 /*ms*/
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define LV_USE_TABLE 1
|
|
||||||
|
|
||||||
/*==================
|
|
||||||
* EXTRA COMPONENTS
|
|
||||||
*==================*/
|
|
||||||
|
|
||||||
/*-----------
|
|
||||||
* Widgets
|
|
||||||
*----------*/
|
|
||||||
#define LV_USE_ANIMIMG 1
|
|
||||||
|
|
||||||
#define LV_USE_CALENDAR 1
|
|
||||||
#if LV_USE_CALENDAR
|
|
||||||
#define LV_CALENDAR_WEEK_STARTS_MONDAY 0
|
|
||||||
#if LV_CALENDAR_WEEK_STARTS_MONDAY
|
|
||||||
#define LV_CALENDAR_DEFAULT_DAY_NAMES {"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"}
|
|
||||||
#else
|
|
||||||
#define LV_CALENDAR_DEFAULT_DAY_NAMES {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define LV_CALENDAR_DEFAULT_MONTH_NAMES {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}
|
|
||||||
#define LV_USE_CALENDAR_HEADER_ARROW 1
|
|
||||||
#define LV_USE_CALENDAR_HEADER_DROPDOWN 1
|
|
||||||
#endif /*LV_USE_CALENDAR*/
|
|
||||||
|
|
||||||
#define LV_USE_CHART 1
|
|
||||||
|
|
||||||
#define LV_USE_COLORWHEEL 1
|
|
||||||
|
|
||||||
#define LV_USE_IMGBTN 1
|
|
||||||
|
|
||||||
#define LV_USE_KEYBOARD 1
|
|
||||||
|
|
||||||
#define LV_USE_LED 1
|
|
||||||
|
|
||||||
#define LV_USE_LIST 1
|
|
||||||
|
|
||||||
#define LV_USE_MENU 1
|
|
||||||
|
|
||||||
#define LV_USE_METER 1
|
|
||||||
|
|
||||||
#define LV_USE_MSGBOX 1
|
|
||||||
|
|
||||||
#define LV_USE_SPAN 1
|
|
||||||
#if LV_USE_SPAN
|
|
||||||
/*A line text can contain maximum num of span descriptor */
|
|
||||||
#define LV_SPAN_SNIPPET_STACK_SIZE 64
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define LV_USE_SPINBOX 1
|
|
||||||
|
|
||||||
#define LV_USE_SPINNER 1
|
|
||||||
|
|
||||||
#define LV_USE_TABVIEW 1
|
|
||||||
|
|
||||||
#define LV_USE_TILEVIEW 1
|
|
||||||
|
|
||||||
#define LV_USE_WIN 1
|
|
||||||
|
|
||||||
/*-----------
|
|
||||||
* Themes
|
|
||||||
*----------*/
|
|
||||||
|
|
||||||
/*A simple, impressive and very complete theme*/
|
|
||||||
#define LV_USE_THEME_DEFAULT 1
|
|
||||||
#if LV_USE_THEME_DEFAULT
|
|
||||||
|
|
||||||
/*0: Light mode; 1: Dark mode*/
|
|
||||||
#define LV_THEME_DEFAULT_DARK 0
|
|
||||||
|
|
||||||
/*1: Enable grow on press*/
|
|
||||||
#define LV_THEME_DEFAULT_GROW 1
|
|
||||||
|
|
||||||
/*Default transition time in [ms]*/
|
|
||||||
#define LV_THEME_DEFAULT_TRANSITION_TIME 80
|
|
||||||
#endif /*LV_USE_THEME_DEFAULT*/
|
|
||||||
|
|
||||||
/*A very simple theme that is a good starting point for a custom theme*/
|
|
||||||
#define LV_USE_THEME_BASIC 1
|
|
||||||
|
|
||||||
/*A theme designed for monochrome displays*/
|
|
||||||
#define LV_USE_THEME_MONO 1
|
|
||||||
|
|
||||||
/*-----------
|
|
||||||
* Layouts
|
|
||||||
*----------*/
|
|
||||||
|
|
||||||
/*A layout similar to Flexbox in CSS.*/
|
|
||||||
#define LV_USE_FLEX 1
|
|
||||||
|
|
||||||
/*A layout similar to Grid in CSS.*/
|
|
||||||
#define LV_USE_GRID 1
|
|
||||||
|
|
||||||
/*---------------------
|
|
||||||
* 3rd party libraries
|
|
||||||
*--------------------*/
|
|
||||||
|
|
||||||
/*File system interfaces for common APIs */
|
|
||||||
|
|
||||||
/*API for fopen, fread, etc*/
|
|
||||||
#define LV_USE_FS_STDIO 0
|
|
||||||
#if LV_USE_FS_STDIO
|
|
||||||
#define LV_FS_STDIO_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
|
|
||||||
#define LV_FS_STDIO_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/
|
|
||||||
#define LV_FS_STDIO_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*API for open, read, etc*/
|
|
||||||
#define LV_USE_FS_POSIX 0
|
|
||||||
#if LV_USE_FS_POSIX
|
|
||||||
#define LV_FS_POSIX_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
|
|
||||||
#define LV_FS_POSIX_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/
|
|
||||||
#define LV_FS_POSIX_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*API for CreateFile, ReadFile, etc*/
|
|
||||||
#define LV_USE_FS_WIN32 0
|
|
||||||
#if LV_USE_FS_WIN32
|
|
||||||
#define LV_FS_WIN32_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
|
|
||||||
#define LV_FS_WIN32_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/
|
|
||||||
#define LV_FS_WIN32_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*API for FATFS (needs to be added separately). Uses f_open, f_read, etc*/
|
|
||||||
#define LV_USE_FS_FATFS 0
|
|
||||||
#if LV_USE_FS_FATFS
|
|
||||||
#define LV_FS_FATFS_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
|
|
||||||
#define LV_FS_FATFS_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*API for LittleFS (library needs to be added separately). Uses lfs_file_open, lfs_file_read, etc*/
|
|
||||||
#define LV_USE_FS_LITTLEFS 0
|
|
||||||
#if LV_USE_FS_LITTLEFS
|
|
||||||
#define LV_FS_LITTLEFS_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
|
|
||||||
#define LV_FS_LITTLEFS_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*PNG decoder library*/
|
|
||||||
#define LV_USE_PNG 0
|
|
||||||
|
|
||||||
/*BMP decoder library*/
|
|
||||||
#define LV_USE_BMP 0
|
|
||||||
|
|
||||||
/* JPG + split JPG decoder library.
|
|
||||||
* Split JPG is a custom format optimized for embedded systems. */
|
|
||||||
#define LV_USE_SJPG 0
|
|
||||||
|
|
||||||
/*GIF decoder library*/
|
|
||||||
#define LV_USE_GIF 0
|
|
||||||
|
|
||||||
/*QR code library*/
|
|
||||||
#define LV_USE_QRCODE 1
|
|
||||||
|
|
||||||
/*FreeType library*/
|
|
||||||
#define LV_USE_FREETYPE 0
|
|
||||||
#if LV_USE_FREETYPE
|
|
||||||
/*Memory used by FreeType to cache characters [bytes] (-1: no caching)*/
|
|
||||||
#define LV_FREETYPE_CACHE_SIZE (16 * 1024)
|
|
||||||
#if LV_FREETYPE_CACHE_SIZE >= 0
|
|
||||||
/* 1: bitmap cache use the sbit cache, 0:bitmap cache use the image cache. */
|
|
||||||
/* sbit cache:it is much more memory efficient for small bitmaps(font size < 256) */
|
|
||||||
/* if font size >= 256, must be configured as image cache */
|
|
||||||
#define LV_FREETYPE_SBIT_CACHE 0
|
|
||||||
/* Maximum number of opened FT_Face/FT_Size objects managed by this cache instance. */
|
|
||||||
/* (0:use system defaults) */
|
|
||||||
#define LV_FREETYPE_CACHE_FT_FACES 0
|
|
||||||
#define LV_FREETYPE_CACHE_FT_SIZES 0
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*Tiny TTF library*/
|
|
||||||
#define LV_USE_TINY_TTF 0
|
|
||||||
#if LV_USE_TINY_TTF
|
|
||||||
/*Load TTF data from files*/
|
|
||||||
#define LV_TINY_TTF_FILE_SUPPORT 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*Rlottie library*/
|
|
||||||
#define LV_USE_RLOTTIE 0
|
|
||||||
|
|
||||||
/*FFmpeg library for image decoding and playing videos
|
|
||||||
*Supports all major image formats so do not enable other image decoder with it*/
|
|
||||||
#define LV_USE_FFMPEG 0
|
|
||||||
#if LV_USE_FFMPEG
|
|
||||||
/*Dump input information to stderr*/
|
|
||||||
#define LV_FFMPEG_DUMP_FORMAT 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*-----------
|
|
||||||
* Others
|
|
||||||
*----------*/
|
|
||||||
|
|
||||||
/*1: Enable API to take snapshot for object*/
|
|
||||||
#define LV_USE_SNAPSHOT 0
|
|
||||||
|
|
||||||
/*1: Enable Monkey test*/
|
|
||||||
#define LV_USE_MONKEY 0
|
|
||||||
|
|
||||||
/*1: Enable grid navigation*/
|
|
||||||
#define LV_USE_GRIDNAV 0
|
|
||||||
|
|
||||||
/*1: Enable lv_obj fragment*/
|
|
||||||
#define LV_USE_FRAGMENT 0
|
|
||||||
|
|
||||||
/*1: Support using images as font in label or span widgets */
|
|
||||||
#define LV_USE_IMGFONT 0
|
|
||||||
|
|
||||||
/*1: Enable a published subscriber based messaging system */
|
|
||||||
#define LV_USE_MSG 0
|
|
||||||
|
|
||||||
/*1: Enable Pinyin input method*/
|
|
||||||
/*Requires: lv_keyboard*/
|
|
||||||
#define LV_USE_IME_PINYIN 0
|
|
||||||
#if LV_USE_IME_PINYIN
|
|
||||||
/*1: Use default thesaurus*/
|
|
||||||
/*If you do not use the default thesaurus, be sure to use `lv_ime_pinyin` after setting the thesauruss*/
|
|
||||||
#define LV_IME_PINYIN_USE_DEFAULT_DICT 1
|
|
||||||
/*Set the maximum number of candidate panels that can be displayed*/
|
|
||||||
/*This needs to be adjusted according to the size of the screen*/
|
|
||||||
#define LV_IME_PINYIN_CAND_TEXT_NUM 6
|
|
||||||
|
|
||||||
/*Use 9 key input(k9)*/
|
|
||||||
#define LV_IME_PINYIN_USE_K9_MODE 1
|
|
||||||
#if LV_IME_PINYIN_USE_K9_MODE == 1
|
|
||||||
#define LV_IME_PINYIN_K9_CAND_TEXT_NUM 3
|
|
||||||
#endif // LV_IME_PINYIN_USE_K9_MODE
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*==================
|
|
||||||
* EXAMPLES
|
|
||||||
*==================*/
|
|
||||||
|
|
||||||
/*Enable the examples to be built with the library*/
|
|
||||||
#define LV_BUILD_EXAMPLES 1
|
|
||||||
|
|
||||||
/*===================
|
|
||||||
* DEMO USAGE
|
|
||||||
====================*/
|
|
||||||
|
|
||||||
/*Show some widget. It might be required to increase `LV_MEM_SIZE` */
|
|
||||||
#define LV_USE_DEMO_WIDGETS 1
|
|
||||||
#if LV_USE_DEMO_WIDGETS
|
|
||||||
#define LV_DEMO_WIDGETS_SLIDESHOW 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*Demonstrate the usage of encoder and keyboard*/
|
|
||||||
#define LV_USE_DEMO_KEYPAD_AND_ENCODER 0
|
|
||||||
|
|
||||||
/*Benchmark your system*/
|
|
||||||
#define LV_USE_DEMO_BENCHMARK 1
|
|
||||||
#if LV_USE_DEMO_BENCHMARK
|
|
||||||
/*Use RGB565A8 images with 16 bit color depth instead of ARGB8565*/
|
|
||||||
#define LV_DEMO_BENCHMARK_RGB565A8 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*Stress test for LVGL*/
|
|
||||||
#define LV_USE_DEMO_STRESS 1
|
|
||||||
|
|
||||||
/*Music player demo*/
|
|
||||||
#define LV_USE_DEMO_MUSIC 1
|
|
||||||
#if LV_USE_DEMO_MUSIC
|
|
||||||
#define LV_DEMO_MUSIC_SQUARE 0
|
|
||||||
#define LV_DEMO_MUSIC_LANDSCAPE 0
|
|
||||||
#define LV_DEMO_MUSIC_ROUND 0
|
|
||||||
#define LV_DEMO_MUSIC_LARGE 0
|
|
||||||
#define LV_DEMO_MUSIC_AUTO_PLAY 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*--END OF LV_CONF_H--*/
|
|
||||||
|
|
||||||
#endif /*LV_CONF_H*/
|
|
||||||
|
|
||||||
#endif /*End of "Content enable"*/
|
|
||||||
+20
-24
@@ -1,7 +1,6 @@
|
|||||||
# SHiNE ESP32 Homeserver UI Nav Minimal Spec
|
# SHiNE ESP32 Subserver UI Nav Minimal Spec
|
||||||
|
|
||||||
Легаси-спецификация старого навигационного теста для `Waveshare ESP32-S3-Touch-AMOLED-2.16`.
|
Минимальный навигационный прототип для `Waveshare ESP32-S3-Touch-AMOLED-2.16`.
|
||||||
Актуальный основной скетч теперь находится в `../main-device/shine_homeserver_main/`; этот документ оставлен только как историческая справка по старому тестовому UI.
|
|
||||||
|
|
||||||
## Цель
|
## Цель
|
||||||
|
|
||||||
@@ -24,7 +23,7 @@
|
|||||||
- `WIFI_SCREEN`
|
- `WIFI_SCREEN`
|
||||||
- `SERVER_SCREEN`
|
- `SERVER_SCREEN`
|
||||||
- `ACCOUNT_SCREEN`
|
- `ACCOUNT_SCREEN`
|
||||||
- `ACCOUNT_HOMESERVER_SCREEN`
|
- `ACCOUNT_SUBSERVER_SCREEN`
|
||||||
- `ACCOUNT_SECRET_SCREEN`
|
- `ACCOUNT_SECRET_SCREEN`
|
||||||
- `SECRET_SHOW_SCREEN`
|
- `SECRET_SHOW_SCREEN`
|
||||||
- `SECRET_GENERATE_*`
|
- `SECRET_GENERATE_*`
|
||||||
@@ -34,7 +33,7 @@
|
|||||||
## HOME
|
## HOME
|
||||||
|
|
||||||
Показывает:
|
Показывает:
|
||||||
- сверху слева значение homeserver или `homeserver not set`;
|
- сверху слева значение сабсервера или `subserver not set`;
|
||||||
- ниже значение логина или `login not set`;
|
- ниже значение логина или `login not set`;
|
||||||
- справа от строки логина индикатор статуса Solana-аккаунта:
|
- справа от строки логина индикатор статуса Solana-аккаунта:
|
||||||
- зелёный — все ключи совпадают;
|
- зелёный — все ключи совпадают;
|
||||||
@@ -52,7 +51,7 @@
|
|||||||
- строка `SHiNE: <server> connected/account not configured/unavailable`;
|
- строка `SHiNE: <server> connected/account not configured/unavailable`;
|
||||||
- при отсутствии пользователя в Solana PDA слева снизу появляется кнопка `REGISTER ACCOUNT`;
|
- при отсутствии пользователя в Solana PDA слева снизу появляется кнопка `REGISTER ACCOUNT`;
|
||||||
- снизу кнопку `SETTINGS`, уменьшенную примерно до половины ширины экрана и сдвинутую к правому краю.
|
- снизу кнопку `SETTINGS`, уменьшенную примерно до половины ширины экрана и сдвинутую к правому краю.
|
||||||
- внизу на тёмной полосе подпись `SHiNE homeserver (v.0.18)`.
|
- внизу на тёмной полосе подпись `SHiNE subserver (v.0.18)`.
|
||||||
|
|
||||||
Строка Wi-Fi на `HOME`:
|
Строка Wi-Fi на `HOME`:
|
||||||
- `Wi-Fi (not configured) not configured`
|
- `Wi-Fi (not configured) not configured`
|
||||||
@@ -64,16 +63,13 @@
|
|||||||
- кнопка `SETTINGS` -> `SETTINGS_MENU`;
|
- кнопка `SETTINGS` -> `SETTINGS_MENU`;
|
||||||
- свайп влево -> `SETTINGS_MENU`.
|
- свайп влево -> `SETTINGS_MENU`.
|
||||||
|
|
||||||
Примечание:
|
|
||||||
- поведение `REGISTER ACCOUNT -> REGISTER_ACCOUNT_PLACEHOLDER` относится к старой тестовой версии и не является актуальным для основного скетча.
|
|
||||||
|
|
||||||
Фоновая логика:
|
Фоновая логика:
|
||||||
- пока открыт `HOME`, экран сам обновляется примерно раз в секунду;
|
- пока открыт `HOME`, экран сам обновляется примерно раз в секунду;
|
||||||
- при наличии `login + secret + homeserver` и Wi-Fi устройство читает Solana `user_pda` напрямую через RPC;
|
- при наличии `login + secret + subserver` и Wi-Fi устройство читает Solana `user_pda` напрямую через RPC;
|
||||||
- сравниваются `root key`, `blockchain key`, `device key` и `homeserver` session-запись типа `100`;
|
- сравниваются `root key`, `blockchain key`, `device key` и `subserver` session-запись типа `100`;
|
||||||
- для строки `SHiNE:` устройство держит отдельную WebSocket-сессию с сервером SHiNE:
|
- для строки `SHiNE:` устройство держит отдельную WebSocket-сессию с сервером SHiNE:
|
||||||
- авторизация через `AuthChallenge/CreateAuthSession` или `SessionChallenge/SessionLogin`;
|
- авторизация через `AuthChallenge/CreateAuthSession` или `SessionChallenge/SessionLogin`;
|
||||||
- session key = публичный `homeserver key`;
|
- session key = публичный `subserver key`;
|
||||||
- подтверждение создания сессии подписывается `device key`;
|
- подтверждение создания сессии подписывается `device key`;
|
||||||
- heartbeat выполняется `Ping` раз в минуту.
|
- heartbeat выполняется `Ping` раз в минуту.
|
||||||
|
|
||||||
@@ -168,26 +164,26 @@
|
|||||||
- заголовок `ACCOUNT`;
|
- заголовок `ACCOUNT`;
|
||||||
- статусное сообщение;
|
- статусное сообщение;
|
||||||
- кнопку `Login (<value|not set>)`;
|
- кнопку `Login (<value|not set>)`;
|
||||||
- кнопку `Homeserver (<value|not set>)`;
|
- кнопку `Subserver (<value|not set>)`;
|
||||||
- кнопку `Secret (<*****|not set>)`.
|
- кнопку `Secret (<*****|not set>)`.
|
||||||
|
|
||||||
Переходы:
|
Переходы:
|
||||||
- свайп вправо -> `SETTINGS_MENU`
|
- свайп вправо -> `SETTINGS_MENU`
|
||||||
- `Login` -> `TEXT_EDIT_SCREEN`
|
- `Login` -> `TEXT_EDIT_SCREEN`
|
||||||
- `Homeserver` -> `ACCOUNT_HOMESERVER_SCREEN`
|
- `Subserver` -> `ACCOUNT_SUBSERVER_SCREEN`
|
||||||
- `Secret` -> `ACCOUNT_SECRET_SCREEN`
|
- `Secret` -> `ACCOUNT_SECRET_SCREEN`
|
||||||
|
|
||||||
## ACCOUNT_HOMESERVER_SCREEN
|
## ACCOUNT_SUBSERVER_SCREEN
|
||||||
|
|
||||||
Показывает:
|
Показывает:
|
||||||
- текущий `homeserver`;
|
- текущий `subserver`;
|
||||||
- рекомендацию оставить `homeserver1`, если устройство одно;
|
- рекомендацию оставить `subserver1`, если устройство одно;
|
||||||
- кнопку `USE HOMESERVER1`;
|
- кнопку `USE SUBSERVER1`;
|
||||||
- кнопку `EDIT MANUALLY`;
|
- кнопку `EDIT MANUALLY`;
|
||||||
- кнопку `BACK`.
|
- кнопку `BACK`.
|
||||||
|
|
||||||
Переходы:
|
Переходы:
|
||||||
- `USE HOMESERVER1` -> сохраняет `homeserver1` и возвращает в `ACCOUNT_SCREEN`
|
- `USE SUBSERVER1` -> сохраняет `subserver1` и возвращает в `ACCOUNT_SCREEN`
|
||||||
- `EDIT MANUALLY` -> `TEXT_EDIT_SCREEN`
|
- `EDIT MANUALLY` -> `TEXT_EDIT_SCREEN`
|
||||||
- свайп вправо -> `ACCOUNT_SCREEN`
|
- свайп вправо -> `ACCOUNT_SCREEN`
|
||||||
|
|
||||||
@@ -216,8 +212,8 @@
|
|||||||
- `Blockchain key priv (base58)`;
|
- `Blockchain key priv (base58)`;
|
||||||
- `Device key (base58)`;
|
- `Device key (base58)`;
|
||||||
- `Device key priv (base58)`;
|
- `Device key priv (base58)`;
|
||||||
- `Homeserver key (base58)`;
|
- `Subserver key (base58)`;
|
||||||
- `Homeserver key priv (base58)`;
|
- `Subserver key priv (base58)`;
|
||||||
- для каждого поля показывается формула derivation;
|
- для каждого поля показывается формула derivation;
|
||||||
- значения ключей показываются полными строками увеличенным шрифтом;
|
- значения ключей показываются полными строками увеличенным шрифтом;
|
||||||
- кнопку `BACK`.
|
- кнопку `BACK`.
|
||||||
@@ -297,7 +293,7 @@
|
|||||||
|
|
||||||
Используется `Preferences` (NVS памяти ESP32):
|
Используется `Preferences` (NVS памяти ESP32):
|
||||||
- `login`
|
- `login`
|
||||||
- `homeserver`
|
- `subserver`
|
||||||
- `secret_set`
|
- `secret_set`
|
||||||
|
|
||||||
## Детали клавиатуры
|
## Детали клавиатуры
|
||||||
@@ -316,7 +312,7 @@
|
|||||||
- `DEL`
|
- `DEL`
|
||||||
- `SAVE`
|
- `SAVE`
|
||||||
- `CANCEL`
|
- `CANCEL`
|
||||||
- ниже рамки клавиатурного блока остаётся отдельная тёмная полоса с версией `SHiNE homeserver (v.0.18)`.
|
- ниже рамки клавиатурного блока остаётся отдельная тёмная полоса с версией `SHiNE subserver (v.0.18)`.
|
||||||
|
|
||||||
## Жесты
|
## Жесты
|
||||||
|
|
||||||
@@ -333,7 +329,7 @@
|
|||||||
- `WIFI_SCREEN`: свайп вправо -> `SETTINGS_MENU`
|
- `WIFI_SCREEN`: свайп вправо -> `SETTINGS_MENU`
|
||||||
- `SERVER_SCREEN`: свайп вправо -> `SETTINGS_MENU`
|
- `SERVER_SCREEN`: свайп вправо -> `SETTINGS_MENU`
|
||||||
- `ACCOUNT_SCREEN`: свайп вправо -> `SETTINGS_MENU`
|
- `ACCOUNT_SCREEN`: свайп вправо -> `SETTINGS_MENU`
|
||||||
- `ACCOUNT_HOMESERVER_SCREEN`: свайп вправо -> `ACCOUNT_SCREEN`
|
- `ACCOUNT_SUBSERVER_SCREEN`: свайп вправо -> `ACCOUNT_SCREEN`
|
||||||
- `ACCOUNT_SECRET_SCREEN`: свайп вправо -> `ACCOUNT_SCREEN`
|
- `ACCOUNT_SECRET_SCREEN`: свайп вправо -> `ACCOUNT_SCREEN`
|
||||||
- `TEXT_EDIT_SCREEN`: свайп влево/вправо -> переключение страниц клавиатуры
|
- `TEXT_EDIT_SCREEN`: свайп влево/вправо -> переключение страниц клавиатуры
|
||||||
- переключение страниц клавиатуры срабатывает только если свайп начался в зоне самой клавиатуры, а не по всему экрану редактора
|
- переключение страниц клавиатуры срабатывает только если свайп начался в зоне самой клавиатуры, а не по всему экрану редактора
|
||||||
+78
-238
@@ -1,9 +1,8 @@
|
|||||||
# SHiNE ESP32 Homeserver UI Spec
|
# SHiNE ESP32 Subserver UI Spec
|
||||||
|
|
||||||
## Назначение
|
## Назначение
|
||||||
|
|
||||||
Этот документ описывает актуальный UI-прототип homeserver `SHiNE` для платы `Waveshare ESP32-S3-Touch-AMOLED-2.16`.
|
Этот документ описывает актуальный UI-прототип сабсервера `SHiNE` для платы `Waveshare ESP32-S3-Touch-AMOLED-2.16`.
|
||||||
Актуальный основной Arduino-скетч лежит в `../main-device/shine_homeserver_main/`.
|
|
||||||
|
|
||||||
Документ является источником истины для Arduino-скетча:
|
Документ является источником истины для Arduino-скетча:
|
||||||
|
|
||||||
@@ -20,33 +19,32 @@
|
|||||||
- локальный UI на тач-экране;
|
- локальный UI на тач-экране;
|
||||||
- хранение настроек и секретов во внутренней памяти `ESP32` через `NVS`;
|
- хранение настроек и секретов во внутренней памяти `ESP32` через `NVS`;
|
||||||
- русский текст в логике интерфейса; в текущей временной сборке отображение на экране идёт через ASCII-транслитерацию, потому что `U8g2`-шрифты на устройстве временно не рисуются;
|
- русский текст в логике интерфейса; в текущей временной сборке отображение на экране идёт через ASCII-транслитерацию, потому что `U8g2`-шрифты на устройстве временно не рисуются;
|
||||||
- экран пополнения с реальным `solana:` URI и рисованием QR-кода через `LVGL`;
|
- экран пополнения с реальным `solana:` URI и рисованием QR-кода;
|
||||||
- реальное подключение к `Wi-Fi` по сохранённым `SSID/паролю`;
|
- реальное подключение к `Wi-Fi` по сохранённым `SSID/паролю`;
|
||||||
- реальная проверка доступности `API`, `RPC` и `WS`-адресов;
|
- реальная проверка доступности `API`, `RPC` и `WS`-адресов;
|
||||||
- реальное чтение баланса кошелька из `Solana RPC`;
|
- реальное чтение баланса кошелька из `Solana RPC`;
|
||||||
- проверка обязательных условий перед регистрацией;
|
- проверка обязательных условий перед регистрацией;
|
||||||
- живая on-chain регистрация серверного `user_pda` в `shine_users` по нажатию кнопки на главном экране;
|
- живая on-chain регистрация серверного `user_pda` в `shine_users` через `device key` устройства;
|
||||||
- живой экран заявок на подключение новых устройств через доверенную homeserver-сессию;
|
- прототип входящих запросов с подтверждением и отклонением;
|
||||||
- PIN-блокировка (в текущей временной сборке вход по PIN отключён, устройство открывает `HOME` сразу после старта);
|
- PIN-блокировка (в текущей временной сборке вход по PIN отключён, устройство открывает `HOME` сразу после старта);
|
||||||
- базовые настройки, статус и главный экран;
|
- базовые настройки, статус и главный экран;
|
||||||
- сохранение `PDA` и `tx signature` после успешной регистрации.
|
- сохранение `PDA` и `tx signature` после успешной регистрации.
|
||||||
- создание и возобновление серверной сессии `SHiNE` через WebSocket с `sessionType = 100` и `clientPlatform = "ESP32"`.
|
|
||||||
|
|
||||||
Что пока считается именно прототипом, а не финальной интеграцией:
|
Что пока считается именно прототипом, а не финальной интеграцией:
|
||||||
|
|
||||||
- пока реализован только сценарий заявок на подключение устройств через доверенную сессию;
|
- приём реальных входящих запросов на вход/подпись пока не подключён к живой сети;
|
||||||
- другие типы входящих запросов на подпись и произвольные approval-flow на этом устройстве ещё не подключены.
|
- входящие запросы пока демонстрационные, чтобы можно было проверить UX и логику подтверждения.
|
||||||
|
|
||||||
## Основная идея устройства
|
## Основная идея устройства
|
||||||
|
|
||||||
Устройство работает как отдельный homeserver:
|
Устройство работает как отдельный сабсервер:
|
||||||
|
|
||||||
- хранит секрет на самом устройстве;
|
- хранит секрет на самом устройстве;
|
||||||
- позволяет ввести логин, секрет и имя homeserver;
|
- позволяет ввести логин, секрет и имя сабсервера;
|
||||||
- показывает адрес кошелька устройства;
|
- показывает адрес кошелька устройства;
|
||||||
- позволяет пополнить баланс перед регистрацией;
|
- позволяет пополнить баланс перед регистрацией;
|
||||||
- после выполнения условий даёт зарегистрировать устройство как homeserver;
|
- после выполнения условий даёт зарегистрировать устройство как сабсервер;
|
||||||
- после авторизации в SHiNE может подтверждать заявки на подключение новых устройств пользователя.
|
- после регистрации может принимать входящие запросы на вход и на подпись.
|
||||||
|
|
||||||
`SD`-карта не нужна для постоянного хранения секрета в этом прототипе.
|
`SD`-карта не нужна для постоянного хранения секрета в этом прототипе.
|
||||||
Основное сохранение идёт во внутреннюю flash-память через `NVS`.
|
Основное сохранение идёт во внутреннюю flash-память через `NVS`.
|
||||||
@@ -59,7 +57,7 @@
|
|||||||
- `Wi-Fi SSID`;
|
- `Wi-Fi SSID`;
|
||||||
- `Wi-Fi password`;
|
- `Wi-Fi password`;
|
||||||
- `login`;
|
- `login`;
|
||||||
- `session/homeserver name`;
|
- `session/subserver name`;
|
||||||
- `master secret`;
|
- `master secret`;
|
||||||
- `wallet address`;
|
- `wallet address`;
|
||||||
- `user pda address`;
|
- `user pda address`;
|
||||||
@@ -71,16 +69,6 @@
|
|||||||
- флаги:
|
- флаги:
|
||||||
`wifiReady`, `serversReady`, `secretReady`, `registered`, `online`.
|
`wifiReady`, `serversReady`, `secretReady`, `registered`, `online`.
|
||||||
|
|
||||||
## Правило серверной сессии SHiNE
|
|
||||||
|
|
||||||
При подключении к серверу `SHiNE` устройство должно авторизовываться как homeserver-сеанс:
|
|
||||||
|
|
||||||
- `sessionType = 100`
|
|
||||||
- `clientPlatform = "ESP32"`
|
|
||||||
- `clientInfo = "ESP32 homeserver"`
|
|
||||||
|
|
||||||
Это относится и к `CreateAuthSession`, и к `SessionLogin`.
|
|
||||||
|
|
||||||
## Правила готовности к регистрации
|
## Правила готовности к регистрации
|
||||||
|
|
||||||
Кнопка регистрации доступна только если одновременно выполнены условия:
|
Кнопка регистрации доступна только если одновременно выполнены условия:
|
||||||
@@ -113,18 +101,11 @@
|
|||||||
13. `PIN_EDIT`
|
13. `PIN_EDIT`
|
||||||
14. `TEXT_INPUT`
|
14. `TEXT_INPUT`
|
||||||
15. `CONFIRM`
|
15. `CONFIRM`
|
||||||
16. `REGISTER_ACCOUNT_CONFIRM`
|
|
||||||
17. `REGISTER_ACCOUNT_RESULT`
|
|
||||||
|
|
||||||
## Общие правила интерфейса
|
## Общие правила интерфейса
|
||||||
|
|
||||||
- Верхняя строка всегда показывает краткий статус устройства:
|
- Верхняя строка всегда показывает краткий статус устройства:
|
||||||
`PIN`, `Wi-Fi`, `сервер`, `регистрация`.
|
`PIN`, `Wi-Fi`, `сервер`, `регистрация`.
|
||||||
- В правом верхнем углу рядом с батареей:
|
|
||||||
- сначала процент заряда;
|
|
||||||
- затем, если устройство реально заряжается, маленькая иконка молнии;
|
|
||||||
- затем контур батареи;
|
|
||||||
- затем индикатор `Wi-Fi`.
|
|
||||||
- Основной язык прототипа: русский.
|
- Основной язык прототипа: русский.
|
||||||
- Для вывода текста в текущей временной сборке используется стандартный шрифт `Arduino_GFX`.
|
- Для вывода текста в текущей временной сборке используется стандартный шрифт `Arduino_GFX`.
|
||||||
- Русские строки на экране временно показываются в ASCII-транслитерации.
|
- Русские строки на экране временно показываются в ASCII-транслитерации.
|
||||||
@@ -161,25 +142,11 @@
|
|||||||
|
|
||||||
- крупный статус регистрации;
|
- крупный статус регистрации;
|
||||||
- имя логина;
|
- имя логина;
|
||||||
- имя homeserver;
|
- имя сабсервера;
|
||||||
- строку `Wi-Fi: <SSID> connected|disconnected`;
|
- короткий статус Wi-Fi;
|
||||||
- строку `SHiNE: <server address> connected|unavailable`;
|
- короткий статус сервера;
|
||||||
- короткий статус баланса.
|
- короткий статус баланса.
|
||||||
|
|
||||||
Особенности верхнего блока:
|
|
||||||
|
|
||||||
- зелёный/контурный статусный кружок аккаунта расположен слева от строки логина;
|
|
||||||
- блок `STATUS` поднят выше относительно предыдущей версии;
|
|
||||||
- если состояние хорошее, слово `connected` в строках `Wi-Fi` и `SHiNE` показывается зелёным.
|
|
||||||
|
|
||||||
В зоне баланса:
|
|
||||||
|
|
||||||
- основная кнопка показа/обновления баланса занимает примерно 80% строки;
|
|
||||||
- текст на кнопке баланса выровнен левее центра;
|
|
||||||
- справа от неё стоит отдельная кнопка `QR`;
|
|
||||||
- после старта устройства баланс пытается загрузиться автоматически, если уже есть секрет и `Wi-Fi`;
|
|
||||||
- нажатие на кнопку `QR` открывает экран `WALLET_QR`.
|
|
||||||
|
|
||||||
Нижние кнопки:
|
Нижние кнопки:
|
||||||
|
|
||||||
- `Статус`
|
- `Статус`
|
||||||
@@ -191,113 +158,18 @@
|
|||||||
|
|
||||||
Дополнительная большая кнопка:
|
Дополнительная большая кнопка:
|
||||||
|
|
||||||
- `REGISTER ACCOUNT`
|
- `Зарегистрировать`
|
||||||
- либо жёлтая `ADD HOMESERVER`
|
|
||||||
- либо жёлтая `FIX HOMESERVER PASSWORD`
|
|
||||||
|
|
||||||
Если регистрация уже сделана:
|
Если регистрация уже сделана:
|
||||||
|
|
||||||
- если пользователь создан, но в `PDA` ещё нет сессии текущего homeserver, показывается жёлтая кнопка `ADD HOMESERVER`;
|
- вместо призыва к регистрации показывается статус `Сабсервер активен`.
|
||||||
- если в `PDA` есть homeserver с тем же именем, но с другим ключом, показывается жёлтая кнопка `FIX HOMESERVER PASSWORD`;
|
|
||||||
- если и пользователь, и homeserver-сессия уже корректны, вместо призыва к регистрации показывается статус `Homeserver активен`.
|
|
||||||
- две нижние кнопки внизу экрана не прилегают вплотную друг к другу, между ними есть небольшой зазор.
|
|
||||||
|
|
||||||
## Экран REGISTER_ACCOUNT_CONFIRM
|
|
||||||
|
|
||||||
Показывает предварительную проверку перед отправкой транзакции регистрации.
|
|
||||||
|
|
||||||
Отображается:
|
|
||||||
|
|
||||||
- повторный `login`;
|
|
||||||
- сообщение о том, что логин свободен или уже занят;
|
|
||||||
- баланс кошелька с проверкой порога `0.020 SOL`;
|
|
||||||
- имя homeserver;
|
|
||||||
- пометка, если используется стандартное значение `homeserver1`;
|
|
||||||
- отдельное предупреждение, если `Wi-Fi` не подключён.
|
|
||||||
|
|
||||||
Кнопки:
|
|
||||||
|
|
||||||
- `ЗАРЕГИСТРИРОВАТЬ В СИЯНИИ`
|
|
||||||
- `BACK`
|
|
||||||
|
|
||||||
Поведение:
|
|
||||||
|
|
||||||
- если `Wi-Fi` не подключён, на экране прямо показывается уведомление об этом и кнопка регистрации недоступна;
|
|
||||||
- если `login` уже занят в `shine_users`, регистрация недоступна;
|
|
||||||
- если баланс меньше `0.020 SOL`, на экране показывается соответствующая ошибка;
|
|
||||||
- если все проверки пройдены, кнопка регистрации активна и запускает on-chain регистрацию.
|
|
||||||
|
|
||||||
## Экран REGISTER_ACCOUNT_RESULT
|
|
||||||
|
|
||||||
Показывает результат отправки регистрации.
|
|
||||||
|
|
||||||
Отображается:
|
|
||||||
|
|
||||||
- текст успеха или ошибки;
|
|
||||||
- подробное сообщение;
|
|
||||||
- при успехе краткий `user_pda`;
|
|
||||||
- при успехе краткий `tx signature`.
|
|
||||||
|
|
||||||
Кнопки:
|
|
||||||
|
|
||||||
- `BACK HOME`
|
|
||||||
- `ACCOUNT`
|
|
||||||
|
|
||||||
Поведение:
|
|
||||||
|
|
||||||
- после успешной регистрации данные `user_pda` и `tx signature` сохраняются в `NVS`;
|
|
||||||
- при ошибке на экране показывается причина отказа;
|
|
||||||
- если ошибку вернул `sendTransaction`, экран старается показать не только общий текст, но и детали `RPC`/preflight/simulate-логов.
|
|
||||||
|
|
||||||
## Экран HOMESERVER_PDA_CONFIRM
|
|
||||||
|
|
||||||
Показывает, что именно не так с homeserver-секцией уже существующей пользовательской `PDA`.
|
|
||||||
|
|
||||||
Отображается:
|
|
||||||
|
|
||||||
- причина (`homeserver` отсутствует в `PDA` или ключ не совпадает с локальным секретом);
|
|
||||||
- `login`;
|
|
||||||
- имя `homeserver`;
|
|
||||||
- короткое пояснение, что именно будет сделано.
|
|
||||||
|
|
||||||
Кнопки:
|
|
||||||
|
|
||||||
- `ADD HOMESERVER` или `FIX HOMESERVER PASSWORD`
|
|
||||||
- `BACK`
|
|
||||||
|
|
||||||
Поведение:
|
|
||||||
|
|
||||||
- если `Wi-Fi` не подключён, действие недоступно;
|
|
||||||
- экран не создаёт нового пользователя, а запускает `update_user_pda`;
|
|
||||||
- при `ADD HOMESERVER` в блок `sessions` добавляется запись `session_type=100`;
|
|
||||||
- при `FIX HOMESERVER PASSWORD` обновляется публичный ключ уже существующей записи `homeserver`.
|
|
||||||
|
|
||||||
## Экран HOMESERVER_PDA_RESULT
|
|
||||||
|
|
||||||
Показывает результат обновления `sessions` в пользовательской `PDA`.
|
|
||||||
|
|
||||||
Отображается:
|
|
||||||
|
|
||||||
- успех или ошибка;
|
|
||||||
- короткое сообщение;
|
|
||||||
- при успехе краткий `tx signature`.
|
|
||||||
|
|
||||||
Кнопки:
|
|
||||||
|
|
||||||
- `BACK HOME`
|
|
||||||
- `ACCOUNT`
|
|
||||||
|
|
||||||
Поведение:
|
|
||||||
|
|
||||||
- при ошибке текст ошибки сохраняется в ту же USB/NVS-диагностику, что и регистрация;
|
|
||||||
- после успешного обновления выполняется повторная проверка `PDA`, и основной экран должен перейти в состояние `ok`.
|
|
||||||
|
|
||||||
## Экран STATUS
|
## Экран STATUS
|
||||||
|
|
||||||
Показывает сводку:
|
Показывает сводку:
|
||||||
|
|
||||||
- логин;
|
- логин;
|
||||||
- homeserver;
|
- сабсервер;
|
||||||
- есть ли секрет;
|
- есть ли секрет;
|
||||||
- зарегистрировано ли устройство;
|
- зарегистрировано ли устройство;
|
||||||
- подключён ли Wi-Fi;
|
- подключён ли Wi-Fi;
|
||||||
@@ -384,7 +256,7 @@
|
|||||||
Показывает:
|
Показывает:
|
||||||
|
|
||||||
- логин;
|
- логин;
|
||||||
- имя homeserver;
|
- имя сабсервера;
|
||||||
- статус секрета;
|
- статус секрета;
|
||||||
- короткий отпечаток секрета;
|
- короткий отпечаток секрета;
|
||||||
- статус регистрации;
|
- статус регистрации;
|
||||||
@@ -394,7 +266,7 @@
|
|||||||
|
|
||||||
- `Изменить логин`
|
- `Изменить логин`
|
||||||
- `Секрет`
|
- `Секрет`
|
||||||
- `Имя homeserver`
|
- `Имя сабсервера`
|
||||||
- `Сгенерировать`
|
- `Сгенерировать`
|
||||||
- `Очистить`
|
- `Очистить`
|
||||||
- `Назад`
|
- `Назад`
|
||||||
@@ -404,7 +276,6 @@
|
|||||||
- `Сгенерировать` создаёт новый `master secret` и пересчитывает из него `device`-кошелёк;
|
- `Сгенерировать` создаёт новый `master secret` и пересчитывает из него `device`-кошелёк;
|
||||||
- `Очистить` удаляет секрет, адрес кошелька, `PDA`, `tx`, регистрацию и онлайн-статус;
|
- `Очистить` удаляет секрет, адрес кошелька, `PDA`, `tx`, регистрацию и онлайн-статус;
|
||||||
- логин приводится к нижнему регистру и trim.
|
- логин приводится к нижнему регистру и trim.
|
||||||
- после успешной регистрации на экране сохраняются и отображаются краткие отпечатки `PDA` и `TX`.
|
|
||||||
|
|
||||||
## Экран WALLET
|
## Экран WALLET
|
||||||
|
|
||||||
@@ -433,104 +304,76 @@
|
|||||||
|
|
||||||
## Экран WALLET_QR
|
## Экран WALLET_QR
|
||||||
|
|
||||||
Экран показывает:
|
|
||||||
|
|
||||||
- крупный реальный `QR` для строки `solana:<wallet_address>`;
|
|
||||||
- снизу крупный текст самого адреса кошелька.
|
|
||||||
|
|
||||||
Поведение:
|
|
||||||
|
|
||||||
- отдельная текстовая подсказка возврата не показывается;
|
|
||||||
- возврат на главный экран выполняется обычным тапом по экрану.
|
|
||||||
|
|
||||||
Показывает:
|
Показывает:
|
||||||
|
|
||||||
- QR-код для строки вида:
|
- QR-код для строки вида:
|
||||||
`solana:<wallet>`;
|
`solana:<wallet>?amount=0.20&label=SHiNE%20Register`;
|
||||||
- мелкую подпись с полным адресом кошелька под QR.
|
- адрес кошелька;
|
||||||
|
- сумму;
|
||||||
Поведение:
|
- текст URI.
|
||||||
|
|
||||||
- QR должен быть сканируемым, а не декоративным;
|
|
||||||
- адрес кошелька берётся из `device key`, вычисленного из сохранённого `master secret`;
|
|
||||||
- нажатие в любую точку экрана возвращает пользователя на `HOME`.
|
|
||||||
|
|
||||||
## Экран REQUESTS
|
|
||||||
|
|
||||||
Экран доступен только если homeserver уже авторизован в SHiNE.
|
|
||||||
|
|
||||||
Показывает:
|
|
||||||
|
|
||||||
- слева сверху кнопку `REFRESH`;
|
|
||||||
- заголовок `REQUESTS:` немного правее стандартного левого положения;
|
|
||||||
- справа сверху только большую цифру числа активных заявок;
|
|
||||||
- ниже прокручиваемый список активных pairing-заявок;
|
|
||||||
- на экране одновременно видны примерно две плитки.
|
|
||||||
|
|
||||||
Каждая плитка показывает:
|
|
||||||
|
|
||||||
- код подключения из `10` цифр в виде `5` пар: `XX XX XX XX XX`;
|
|
||||||
- строку `Session: <platform/name>`;
|
|
||||||
- строку `Kind: Client session` или `Kind: Wallet session`.
|
|
||||||
|
|
||||||
Поведение:
|
|
||||||
|
|
||||||
- список берётся из живой операции `ListTrustedDeviceLoginRequests`;
|
|
||||||
- если заявок нет, экран показывает `No active requests`;
|
|
||||||
- отдельная строка вида `Active requests: N` на этом экране не показывается;
|
|
||||||
- вертикальный скролл позволяет просматривать все активные заявки;
|
|
||||||
- нажатие по плитке открывает `REQUEST_DETAIL`;
|
|
||||||
- свайп вправо возвращает в `SETTINGS`.
|
|
||||||
|
|
||||||
## Экран REQUEST_DETAIL
|
|
||||||
|
|
||||||
Показывает детали выбранной pairing-заявки:
|
|
||||||
|
|
||||||
- вопрос `Connect session ...?`;
|
|
||||||
- код подключения `XX XX XX XX XX`;
|
|
||||||
- строку `Session: <platform/name>`;
|
|
||||||
- строку `Kind: Client session` или `Kind: Wallet session`;
|
|
||||||
- пояснение:
|
|
||||||
- для client session: `Only device key will be transferred. No additional keys will be sent.`
|
|
||||||
- для wallet session: `No keys will be transferred.`
|
|
||||||
|
|
||||||
Кнопки:
|
Кнопки:
|
||||||
|
|
||||||
- `YES`
|
- `Назад`
|
||||||
- `NO`
|
|
||||||
|
QR должен быть сканируемым, а не декоративным.
|
||||||
|
|
||||||
|
## Экран REQUESTS
|
||||||
|
|
||||||
|
Показывает список демонстрационных запросов:
|
||||||
|
|
||||||
|
- `Вход в сессию`
|
||||||
|
- `Подпись сообщения`
|
||||||
|
|
||||||
|
Для каждого запроса:
|
||||||
|
|
||||||
|
- тип;
|
||||||
|
- источник;
|
||||||
|
- короткий статус.
|
||||||
|
|
||||||
|
Кнопки:
|
||||||
|
|
||||||
|
- `Открыть запрос 1`
|
||||||
|
- `Открыть запрос 2`
|
||||||
|
- `Назад`
|
||||||
|
|
||||||
|
## Экран REQUEST_DETAIL
|
||||||
|
|
||||||
|
Показывает детали выбранного запроса:
|
||||||
|
|
||||||
|
- тип запроса;
|
||||||
|
- кто запросил;
|
||||||
|
- время;
|
||||||
|
- описание;
|
||||||
|
- отпечаток/идентификатор.
|
||||||
|
|
||||||
|
Кнопки:
|
||||||
|
|
||||||
|
- `Разрешить`
|
||||||
|
- `Отклонить`
|
||||||
|
- `Назад`
|
||||||
|
|
||||||
Поведение:
|
Поведение:
|
||||||
|
|
||||||
- `YES` подтверждает заявку:
|
- после разрешения или отклонения запрос помечается обработанным;
|
||||||
- для client session устройство передаёт только `device key`;
|
- экран списка отражает новый статус.
|
||||||
- для wallet session устройство выпускает отдельную `wallet-session` без передачи ключей;
|
|
||||||
- `NO` отклоняет заявку;
|
|
||||||
- после любого решения устройство возвращается в список `REQUESTS` и обновляет его;
|
|
||||||
- свайп вправо возвращает в `REQUESTS`.
|
|
||||||
|
|
||||||
## Экран SETTINGS
|
## Экран SETTINGS
|
||||||
|
|
||||||
Показывает вертикальное меню крупных пунктов.
|
Показывает:
|
||||||
|
|
||||||
Если homeserver ещё не авторизован в SHiNE:
|
- текущий PIN;
|
||||||
|
- базовые флаги безопасности;
|
||||||
|
- технические действия прототипа.
|
||||||
|
|
||||||
- `1. Wi-Fi`
|
Кнопки:
|
||||||
- `2. Server`
|
|
||||||
- `3. Account`
|
|
||||||
|
|
||||||
Если homeserver уже авторизован:
|
- `Сменить PIN`
|
||||||
|
- `Сбросить онлайн`
|
||||||
|
- `Полный сброс`
|
||||||
|
- `Назад`
|
||||||
|
|
||||||
- `1. Device requests`
|
`Полный сброс` очищает весь локальный конфиг и возвращает устройство к стартовому состоянию.
|
||||||
- `2. Wi-Fi`
|
|
||||||
- `3. Server`
|
|
||||||
- `4. Account`
|
|
||||||
|
|
||||||
Поведение:
|
|
||||||
|
|
||||||
- одновременно видны только две карточки;
|
|
||||||
- список листается свайпом вверх/вниз;
|
|
||||||
- свайп вправо возвращает на `HOME`;
|
|
||||||
- пункт `Device requests` должен быть первым и появляется только для авторизованного homeserver.
|
|
||||||
|
|
||||||
## Экран PIN_EDIT
|
## Экран PIN_EDIT
|
||||||
|
|
||||||
@@ -551,7 +394,7 @@
|
|||||||
- `SSID`
|
- `SSID`
|
||||||
- `Пароль Wi-Fi`
|
- `Пароль Wi-Fi`
|
||||||
- `Логин`
|
- `Логин`
|
||||||
- `Имя homeserver`
|
- `Имя сабсервера`
|
||||||
- `API URL`
|
- `API URL`
|
||||||
- `RPC URL`
|
- `RPC URL`
|
||||||
- `WS URL`
|
- `WS URL`
|
||||||
@@ -589,16 +432,13 @@
|
|||||||
5. проверить или задать серверные адреса;
|
5. проверить или задать серверные адреса;
|
||||||
6. открыть `Аккаунт`;
|
6. открыть `Аккаунт`;
|
||||||
7. ввести логин;
|
7. ввести логин;
|
||||||
8. задать имя homeserver;
|
8. задать имя сабсервера;
|
||||||
9. сгенерировать секрет;
|
9. сгенерировать секрет;
|
||||||
10. открыть `Кошелёк`;
|
10. открыть `Кошелёк`;
|
||||||
11. при необходимости пополнить баланс;
|
11. при необходимости пополнить баланс;
|
||||||
12. вернуться на `HOME`;
|
12. вернуться на `HOME`;
|
||||||
13. нажать `REGISTER ACCOUNT`;
|
13. нажать `Зарегистрировать`;
|
||||||
14. на экране проверки ещё раз увидеть `login`, статус свободного `PDA`, баланс, `homeserver1` и при необходимости сообщение о неподключённом `Wi-Fi`;
|
14. после подтверждения увидеть статус `Сабсервер активен`.
|
||||||
15. нажать `ЗАРЕГИСТРИРОВАТЬ В СИЯНИИ`;
|
|
||||||
16. после завершения увидеть либо экран успеха с `user_pda` и `tx signature`, либо подробную ошибку;
|
|
||||||
17. после успешной регистрации увидеть статус `Homeserver активен`.
|
|
||||||
|
|
||||||
Примечание:
|
Примечание:
|
||||||
|
|
||||||
+7
-13
@@ -1,6 +1,6 @@
|
|||||||
# Main Device
|
# Test Device
|
||||||
|
|
||||||
Основной скетч homeserver и старые тестовые скетчи для быстрой проверки платы.
|
Скрипт заливает официальные Arduino-примеры для быстрой проверки платы.
|
||||||
`burn.sh` теперь:
|
`burn.sh` теперь:
|
||||||
- сам пытается найти USB-порт ESP32;
|
- сам пытается найти USB-порт ESP32;
|
||||||
- сначала делает быструю инкрементальную сборку;
|
- сначала делает быструю инкрементальную сборку;
|
||||||
@@ -14,10 +14,7 @@
|
|||||||
- `hello` — базовый тест экрана (пример `01_HelloWorld`)
|
- `hello` — базовый тест экрана (пример `01_HelloWorld`)
|
||||||
- `simple` — простой кастомный тест: экран + touch + запись/проигрывание + наклон (IMU)
|
- `simple` — простой кастомный тест: экран + touch + запись/проигрывание + наклон (IMU)
|
||||||
- `argon2` — генерация masterSecret через Argon2id с SD-картой как памятью (тест скорости)
|
- `argon2` — генерация masterSecret через Argon2id с SD-картой как памятью (тест скорости)
|
||||||
- `homeserver-ui` — совместимый алиас, указывает на `shine_homeserver_main/`
|
- `subserver-ui` — основной UI-прототип сабсервера SHiNE: NVS, PIN, Wi-Fi, серверы, кошелёк, QR, запросы
|
||||||
- `shine-homeserver-main` — основной скетч проекта `SHiNE` для ESP32, текущая рабочая версия UI
|
|
||||||
- `shine-homeserver-ui-main` — старое имя основного скетча, оставлено как совместимый алиас
|
|
||||||
- `legacy-homeserver-ui` — старый UI-прототип `shine_homeserver_ui/`, оставлен как тестовый и не является основным
|
|
||||||
- `text-test` — диагностический экран рендера текста: default font, U8g2 ASCII, U8g2 кириллица, кнопки с подписями
|
- `text-test` — диагностический экран рендера текста: default font, U8g2 ASCII, U8g2 кириллица, кнопки с подписями
|
||||||
- `gfx-text-test` — тот же тест рендера текста, но уже внутри новой папки `test_sketches/`
|
- `gfx-text-test` — тот же тест рендера текста, но уже внутри новой папки `test_sketches/`
|
||||||
- `gfx-layout-test` — тест геометрии и нижних рядов кнопок
|
- `gfx-layout-test` — тест геометрии и нижних рядов кнопок
|
||||||
@@ -25,9 +22,9 @@
|
|||||||
- `lvgl-interaction-test` — экран на `LVGL` с большим числом кнопок и сообщением о нажатой кнопке
|
- `lvgl-interaction-test` — экран на `LVGL` с большим числом кнопок и сообщением о нажатой кнопке
|
||||||
- `lvgl-touch-debug-test` — точечная диагностика touch: сырые координаты, маркер точки и большая тест-кнопка `LVGL`
|
- `lvgl-touch-debug-test` — точечная диагностика touch: сырые координаты, маркер точки и большая тест-кнопка `LVGL`
|
||||||
- `lvgl-official-based-test` — наш минимальный экран, но на максимально близкой к официальному `LVGL_Widgets` инициализации
|
- `lvgl-official-based-test` — наш минимальный экран, но на максимально близкой к официальному `LVGL_Widgets` инициализации
|
||||||
- `lvgl-subserver-touch-test` — старый гибридный тест: `LVGL`-интерфейс, но display/touch init и raw touch-read взяты из старого `shine_homeserver_ui`; подтверждено на устройстве, touch работает, зелёных линий по краям нет
|
- `lvgl-subserver-touch-test` — гибрид: `LVGL`-интерфейс, но display/touch init и raw touch-read взяты из `shine_subserver_ui`; подтверждено на устройстве, touch работает, зелёных линий по краям нет
|
||||||
- `lvgl-russian-font-test` — тест кастомного `LVGL`-шрифта с кириллицей: русские кнопки, длинные подписи и статусы
|
- `lvgl-russian-font-test` — тест кастомного `LVGL`-шрифта с кириллицей: русские кнопки, длинные подписи и статусы
|
||||||
- `lvgl-nav-minimal-test` — старое имя основного скетча, теперь ведёт на `shine_homeserver_main/` для совместимости
|
- `lvgl-nav-minimal-test` — новый минимальный UI-каркас сабсервера: `HOME`, `SETTINGS_MENU`, `Wi-Fi`, `Server`, `Account`, свайпы, крупные кнопки и реальная настройка Wi-Fi с сохранением в NVS
|
||||||
|
|
||||||
Запуск:
|
Запуск:
|
||||||
|
|
||||||
@@ -35,10 +32,7 @@
|
|||||||
- `./burn.sh audio`
|
- `./burn.sh audio`
|
||||||
- `./burn.sh hello`
|
- `./burn.sh hello`
|
||||||
- `./burn.sh simple`
|
- `./burn.sh simple`
|
||||||
- `./burn.sh homeserver-ui`
|
- `./burn.sh subserver-ui`
|
||||||
- `./burn.sh shine-homeserver-main`
|
|
||||||
- `./burn.sh shine-homeserver-ui-main`
|
|
||||||
- `./burn.sh legacy-homeserver-ui`
|
|
||||||
- `./burn.sh text-test`
|
- `./burn.sh text-test`
|
||||||
- `./burn.sh gfx-text-test`
|
- `./burn.sh gfx-text-test`
|
||||||
- `./burn.sh gfx-layout-test`
|
- `./burn.sh gfx-layout-test`
|
||||||
@@ -49,4 +43,4 @@
|
|||||||
- `./burn.sh lvgl-subserver-touch-test`
|
- `./burn.sh lvgl-subserver-touch-test`
|
||||||
- `./burn.sh lvgl-russian-font-test`
|
- `./burn.sh lvgl-russian-font-test`
|
||||||
- `./burn.sh lvgl-nav-minimal-test`
|
- `./burn.sh lvgl-nav-minimal-test`
|
||||||
- `./flash_shine_homeserver_main.sh` - автоматически находит USB-порт и заливает `shine_homeserver_main`
|
- `./flash_shine_subserver_ui.sh` - автоматически находит USB-порт и заливает `shine_subserver_ui`
|
||||||
+3
-6
@@ -34,10 +34,7 @@ case "${MODE}" in
|
|||||||
audio) SKETCH_DIR="${DEMO_BASE}/examples/07_ES8311" ;;
|
audio) SKETCH_DIR="${DEMO_BASE}/examples/07_ES8311" ;;
|
||||||
simple) SKETCH_DIR="${ROOT_DIR}/simple_av_test" ;;
|
simple) SKETCH_DIR="${ROOT_DIR}/simple_av_test" ;;
|
||||||
argon2) SKETCH_DIR="${ROOT_DIR}/argon2_sd_test" ;;
|
argon2) SKETCH_DIR="${ROOT_DIR}/argon2_sd_test" ;;
|
||||||
homeserver-ui) SKETCH_DIR="${ROOT_DIR}/shine_homeserver_main" ;;
|
subserver-ui) SKETCH_DIR="${ROOT_DIR}/shine_subserver_ui" ;;
|
||||||
shine-homeserver-main) SKETCH_DIR="${ROOT_DIR}/shine_homeserver_main" ;;
|
|
||||||
shine-homeserver-ui-main) SKETCH_DIR="${ROOT_DIR}/shine_homeserver_main" ;;
|
|
||||||
legacy-homeserver-ui) SKETCH_DIR="${ROOT_DIR}/shine_homeserver_ui" ;;
|
|
||||||
text-test) SKETCH_DIR="${ROOT_DIR}/text_render_test" ;;
|
text-test) SKETCH_DIR="${ROOT_DIR}/text_render_test" ;;
|
||||||
gfx-text-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/gfx_text_render_test" ;;
|
gfx-text-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/gfx_text_render_test" ;;
|
||||||
gfx-layout-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/gfx_button_layout_test" ;;
|
gfx-layout-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/gfx_button_layout_test" ;;
|
||||||
@@ -47,10 +44,10 @@ case "${MODE}" in
|
|||||||
lvgl-official-based-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/lvgl_official_based_test" ;;
|
lvgl-official-based-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/lvgl_official_based_test" ;;
|
||||||
lvgl-subserver-touch-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/lvgl_subserver_touch_test" ;;
|
lvgl-subserver-touch-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/lvgl_subserver_touch_test" ;;
|
||||||
lvgl-russian-font-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/lvgl_russian_font_test" ;;
|
lvgl-russian-font-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/lvgl_russian_font_test" ;;
|
||||||
lvgl-nav-minimal-test) SKETCH_DIR="${ROOT_DIR}/shine_homeserver_main" ;;
|
lvgl-nav-minimal-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/lvgl_nav_minimal_test" ;;
|
||||||
*)
|
*)
|
||||||
echo "Unknown mode: ${MODE}" >&2
|
echo "Unknown mode: ${MODE}" >&2
|
||||||
echo "Use one of: hello, widgets, audio, simple, argon2, homeserver-ui, shine-homeserver-main, shine-homeserver-ui-main, legacy-homeserver-ui, text-test, gfx-text-test, gfx-layout-test, lvgl-basic-test, lvgl-interaction-test, lvgl-touch-debug-test, lvgl-official-based-test, lvgl-subserver-touch-test, lvgl-russian-font-test" >&2
|
echo "Use one of: hello, widgets, audio, simple, argon2, subserver-ui, text-test, gfx-text-test, gfx-layout-test, lvgl-basic-test, lvgl-interaction-test, lvgl-touch-debug-test, lvgl-official-based-test, lvgl-subserver-touch-test, lvgl-russian-font-test, lvgl-nav-minimal-test" >&2
|
||||||
exit 2
|
exit 2
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
+2
-2
@@ -43,9 +43,9 @@ fi
|
|||||||
if [[ -z "${PORT}" ]]; then
|
if [[ -z "${PORT}" ]]; then
|
||||||
echo "Не удалось автоматически найти USB-порт ESP32." >&2
|
echo "Не удалось автоматически найти USB-порт ESP32." >&2
|
||||||
echo "Подключите плату и проверьте 'arduino-cli board list'." >&2
|
echo "Подключите плату и проверьте 'arduino-cli board list'." >&2
|
||||||
echo "Либо укажите порт вручную: PORT=/dev/ttyACM0 ./flash_shine_homeserver_main.sh" >&2
|
echo "Либо укажите порт вручную: PORT=/dev/ttyACM0 ./flash_shine_subserver_ui.sh" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "== Найден порт: ${PORT}"
|
echo "== Найден порт: ${PORT}"
|
||||||
PORT="${PORT}" "${ROOT_DIR}/burn.sh" shine-homeserver-main
|
PORT="${PORT}" "${ROOT_DIR}/burn.sh" subserver-ui
|
||||||
+21
-21
@@ -97,7 +97,7 @@ enum ActionId {
|
|||||||
ACT_VERIFY_SERVERS,
|
ACT_VERIFY_SERVERS,
|
||||||
ACT_SET_TEST_SERVERS,
|
ACT_SET_TEST_SERVERS,
|
||||||
ACT_EDIT_LOGIN,
|
ACT_EDIT_LOGIN,
|
||||||
ACT_EDIT_HOMESERVER,
|
ACT_EDIT_SUBSERVER,
|
||||||
ACT_GENERATE_SECRET,
|
ACT_GENERATE_SECRET,
|
||||||
ACT_CLEAR_ACCOUNT,
|
ACT_CLEAR_ACCOUNT,
|
||||||
ACT_SHOW_QR,
|
ACT_SHOW_QR,
|
||||||
@@ -137,7 +137,7 @@ enum EditTarget {
|
|||||||
EDIT_SSID,
|
EDIT_SSID,
|
||||||
EDIT_WIFI_PASSWORD,
|
EDIT_WIFI_PASSWORD,
|
||||||
EDIT_LOGIN,
|
EDIT_LOGIN,
|
||||||
EDIT_HOMESERVER,
|
EDIT_SUBSERVER,
|
||||||
EDIT_API,
|
EDIT_API,
|
||||||
EDIT_RPC,
|
EDIT_RPC,
|
||||||
EDIT_WS,
|
EDIT_WS,
|
||||||
@@ -174,7 +174,7 @@ struct AppData {
|
|||||||
String wifiSsid;
|
String wifiSsid;
|
||||||
String wifiPassword;
|
String wifiPassword;
|
||||||
String login;
|
String login;
|
||||||
String homeserverName;
|
String subserverName;
|
||||||
String secret;
|
String secret;
|
||||||
String walletAddress;
|
String walletAddress;
|
||||||
String userPdaAddress;
|
String userPdaAddress;
|
||||||
@@ -551,7 +551,7 @@ static bool canRegister() {
|
|||||||
|
|
||||||
static String registrationSummary() {
|
static String registrationSummary() {
|
||||||
if (gData.registered) {
|
if (gData.registered) {
|
||||||
return "Homeserver активен";
|
return "Сабсервер активен";
|
||||||
}
|
}
|
||||||
if (!gData.wifiReady) {
|
if (!gData.wifiReady) {
|
||||||
return "Нужен Wi-Fi";
|
return "Нужен Wi-Fi";
|
||||||
@@ -1179,7 +1179,7 @@ static bool awaitTransactionConfirmation(const String &signatureB58, String &mes
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool registerHomeserverOnSolana(String &messageOut) {
|
static bool registerSubserverOnSolana(String &messageOut) {
|
||||||
messageOut = "";
|
messageOut = "";
|
||||||
if (!gDerivedKeys.ready) {
|
if (!gDerivedKeys.ready) {
|
||||||
if (!restoreDerivedKeysFromSecret()) {
|
if (!restoreDerivedKeysFromSecret()) {
|
||||||
@@ -1656,7 +1656,7 @@ static bool refreshWalletBalance(String &messageOut) {
|
|||||||
static void seedRequests() {
|
static void seedRequests() {
|
||||||
gRequests[0].type = "Вход в сессию";
|
gRequests[0].type = "Вход в сессию";
|
||||||
gRequests[0].actor = "Chrome / aidarkc";
|
gRequests[0].actor = "Chrome / aidarkc";
|
||||||
gRequests[0].details = "Клиент просит подключиться к homeserverу и открыть сессию без ввода пароля.";
|
gRequests[0].details = "Клиент просит подключиться к сабсерверу и открыть сессию без ввода пароля.";
|
||||||
gRequests[0].status = "Ожидает";
|
gRequests[0].status = "Ожидает";
|
||||||
|
|
||||||
gRequests[1].type = "Подпись сообщения";
|
gRequests[1].type = "Подпись сообщения";
|
||||||
@@ -1670,7 +1670,7 @@ static void loadDefaults() {
|
|||||||
gData.wifiSsid = "";
|
gData.wifiSsid = "";
|
||||||
gData.wifiPassword = "";
|
gData.wifiPassword = "";
|
||||||
gData.login = "";
|
gData.login = "";
|
||||||
gData.homeserverName = "homeserver1";
|
gData.subserverName = "subserver1";
|
||||||
gData.secret = "";
|
gData.secret = "";
|
||||||
gData.walletAddress = "";
|
gData.walletAddress = "";
|
||||||
gData.userPdaAddress = "";
|
gData.userPdaAddress = "";
|
||||||
@@ -1692,7 +1692,7 @@ static void saveData() {
|
|||||||
gPrefs.putString("wifi_ssid", gData.wifiSsid);
|
gPrefs.putString("wifi_ssid", gData.wifiSsid);
|
||||||
gPrefs.putString("wifi_pass", gData.wifiPassword);
|
gPrefs.putString("wifi_pass", gData.wifiPassword);
|
||||||
gPrefs.putString("login", gData.login);
|
gPrefs.putString("login", gData.login);
|
||||||
gPrefs.putString("homeserver", gData.homeserverName);
|
gPrefs.putString("subserver", gData.subserverName);
|
||||||
gPrefs.putString("secret", gData.secret);
|
gPrefs.putString("secret", gData.secret);
|
||||||
gPrefs.putString("wallet", gData.walletAddress);
|
gPrefs.putString("wallet", gData.walletAddress);
|
||||||
gPrefs.putString("user_pda", gData.userPdaAddress);
|
gPrefs.putString("user_pda", gData.userPdaAddress);
|
||||||
@@ -1714,7 +1714,7 @@ static void loadData() {
|
|||||||
gData.wifiSsid = gPrefs.getString("wifi_ssid", gData.wifiSsid);
|
gData.wifiSsid = gPrefs.getString("wifi_ssid", gData.wifiSsid);
|
||||||
gData.wifiPassword = gPrefs.getString("wifi_pass", gData.wifiPassword);
|
gData.wifiPassword = gPrefs.getString("wifi_pass", gData.wifiPassword);
|
||||||
gData.login = gPrefs.getString("login", gData.login);
|
gData.login = gPrefs.getString("login", gData.login);
|
||||||
gData.homeserverName = gPrefs.getString("homeserver", gData.homeserverName);
|
gData.subserverName = gPrefs.getString("subserver", gData.subserverName);
|
||||||
gData.secret = gPrefs.getString("secret", gData.secret);
|
gData.secret = gPrefs.getString("secret", gData.secret);
|
||||||
gData.walletAddress = gPrefs.getString("wallet", gData.walletAddress);
|
gData.walletAddress = gPrefs.getString("wallet", gData.walletAddress);
|
||||||
gData.userPdaAddress = gPrefs.getString("user_pda", gData.userPdaAddress);
|
gData.userPdaAddress = gPrefs.getString("user_pda", gData.userPdaAddress);
|
||||||
@@ -1758,8 +1758,8 @@ static void generateSecretAndWallet() {
|
|||||||
gData.registrationSignature = "";
|
gData.registrationSignature = "";
|
||||||
gData.registered = false;
|
gData.registered = false;
|
||||||
gData.online = false;
|
gData.online = false;
|
||||||
if (gData.homeserverName.length() == 0) {
|
if (gData.subserverName.length() == 0) {
|
||||||
gData.homeserverName = "homeserver1";
|
gData.subserverName = "subserver1";
|
||||||
}
|
}
|
||||||
saveData();
|
saveData();
|
||||||
}
|
}
|
||||||
@@ -1815,7 +1815,7 @@ static String editTargetLabel() {
|
|||||||
case EDIT_SSID: return "SSID";
|
case EDIT_SSID: return "SSID";
|
||||||
case EDIT_WIFI_PASSWORD: return "Пароль Wi-Fi";
|
case EDIT_WIFI_PASSWORD: return "Пароль Wi-Fi";
|
||||||
case EDIT_LOGIN: return "Логин";
|
case EDIT_LOGIN: return "Логин";
|
||||||
case EDIT_HOMESERVER: return "Имя homeserver";
|
case EDIT_SUBSERVER: return "Имя сабсервера";
|
||||||
case EDIT_API: return "API URL";
|
case EDIT_API: return "API URL";
|
||||||
case EDIT_RPC: return "RPC URL";
|
case EDIT_RPC: return "RPC URL";
|
||||||
case EDIT_WS: return "WS URL";
|
case EDIT_WS: return "WS URL";
|
||||||
@@ -1846,7 +1846,7 @@ static void drawHomeScreen() {
|
|||||||
drawPanel(20, 92, 440, 98, C_PANEL, C_BORDER, 16);
|
drawPanel(20, 92, 440, 98, C_PANEL, C_BORDER, 16);
|
||||||
drawText(36, 122, registrationSummary(), canRegister() || gData.registered ? C_ACCENT : C_WARN, (const uint8_t *)FONT_HEAD);
|
drawText(36, 122, registrationSummary(), canRegister() || gData.registered ? C_ACCENT : C_WARN, (const uint8_t *)FONT_HEAD);
|
||||||
drawText(36, 152, "Логин: " + (gData.login.length() ? gData.login : "не задан"), C_TEXT, (const uint8_t *)FONT_BODY);
|
drawText(36, 152, "Логин: " + (gData.login.length() ? gData.login : "не задан"), C_TEXT, (const uint8_t *)FONT_BODY);
|
||||||
drawText(36, 174, "Homeserver: " + gData.homeserverName, C_MUTE, (const uint8_t *)FONT_BODY);
|
drawText(36, 174, "Сабсервер: " + gData.subserverName, C_MUTE, (const uint8_t *)FONT_BODY);
|
||||||
|
|
||||||
drawPanel(20, 204, 210, 82, C_CARD, C_BORDER, 12);
|
drawPanel(20, 204, 210, 82, C_CARD, C_BORDER, 12);
|
||||||
drawText(34, 232, "Wi-Fi", C_TEXT, (const uint8_t *)FONT_BODY);
|
drawText(34, 232, "Wi-Fi", C_TEXT, (const uint8_t *)FONT_BODY);
|
||||||
@@ -1871,7 +1871,7 @@ static void drawStatusScreen() {
|
|||||||
drawTopBar("Статус");
|
drawTopBar("Статус");
|
||||||
drawPanel(20, 92, 440, 286, C_PANEL, C_BORDER, 16);
|
drawPanel(20, 92, 440, 286, C_PANEL, C_BORDER, 16);
|
||||||
drawText(36, 122, "Логин: " + (gData.login.length() ? gData.login : "не задан"), C_TEXT);
|
drawText(36, 122, "Логин: " + (gData.login.length() ? gData.login : "не задан"), C_TEXT);
|
||||||
drawText(36, 148, "Homeserver: " + gData.homeserverName, C_TEXT);
|
drawText(36, 148, "Сабсервер: " + gData.subserverName, C_TEXT);
|
||||||
drawText(36, 174, "Секрет: " + boolText(gData.secretReady, "сохранён", "не задан"), gData.secretReady ? C_ACCENT : C_WARN);
|
drawText(36, 174, "Секрет: " + boolText(gData.secretReady, "сохранён", "не задан"), gData.secretReady ? C_ACCENT : C_WARN);
|
||||||
drawText(36, 200, "Отпечаток: " + (gData.secretReady ? shortenValue(gData.secret) : "-"), C_MUTE, (const uint8_t *)FONT_SMALL);
|
drawText(36, 200, "Отпечаток: " + (gData.secretReady ? shortenValue(gData.secret) : "-"), C_MUTE, (const uint8_t *)FONT_SMALL);
|
||||||
drawText(36, 226, "Wi-Fi: " + boolText(gData.wifiReady, "готов", "не готов"), gData.wifiReady ? C_ACCENT : C_WARN);
|
drawText(36, 226, "Wi-Fi: " + boolText(gData.wifiReady, "готов", "не готов"), gData.wifiReady ? C_ACCENT : C_WARN);
|
||||||
@@ -1947,13 +1947,13 @@ static void drawAccountScreen() {
|
|||||||
drawTopBar("Аккаунт");
|
drawTopBar("Аккаунт");
|
||||||
drawPanel(20, 92, 440, 188, C_PANEL, C_BORDER, 16);
|
drawPanel(20, 92, 440, 188, C_PANEL, C_BORDER, 16);
|
||||||
drawText(36, 122, "Логин: " + (gData.login.length() ? gData.login : "не задан"), C_TEXT);
|
drawText(36, 122, "Логин: " + (gData.login.length() ? gData.login : "не задан"), C_TEXT);
|
||||||
drawText(36, 152, "Homeserver: " + gData.homeserverName, C_TEXT);
|
drawText(36, 152, "Сабсервер: " + gData.subserverName, C_TEXT);
|
||||||
drawText(36, 182, "Секрет: " + boolText(gData.secretReady, "сохранён", "не задан"), gData.secretReady ? C_ACCENT : C_WARN);
|
drawText(36, 182, "Секрет: " + boolText(gData.secretReady, "сохранён", "не задан"), gData.secretReady ? C_ACCENT : C_WARN);
|
||||||
drawText(36, 212, "Кошелёк: " + (gData.walletAddress.length() ? shortenValue(gData.walletAddress, 10, 8) : "не создан"), C_MUTE, (const uint8_t *)FONT_SMALL);
|
drawText(36, 212, "Кошелёк: " + (gData.walletAddress.length() ? shortenValue(gData.walletAddress, 10, 8) : "не создан"), C_MUTE, (const uint8_t *)FONT_SMALL);
|
||||||
drawText(36, 236, "Регистрация: " + boolText(gData.registered, "выполнена", "не выполнена"), gData.registered ? C_ACCENT : C_WARN);
|
drawText(36, 236, "Регистрация: " + boolText(gData.registered, "выполнена", "не выполнена"), gData.registered ? C_ACCENT : C_WARN);
|
||||||
drawText(36, 260, "PDA: " + registrationDetailsShort(), C_MUTE, (const uint8_t *)FONT_SMALL);
|
drawText(36, 260, "PDA: " + registrationDetailsShort(), C_MUTE, (const uint8_t *)FONT_SMALL);
|
||||||
addButton(20, 300, 212, 48, ACT_EDIT_LOGIN, "Изменить логин");
|
addButton(20, 300, 212, 48, ACT_EDIT_LOGIN, "Изменить логин");
|
||||||
addButton(248, 300, 212, 48, ACT_EDIT_HOMESERVER, "Имя homeserver");
|
addButton(248, 300, 212, 48, ACT_EDIT_SUBSERVER, "Имя сабсервера");
|
||||||
addButton(20, 360, 212, 48, ACT_GENERATE_SECRET, "Сгенерировать", true, C_OK);
|
addButton(20, 360, 212, 48, ACT_GENERATE_SECRET, "Сгенерировать", true, C_OK);
|
||||||
addButton(248, 360, 212, 48, ACT_CLEAR_ACCOUNT, "Очистить", true, C_BUTTON2);
|
addButton(248, 360, 212, 48, ACT_CLEAR_ACCOUNT, "Очистить", true, C_BUTTON2);
|
||||||
addButton(20, 420, 440, 36, ACT_BACK, "Назад");
|
addButton(20, 420, 440, 36, ACT_BACK, "Назад");
|
||||||
@@ -2193,9 +2193,9 @@ static void applyEditValue() {
|
|||||||
gData.registrationSignature = "";
|
gData.registrationSignature = "";
|
||||||
gNotice = "Логин сохранён";
|
gNotice = "Логин сохранён";
|
||||||
break;
|
break;
|
||||||
case EDIT_HOMESERVER:
|
case EDIT_SUBSERVER:
|
||||||
gData.homeserverName = value.length() ? value : "homeserver1";
|
gData.subserverName = value.length() ? value : "subserver1";
|
||||||
gNotice = "Имя homeserver сохранено";
|
gNotice = "Имя сабсервера сохранено";
|
||||||
break;
|
break;
|
||||||
case EDIT_API:
|
case EDIT_API:
|
||||||
gData.apiUrl = value;
|
gData.apiUrl = value;
|
||||||
@@ -2351,7 +2351,7 @@ static void handleAction(ActionId action) {
|
|||||||
}
|
}
|
||||||
if (action == ACT_CONFIRM_YES) {
|
if (action == ACT_CONFIRM_YES) {
|
||||||
if (gConfirmTarget == CONFIRM_REGISTER) {
|
if (gConfirmTarget == CONFIRM_REGISTER) {
|
||||||
registerHomeserverOnSolana(gNotice);
|
registerSubserverOnSolana(gNotice);
|
||||||
} else if (gConfirmTarget == CONFIRM_CLEAR_ACCOUNT) {
|
} else if (gConfirmTarget == CONFIRM_CLEAR_ACCOUNT) {
|
||||||
gData.secret = "";
|
gData.secret = "";
|
||||||
gData.walletAddress = "";
|
gData.walletAddress = "";
|
||||||
@@ -2445,7 +2445,7 @@ static void handleAction(ActionId action) {
|
|||||||
gNeedRedraw = true;
|
gNeedRedraw = true;
|
||||||
break;
|
break;
|
||||||
case ACT_EDIT_LOGIN: openEdit(EDIT_LOGIN, gData.login, false); break;
|
case ACT_EDIT_LOGIN: openEdit(EDIT_LOGIN, gData.login, false); break;
|
||||||
case ACT_EDIT_HOMESERVER: openEdit(EDIT_HOMESERVER, gData.homeserverName, false); break;
|
case ACT_EDIT_SUBSERVER: openEdit(EDIT_SUBSERVER, gData.subserverName, false); break;
|
||||||
case ACT_GENERATE_SECRET:
|
case ACT_GENERATE_SECRET:
|
||||||
generateSecretAndWallet();
|
generateSecretAndWallet();
|
||||||
gNotice = gData.secretReady ? "Секрет сгенерирован, device-кошелёк выведен из него" : "Не удалось сгенерировать секрет";
|
gNotice = gData.secretReady ? "Секрет сгенерирован, device-кошелёк выведен из него" : "Не удалось сгенерировать секрет";
|
||||||
+5
-5
@@ -1,9 +1,8 @@
|
|||||||
# Test Sketches
|
# Test Sketches
|
||||||
|
|
||||||
Набор старых отдельных диагностических скетчей для `Waveshare ESP32-S3-Touch-AMOLED-2.16`.
|
Набор отдельных диагностических скетчей для `Waveshare ESP32-S3-Touch-AMOLED-2.16`.
|
||||||
|
|
||||||
Скетчи в этой папке нужны для быстрой проверки конкретных гипотез и не являются основным UI проекта.
|
Скетчи в этой папке нужны для быстрой проверки конкретных гипотез без влияния на основной `shine_subserver_ui`.
|
||||||
Основной скетч сейчас лежит в `main-device/shine_homeserver_main/`.
|
|
||||||
|
|
||||||
## Список
|
## Список
|
||||||
|
|
||||||
@@ -13,9 +12,9 @@
|
|||||||
- `lvgl_interaction_test/` - расширенный тест `LVGL` с 9 кнопками, touch-вводом и статусом нажатия
|
- `lvgl_interaction_test/` - расширенный тест `LVGL` с 9 кнопками, touch-вводом и статусом нажатия
|
||||||
- `lvgl_touch_debug_test/` - диагностика touch: сырые координаты, точка касания и одна большая кнопка `LVGL`
|
- `lvgl_touch_debug_test/` - диагностика touch: сырые координаты, точка касания и одна большая кнопка `LVGL`
|
||||||
- `lvgl_official_based_test/` - минимальный наш экран поверх максимально близкой к официальному `05_LVGL_Widgets` инициализации
|
- `lvgl_official_based_test/` - минимальный наш экран поверх максимально близкой к официальному `05_LVGL_Widgets` инициализации
|
||||||
- `lvgl_subserver_touch_test/` - старый гибридный тест: `LVGL`-экран с инициализацией дисплея и чтением touch из старого `shine_homeserver_ui`; подтверждён на реальном устройстве
|
- `lvgl_subserver_touch_test/` - гибридный тест: `LVGL`-экран с инициализацией дисплея и чтением touch из `shine_subserver_ui`; подтверждён на реальном устройстве
|
||||||
- `lvgl_russian_font_test/` - тест кастомного кириллического `LVGL`-шрифта с русскими кнопками, длинными строками и рабочим touch
|
- `lvgl_russian_font_test/` - тест кастомного кириллического `LVGL`-шрифта с русскими кнопками, длинными строками и рабочим touch
|
||||||
- `lvgl_nav_minimal_test/` - старое тестовое имя, этот скетч перенесён в `shine_homeserver_main/` и теперь является основным
|
- `lvgl_nav_minimal_test/` - новый минимальный навигационный каркас сабсервера на рабочем `LVGL + subserver touch`, расширенный настройкой Wi-Fi и сохранением в NVS
|
||||||
|
|
||||||
## Запуск
|
## Запуск
|
||||||
|
|
||||||
@@ -29,3 +28,4 @@
|
|||||||
- `./burn.sh lvgl-official-based-test`
|
- `./burn.sh lvgl-official-based-test`
|
||||||
- `./burn.sh lvgl-subserver-touch-test`
|
- `./burn.sh lvgl-subserver-touch-test`
|
||||||
- `./burn.sh lvgl-russian-font-test`
|
- `./burn.sh lvgl-russian-font-test`
|
||||||
|
- `./burn.sh lvgl-nav-minimal-test`
|
||||||
+3332
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user