Skip to content
Closed
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
4 changes: 4 additions & 0 deletions deltachat-ffi/deltachat.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment on lines +4239 to +4240
Copy link
Copy Markdown
Collaborator

@Amzd Amzd Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the spec says when this is true the update sender also sees their own updates, not just the app sender

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, that question pop up for me as well, see spec. we should be more explicit on that

* - 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.
Expand Down
9 changes: 9 additions & 0 deletions deltachat-jsonrpc/src/api/types/webxdc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment on lines +42 to +43
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

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,
Expand All @@ -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?;
Expand All @@ -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,
})
Expand Down
42 changes: 41 additions & 1 deletion src/webxdc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
})
Expand All @@ -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<String> {
// 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.
Expand Down
42 changes: 42 additions & 0 deletions src/webxdc/webxdc_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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(())
}
Loading