p2p_chat/ui/runner/actions/commands/
send.rs1use std::collections::HashSet;
3
4use anyhow::Result;
5use chrono::Utc;
6use libp2p::PeerId;
7use rand::random;
8use tracing::debug;
9use uuid::Uuid;
10
11use crate::cli::commands::MailboxDeliveryResult;
12use crate::types::{DeliveryStatus, Friend, Message};
13
14use super::super::context::CommandContext;
15use super::super::resolver::resolve_peer_id;
16
17pub async fn handle_send(parts: &[&str], context: &CommandContext) -> Result<()> {
33 if parts.len() < 3 {
34 context.emit_chat("Usage: send <peer_id_or_nickname> <message...>");
35 return Ok(());
36 }
37
38 let destination = parts[1];
39 let message_body = parts[2..].join(" ");
40
41 let recipient_peer_id = match resolve_peer_id(destination, context).await {
42 Ok(id) => id,
43 Err(e) => {
44 context.emit_chat(format!("❌ {}", e));
45 return Ok(());
46 }
47 };
48
49 let friend = match context
50 .node()
51 .friends
52 .get_friend(&recipient_peer_id)
53 .await?
54 {
55 Some(f) => f,
56 None => {
57 context.emit_chat("❌ Friend not found. Add them first with 'friend' command.");
58 return Ok(());
59 }
60 };
61
62 let encrypted_content = match context
63 .node()
64 .identity
65 .encrypt_for(&friend.e2e_public_key, message_body.as_bytes())
66 {
67 Ok(content) => content,
68 Err(e) => {
69 context.emit_chat(format!("❌ Encryption failed: {}", e));
70 return Ok(());
71 }
72 };
73
74 let message = Message {
75 id: Uuid::new_v4(),
76 sender: context.node().identity.peer_id,
77 recipient: recipient_peer_id,
78 timestamp: Utc::now().timestamp_millis(),
79 content: encrypted_content,
80 nonce: random(),
81 delivery_status: DeliveryStatus::Sending,
82 };
83
84 context
86 .node()
87 .history
88 .store_message(message.clone())
89 .await?;
90 context.node().outbox.add_pending(message.clone()).await?;
91
92 if attempt_direct_delivery(destination, &message, context).await? {
94 return Ok(());
95 }
96
97 attempt_mailbox_delivery(destination, &message, &friend, context).await
99}
100
101async fn attempt_direct_delivery(
120 destination: &str,
121 message: &Message,
122 context: &CommandContext,
123) -> Result<bool> {
124 match context
125 .node()
126 .network
127 .send_message(message.recipient, message.clone())
128 .await
129 {
130 Ok(()) => {
131 context.node().outbox.remove_pending(&message.id).await?;
132 context.emit_chat(format!("✅ Message sent directly to {}", destination));
133 Ok(true)
134 }
135 Err(_) => {
136 context.emit_chat(format!(
137 "⚠️ {} is offline. Attempting mailbox delivery...",
138 destination
139 ));
140 Ok(false)
141 }
142 }
143}
144
145async fn attempt_mailbox_delivery(
161 destination: &str,
162 message: &Message,
163 friend: &Friend,
164 context: &CommandContext,
165) -> Result<()> {
166 let providers = {
167 let mut sync_engine = context.node().sync_engine.lock().await;
168 let current = sync_engine.get_mailbox_providers().clone();
169 if current.is_empty() {
170 debug!("No known mailboxes, triggering discovery");
171 if let Err(e) = sync_engine.discover_mailboxes().await {
172 debug!("Mailbox discovery failed: {}", e);
173 }
174 sync_engine.get_mailbox_providers().clone()
175 } else {
176 current
177 }
178 };
179
180 if !providers.is_empty() {
181 return deliver_via_mailboxes(destination, message, friend, context, providers.into_iter())
182 .await;
183 }
184
185 let emergency_set: HashSet<PeerId> = {
186 let sync_engine = context.node().sync_engine.lock().await;
187 sync_engine
188 .get_emergency_mailboxes()
189 .await
190 .into_iter()
191 .collect()
192 };
193
194 if emergency_set.is_empty() {
195 context.emit_chat(format!(
196 "⚠️ No mailboxes or connected peers available. Message queued for when {} comes online",
197 destination
198 ));
199 return Ok(());
200 }
201
202 deliver_via_mailboxes(
203 destination,
204 message,
205 friend,
206 context,
207 emergency_set.into_iter(),
208 )
209 .await
210}
211
212async fn deliver_via_mailboxes<I>(
226 destination: &str,
227 message: &Message,
228 friend: &Friend,
229 context: &CommandContext,
230 providers: I,
231) -> Result<()>
232where
233 I: IntoIterator<Item = PeerId>,
234{
235 let provider_set: HashSet<PeerId> = providers.into_iter().collect();
236 match context
237 .node()
238 .forward_to_mailboxes(message, friend, &provider_set)
239 .await
240 {
241 Ok(MailboxDeliveryResult::Success(count)) => {
242 context.node().outbox.remove_pending(&message.id).await?;
243 context.emit_chat(format!(
244 "📬 Message stored in {} network mailbox(es) for {}",
245 count, destination
246 ));
247 }
248 Ok(MailboxDeliveryResult::Failure) => {
249 context.emit_chat(format!(
250 "⚠️ Mailbox delivery failed. Message queued for retry when {} comes online",
251 destination
252 ));
253 }
254 Err(e) => {
255 context.emit_chat(format!("⚠️ Mailbox error: {}. Message queued for retry", e));
256 }
257 }
258
259 Ok(())
260}