feat(mesh): MessageKey + Reply/Reaction variants and sender seq (Phase 2a)
Per-target outbound seq counter on MeshState allocates a monotonic seq
before each typed envelope is encoded; send_typed_wire +
send_channel_typed_wire record it (alongside our own pubkey_hex) on the
Sent MeshMessage so the local store carries the same MessageKey the
receiver will see. TypedEnvelope.with_seq lets the RPC layer stamp the
seq AFTER signing (signature covers t/v/ts only).
New MessageKey struct pairs sender_pubkey+sender_seq as the stable
cross-transport identity. Adds variants 13 Reply and 14 Reaction with
ReplyPayload {target, text} and ReactionPayload {target, emoji}, plus
mesh.send-reply / mesh.send-reaction RPCs and receive-side dispatch
arms that store the payload json for the UI to index.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
a530a906b8
commit
4991c213ae
@@ -507,6 +507,11 @@ impl MeshService {
|
||||
/// Send a typed envelope wire payload and record a rich Sent MeshMessage.
|
||||
/// Used by RPC handlers that transmit invoice/coordinate/alert/etc. so the
|
||||
/// UI sees a proper rich Sent card instead of garbage wire-byte plaintext.
|
||||
///
|
||||
/// `sender_seq` should match the seq encoded into the TypedEnvelope by the
|
||||
/// caller — the RPC handler allocates it via `next_send_seq` before
|
||||
/// building the envelope and threads it through here so the local Sent
|
||||
/// record carries the same MessageKey the receiver will see.
|
||||
pub async fn send_typed_wire(
|
||||
&self,
|
||||
contact_id: u32,
|
||||
@@ -514,13 +519,21 @@ impl MeshService {
|
||||
type_label: &str,
|
||||
display_text: &str,
|
||||
typed_payload: Option<serde_json::Value>,
|
||||
sender_seq: u64,
|
||||
) -> Result<MeshMessage> {
|
||||
self.send_raw_payload(contact_id, wire).await?;
|
||||
Ok(self
|
||||
.record_sent_typed(contact_id, type_label, display_text, typed_payload)
|
||||
.record_sent_typed(contact_id, type_label, display_text, typed_payload, sender_seq)
|
||||
.await)
|
||||
}
|
||||
|
||||
/// Allocate the next outbound seq for a target. Convenience passthrough
|
||||
/// to MeshState::next_send_seq; used by RPC handlers before encoding a
|
||||
/// TypedEnvelope so the seq on the wire matches the Sent record.
|
||||
pub async fn next_send_seq(&self, target: u32) -> u64 {
|
||||
self.state.next_send_seq(target).await
|
||||
}
|
||||
|
||||
/// Broadcast a typed envelope wire payload on a mesh channel and record a
|
||||
/// rich Sent MeshMessage. Bytes are sent directly — do NOT utf8_lossy-encode
|
||||
/// binary envelope bytes before handing them here.
|
||||
@@ -531,6 +544,7 @@ impl MeshService {
|
||||
type_label: &str,
|
||||
display_text: &str,
|
||||
typed_payload: Option<serde_json::Value>,
|
||||
sender_seq: u64,
|
||||
) -> Result<MeshMessage> {
|
||||
let status = self.state.status.read().await;
|
||||
if !status.device_connected {
|
||||
@@ -569,8 +583,8 @@ impl MeshService {
|
||||
encrypted: false,
|
||||
message_type: type_label.to_string(),
|
||||
typed_payload,
|
||||
sender_pubkey: None,
|
||||
sender_seq: None,
|
||||
sender_pubkey: Some(self.our_ed_pubkey_hex.clone()),
|
||||
sender_seq: Some(sender_seq),
|
||||
};
|
||||
self.state.store_message(msg.clone()).await;
|
||||
{
|
||||
@@ -631,6 +645,7 @@ impl MeshService {
|
||||
type_label: &str,
|
||||
display_text: &str,
|
||||
typed_payload: Option<serde_json::Value>,
|
||||
sender_seq: u64,
|
||||
) -> MeshMessage {
|
||||
let msg_id = self.state.next_id().await;
|
||||
let peer_name = self
|
||||
@@ -651,8 +666,8 @@ impl MeshService {
|
||||
encrypted: false,
|
||||
message_type: type_label.to_string(),
|
||||
typed_payload,
|
||||
sender_pubkey: None,
|
||||
sender_seq: None,
|
||||
sender_pubkey: Some(self.our_ed_pubkey_hex.clone()),
|
||||
sender_seq: Some(sender_seq),
|
||||
};
|
||||
self.state.store_message(msg.clone()).await;
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user