Интеграция клиента AeroNyx Chat Relay
Клиентский контракт blind relay, presence взаимных контактов, reciprocal read receipts, encrypted reactions, offline queue и возобновляемых media.
Официальный контракт frames и media API для App, frontend, backend и coding agents, реализующих клиент AeroNyx. Relay маршрутизирует только ciphertext и не понимает содержимое.
Неизменяемый принцип конфиденциальности
Relay не может анализировать, хранить или выводить chat/reaction plaintext, voice/media, keys, nonces, waveform, filenames, transcripts, MemChain, packet payload, DNS, destinations, URL, history, wallet traffic или private seeds. Client выполняет E2E до отправки.
E2E content находится в payload_b64 и payload_sig. Visible metadata ограничена type, IDs, receiver/group, timestamps, delivery state, blob size/expiry, access mode и counters.
Принцип конфиденциальности статуса
Online и last seen доступны только при активном P2PContact в обоих направлениях. Backend проверяет обе стороны и не позволяет сканировать public keys. Presence и точный last seen отключаются отдельно.
Hidden result не содержит online и last_seen_ts; reason: not_mutual_contact или presence_hidden.
API конфиденциальности профиля
Client читает profile privacy flags после подключения и синхронизирует UI с backend enforcement. PATCH принимает privacy и старые top-level fields.
GET /api/relay/profile/
PATCH /api/relay/profile/
Authorization: Relay <pubkey>:<timestamp>:<signature>
{
"privacy": {
"presence_enabled": true,
"last_seen_enabled": false,
"read_receipts_enabled": false
}
}
Кадры статуса присутствия
Отправляйте presence_subscribe только контактам. При last_seen_enabled=false показывайте приблизительное состояние или скрывайте время, не вычисляя точное значение по другим сигналам.
{
"type": "presence_subscribe",
"pubkeys": ["contact-pubkey-a", "contact-pubkey-b"]
}
{
"type": "presence_subscribe_ack",
"updates": [{
"pubkey": "contact-pubkey-a",
"visible": true,
"presence_visible": true,
"last_seen_visible": true,
"online": true,
"last_seen_ts": 1780000000,
"reason": "allowed"
}],
"server_ts": 1780000001
}
Взаимное правило уведомлений о прочтении
Read receipts взаимны: отключивший их client не отправляет message_read и не показывает peer read. При отключении любой стороны или отсутствии mutual contact backend suppress frame. Это только metadata.
{
"type": "message_read",
"msg_id": "message-id",
"receiver_pubkey": "original-sender-pubkey",
"timestamp": 1780000200
}
{
"type": "message_read_ack",
"msg_id": "message-id",
"delivered": false,
"suppressed": true,
"reason": "receiver_read_receipts_disabled"
}
Reasons: client_disabled, not_mutual_contact, reader_read_receipts_disabled, receiver_read_receipts_disabled; тот же gate при offline pull.
Реакции эмодзи
Reaction также является E2E ciphertext. Relay маршрутизирует по receiver/membership, deduplicate по reaction_id и использует store-and-forward offline. Aggregate state хранит client.
{
"type": "message_reaction",
"msg_id": "target-message-id",
"receiver_pubkey": "peer-pubkey",
"reaction_id": "unique-reaction-event-id",
"timestamp": 1780000300,
"payload_b64": "e2e-ciphertext",
"payload_sig": "ed25519-signature"
}
Discriminant 12; reaction_id — idempotency/offline ACK key. ACK message_reaction_ack; group: group_message_reaction, group_id, key_version.
Модель зашифрованного медиа-объекта
Voice, images, video и files шифруются до upload. blob_id, key, nonce, duration, waveform, display filename и preview metadata остаются в relay_send.payload_b64, не в открытых полях blob API.
{
"kind": "voice",
"blob_id": "blob-uuid",
"key_b64": "inside-e2e-envelope",
"nonce_b64": "inside-e2e-envelope",
"duration_ms": 43000,
"waveform": [0, 3, 8, 6, 2],
"media_type": "audio/ogg; codecs=opus",
"file_size": 7340032
}
Простая загрузка зашифрованного объекта
Для короткой voice и small image используйте multipart. Server принимает только encrypted bytes; TTL 7 дней, policy 1–30. Download через unguessable capability или authenticated P2P key.
POST /api/relay/blob/
Authorization: Relay <pubkey>:<timestamp>:<signature>
Content-Type: multipart/form-data
| field | required | value |
|---|---|---|
file | true | ciphertext |
media_kind | false | voice, image, video, file, avatar, other |
media_type | false | MIME |
ttl_days | false | 1..30 |
access_mode | false | capability, authenticated |
allowed_downloaders | false | JSON P2P pubkey array |
Simple limit 10 MB; превышение: HTTP 413, error_code=blob_too_large, chunked_max_bytes=104857600.
Возобновляемая загрузка зашифрованного объекта
Сверх simple limit используйте chunk session. Максимум ciphertext 100 MB, повтор chunk index безопасен. Сохраняйте upload_id, chunk_size и completed indexes локально.
1. Создание сеанса загрузки
POST /api/relay/blob/session/
Authorization: Relay <pubkey>:<timestamp>:<signature>
Content-Type: application/json
{
"total_size": 7340032,
"chunk_size": 1048576,
"media_type": "audio/ogg",
"media_kind": "voice",
"ttl_days": 7,
"access_mode": "authenticated",
"allowed_downloaders": ["receiver-pubkey"]
}
2. Загрузка частей
PUT /api/relay/blob/session/{upload_id}/chunk/{chunk_index}/
Authorization: Relay <pubkey>:<timestamp>:<signature>
Content-Type: application/octet-stream
Default chunk 1 MB, max 4 MB; retry того же index заменяет chunk и idempotent.
3. Возобновление после разрыва сети
GET /api/relay/blob/session/{upload_id}/
Authorization: Relay <pubkey>:<timestamp>:<signature>
Читайте missing_chunks и отправляйте только отсутствующие. Session действует 24 часа.
4. Завершение загрузки
POST /api/relay/blob/session/{upload_id}/complete/
Authorization: Relay <pubkey>:<timestamp>:<signature>
Complete проверяет chunks и total bytes; безопасный retry возвращает существующий final blob без duplicate.
5. Отмена загрузки
DELETE /api/relay/blob/session/{upload_id}/
Authorization: Relay <pubkey>:<timestamp>:<signature>
Скачивание зашифрованного объекта
Capability не требует RelayAuth: UUID является bearer capability. Authenticated требует подпись и разрешает uploader или allowed_downloaders. Expired blob удаляется при доступе.
GET /api/relay/blob/{blob_id}/
| HTTP | error_code |
|---|---|
| 400 | blob_id_invalid |
| 401 | auth_required |
| 403 | download_forbidden |
| 404 | blob_not_found |
| 410 | blob_expired |
| 413 | blob_too_large, blob_total_size_too_large, chunk_too_large |
| 409 | upload_incomplete |
Рекомендации по UX клиента
Small file использует simple upload, chunks после max_bytes. После restart запросите missing_chunks. Secrets остаются E2E; 410 blob_expired превращается в resend action.
Порядок интеграции AI-агента
Agent реализует RelayAuth, profile privacy, presence UI, reciprocal read receipts, reaction idempotency, simple blob, chunk resume и reference в relay_send. Server-side chat search не создаётся.
Проверенная двухузловая доставка
Eligible ChatRelay может выбрать network-diverse two-hop path. Source учитывает delivery после проверки signed receipt ожидаемого terminal; middle node маршрутизирует ciphertext.