SHA256
Исправить edit/delete сообщений, упростить вкладки каналов и улучшить автоскролл DM
This commit is contained in:
@@ -19,7 +19,14 @@ TEXT-тип хранит сообщения и редактирования.
|
||||
4. `subType=21` — `TEXT_EDIT_REPLY`
|
||||
- редактирование ответа;
|
||||
- target на исходный REPLY + новый текст.
|
||||
- допускается пустой `text` для логического удаления сообщения (без физического удаления блока).
|
||||
|
||||
## Правило для edit
|
||||
|
||||
`EDIT_POST` и `EDIT_REPLY` должны ссылаться на **оригинальный** блок, а не на предыдущий edit.
|
||||
|
||||
## Пустой text в edit
|
||||
|
||||
- Для `TEXT_EDIT_POST` и `TEXT_EDIT_REPLY` допустим `textLen=0`.
|
||||
- Такой edit трактуется как логическое удаление содержимого сообщения.
|
||||
- Для удаления используется именно edit-блок; отдельного `DELETE`-подтипа нет.
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# История изменений документации блокчейна
|
||||
|
||||
## 2026-05-19 20:30:21 +0300
|
||||
- Базовый коммит-ориентир: `7986184`.
|
||||
- Уточнён документ `11_TEXT_Blocks.md`: для `TEXT_EDIT_POST` и `TEXT_EDIT_REPLY` зафиксировано, что `textLen=0` допустим и трактуется как логическое удаление сообщения.
|
||||
- Явно закреплено, что отдельного `DELETE`-подтипа нет, удаление выполняется edit-блоком.
|
||||
|
||||
## 2026-05-19 00:22:46 +0300
|
||||
- Базовый коммит-ориентир: `c27da63a3e65`.
|
||||
- Актуализирован `README.md` как точка входа для MVP-документации по протоколу.
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
# Редактирование сообщений: история и delete через пустой edit
|
||||
|
||||
Статус: `pending`
|
||||
|
||||
## Краткое описание
|
||||
- Исправлено применение edit-блоков в чтении канала/треда (актуальный текст и версии).
|
||||
- Для удаления сообщения используется edit с пустым `text` (`textLen=0`).
|
||||
- В UI добавлена метка `изменено N`, по нажатию открывается история версий.
|
||||
- Кнопка редактирования оставлена как иконка карандаша без текста.
|
||||
- В модалке редактирования: сверху `Отмена` и `ОК`, снизу отдельная `Удалить`.
|
||||
|
||||
## Что проверять
|
||||
1. В канале отредактировать свой пост обычным текстом.
|
||||
2. Убедиться, что текст сообщения сразу обновился и появилась метка `изменено 1`.
|
||||
3. Нажать на метку `изменено 1` и проверить историю: сверху оригинал, ниже изменения, последнее внизу.
|
||||
4. Нажать `Удалить` в модалке редактирования, убедиться, что сообщение отображается как `удалено`.
|
||||
5. Повторно отредактировать удалённое сообщение непустым текстом и проверить, что текст снова отображается.
|
||||
6. Повторить пп.1-5 в экране треда.
|
||||
7. Проверить личный канал (пара A↔B), что edit и история корректно видны для сообщений владельца.
|
||||
|
||||
## Ожидаемый результат
|
||||
- Edit всегда влияет на отображаемый текст сообщения.
|
||||
- История версий открывается из метки `изменено N` и содержит полный хронологический список версий.
|
||||
- Удаление работает как edit с пустым текстом, без физического удаления блока.
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
# Каналы: убрать кнопку тред, две вкладки, и автоскролл DM
|
||||
|
||||
Статус: `pending`
|
||||
|
||||
## Краткое описание
|
||||
- В карточках сообщений канала и треда убрана кнопка `Тред` (`#`).
|
||||
- В `channels-list` оставлены только две вкладки: `Каналы` и `Мои каналы` (вкладка `Чаты` удалена).
|
||||
- Удалённые сообщения в канале и треде отображаются с красной пометкой `Сообщение удалено`.
|
||||
- В личных сообщениях усилен автоскролл вниз: при открытии чата и после отправки нового сообщения.
|
||||
|
||||
## Что проверять
|
||||
1. Открыть канал, проверить, что в действиях сообщения нет кнопки `Тред`.
|
||||
2. Открыть тред, проверить, что в действиях сообщения также нет кнопки `Тред`.
|
||||
3. В списке каналов сверху проверить только 2 вкладки: `Каналы` и `Мои каналы`.
|
||||
4. Удалить сообщение edit-ом в канале и в треде, убедиться, что видно красную пометку `Сообщение удалено`.
|
||||
5. Открыть любой личный чат, убедиться, что экран сразу внизу ленты.
|
||||
6. Отправить новое сообщение в личке, убедиться, что лента остаётся прокрученной вниз и сообщение видно сразу.
|
||||
|
||||
## Ожидаемый результат
|
||||
- Лишняя кнопка `Тред` отсутствует.
|
||||
- Вкладка `Чаты` отсутствует, навигация в списке каналов состоит из 2 вкладок.
|
||||
- Удалённые сообщения визуально выделены красным в канале и в треде.
|
||||
- В DM нет ручной прокрутки после входа в чат и после отправки новых сообщений.
|
||||
@@ -0,0 +1,221 @@
|
||||
# Личные сообщения (DM): как это устроено
|
||||
|
||||
## Коротко (для быстрого понимания)
|
||||
|
||||
Личные сообщения в SHiNE сейчас работают как пара **подписанных клиентом блоков** в формате `SHiNE_dm2`:
|
||||
|
||||
- тип `1` — входящее сообщение для собеседника;
|
||||
- тип `2` — исходящая копия того же сообщения для автора.
|
||||
|
||||
Оба блока отправляются вместе одной операцией (`SendMessagePair` / `ReceiveOutcomingMessage`) и либо сохраняются оба, либо не сохраняются вовсе.
|
||||
Дальше сервер доставляет их по активным сессиям целевого логина событием `SignedMessageArrived`, а клиент подтверждает доставку на конкретную сессию через `AckSessionDelivery`.
|
||||
|
||||
Подтверждение прочтения также идёт парой блоков:
|
||||
|
||||
- тип `3` — «прочитано» для исходящего сообщения автора;
|
||||
- тип `4` — зеркальная копия для второй стороны.
|
||||
|
||||
UI чата строится на этих типах: текстовые сообщения (1/2), read-receipt (3/4), непрочитанные, галочки и история.
|
||||
|
||||
---
|
||||
|
||||
## Подробно
|
||||
|
||||
## 1) Общая схема потока
|
||||
|
||||
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`) для непрочитанных входящих.
|
||||
|
||||
## 2) Формат signed DM-блока (`SHiNE_dm2`)
|
||||
|
||||
Префикс: `SHiNE_dm2` (ASCII).
|
||||
|
||||
Далее поля (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`
|
||||
|
||||
Эти ключи используются:
|
||||
|
||||
- для дедупликации;
|
||||
- для связи read-receipt с исходным сообщением;
|
||||
- для ACK доставки по сессии.
|
||||
|
||||
## 5) RPC и события
|
||||
|
||||
## `SendMessagePair` (алиас `ReceiveOutcomingMessage`)
|
||||
|
||||
Запрос:
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "SendMessagePair",
|
||||
"requestId": "req-1",
|
||||
"payload": {
|
||||
"incomingBlobB64": "<base64 signed block type 1 or 3>",
|
||||
"outgoingBlobB64": "<base64 signed block type 2 or 4>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Успешный ответ:
|
||||
|
||||
```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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## `SignedMessageArrived` (server event)
|
||||
|
||||
Событие в сессию получателя содержит:
|
||||
|
||||
- `messageKey`, `baseKey`;
|
||||
- `fromLogin`, `toLogin`, `targetLogin`;
|
||||
- `messageType`, `timeMs`, `nonce`;
|
||||
- `blobB64`;
|
||||
- `backlog` (признак догрузки из очереди).
|
||||
|
||||
## `AckSessionDelivery`
|
||||
|
||||
Запрос:
|
||||
|
||||
```json
|
||||
{
|
||||
"op": "AckSessionDelivery",
|
||||
"requestId": "ack-1",
|
||||
"payload": {
|
||||
"messageKey": "from|to|time|nonce|1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Ответ: `status=200`, echo `messageKey`.
|
||||
|
||||
## 6) Хранение на сервере (SQLite)
|
||||
|
||||
Основные таблицы:
|
||||
|
||||
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`.
|
||||
|
||||
## 7) Доставка и backlog
|
||||
|
||||
- При сохранении пары сервер пытается сразу доставить в онлайн-сессии.
|
||||
- Для офлайн/недоступных сессий остаётся pending-запись доставки.
|
||||
- При подключении сессии сервер догружает pending (`listPendingForSession`) и шлёт `SignedMessageArrived(backlog=true)`.
|
||||
- После получения клиент должен отправить `AckSessionDelivery`, чтобы отметить `delivered=1`.
|
||||
|
||||
## 8) Read-receipt логика
|
||||
|
||||
Когда клиент открывает чат:
|
||||
|
||||
1. ищет входящие `messageType=1` без `readReceiptSent`;
|
||||
2. для каждого отправляет read-receipt как пару `type=3/4`;
|
||||
3. после успешной отправки помечает `readReceiptSent`.
|
||||
|
||||
Сервер для read-receipt хранит ссылку на исходное сообщение:
|
||||
|
||||
- `receipt_ref_base_key`;
|
||||
- `receipt_ref_type`.
|
||||
|
||||
Есть уникальность, чтобы не плодить дубликаты receipt на один и тот же `baseKey` для одного `target_login`.
|
||||
|
||||
## 9) Логика UI-клиента
|
||||
|
||||
В UI:
|
||||
|
||||
- чат хранится в `state.chats[chatId]`;
|
||||
- `chatId` для `type=1` — `fromLogin`, для `type=2` — `toLogin`;
|
||||
- непрочитанные считаются по `from='in' && unread=true`;
|
||||
- доставка/прочтение исходящих:
|
||||
- `firstTick` — сообщение принято в парный поток,
|
||||
- `secondTick` — пришло подтверждение прочтения;
|
||||
- при открытии диалога UI автопрокручивает ленту в самый низ;
|
||||
- после отправки нового сообщения UI сразу автопрокручивает ленту вниз, чтобы новое сообщение было в зоне видимости;
|
||||
- сообщения дополнительно кешируются в IndexedDB (`shine-ui-messages-v1`, store `messages`).
|
||||
|
||||
## 10) Инварианты (обязательно соблюдать при доработках)
|
||||
|
||||
1. Пара блоков (1/2 или 3/4) должна оставаться атомарной.
|
||||
2. `messageKey`/`baseKey` формат должен быть совместим с текущей логикой дедупликации и receipt.
|
||||
3. Доставка должна оставаться **по сессиям** с явным `AckSessionDelivery`.
|
||||
4. Read-receipt не должен отправляться многократно на один и тот же `baseKey`.
|
||||
5. Любые изменения DM-логики в коде должны сразу отражаться в этом документе.
|
||||
|
||||
## 11) Ключевые файлы реализации
|
||||
|
||||
- UI:
|
||||
- `shine-UI/js/services/auth-service.js`
|
||||
- `shine-UI/js/app.js`
|
||||
- `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`
|
||||
Reference in New Issue
Block a user