Интеграция клиента AeroNyx Chat Relay

AeroNyx19 июня 2026 г.3 мин чтения32 просмотров

Клиентский контракт 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.

http
GET /api/relay/profile/
PATCH /api/relay/profile/
Authorization: Relay <pubkey>:<timestamp>:<signature>
json
{
  "privacy": {
    "presence_enabled": true,
    "last_seen_enabled": false,
    "read_receipts_enabled": false
  }
}

Кадры статуса присутствия

Отправляйте presence_subscribe только контактам. При last_seen_enabled=false показывайте приблизительное состояние или скрывайте время, не вычисляя точное значение по другим сигналам.

json
{
  "type": "presence_subscribe",
  "pubkeys": ["contact-pubkey-a", "contact-pubkey-b"]
}
json
{
  "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.

json
{
  "type": "message_read",
  "msg_id": "message-id",
  "receiver_pubkey": "original-sender-pubkey",
  "timestamp": 1780000200
}
json
{
  "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.

json
{
  "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.

json
{
  "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.

http
POST /api/relay/blob/
Authorization: Relay <pubkey>:<timestamp>:<signature>
Content-Type: multipart/form-data
fieldrequiredvalue
filetrueciphertext
media_kindfalsevoice, image, video, file, avatar, other
media_typefalseMIME
ttl_daysfalse1..30
access_modefalsecapability, authenticated
allowed_downloadersfalseJSON 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. Создание сеанса загрузки

http
POST /api/relay/blob/session/
Authorization: Relay <pubkey>:<timestamp>:<signature>
Content-Type: application/json
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. Загрузка частей

http
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. Возобновление после разрыва сети

http
GET /api/relay/blob/session/{upload_id}/
Authorization: Relay <pubkey>:<timestamp>:<signature>

Читайте missing_chunks и отправляйте только отсутствующие. Session действует 24 часа.

4. Завершение загрузки

http
POST /api/relay/blob/session/{upload_id}/complete/
Authorization: Relay <pubkey>:<timestamp>:<signature>

Complete проверяет chunks и total bytes; безопасный retry возвращает существующий final blob без duplicate.

5. Отмена загрузки

http
DELETE /api/relay/blob/session/{upload_id}/
Authorization: Relay <pubkey>:<timestamp>:<signature>

Скачивание зашифрованного объекта

Capability не требует RelayAuth: UUID является bearer capability. Authenticated требует подпись и разрешает uploader или allowed_downloaders. Expired blob удаляется при доступе.

http
GET /api/relay/blob/{blob_id}/
HTTPerror_code
400blob_id_invalid
401auth_required
403download_forbidden
404blob_not_found
410blob_expired
413blob_too_large, blob_total_size_too_large, chunk_too_large
409upload_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.

Обнаружение узлов и проверяемая зашифрованная доставка