Integrasi client AeroNyx Chat Relay
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.
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
}
}
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.
{
"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
}
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.
{
"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; 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.
{
"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.
{
"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.
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; 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
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. Mengunggah potongan data
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
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
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
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.
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 |
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.