Tích hợp client AeroNyx Chat Relay
Client contract cho blind relay, presence giữa mutual contacts, reciprocal read receipts, encrypted reactions, offline queue và resumable media.
Contract chính thức về frame và media API cho đội App, frontend, backend và coding agent xây client AeroNyx. Relay chỉ route ciphertext và không hiểu nội dung.
Nguyên tắc riêng tư không thể thay đổi
Relay không được phân tích, lưu hay suy ra chat/reaction plaintext, voice/media, key, nonce, waveform, filename, transcript, MemChain, packet payload, DNS, destination, URL, history, wallet traffic hoặc private seed. Client mã hóa E2E trước transport.
E2E content nằm trong payload_b64 và payload_sig. Visible metadata chỉ gồm type, IDs, receiver/group, timestamps, delivery state, blob size/expiry, access mode và counters.
Nguyên tắc riêng tư của trạng thái trực tuyến
Online và last seen chỉ hiện khi P2PContact hoạt động theo cả hai chiều. Backend kiểm tra hai phía để ngăn public key scan. Presence và exact last seen có thể tắt riêng.
Hidden result không có online hay last_seen_ts; trả reason=not_mutual_contact hoặc reason=presence_hidden.
API quyền riêng tư hồ sơ
Client đọc profile privacy flags khi kết nối và giữ UI phù hợp backend enforcement. PATCH nhận nested privacy và top-level fields cũ.
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
}
}
Khung trạng thái trực tuyến
Chỉ gửi presence_subscribe cho contacts. Khi last_seen_enabled=false, hiển thị trạng thái gần đúng hoặc ẩn thời gian; không suy ra exact status từ tín hiệu khác.
{
"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
}
Quy tắc biên nhận đã đọc đối ứng
Read receipts là reciprocal: người tắt không gửi message_read và không hiển thị peer read. Nếu một bên tắt hoặc không phải mutual contact, backend suppress frame. Chỉ có 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 dùng cùng gate.
Phản ứng biểu tượng cảm xúc
Reaction cũng là E2E ciphertext. Relay route theo receiver/membership, deduplicate bằng reaction_id, dùng store-and-forward khi offline. Aggregate state thuộc 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 là idempotency/offline ACK key. ACK message_reaction_ack; group dùng group_message_reaction, group_id, key_version.
Mô hình đối tượng phương tiện mã hóa
Voice, image, video, file được mã hóa trước upload. blob_id, key, nonce, duration, waveform, display filename, preview metadata nằm trong relay_send.payload_b64, không ở 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
}
Tải lên đơn giản đối tượng mã hóa
Dùng multipart cho short voice và small image. Server chỉ nhận encrypted bytes; TTL mặc định 7 ngày, policy 1–30. Download bằng unguessable capability hoặc 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; quá giới hạn trả HTTP 413, error_code=blob_too_large, chunked_max_bytes=104857600.
Tải lên có thể tiếp tục đối tượng mã hóa
Trên simple limit dùng chunk session. Ciphertext total tối đa 100 MB; retry cùng chunk index là an toàn. Lưu local upload_id, chunk_size, completed indexes.
1. Tạo phiên tải lên
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. Tải các phần dữ liệu
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 cùng index thay chunk cũ và idempotent.
3. Tiếp tục sau gián đoạn mạng
GET /api/relay/blob/session/{upload_id}/
Authorization: Relay <pubkey>:<timestamp>:<signature>
Đọc missing_chunks, chỉ gửi index thiếu. Session hiệu lực 24 giờ.
4. Hoàn tất tải lên
POST /api/relay/blob/session/{upload_id}/complete/
Authorization: Relay <pubkey>:<timestamp>:<signature>
Complete kiểm tra chunks và total bytes; safe retry trả final blob cũ, không tạo duplicate.
5. Hủy tải lên
DELETE /api/relay/blob/session/{upload_id}/
Authorization: Relay <pubkey>:<timestamp>:<signature>
Tải xuống đối tượng mã hóa
Capability không cần RelayAuth vì UUID là bearer capability. Authenticated cần chữ ký và chỉ uploader hoặc allowed_downloaders được truy cập. Expired blob được xóa khi 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 |
Hướng dẫn UX cho client
Small file dùng simple upload, trên max_bytes mới dùng chunks. Sau App restart đọc missing_chunks. Giữ secrets trong E2E; biến 410 blob_expired thành resend action.
Thứ tự tích hợp AI agent
Agent triển khai RelayAuth, profile privacy, presence UI, reciprocal reads, reaction idempotency, simple blob, chunk resume, rồi relay_send reference. Không tạo server-side chat search.
Phân phối hai bước nhảy đã xác minh
Eligible ChatRelay có thể chọn network-diverse two-hop path. Source chỉ tính delivery sau khi xác minh signed receipt của expected terminal; middle node chỉ route ciphertext.