diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 55235216b4..ae07c3d481 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -1154,7 +1154,13 @@ uint32_t dc_send_videochat_invitation (dc_context_t* context, uint32_t chat_id); * @memberof dc_context_t * @param context The context object. * @param msg_id The ID of the message with the webxdc instance. - * @param json program-readable data, the actual payload + * @param json program-readable data, this is created in JS land as: + * - `payload`: any JS object or primitive. + * - `info`: optional informational message. Will be shown in chat and may be added as system notification. + * note that also users that are not notified explicitly get the `info` or `summary` update shown in the chat. + * - `document`: optional document name. shown eg. in title bar. + * - `summary`: optional summary. shown beside app icon. + * - `notify`: optional array of other users `selfAddr` to be notified e.g. by a sound about `info` or `summary`. * @param descr The user-visible description of JSON data, * in case of a chess game, e.g. the move. * @return 1=success, 0=error @@ -6080,12 +6086,29 @@ void dc_event_unref(dc_event_t* event); #define DC_EVENT_INCOMING_REACTION 2002 + +/** + * A webxdc wants an info message or a changed summary to be notified. + * + * @param data1 contact_id ID of the contact sending. + * @param data2 (int) msg_id + (char*) text_to_notify. + * msg_id in dc_event_get_data2_int(), referring to webxdc-info-message + * or webxdc-instance in case of summary change. + * text_to_notify in dc_event_get_data2_str(). + * string must be passed to dc_str_unref() afterwards. + */ +#define DC_EVENT_INCOMING_WEBXDC_NOTIFY 2003 + + /** * There is a fresh message. Typically, the user will show an notification * when receiving this message. * * There is no extra #DC_EVENT_MSGS_CHANGED event send together with this event. * + * If the message is a webxdc info message, + * dc_msg_get_parent() returns the webxdc instance the notification belongs to. + * * @param data1 (int) chat_id * @param data2 (int) msg_id */ diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 47951a2e15..f937084112 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -542,6 +542,7 @@ pub unsafe extern "C" fn dc_event_get_id(event: *mut dc_event_t) -> libc::c_int EventType::MsgsChanged { .. } => 2000, EventType::ReactionsChanged { .. } => 2001, EventType::IncomingReaction { .. } => 2002, + EventType::IncomingWebxdcNotify { .. } => 2003, EventType::IncomingMsg { .. } => 2005, EventType::IncomingMsgBunch { .. } => 2006, EventType::MsgsNoticed { .. } => 2008, @@ -602,7 +603,8 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc: | EventType::ErrorSelfNotInGroup(_) | EventType::AccountsBackgroundFetchDone => 0, EventType::ChatlistChanged => 0, - EventType::IncomingReaction { contact_id, .. } => contact_id.to_u32() as libc::c_int, + EventType::IncomingReaction { contact_id, .. } + | EventType::IncomingWebxdcNotify { contact_id, .. } => contact_id.to_u32() as libc::c_int, EventType::MsgsChanged { chat_id, .. } | EventType::ReactionsChanged { chat_id, .. } | EventType::IncomingMsg { chat_id, .. } @@ -681,6 +683,7 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc: EventType::MsgsChanged { msg_id, .. } | EventType::ReactionsChanged { msg_id, .. } | EventType::IncomingReaction { msg_id, .. } + | EventType::IncomingWebxdcNotify { msg_id, .. } | EventType::IncomingMsg { msg_id, .. } | EventType::MsgDelivered { msg_id, .. } | EventType::MsgFailed { msg_id, .. } @@ -775,6 +778,9 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut .to_c_string() .unwrap_or_default() .into_raw(), + EventType::IncomingWebxdcNotify { text, .. } => { + text.to_c_string().unwrap_or_default().into_raw() + } #[allow(unreachable_patterns)] #[cfg(test)] _ => unreachable!("This is just to silence a rust_analyzer false-positive"), diff --git a/deltachat-jsonrpc/src/api/types/events.rs b/deltachat-jsonrpc/src/api/types/events.rs index 34ccdf395e..f7abb7a7e8 100644 --- a/deltachat-jsonrpc/src/api/types/events.rs +++ b/deltachat-jsonrpc/src/api/types/events.rs @@ -106,6 +106,14 @@ pub enum EventType { reaction: String, }, + /// Incoming webxdc info or summary update, should be notified. + #[serde(rename_all = "camelCase")] + IncomingWebxdcNotify { + contact_id: u32, + msg_id: u32, + text: String, + }, + /// There is a fresh message. Typically, the user will show an notification /// when receiving this message. /// @@ -319,6 +327,15 @@ impl From for EventType { msg_id: msg_id.to_u32(), reaction: reaction.as_str().to_string(), }, + CoreEventType::IncomingWebxdcNotify { + contact_id, + msg_id, + text, + } => IncomingWebxdcNotify { + contact_id: contact_id.to_u32(), + msg_id: msg_id.to_u32(), + text, + }, CoreEventType::IncomingMsg { chat_id, msg_id } => IncomingMsg { chat_id: chat_id.to_u32(), msg_id: msg_id.to_u32(), diff --git a/node/constants.js b/node/constants.js index d087c4543c..e277e957de 100644 --- a/node/constants.js +++ b/node/constants.js @@ -52,6 +52,7 @@ module.exports = { DC_EVENT_INCOMING_MSG: 2005, DC_EVENT_INCOMING_MSG_BUNCH: 2006, DC_EVENT_INCOMING_REACTION: 2002, + DC_EVENT_INCOMING_WEBXDC_NOTIFY: 2003, DC_EVENT_INFO: 100, DC_EVENT_LOCATION_CHANGED: 2035, DC_EVENT_MSGS_CHANGED: 2000, diff --git a/node/events.js b/node/events.js index 06c9295853..b75bcfd95a 100644 --- a/node/events.js +++ b/node/events.js @@ -17,6 +17,7 @@ module.exports = { 2000: 'DC_EVENT_MSGS_CHANGED', 2001: 'DC_EVENT_REACTIONS_CHANGED', 2002: 'DC_EVENT_INCOMING_REACTION', + 2003: 'DC_EVENT_INCOMING_WEBXDC_NOTIFY', 2005: 'DC_EVENT_INCOMING_MSG', 2006: 'DC_EVENT_INCOMING_MSG_BUNCH', 2008: 'DC_EVENT_MSGS_NOTICED', diff --git a/node/lib/constants.ts b/node/lib/constants.ts index 6a2aaaaec7..3971bdfafb 100644 --- a/node/lib/constants.ts +++ b/node/lib/constants.ts @@ -52,6 +52,7 @@ export enum C { DC_EVENT_INCOMING_MSG = 2005, DC_EVENT_INCOMING_MSG_BUNCH = 2006, DC_EVENT_INCOMING_REACTION = 2002, + DC_EVENT_INCOMING_WEBXDC_NOTIFY = 2003, DC_EVENT_INFO = 100, DC_EVENT_LOCATION_CHANGED = 2035, DC_EVENT_MSGS_CHANGED = 2000, @@ -325,6 +326,7 @@ export const EventId2EventName: { [key: number]: string } = { 2000: 'DC_EVENT_MSGS_CHANGED', 2001: 'DC_EVENT_REACTIONS_CHANGED', 2002: 'DC_EVENT_INCOMING_REACTION', + 2003: 'DC_EVENT_INCOMING_WEBXDC_NOTIFY', 2005: 'DC_EVENT_INCOMING_MSG', 2006: 'DC_EVENT_INCOMING_MSG_BUNCH', 2008: 'DC_EVENT_MSGS_NOTICED', diff --git a/src/debug_logging.rs b/src/debug_logging.rs index 766a39675f..36f5623b0e 100644 --- a/src/debug_logging.rs +++ b/src/debug_logging.rs @@ -63,6 +63,7 @@ pub async fn debug_logging_loop(context: &Context, events: Receiver, + + /// Array of other users `selfAddr` that should be notified about this update. + #[serde(skip_serializing_if = "Option::is_none")] + pub notify: Option>, } /// Update items as passed to the UIs. @@ -314,10 +318,44 @@ impl Context { return Ok(None); }; + let notify = if let Some(notify_list) = status_update_item.notify { + let self_addr = instance.get_webxdc_self_addr(self).await?; + notify_list.contains(&self_addr) + } else { + false + }; + let mut notify_msg_id = instance.id; + let mut notify_text = "".to_string(); + let mut param_changed = false; + + let mut instance = instance.clone(); + if let Some(ref document) = status_update_item.document { + if instance + .param + .update_timestamp(Param::WebxdcDocumentTimestamp, timestamp)? + { + instance.param.set(Param::WebxdcDocument, document); + param_changed = true; + } + } + + if let Some(ref summary) = status_update_item.summary { + if instance + .param + .update_timestamp(Param::WebxdcSummaryTimestamp, timestamp)? + { + let summary = sanitize_bidi_characters(summary); + instance.param.set(Param::WebxdcSummary, summary.clone()); + param_changed = true; + notify_text = summary; + } + } + if can_info_msg { if let Some(ref info) = status_update_item.info { - if let Some(info_msg_id) = - self.get_overwritable_info_msg_id(instance, from_id).await? + if let Some(info_msg_id) = self + .get_overwritable_info_msg_id(&instance, from_id) + .await? { chat::update_msg_text_and_timestamp( self, @@ -327,44 +365,21 @@ impl Context { timestamp, ) .await?; + notify_msg_id = info_msg_id; } else { - chat::add_info_msg_with_cmd( + notify_msg_id = chat::add_info_msg_with_cmd( self, instance.chat_id, info.as_str(), SystemMessage::WebxdcInfoMessage, timestamp, None, - Some(instance), + Some(&instance), Some(from_id), ) .await?; } - } - } - - let mut param_changed = false; - - let mut instance = instance.clone(); - if let Some(ref document) = status_update_item.document { - if instance - .param - .update_timestamp(Param::WebxdcDocumentTimestamp, timestamp)? - { - instance.param.set(Param::WebxdcDocument, document); - param_changed = true; - } - } - - if let Some(ref summary) = status_update_item.summary { - if instance - .param - .update_timestamp(Param::WebxdcSummaryTimestamp, timestamp)? - { - instance - .param - .set(Param::WebxdcSummary, sanitize_bidi_characters(summary)); - param_changed = true; + notify_text = info.to_string(); } } @@ -380,6 +395,14 @@ impl Context { }); } + if notify && !notify_text.is_empty() { + self.emit_event(EventType::IncomingWebxdcNotify { + contact_id: from_id, + msg_id: notify_msg_id, + text: notify_text, + }); + } + Ok(Some(status_update_serial)) } @@ -1437,6 +1460,7 @@ mod tests { document: None, summary: None, uid: Some("iecie2Ze".to_string()), + notify: None, }, 1640178619, true, @@ -1461,6 +1485,7 @@ mod tests { document: None, summary: None, uid: Some("iecie2Ze".to_string()), + notify: None, }, 1640178619, true, @@ -1494,6 +1519,7 @@ mod tests { document: None, summary: None, uid: None, + notify: None, }, 1640178619, true, @@ -1513,6 +1539,7 @@ mod tests { document: None, summary: None, uid: None, + notify: None, }, 1640178619, true, @@ -2903,4 +2930,143 @@ sth_for_the = "future""# Ok(()) } + + async fn has_incoming_webxdc_event( + t: &TestContext, + expected_msg: Message, + expected_text: &str, + ) -> bool { + t.evtracker + .get_matching_opt(t, |evt| { + if let EventType::IncomingWebxdcNotify { msg_id, text, .. } = evt { + *msg_id == expected_msg.id && text == expected_text + } else { + false + } + }) + .await + .is_some() + } + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn test_webxdc_notify_one() -> Result<()> { + let mut tcm = TestContextManager::new(); + let alice = tcm.alice().await; + let bob = tcm.bob().await; + let fiona = tcm.fiona().await; + + let grp_id = alice + .create_group_with_members(ProtectionStatus::Unprotected, "grp", &[&bob, &fiona]) + .await; + let alice_instance = send_webxdc_instance(&alice, grp_id).await?; + let sent1 = alice.pop_sent_msg().await; + let bob_instance = bob.recv_msg(&sent1).await; + let _fiona_instance = fiona.recv_msg(&sent1).await; + + alice + .send_webxdc_status_update( + alice_instance.id, + &format!( + "{{\"payload\":7,\"info\": \"Alice moved\",\"notify\":[\"{}\"]}}", + bob_instance.get_webxdc_self_addr(&bob).await? + ), + "d", + ) + .await?; + alice.flush_status_updates().await?; + let sent2 = alice.pop_sent_msg().await; + let info_msg = alice.get_last_msg().await; + assert!(info_msg.is_info()); + assert!(!has_incoming_webxdc_event(&alice, info_msg, "Alice moved").await); + + bob.recv_msg_trash(&sent2).await; + let info_msg = bob.get_last_msg().await; + assert!(info_msg.is_info()); + assert!(has_incoming_webxdc_event(&bob, info_msg, "Alice moved").await); + + fiona.recv_msg_trash(&sent2).await; + let info_msg = fiona.get_last_msg().await; + assert!(info_msg.is_info()); + assert!(!has_incoming_webxdc_event(&fiona, info_msg, "Alice moved").await); + + Ok(()) + } + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn test_webxdc_notify_multiple() -> Result<()> { + let mut tcm = TestContextManager::new(); + let alice = tcm.alice().await; + let bob = tcm.bob().await; + let fiona = tcm.fiona().await; + + let grp_id = alice + .create_group_with_members(ProtectionStatus::Unprotected, "grp", &[&bob, &fiona]) + .await; + let alice_instance = send_webxdc_instance(&alice, grp_id).await?; + let sent1 = alice.pop_sent_msg().await; + let bob_instance = bob.recv_msg(&sent1).await; + let fiona_instance = fiona.recv_msg(&sent1).await; + + alice + .send_webxdc_status_update( + alice_instance.id, + &format!( + "{{\"payload\":7,\"info\": \"moved\", \"summary\": \"ignored for notify as info is set\", \"notify\":[\"{}\",\"{}\"]}}", + bob_instance.get_webxdc_self_addr(&bob).await?, + fiona_instance.get_webxdc_self_addr(&fiona).await? + ), + "d", + ) + .await?; + alice.flush_status_updates().await?; + let sent2 = alice.pop_sent_msg().await; + let info_msg = alice.get_last_msg().await; + assert!(info_msg.is_info()); + assert!(!has_incoming_webxdc_event(&alice, info_msg, "moved").await); + + bob.recv_msg_trash(&sent2).await; + let info_msg = bob.get_last_msg().await; + assert!(info_msg.is_info()); + assert!(has_incoming_webxdc_event(&bob, info_msg, "moved").await); + + fiona.recv_msg_trash(&sent2).await; + let info_msg = fiona.get_last_msg().await; + assert!(info_msg.is_info()); + assert!(has_incoming_webxdc_event(&fiona, info_msg, "moved").await); + + Ok(()) + } + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn test_webxdc_notify_summary() -> Result<()> { + let mut tcm = TestContextManager::new(); + let alice = tcm.alice().await; + let bob = tcm.bob().await; + + let grp_id = alice + .create_group_with_members(ProtectionStatus::Unprotected, "grp", &[&bob]) + .await; + let alice_instance = send_webxdc_instance(&alice, grp_id).await?; + let sent1 = alice.pop_sent_msg().await; + let bob_instance = bob.recv_msg(&sent1).await; + + alice + .send_webxdc_status_update( + alice_instance.id, + &format!( + "{{\"payload\":7,\"summary\": \"4 moves done\",\"notify\":[\"{}\"]}}", + bob_instance.get_webxdc_self_addr(&bob).await? + ), + "d", + ) + .await?; + alice.flush_status_updates().await?; + let sent2 = alice.pop_sent_msg().await; + assert!(!has_incoming_webxdc_event(&alice, alice_instance, "4 moves done").await); + + bob.recv_msg_trash(&sent2).await; + assert!(has_incoming_webxdc_event(&bob, bob_instance, "4 moves done").await); + + Ok(()) + } } diff --git a/src/webxdc/maps_integration.rs b/src/webxdc/maps_integration.rs index 4209d8710d..a69a2cf4e9 100644 --- a/src/webxdc/maps_integration.rs +++ b/src/webxdc/maps_integration.rs @@ -149,6 +149,7 @@ pub(crate) async fn intercept_get_updates( document: None, summary: None, uid: None, + notify: None, }, serial: StatusUpdateSerial(location.location_id), max_serial: StatusUpdateSerial(location.location_id),