From de0b23d0632ed820797107d9c28c25f24d835fe6 Mon Sep 17 00:00:00 2001 From: iequidoo Date: Fri, 6 Feb 2026 05:55:46 -0300 Subject: [PATCH] feat: Send webxdc status updates in InBroadcast-s (#7679) They are already applied by the broadcast owner and other subscriber's devices, no changes are needed on the receiver side. --- src/chat.rs | 4 ++- src/webxdc.rs | 13 +++++++--- src/webxdc/webxdc_tests.rs | 51 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 5 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index 546f069d7..6fe43637e 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -2697,7 +2697,9 @@ async fn prepare_send_msg( .unwrap_or_default(), _ => false, }; - if let Some(reason) = chat.why_cant_send_ex(context, &skip_fn).await? { + if msg.param.get_cmd() == SystemMessage::WebxdcStatusUpdate { + // Already checked in `send_webxdc_status_update_struct()`. + } else if let Some(reason) = chat.why_cant_send_ex(context, &skip_fn).await? { bail!("Cannot send to {chat_id}: {reason}"); } diff --git a/src/webxdc.rs b/src/webxdc.rs index 29184d23f..183ea4aba 100644 --- a/src/webxdc.rs +++ b/src/webxdc.rs @@ -34,7 +34,7 @@ use serde_json::Value; use sha2::{Digest, Sha256}; use tokio::{fs::File, io::BufReader}; -use crate::chat::{self, Chat}; +use crate::chat::{self, CantSendReason, Chat}; use crate::constants::Chattype; use crate::contact::ContactId; use crate::context::Context; @@ -536,9 +536,14 @@ impl Context { let chat = Chat::load_from_db(self, chat_id) .await .with_context(|| format!("Failed to load chat {chat_id} from the database"))?; - if let Some(reason) = chat.why_cant_send(self).await.with_context(|| { - format!("Failed to check if webxdc update can be sent to chat {chat_id}") - })? { + let skip_fn = |reason: &CantSendReason| *reason == CantSendReason::InBroadcast; + if let Some(reason) = chat + .why_cant_send_ex(self, &skip_fn) + .await + .with_context(|| { + format!("Failed to check if webxdc update can be sent to chat {chat_id}") + })? + { bail!("Cannot send to {chat_id}: {reason}."); } diff --git a/src/webxdc/webxdc_tests.rs b/src/webxdc/webxdc_tests.rs index cf72c459d..00ff207bc 100644 --- a/src/webxdc/webxdc_tests.rs +++ b/src/webxdc/webxdc_tests.rs @@ -11,7 +11,9 @@ use crate::chat::{ use crate::chatlist::Chatlist; use crate::config::Config; use crate::ephemeral; +use crate::imex::{BackupProvider, get_backup}; use crate::receive_imf::receive_imf; +use crate::securejoin::get_securejoin_qr; use crate::test_utils::{E2EE_INFO_MSGS, TestContext, TestContextManager}; use crate::tools::{self, SystemTime}; use crate::{message, sql}; @@ -1588,6 +1590,55 @@ async fn test_webxdc_no_internet_access() -> Result<()> { Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_in_broadcast_send_status_update() -> Result<()> { + let mut tcm = TestContextManager::new(); + let alice = &tcm.alice().await; + let bob = &tcm.bob().await; + + let alice_chat_id = create_broadcast(alice, "bc".to_string()).await?; + let qr = get_securejoin_qr(alice, Some(alice_chat_id)).await?; + let bob_chat_id = tcm.exec_securejoin_qr(bob, alice, &qr).await; + + let alice_instance = send_webxdc_instance(alice, alice_chat_id).await?; + let sent_msg = alice.pop_sent_msg().await; + let bob_instance = bob.recv_msg(&sent_msg).await; + assert_eq!(bob_instance.chat_id, bob_chat_id); + + let provider = BackupProvider::prepare(bob).await?; + let bob1 = &tcm.unconfigured().await; + get_backup(bob1, provider.qr()).await?; + + bob.send_webxdc_status_update(bob_instance.id, r#"{"payload":42}"#) + .await?; + bob.flush_status_updates().await?; + let sent_msg = bob.pop_sent_msg().await; + + alice.recv_msg_trash(&sent_msg).await; + assert_eq!( + alice + .get_webxdc_status_updates(alice_instance.id, StatusUpdateSerial(0)) + .await?, + r#"[{"payload":42,"serial":1,"max_serial":1}]"# + ); + + bob1.recv_msg_trash(&sent_msg).await; + assert_eq!( + bob1.get_webxdc_status_updates(bob_instance.id, StatusUpdateSerial(0)) + .await?, + r#"[{"payload":42,"serial":1,"max_serial":1}]"# + ); + + // Non-subscriber's status updates are rejected. + let alice_bob_id = alice.add_or_lookup_contact_id(bob).await; + remove_contact_from_chat(alice, alice_chat_id, alice_bob_id).await?; + alice.pop_sent_msg().await; + let status = + helper_send_receive_status_update(bob, alice, &bob_instance, &alice_instance).await?; + assert_eq!(status, r#"[{"payload":42,"serial":1,"max_serial":1}]"#); + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_webxdc_chatlist_summary() -> Result<()> { let t = TestContext::new_alice().await;