НЕ ПРОВЕРЕНО: DM-вложения, upload файлов и ревизии личных сообщений

This commit is contained in:
AidarKC
2026-06-18 11:46:58 +04:00
parent 2225c2d173
commit 92fd315505
25 changed files with 1910 additions and 516 deletions
+1
View File
@@ -61,5 +61,6 @@
## Важные замечания
- `ReceiveOutcomingMessage` сейчас зарегистрирован как алиас того же handler/request-класса, что и `SendMessagePair`.
- HTTP endpoints для DM-файлов (`HEAD/GET /f/<hashB64url>` и `POST /upload`) не являются WebSocket `op`, поэтому в таблицу выше не входят; они описаны в `12_Direct_Messages_Push_Calls_API.md`.
- Классы `Net_MarkChannelMessagesSeen_*` существуют в коде, но операция `MarkChannelMessagesSeen` не зарегистрирована в `JsonHandlerRegistry`, поэтому в публичный список API не входит.
- HTTP debug endpoints из `src/main/java/server/debug/` не входят в этот индекс WebSocket `op`; они описаны отдельно в `13_HTTP_Debug_API.md`.
+101 -185
View File
@@ -1,8 +1,8 @@
# API для разработчиков: DM, push и сигналы звонков
# API для разработчиков: DM, файлы, push и сигналы звонков
Документ описывает WebSocket-операции для подписанных личных сообщений, WebPush и realtime-сигналов звонков.
Документ описывает публичные операции и endpoints, связанные с личными сообщениями, файлами для DM, WebPush и сигналами звонков.
Логика личных сообщений дополнительно описана в `Dev_Docs/Personal_Messages/README.md`; этот файл фиксирует именно публичные `op`, поля запросов и поля ответов.
Подробная логика DM и бинарного формата: `Dev_Docs/Personal_Messages/README.md`.
## 1. `UpsertPushToken`
@@ -40,11 +40,9 @@
}
```
---
## 2. `SendTestWebPush`
Требует авторизации. Если `login` передан, он должен совпадать с логином текущей сессии.
Требует авторизации.
### Запрос
@@ -61,65 +59,18 @@
}
```
### Успешный ответ
## 3. `SendMessagePair` и `ReceiveOutcomingMessage`
```json
{
"op": "SendTestWebPush",
"requestId": "push-test-001",
"status": 200,
"ok": true,
"payload": {
"targetLogin": "alice",
"attemptedSessions": 1,
"sessionsWithPushConfig": 1,
"delivered": 1,
"failed": 0,
"sentAtMs": 1774700000123
}
}
```
`ReceiveOutcomingMessage` — алиас `SendMessagePair`.
---
### Назначение
## 3. `SendDirectMessage`
Передаёт пару signed DM-блоков:
Отправляет один подписанный DM-пакет.
- `incomingBlobB64` — блок `type=1` или `type=3`
- `outgoingBlobB64` — блок `type=2` или `type=4`
### Запрос
```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.
Для контентных сообщений `type=1/2` внутри base64 лежит новый бинарный формат `SHiNE_DM`.
### Запрос
@@ -143,55 +94,28 @@
"status": 200,
"ok": true,
"payload": {
"baseKey": "base-key",
"incomingKey": "incoming-key",
"outgoingKey": "outgoing-key",
"baseKey": "from|to|time|nonce",
"incomingKey": "from|to|time|nonce|1",
"outgoingKey": "from|to|time|nonce|2",
"deliveredWsSessions": 1,
"deliveredWebPushSessions": 0
}
}
```
---
### Ошибки
## 5. `ReceiveIncomingMessage`
- `400 / BAD_FIELDS` — пустой `incomingBlobB64` или `outgoingBlobB64`
- `400 / BAD_BLOCK_FORMAT` — base64 или бинарный контейнер повреждён
- `400 / BAD_CONTENT_FORMAT` — для контентного сообщения пришёл не `SHiNE_DM`
- `400 / TOO_MANY_ATTACHMENTS` — больше 12 вложений
- `400 / ATTACHMENT_NOT_FOUND` — сообщение ссылается на blob, которого нет на сервере
- `404 / USER_NOT_FOUND` — один из логинов не найден
- `460 / BAD_SIGNATURE` — подпись блока не прошла проверку
Принимает входящий подписанный DM-блок.
## 4. `AckSessionDelivery`
### Запрос
```json
{
"op": "ReceiveIncomingMessage",
"requestId": "dm-in-001",
"payload": {
"incomingBlobB64": "BASE64_INCOMING_SIGNED_BLOCK"
}
}
```
### Успешный ответ
```json
{
"op": "ReceiveIncomingMessage",
"requestId": "dm-in-001",
"status": 200,
"ok": true,
"payload": {
"messageKey": "incoming-key",
"baseKey": "base-key",
"deliveredWsSessions": 1,
"deliveredWebPushSessions": 0
}
}
```
---
## 6. `AckSessionDelivery`
Требует авторизации. Подтверждает доставку сообщения в текущую сессию.
Требует авторизации. Подтверждает доставку в текущую сессию.
### Запрос
@@ -200,107 +124,99 @@
"op": "AckSessionDelivery",
"requestId": "ack-001",
"payload": {
"messageKey": "incoming-key"
"messageKey": "from|to|time|nonce|1"
}
}
```
## 5. Событие `SignedMessageArrived`
Сервер присылает его по WebSocket в активные сессии адресата.
### Payload события
```json
{
"messageKey": "from|to|time|nonce|1",
"baseKey": "from|to|time|nonce",
"fromLogin": "alice",
"toLogin": "bob",
"targetLogin": "bob",
"messageType": 1,
"timeMs": 1774700000123,
"nonce": 123456789,
"blobB64": "BASE64_SIGNED_BLOCK",
"backlog": false
}
```
Если это новая ревизия того же письма, `messageKey` остаётся тем же, а `revisionTimeMs` меняется внутри бинарного блока.
## 6. HTTP `HEAD /f/<hashB64url>`
Проверка, есть ли ciphertext-файл на сервере.
### Ответы
- `200` — файл существует
- `404` — файла нет
## 7. HTTP `GET /f/<hashB64url>`
Отдаёт ciphertext-файл.
### Особенности
- `Content-Type: application/octet-stream`
- файл сейчас доступен публично
- имя файла на диске и в URL — `base64url(SHA-256(ciphertext))`
## 8. HTTP `POST /upload?hash=<hashB64url>&size=<bytes>`
Загружает ciphertext-файл для будущего DM.
### Тело запроса
Raw bytes ciphertext-файла.
### Поведение сервера
- пересчитывает `SHA-256`
- сверяет размер
- сохраняет blob в папку `f/`, если его ещё не было
- если blob уже есть, не перезаписывает его
- создаёт или обновляет запись в `dm_files`
### Успешный ответ
```json
{
"op": "AckSessionDelivery",
"requestId": "ack-001",
"status": 200,
"ok": true,
"payload": {
"messageKey": "incoming-key"
}
"hash": "base64url_sha256",
"size": 245120,
"alreadyExists": false
}
```
---
### Ошибки
## 7. `CallInviteBroadcast`
- `400 / bad hash`
- `400 / bad size`
- `400 / SIZE_MISMATCH`
- `400 / HASH_MISMATCH`
- `400 / UPLOAD_TOO_LARGE`
- `500 / upload_failed`
Требует авторизации. Отправляет приглашение к звонку на активные сессии пользователя `toLogin`.
## 9. `CallInviteBroadcast`
### Запрос
Требует авторизации. Шлёт приглашение к звонку в активные сессии `toLogin`.
```json
{
"op": "CallInviteBroadcast",
"requestId": "call-invite-001",
"payload": {
"toLogin": "bob",
"callId": "call-1",
"type": 100
}
}
```
## 10. `CallSignalToSession`
### Успешный ответ
Требует авторизации. Шлёт сигнал звонка в конкретную сессию.
```json
{
"op": "CallInviteBroadcast",
"requestId": "call-invite-001",
"status": 200,
"ok": true,
"payload": {
"callId": "call-1",
"deliveredWsSessions": 1,
"deliveredFcmSessions": 0,
"deliveredWebPushSessions": 0
}
}
```
## 11. Замечания
---
## 8. `CallSignalToSession`
Требует авторизации. Отправляет сигнал звонка в конкретную сессию получателя.
### Запрос
```json
{
"op": "CallSignalToSession",
"requestId": "call-signal-001",
"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` — повторное сообщение заблокировано.
- Для нового DM-файла сценарий такой: `HEAD /f/<hash>` → при `404` `POST /upload` → затем `SendMessagePair`.
- Сервер хранит только последнюю версию контентного сообщения по `messageKey`.
- Удаление сообщения реализуется новой ревизией с пустым телом и нулём вложений.