feat(mesh): rich typed Sent records and echo dedup

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>
This commit is contained in:
Dorian
2026-04-13 08:01:10 -04:00
co-authored by Claude Opus 4.6
parent 18284e1592
commit 3ed9243c50
8 changed files with 244 additions and 32 deletions
@@ -38,8 +38,15 @@ impl RpcHandler {
.as_ref()
.ok_or_else(|| anyhow::anyhow!("Mesh service not running"))?;
let wire_str = String::from_utf8_lossy(&wire).to_string();
let msg = svc.send_message(contact_id, &wire_str).await?;
let display = format!(
"Invoice: {} sats{}",
amount_sats,
memo.as_ref().map(|m| format!("{}", m)).unwrap_or_default()
);
let typed_json = serde_json::to_value(&invoice).ok();
let msg = svc
.send_typed_wire(contact_id, wire, "invoice", &display, typed_json)
.await?;
info!(contact_id, amount_sats, "Sent invoice over mesh");
Ok(serde_json::json!({
@@ -77,8 +84,16 @@ impl RpcHandler {
.as_ref()
.ok_or_else(|| anyhow::anyhow!("Mesh service not running"))?;
let wire_str = String::from_utf8_lossy(&wire).to_string();
let msg = svc.send_message(contact_id, &wire_str).await?;
let display = format!(
"Location: {:.6}, {:.6}{}",
coord.lat_degrees(),
coord.lng_degrees(),
coord.label.as_ref().map(|l| format!(" ({})", l)).unwrap_or_default()
);
let typed_json = serde_json::to_value(&coord).ok();
let msg = svc
.send_typed_wire(contact_id, wire, "coordinate", &display, typed_json)
.await?;
info!(contact_id, "Sent coordinate over mesh");
Ok(serde_json::json!({
@@ -153,13 +168,23 @@ impl RpcHandler {
.as_ref()
.ok_or_else(|| anyhow::anyhow!("Mesh service not running"))?;
let wire_str = String::from_utf8_lossy(&wire).to_string();
let display = alert.message.clone();
let typed_json = serde_json::to_value(&alert).ok();
if broadcast {
// Send on channel (all peers)
svc.send_message(0, &wire_str).await?;
// Send on public channel (all peers) as raw bytes so the binary
// envelope is not corrupted by utf8 conversion.
svc.send_channel_typed_wire(0, wire, "alert", &display, typed_json.clone())
.await?;
info!(alert_type = alert_type_str, "Broadcast alert over mesh");
} else if let Some(contact_id) = params["contact_id"].as_u64() {
svc.send_message(contact_id as u32, &wire_str).await?;
svc.send_typed_wire(
contact_id as u32,
wire,
"alert",
&display,
typed_json,
)
.await?;
info!(contact_id, alert_type = alert_type_str, "Sent alert to peer");
} else {
anyhow::bail!("Must specify contact_id or broadcast: true");