Integrasi client AeroNyx Chat Relay

AeroNyx19 Juni 20264 menit baca31 tayangan

Client contract untuk blind relay, presence mutual contacts, reciprocal read receipts, encrypted reactions, offline queue, dan resumable encrypted media.

Contract resmi frame dan media API untuk tim App, frontend, backend, dan coding agent yang membuat client AeroNyx. Relay hanya route ciphertext dan tidak memahami konten.

Prinsip privasi yang tidak dapat diubah

Relay tidak boleh menganalisis, menyimpan, atau menyimpulkan chat/reaction plaintext, voice/media, key, nonce, waveform, filename, transcript, MemChain, packet payload, DNS, destination, URL, history, wallet traffic, atau private seed. Client melakukan E2E sebelum transport.

E2E content ada di payload_b64 dan payload_sig. Visible metadata terbatas pada type, IDs, receiver/group, timestamps, delivery state, blob size/expiry, access mode, dan counters.

Prinsip privasi status daring

Online dan last seen hanya terlihat bila P2PContact aktif di dua arah. Backend memeriksa kedua sisi untuk mencegah public key scan. Presence dan exact last seen dapat dimatikan terpisah.

Hidden result tidak memuat online atau last_seen_ts; memakai reason=not_mutual_contact atau reason=presence_hidden.

API privasi profil

Client membaca profile privacy flags saat terhubung dan menyamakan UI dengan backend enforcement. PATCH menerima nested privacy dan top-level fields lama.

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
  }
}

Frame status daring

Kirim presence_subscribe hanya untuk contacts. Saat last_seen_enabled=false, tampilkan status perkiraan atau sembunyikan waktu; jangan simpulkan exact status dari sinyal lain.

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
}

Aturan timbal balik tanda dibaca

Read receipts bersifat reciprocal: pengguna yang mematikan tidak mengirim message_read dan tidak menampilkan peer read. Jika salah satu mematikan atau bukan mutual contact, backend suppress frame. Hanya 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; offline pull memakai gate yang sama.

Reaksi emoji

Reaction juga E2E ciphertext. Relay route berdasarkan receiver/membership, deduplicate dengan reaction_id, dan memakai store-and-forward saat offline. Aggregate state milik 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 adalah idempotency/offline ACK key. ACK message_reaction_ack; group memakai group_message_reaction, group_id, key_version.

Model objek media terenkripsi

Voice, image, video, file dienkripsi sebelum upload. blob_id, key, nonce, duration, waveform, display filename, preview metadata tetap di relay_send.payload_b64, bukan blob API plaintext fields.

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
}

Unggah sederhana objek terenkripsi

Gunakan multipart untuk short voice dan small image. Server hanya menerima encrypted bytes; TTL default 7 hari, policy 1–30. Download melalui unguessable capability atau 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; lebih besar mengembalikan HTTP 413, error_code=blob_too_large, chunked_max_bytes=104857600.

Unggah objek terenkripsi yang dapat dilanjutkan

Di atas simple limit gunakan chunk session. Ciphertext total maksimum 100 MB; retry chunk index yang sama aman. Simpan upload_id, chunk_size, completed indexes secara local.

1. Membuat sesi unggah

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. Mengunggah potongan data

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, maximum 4 MB; retry index sama mengganti chunk lama dan idempotent.

3. Melanjutkan setelah gangguan jaringan

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

Baca missing_chunks, kirim hanya index yang kurang. Session berlaku 24 jam.

4. Menyelesaikan unggahan

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

Complete memeriksa chunks dan total bytes; safe retry mengembalikan final blob lama tanpa duplicate.

5. Membatalkan unggahan

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

Mengunduh objek terenkripsi

Capability tidak perlu RelayAuth karena UUID adalah bearer capability. Authenticated perlu signature dan hanya uploader atau allowed_downloaders dapat mengakses. Expired blob dihapus saat access.

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

Panduan UX client

Small file memakai simple upload, chunks setelah max_bytes. Setelah App restart baca missing_chunks. Simpan secrets di E2E; ubah 410 blob_expired menjadi resend action.

Urutan integrasi AI agent

Agent mengimplementasikan RelayAuth, profile privacy, presence UI, reciprocal reads, reaction idempotency, simple blob, chunk resume, lalu relay_send reference. Jangan membuat server-side chat search.

Pengiriman dua-hop terverifikasi

Eligible ChatRelay dapat memilih network-diverse two-hop path. Source menghitung delivery setelah memverifikasi signed receipt expected terminal; middle node hanya route ciphertext.

Node discovery dan encrypted delivery terverifikasi