Repository: deltachat/deltachat-core-rust (language: python)

Issue:
feat: webxdc notify
this PR adds support for the property `update.notify` to notify about changes in `update.info` or `update.summary`.  the property can be set to an array of addresses [^1]

core emits then the event `IncomingWebxdcNotify`, resulting in all UIs to display a system notification, maybe even via PUSH. 

for using the existing `update.info` and `update.summary`: the message is no secret and should be visible to all group members as usual, to not break the UX of having same group messages on all devices of all users - as known already from the normal messages.

also, that way, there is no question what happens if user have disabled notifications as the change is presented in the chat as well

doc counterpart at https://github.com/webxdc/website/pull/90

closes #6217 

[^1]: addresses  come in either via the payload as currently or as an explicit sender in the future - this does not affect this PR. same for translations, see discussions at #6217 and #6097
privacy preserving `selfAddr` (M2)
Design, implement and test privacy-preserving `selfAddr` generation inside Delta Chat core. `selfAddr` should not be trackable across apps, are scoped to app instances and need to work consistently in multi-device settings (i.e. the `selfAddr` for a given app needs to be the same on all devices)

Notes:

we should have multi-transport already in mind here, so things derived from the email address should be double-checked

while renaming `selfAddr` to sth. else seems to be tempting, it leads to a lot of additional discussions and work if we want to avoid breaking changes, so this is out of scope of M2 and this issue.

also out of scope of this issue is adding a `senderAddr` out of the payload. however, as that would not introduce breaking changes (only rollout before the feature gets into use), we can consider that when everything else of M2/M3 is done.

some previous discussion at https://github.com/deltachat/deltachat-core-rust/issues/6097


Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 88c7c02f28..b0a9c67e81 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1372,6 +1372,7 @@ dependencies = [
  "serde_json",
  "serde_urlencoded",
  "sha-1",
+ "sha2",
  "shadowsocks",
  "smallvec",
  "strum",
diff --git a/Cargo.toml b/Cargo.toml
index 5579a0e1a7..309bdcd1d8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -92,6 +92,7 @@ serde_json = { workspace = true }
 serde_urlencoded = "0.7.1"
 serde = { workspace = true, features = ["derive"] }
 sha-1 = "0.10"
+sha2 = "0.10"
 shadowsocks = { version = "1.21.0", default-features = false, features = ["aead-cipher-2022"] }
 smallvec = "1.13.2"
 strum = "0.26"
diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h
index 36bf24607e..55235216b4 100644
--- a/deltachat-ffi/deltachat.h
+++ b/deltachat-ffi/deltachat.h
@@ -4197,6 +4197,7 @@ char*             dc_msg_get_webxdc_blob      (const dc_msg_t* msg, const char*
  *   true if the Webxdc should get full internet access, including Webrtc.
  *   currently, this is only true for encrypted Webxdc's in the self chat
  *   that have requested internet access in the manifest.
+ * - self_addr: address to be used for `window.webxdc.selfAddr` in JS land.
  *
  * @memberof dc_msg_t
  * @param msg The webxdc instance.
diff --git a/deltachat-jsonrpc/src/api/types/webxdc.rs b/deltachat-jsonrpc/src/api/types/webxdc.rs
index 71db9ed931..28f0f384b3 100644
--- a/deltachat-jsonrpc/src/api/types/webxdc.rs
+++ b/deltachat-jsonrpc/src/api/types/webxdc.rs
@@ -35,6 +35,8 @@ pub struct WebxdcMessageInfo {
     source_code_url: Option<String>,
     /// True if full internet access should be granted to the app.
     internet_access: bool,
+    /// Address to be used for `window.webxdc.selfAddr` in JS land.
+    self_addr: String,
 }
 
 impl WebxdcMessageInfo {
@@ -50,6 +52,7 @@ impl WebxdcMessageInfo {
             summary,
             source_code_url,
             internet_access,
+            self_addr,
         } = message.get_webxdc_info(context).await?;
 
         Ok(Self {
@@ -59,6 +62,7 @@ impl WebxdcMessageInfo {
             summary: maybe_empty_string_to_option(summary),
             source_code_url: maybe_empty_string_to_option(source_code_url),
             internet_access,
+            self_addr,
         })
     }
 }
diff --git a/src/summary.rs b/src/summary.rs
index 4881b0a671..72bf3395d0 100644
--- a/src/summary.rs
+++ b/src/summary.rs
@@ -289,7 +289,7 @@ mod tests {
     use super::*;
     use crate::chat::ChatId;
     use crate::param::Param;
-    use crate::test_utils as test;
+    use crate::test_utils::TestContext;
 
     async fn assert_summary_texts(msg: &Message, ctx: &Context, expected: &str) {
         assert_eq!(msg.get_summary_text(ctx).await, expected);
@@ -298,7 +298,7 @@ mod tests {
 
     #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
     async fn test_get_summary_text() {
-        let d = test::TestContext::new().await;
+        let d = TestContext::new_alice().await;
         let ctx = &d.ctx;
         let chat_id = ChatId::create_for_contact(ctx, ContactId::SELF)
             .await
diff --git a/src/webxdc.rs b/src/webxdc.rs
index 8dacd778cc..c19bbebd34 100644
--- a/src/webxdc.rs
+++ b/src/webxdc.rs
@@ -30,12 +30,14 @@ use lettre_email::PartBuilder;
 use rusqlite::OptionalExtension;
 use serde::{Deserialize, Serialize};
 use serde_json::Value;
+use sha2::{Digest, Sha256};
 
 use crate::chat::{self, Chat};
 use crate::constants::Chattype;
 use crate::contact::ContactId;
 use crate::context::Context;
 use crate::events::EventType;
+use crate::key::{load_self_public_key, DcKey};
 use crate::message::{Message, MessageState, MsgId, Viewtype};
 use crate::mimefactory::wrapped_base64_encode;
 use crate::mimeparser::SystemMessage;
@@ -97,6 +99,9 @@ pub struct WebxdcInfo {
     /// It should request access, be encrypted
     /// and sent to self for this.
     pub internet_access: bool,
+
+    /// Address to be used for `window.webxdc.selfAddr` in JS land.
+    pub self_addr: String,
 }
 
 /// Status Update ID.
@@ -872,6 +877,8 @@ impl Message {
             && self.chat_id.is_self_talk(context).await.unwrap_or_default()
             && self.get_showpadlock();
 
+        let self_addr = self.get_webxdc_self_addr(context).await?;
+
         Ok(WebxdcInfo {
             name: if let Some(name) = manifest.name {
                 name
@@ -904,8 +911,16 @@ impl Message {
                 "".to_string()
             },
             internet_access,
+            self_addr,
         })
     }
+
+    async fn get_webxdc_self_addr(&self, context: &Context) -> Result<String> {
+        let fingerprint = load_self_public_key(context).await?.dc_fingerprint().hex();
+        let data = format!("{}-{}", fingerprint, self.rfc724_mid);
+        let hash = Sha256::digest(data.as_bytes());
+        Ok(format!("{:x}", hash))
+    }
 }
 
 #[cfg(test)]
@@ -2288,6 +2303,23 @@ sth_for_the = "future""#
         Ok(())
     }
 
+    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+    async fn test_get_webxdc_self_addr() -> Result<()> {
+        let t = TestContext::new_alice().await;
+        let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
+
+        let instance = send_webxdc_instance(&t, chat_id).await?;
+        let info1 = instance.get_webxdc_info(&t).await?;
+        let instance = send_webxdc_instance(&t, chat_id).await?;
+        let info2 = instance.get_webxdc_info(&t).await?;
+
+        let real_addr = t.get_primary_self_addr().await?;
+        assert!(!info1.self_addr.contains(&real_addr));
+        assert_ne!(info1.self_addr, info2.self_addr);
+
+        Ok(())
+    }
+
     #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
     async fn test_webxdc_info_summary() -> Result<()> {
         let alice = TestContext::new_alice().await;


Test patch:
diff --git a/deltachat-rpc-client/tests/test_webxdc.py b/deltachat-rpc-client/tests/test_webxdc.py
index 3c57157311..e189251df8 100644
--- a/deltachat-rpc-client/tests/test_webxdc.py
+++ b/deltachat-rpc-client/tests/test_webxdc.py
@@ -24,6 +24,7 @@ def test_webxdc(acfactory) -> None:
         "name": "Chess Board",
         "sourceCodeUrl": None,
         "summary": None,
+        "selfAddr": webxdc_info["selfAddr"],
     }
 
     status_updates = message.get_webxdc_status_updates()