//! Offline outbox — queue messages for deferred delivery. use bytes::Bytes; use prost::Message; use tracing::{debug, warn}; use quicproquo_proto::method_ids; use quicproquo_proto::qpq::v1::{EnqueueRequest, EnqueueResponse}; use quicproquo_rpc::client::RpcClient; use crate::conversation::{ConversationId, ConversationStore}; use crate::error::SdkError; /// Queue a message for sending when connectivity is restored. pub fn queue_outbox( conv_store: &ConversationStore, conv_id: &ConversationId, recipient_key: &[u8], payload: &[u8], ) -> Result<(), SdkError> { conv_store .enqueue_outbox(conv_id, recipient_key, payload) .map_err(|e| SdkError::Storage(format!("enqueue outbox: {e}"))) } /// Process all pending outbox entries — send them to the server. /// /// Returns the number of entries successfully sent. pub async fn flush_outbox( rpc: &RpcClient, conv_store: &ConversationStore, ) -> Result { let entries = conv_store .load_pending_outbox() .map_err(|e| SdkError::Storage(format!("load outbox: {e}")))?; let mut sent = 0usize; for entry in &entries { let req = EnqueueRequest { recipient_key: entry.recipient_key.clone(), payload: entry.payload.clone(), channel_id: Vec::new(), ttl_secs: 0, }; match rpc .call(method_ids::ENQUEUE, Bytes::from(req.encode_to_vec())) .await { Ok(resp_bytes) => { if let Err(e) = EnqueueResponse::decode(resp_bytes) { warn!(outbox_id = entry.id, "decode enqueue response: {e}"); } conv_store .mark_outbox_sent(entry.id) .map_err(|e| SdkError::Storage(format!("mark_outbox_sent: {e}")))?; sent += 1; debug!(outbox_id = entry.id, "outbox entry sent"); } Err(e) => { warn!(outbox_id = entry.id, "outbox send failed: {e}"); conv_store .mark_outbox_failed(entry.id, entry.retry_count + 1) .map_err(|e| SdkError::Storage(format!("mark_outbox_failed: {e}")))?; } } } Ok(sent) } /// Get the number of pending outbox entries. pub fn outbox_count(conv_store: &ConversationStore) -> Result { conv_store .count_pending_outbox() .map_err(|e| SdkError::Storage(format!("count outbox: {e}"))) }