mesh.send-content was failing with "Message too large for LoRa: 624
bytes (max 160)" because a single ContentRef envelope (cid + onion +
cap_token + thumb) dwarfs a LoRa frame. Add a federation Tor fallback:
- New POST /archipelago/mesh-typed endpoint accepts
{from_pubkey, typed_envelope_b64, signature}, verifies ed25519 over
the raw wire bytes, and injects the decoded envelope into MeshState
via a new MeshService::inject_typed_from_federation helper. This
shares the same dispatch match as LoRa receives via a new pub(crate)
handle_typed_envelope_direct extracted from handle_typed_message.
- MeshService::send_typed_wire_via_federation POSTs the signed wire to
a peer's onion over TOR_SOCKS_PROXY and records a local Sent record.
- handle_mesh_send_content looks up the peer's onion in federation
storage and routes via federation when available, falling back to
LoRa only when no federation presence is known (still fails on
oversized — chunking is Phase 4).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Marketplace app iframes (Penpot, Gitea, IndeedHub, ...) can POST a file
to /api/share-to-mesh and postMessage the returned CID to the parent
window. The endpoint mirrors /api/blob's body format but adds CORS for
the requesting app origin (any port on host_ip) so proxied apps can
reach it with credentials:'include'. Session cookie is still the primary
auth; the origin check is a sanity guard.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds attachment sharing over the mesh: a ContentRef envelope (variant 19)
carries the blob CID, size, mime, optional thumb/caption, and a per-peer
HMAC capability URL so the recipient fetches the full blob out-of-band via
`GET {sender_onion}/blob/{cid}?cap=..&exp=..&peer=..`. BlobStore is shared
from ApiHandler into RpcHandler so mesh.send-content and mesh.fetch-content
(reqwest via TOR_SOCKS_PROXY) hit the same store and cap_key.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Plumbs the BlobStore from blobs.rs into ApiHandler. The HMAC capability
key is derived from the node's Ed25519 signing key via a domain-separated
SHA-256 — rotating the identity rotates every outstanding cap (intentional
so a replaced node cannot honour old tokens).
New routes (added to nginx config in both server blocks):
- POST /api/blob — session-authenticated raw upload, returns
{cid, size, mime, filename, self_test_url}. The self_test_url is a
pre-signed cap pointing at the local node so the UI can verify the
round-trip without needing a peer pubkey.
- GET /blob/<cid>?cap=<hex>&exp=<epoch>&peer=<pubkey> — peer-facing,
HMAC-verified in constant time, expiry-checked, then streams bytes.
Mesh.vue gets a minimal "Attachment test (blob store)" section: file
picker → upload → cid display → "Verify round-trip" and "Open in new
tab" buttons. This validates Phase 3a end-to-end before we layer the
ContentRef typed envelope variant on top.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds message_type + typed_payload (JSON) to MeshMessage so the UI can
render invoice/alert/coordinate/tx/lightning messages as structured
cards in both directions instead of showing raw wire bytes on the
Sent side. RPC handlers now route through send_typed_wire /
send_channel_typed_wire which transmit the binary envelope directly
(no utf8_lossy corruption) and record a rich Sent MeshMessage.
Also: store_message deduplicates echo-back doubles (20-msg lookback,
30s window), from_name is plumbed through the federation Incoming
path, and peer_dest_prefix / send_raw_payload are factored out of
send_message.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
App catalog served from Gitea repos (app-catalog) with 35 apps.
Nodes fetch catalog dynamically — new apps appear without frontend
rebuild. Test app added and removed to verify pipeline.
Gitea manifest updated with internal_port/nginx_proxy for iframe.
Updated catalog.json, nginx configs, app session configs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move handle_remote_relay from remote_input.rs to remote_relay.rs
- Android: lifecycle-aware WebSocket reconnection on app resume
- Cleaner module boundaries between xdotool input and browser relay
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- NESPortraitController layout for vertical phone use
- Updated NESController and NESKeyboard components
- Remote input WebSocket handler and API route registration
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>