diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 5de7d8deb..a08538cfb 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -4234,6 +4234,10 @@ char* dc_msg_get_webxdc_blob (const dc_msg_t* msg, const char* * true if the Webxdc should get internet access; * this is the case i.e. for experimental maps integration. * - self_addr: address to be used for `window.webxdc.selfAddr` in JS land. + * - app_sender_addr: address of the peer who initially shared the webxdc in the chat. + * Can be compared to self_addr to determine whether the app runs for the sender or a receiver. + * - can_only_send_updates_to_app_sender: true if updates sent by the local user + * will only be seen by the app sender. * - send_update_interval: Milliseconds to wait before calling `sendUpdate()` again since the last call. * Should be exposed to `webxdc.sendUpdateInterval` in JS land. * - send_update_max_size: Maximum number of bytes accepted for a serialized update object. diff --git a/deltachat-jsonrpc/src/api/types/webxdc.rs b/deltachat-jsonrpc/src/api/types/webxdc.rs index 28d27a47a..a61641f8f 100644 --- a/deltachat-jsonrpc/src/api/types/webxdc.rs +++ b/deltachat-jsonrpc/src/api/types/webxdc.rs @@ -37,6 +37,11 @@ pub struct WebxdcMessageInfo { internet_access: bool, /// Address to be used for `window.webxdc.selfAddr` in JS land. self_addr: String, + /// Address of the peer who initially shared the webxdc in the chat. + app_sender_addr: String, + /// True if updates sent by the local user + /// will only be seen by the app sender. + can_only_send_updates_to_app_sender: bool, /// Milliseconds to wait before calling `sendUpdate()` again since the last call. /// Should be exposed to `window.sendUpdateInterval` in JS land. send_update_interval: usize, @@ -60,6 +65,8 @@ impl WebxdcMessageInfo { request_integration: _, internet_access, self_addr, + app_sender_addr, + can_only_send_updates_to_app_sender, send_update_interval, send_update_max_size, } = message.get_webxdc_info(context).await?; @@ -72,6 +79,8 @@ impl WebxdcMessageInfo { source_code_url: maybe_empty_string_to_option(source_code_url), internet_access, self_addr, + app_sender_addr, + can_only_send_updates_to_app_sender, send_update_interval, send_update_max_size, }) diff --git a/src/webxdc.rs b/src/webxdc.rs index 29184d23f..71b41c98f 100644 --- a/src/webxdc.rs +++ b/src/webxdc.rs @@ -36,7 +36,7 @@ use tokio::{fs::File, io::BufReader}; use crate::chat::{self, Chat}; use crate::constants::Chattype; -use crate::contact::ContactId; +use crate::contact::{Contact, ContactId}; use crate::context::Context; use crate::events::EventType; use crate::key::self_fingerprint; @@ -111,6 +111,15 @@ pub struct WebxdcInfo { /// Address to be used for `window.webxdc.selfAddr` in JS land. pub self_addr: String, + /// Address of the peer who initially shared the webxdc in the chat. + /// Can be compared to `self_addr` to determine + /// whether the app runs for the sender or a receiver. + pub app_sender_addr: String, + + /// `true` if updates sent by the local user + /// will only be seen by the app sender. + pub can_only_send_updates_to_app_sender: bool, + /// Milliseconds to wait before calling `sendUpdate()` again since the last call. /// Should be exposed to `window.sendUpdateInterval` in JS land. pub send_update_interval: usize, @@ -923,6 +932,12 @@ impl Message { let internet_access = is_integrated; let self_addr = self.get_webxdc_self_addr(context).await?; + let app_sender_addr = self.get_webxdc_app_sender_addr(context).await?; + + let chat = Chat::load_from_db(context, self.chat_id) + .await + .with_context(|| "Failed to load chat from the database")?; + let can_only_send_updates_to_app_sender = chat.typ == Chattype::InBroadcast; Ok(WebxdcInfo { name: if let Some(name) = manifest.name { @@ -961,6 +976,8 @@ impl Message { request_integration, internet_access, self_addr, + app_sender_addr, + can_only_send_updates_to_app_sender, send_update_interval: context.ratelimit.read().await.update_interval(), send_update_max_size: RECOMMENDED_FILE_SIZE as usize, }) @@ -973,6 +990,29 @@ impl Message { Ok(format!("{hash:x}")) } + /// Computes the webxdc address of the message sender. + async fn get_webxdc_app_sender_addr(&self, context: &Context) -> Result { + // UNDEFINED may be preset during drafts or tests, will be SELF on sending + let fingerprint = if self.from_id == ContactId::SELF || self.from_id == ContactId::UNDEFINED + { + self_fingerprint(context).await?.to_string() + } else { + let contact = Contact::get_by_id(context, self.from_id).await?; + contact + .fingerprint() + .with_context(|| { + format!( + "No fingerprint for contact {} (webxdc sender)", + self.from_id + ) + })? + .hex() + }; + let data = format!("{}-{}", fingerprint, self.rfc724_mid); + let hash = Sha256::digest(data.as_bytes()); + Ok(format!("{hash:x}")) + } + /// Get link attached to an info message. /// /// The info message needs to be of type SystemMessage::WebxdcInfoMessage. diff --git a/src/webxdc/webxdc_tests.rs b/src/webxdc/webxdc_tests.rs index 7590a0c33..2228fa9a6 100644 --- a/src/webxdc/webxdc_tests.rs +++ b/src/webxdc/webxdc_tests.rs @@ -12,6 +12,7 @@ use crate::chatlist::Chatlist; use crate::config::Config; use crate::ephemeral; 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}; @@ -2195,3 +2196,44 @@ async fn test_self_addr_consistency() -> Result<()> { assert_eq!(db_msg.get_webxdc_self_addr(alice).await?, self_addr); Ok(()) } + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_webxdc_info_app_sender() -> Result<()> { + let mut tcm = TestContextManager::new(); + let alice = &tcm.alice().await; + let bob = &tcm.bob().await; + + // Alice sends webxdc in a group chat + let alice_chat_id = alice.create_group_with_members("Group", &[bob]).await; + let alice_instance = send_webxdc_instance(alice, alice_chat_id).await?; + let sent1 = alice.pop_sent_msg().await; + let alice_info = alice_instance.get_webxdc_info(alice).await?; + assert_eq!(alice_info.self_addr, alice_info.app_sender_addr); + assert!(!alice_info.can_only_send_updates_to_app_sender); + + // Bob receives group webxdc + let bob_instance = bob.recv_msg(&sent1).await; + let bob_info = bob_instance.get_webxdc_info(bob).await?; + assert_ne!(bob_info.self_addr, bob_info.app_sender_addr); + assert_eq!(bob_info.app_sender_addr, alice_info.self_addr); + assert!(!bob_info.can_only_send_updates_to_app_sender); + + // Alice sends webxdc to broadcast channel + let alice_chat_id = create_broadcast(alice, "Broadcast".to_string()).await?; + let qr = get_securejoin_qr(alice, Some(alice_chat_id)).await.unwrap(); + tcm.exec_securejoin_qr(bob, alice, &qr).await; + let alice_instance = send_webxdc_instance(alice, alice_chat_id).await?; + let sent2 = alice.pop_sent_msg().await; + let alice_info = alice_instance.get_webxdc_info(alice).await?; + assert_eq!(alice_info.self_addr, alice_info.app_sender_addr); + assert!(!alice_info.can_only_send_updates_to_app_sender); + + // Bob receives broadcast webxdc + let bob_instance = bob.recv_msg(&sent2).await; + let bob_info = bob_instance.get_webxdc_info(bob).await?; + assert_ne!(bob_info.self_addr, bob_info.app_sender_addr); + assert_eq!(bob_info.app_sender_addr, alice_info.self_addr); + assert!(bob_info.can_only_send_updates_to_app_sender); + + Ok(()) +}