Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2700,8 +2700,10 @@ async fn prepare_send_msg(
.unwrap_or_default(),
_ => false,
};
if let Some(reason) = chat.why_cant_send_ex(context, &skip_fn).await? {
bail!("Cannot send to {chat_id}: {reason}");
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 prepare sending to {chat_id}: {reason}");
}

// Check a quote reply is not leaking data from other chats.
Expand Down
16 changes: 11 additions & 5 deletions src/webxdc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -542,10 +542,16 @@ 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}")
})? {
bail!("Cannot send to {chat_id}: {reason}.");

let skip_fn = |reason: &CantSendReason| matches!(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 update to {chat_id}: {reason}.");
}

let send_now = !matches!(
Expand Down
88 changes: 88 additions & 0 deletions src/webxdc/webxdc_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2235,3 +2235,91 @@ async fn test_webxdc_info_app_sender() -> Result<()> {

Ok(())
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_webxdc_broadcast_channel_updates() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = &tcm.alice().await;
let bob = &tcm.bob().await;
let charlie = &tcm.charlie().await;

// Alice creates a broadcast channel, Bob and Charlie join via securejoin QR
let alice_chat_id = create_broadcast(alice, "Channel".to_string()).await?;
let qr = get_securejoin_qr(alice, Some(alice_chat_id)).await?;
tcm.exec_securejoin_qr(bob, alice, &qr).await;
tcm.exec_securejoin_qr(charlie, alice, &qr).await;

// Alice sends a webxdc app to the channel, Bob and Charlie both receive it
let alice_instance = send_webxdc_instance(alice, alice_chat_id).await?;
let sent1 = alice.pop_sent_msg().await;

let bob_instance = bob.recv_msg(&sent1).await;
assert_eq!(bob_instance.viewtype, Viewtype::Webxdc);

let charlie_instance = charlie.recv_msg(&sent1).await;
assert_eq!(charlie_instance.viewtype, Viewtype::Webxdc);

// Verify broadcast context flags
let alice_info = alice_instance.get_webxdc_info(alice).await?;
let bob_info = bob_instance.get_webxdc_info(bob).await?;
let charlie_info = charlie_instance.get_webxdc_info(charlie).await?;
assert!(alice_info.is_app_sender);
assert!(alice_info.is_broadcast);
assert!(!bob_info.is_app_sender);
assert!(bob_info.is_broadcast);
assert!(!charlie_info.is_app_sender);
assert!(charlie_info.is_broadcast);

// Alice sends a webxdc update, Bob and Charlie both receive it
alice
.send_webxdc_status_update(alice_instance.id, r#"{"payload": "hello from alice"}"#)
.await?;
alice.flush_status_updates().await?;
let sent2 = alice.pop_sent_msg().await;

bob.recv_msg_trash(&sent2).await;
assert_eq!(
bob.get_webxdc_status_updates(bob_instance.id, StatusUpdateSerial(0))
.await?,
r#"[{"payload":"hello from alice","serial":1,"max_serial":1}]"#
);

charlie.recv_msg_trash(&sent2).await;
assert_eq!(
charlie
.get_webxdc_status_updates(charlie_instance.id, StatusUpdateSerial(0))
.await?,
r#"[{"payload":"hello from alice","serial":1,"max_serial":1}]"#
);

// Bob sends a webxdc update, Bob sees ones own update, Alice receives it, but Charlie does not
bob.send_webxdc_status_update(bob_instance.id, r#"{"payload": "hello from bob"}"#)
.await?;
bob.flush_status_updates().await?;
let sent3 = bob.pop_sent_msg().await;
assert_eq!(
bob.get_webxdc_status_updates(bob_instance.id, StatusUpdateSerial(0))
.await?,
r#"[{"payload":"hello from alice","serial":1,"max_serial":2},
{"payload":"hello from bob","serial":2,"max_serial":2}]"#
);

alice.recv_msg_trash(&sent3).await;
assert_eq!(
alice
.get_webxdc_status_updates(alice_instance.id, StatusUpdateSerial(0))
.await?,
r#"[{"payload":"hello from alice","serial":1,"max_serial":2},
{"payload":"hello from bob","serial":2,"max_serial":2}]"#
);

charlie.recv_msg_trash(&sent3).await;
assert_eq!(
charlie
.get_webxdc_status_updates(charlie_instance.id, StatusUpdateSerial(0))
.await?,
r#"[{"payload":"hello from alice","serial":1,"max_serial":1}]"#
);

Ok(())
}