feat(mesh): Telegram primitives pass + attachment transport router
Bundles the Phase 2b/3/4/5 work that accumulated across prior sessions and the new attachment chunking router from this session. Everything ships in one shot so the full mesh surface stays coherent on-wire. Telegram primitives (variants 13–18, 20–22): - Reply / Reaction / ReadReceipt / Forward / Edit / Delete - Presence heartbeat + last-seen tracking - ChannelInvite + ContactCard payload types - MessageKey (sender_pubkey, sender_seq) as cross-transport identity - Action menu, reply banner, edit banner, tombstones, (edited) marker - Debounced auto-read-receipts on scroll + message arrival Activated prototypes (Phase 4): - PsbtHash send RPC - Contacts CRUD (in-memory alias/notes/pinned/blocked) - Outbox 📤 badge, rotate-prekeys button - Chunked send fallback (MCIIXXTT framing) as auto-failover inside send_typed_wire when a typed wire exceeds the LoRa per-frame budget Unified inbox (Phase 1): - conversations.list + conversations.messages RPCs (UI collapse deferred) Attachment transport router (new this session): - ContentInline variant 23 + ContentInlinePayload carrying file bytes directly in the envelope for small files with no Tor path - mesh.send-content-inline RPC — mirrors to local BlobStore, rides send_typed_wire which auto-chunks over MCIIXXTT framing (~2.3 KB cap) - mesh.transport-advice RPC as single source of truth for tier decisions: auto-mesh / choose / tor-only / impossible - Receive arm writes inline bytes to local BlobStore so the existing content_ref card renderer handles both transports uniformly - MeshState.blob_store field + order-independent propagation from RpcHandler::set_blob_store / set_mesh_service - Frontend handleAttachFile calls advice first, branches into silent auto-send, transport-chooser modal, Tor-only path, or red error - Transport modal with 📡 mesh / 🧅 Tor options + ETA + disabled state when peer has no Tor reachability Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -407,6 +407,49 @@ export const useMeshStore = defineStore('mesh', () => {
|
||||
}
|
||||
}
|
||||
|
||||
async function transportAdvice(contactId: number, size: number) {
|
||||
return rpcClient.call<{
|
||||
tier: 'auto-mesh' | 'choose' | 'tor-only' | 'impossible'
|
||||
est_seconds: number
|
||||
has_tor: boolean
|
||||
reason: string
|
||||
size: number
|
||||
mesh_auto_max: number
|
||||
mesh_hard_max: number
|
||||
}>({
|
||||
method: 'mesh.transport-advice',
|
||||
params: { contact_id: contactId, size },
|
||||
})
|
||||
}
|
||||
|
||||
async function sendContentInline(
|
||||
contactId: number,
|
||||
mime: string,
|
||||
bytes: Uint8Array,
|
||||
filename?: string,
|
||||
caption?: string,
|
||||
) {
|
||||
try {
|
||||
sending.value = true
|
||||
error.value = null
|
||||
// Base64-encode bytes for JSON transport.
|
||||
let binary = ''
|
||||
for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]!)
|
||||
const bytes_b64 = btoa(binary)
|
||||
const res = await rpcClient.call<{ sent: boolean; message_id: number; cid: string; size: number }>({
|
||||
method: 'mesh.send-content-inline',
|
||||
params: { contact_id: contactId, mime, filename, caption, bytes_b64 },
|
||||
})
|
||||
if (res.sent) await fetchMessages()
|
||||
return res
|
||||
} catch (err: unknown) {
|
||||
error.value = err instanceof Error ? err.message : 'Failed to send inline content'
|
||||
throw err
|
||||
} finally {
|
||||
sending.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function sendReply(contactId: number, targetPubkey: string, targetSeq: number, text: string) {
|
||||
sending.value = true
|
||||
try {
|
||||
@@ -633,6 +676,8 @@ export const useMeshStore = defineStore('mesh', () => {
|
||||
sendCoordinate,
|
||||
sendAlert,
|
||||
sendContent,
|
||||
sendContentInline,
|
||||
transportAdvice,
|
||||
fetchContent,
|
||||
sendReply,
|
||||
sendReaction,
|
||||
|
||||
Reference in New Issue
Block a user