mirror of
https://github.com/chatmail/core.git
synced 2026-04-23 00:16:34 +03:00
Compare commits
12 Commits
v2.41.0
...
webxdc-dee
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df4466cff9 | ||
|
|
d847669309 | ||
|
|
1a8cc1094d | ||
|
|
f753dbd823 | ||
|
|
c1252ee814 | ||
|
|
79993f3011 | ||
|
|
1bc6b1e17e | ||
|
|
64bd7f66a5 | ||
|
|
8ba3e88848 | ||
|
|
de95c6e27e | ||
|
|
331e3dc29a | ||
|
|
d8ae5ebec8 |
@@ -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 create in JS land as:
|
||||
* - `payload`: any JS object or primitive.
|
||||
* - `info`: optional informational message. will be shown in chat an may be added as system notification.
|
||||
* - `notify`: optional array of user `addr` that should be notified eg. by a sound.
|
||||
* note that still all users get the update payload and the `info` message shown in a chat.
|
||||
* - `document`: optional document name. shown eg. in title bar.
|
||||
* - `summary`: optional summary. shown beside app icon.
|
||||
* @param descr The user-visible description of JSON data,
|
||||
* in case of a chess game, e.g. the move.
|
||||
* @return 1=success, 0=error
|
||||
@@ -4511,6 +4517,26 @@ int dc_msg_get_info_type (const dc_msg_t* msg);
|
||||
#define DC_INFO_INVALID_UNENCRYPTED_MAIL 13
|
||||
#define DC_INFO_WEBXDC_INFO_MESSAGE 32
|
||||
|
||||
|
||||
/**
|
||||
* Get deeplink attached to an info message.
|
||||
* The info message need to be of type DC_INFO_WEBXDC_INFO_MESSAGE.
|
||||
*
|
||||
* Typically, this is used to start the corresponding webxdc directly or indirectly
|
||||
* and passing the deeplink to `window.webxdc.deeplink` in JS land.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object.
|
||||
* @param info_msg_id The info message object.
|
||||
* Not: the webxdc instance.
|
||||
* @return The deeplink that can be passed to `window.webxdc.deeplink` in JS land.
|
||||
* The content of the deeplink is a full status update and its semantic is defined by the app.
|
||||
* Returns NULL if there is no deeplink attached to the info message and on errors.
|
||||
*
|
||||
*/
|
||||
char* dc_msg_get_webxdc_deeplink (const dc_msg_t* msg);
|
||||
|
||||
|
||||
/**
|
||||
* Check if a message is still in creation. A message is in creation between
|
||||
* the calls to dc_prepare_msg() and dc_send_msg().
|
||||
@@ -6085,6 +6111,10 @@ void dc_event_unref(dc_event_t* event);
|
||||
*
|
||||
* There is no extra #DC_EVENT_MSGS_CHANGED event send together with this event.
|
||||
*
|
||||
* If the message is an webxdc info message,
|
||||
* dc_msg_get_parent() returns the webxdc instance the notification belongs to.
|
||||
* Use dc_msg_get_webxdc_deeplink() to get the deeplink to pass to `window.webxdc.deeplink` in JS land.
|
||||
*
|
||||
* @param data1 (int) chat_id
|
||||
* @param data2 (int) msg_id
|
||||
*/
|
||||
|
||||
@@ -3681,6 +3681,31 @@ pub unsafe extern "C" fn dc_msg_get_info_type(msg: *mut dc_msg_t) -> libc::c_int
|
||||
ffi_msg.message.get_info_type() as libc::c_int
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_msg_get_webxdc_deeplink(msg: *mut dc_msg_t) -> *mut libc::c_char {
|
||||
if msg.is_null() {
|
||||
eprintln!("ignoring careless call to dc_msg_get_webxdc_deeplink()");
|
||||
return "".strdup();
|
||||
}
|
||||
|
||||
let ffi_msg = &*msg;
|
||||
let ctx = &*ffi_msg.context;
|
||||
let res = block_on(async move {
|
||||
ffi_msg
|
||||
.message
|
||||
.get_webxdc_deeplink(ctx)
|
||||
.await
|
||||
.context("failed to get deeplink")
|
||||
.log_err(ctx)
|
||||
.unwrap_or(None)
|
||||
});
|
||||
|
||||
match res {
|
||||
Some(str) => str.strdup(),
|
||||
None => ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_msg_is_increation(msg: *mut dc_msg_t) -> libc::c_int {
|
||||
if msg.is_null() {
|
||||
|
||||
30
src/chat.rs
30
src/chat.rs
@@ -4508,9 +4508,34 @@ pub(crate) async fn delete_and_reset_all_device_msgs(context: &Context) -> Resul
|
||||
/// Adds an informational message to chat.
|
||||
///
|
||||
/// For example, it can be a message showing that a member was added to a group.
|
||||
/// Doesn't fail if the chat doesn't exist.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) async fn add_info_msg_with_cmd(
|
||||
context: &Context,
|
||||
chat_id: ChatId,
|
||||
text: &str,
|
||||
cmd: SystemMessage,
|
||||
timestamp_sort: i64,
|
||||
timestamp_sent_rcvd: Option<i64>,
|
||||
parent: Option<&Message>,
|
||||
from_id: Option<ContactId>,
|
||||
) -> Result<MsgId> {
|
||||
add_info_msg_with_importance(
|
||||
context,
|
||||
chat_id,
|
||||
text,
|
||||
cmd,
|
||||
timestamp_sort,
|
||||
timestamp_sent_rcvd,
|
||||
parent,
|
||||
from_id,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Adds an informational message to chat, optionally showing a notification for important messages.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) async fn add_info_msg_with_importance(
|
||||
context: &Context,
|
||||
chat_id: ChatId,
|
||||
text: &str,
|
||||
@@ -4520,6 +4545,7 @@ pub(crate) async fn add_info_msg_with_cmd(
|
||||
timestamp_sent_rcvd: Option<i64>,
|
||||
parent: Option<&Message>,
|
||||
from_id: Option<ContactId>,
|
||||
important: bool,
|
||||
) -> Result<MsgId> {
|
||||
let rfc724_mid = create_outgoing_rfc724_mid();
|
||||
let ephemeral_timer = chat_id.get_ephemeral_timer(context).await?;
|
||||
@@ -4553,7 +4579,7 @@ pub(crate) async fn add_info_msg_with_cmd(
|
||||
context.new_msgs_notify.notify_one();
|
||||
|
||||
let msg_id = MsgId::new(row_id.try_into()?);
|
||||
context.emit_msgs_changed(chat_id, msg_id);
|
||||
chat_id.emit_msg_event(context, msg_id, important);
|
||||
|
||||
Ok(msg_id)
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ pub async fn debug_logging_loop(context: &Context, events: Receiver<DebugEventLo
|
||||
summary: None,
|
||||
document: None,
|
||||
uid: None,
|
||||
notify: None,
|
||||
},
|
||||
time,
|
||||
)
|
||||
|
||||
242
src/webxdc.rs
242
src/webxdc.rs
@@ -177,6 +177,10 @@ pub struct StatusUpdateItem {
|
||||
/// If there is no ID, message is always considered to be unique.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub uid: Option<String>,
|
||||
|
||||
/// Array of Addr that should be notified about this update.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub notify: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
/// Update items as passed to the UIs.
|
||||
@@ -311,29 +315,53 @@ impl Context {
|
||||
|
||||
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?
|
||||
{
|
||||
chat::update_msg_text_and_timestamp(
|
||||
self,
|
||||
instance.chat_id,
|
||||
info_msg_id,
|
||||
info.as_str(),
|
||||
timestamp,
|
||||
)
|
||||
.await?;
|
||||
let mut info_msg_id = self.get_overwritable_info_msg_id(instance, from_id).await?;
|
||||
let notify_list = status_update_item.notify;
|
||||
|
||||
if notify_list.is_none() && info_msg_id.is_some() {
|
||||
if let Some(info_msg_id) = info_msg_id {
|
||||
chat::update_msg_text_and_timestamp(
|
||||
self,
|
||||
instance.chat_id,
|
||||
info_msg_id,
|
||||
info.as_str(),
|
||||
timestamp,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
} else {
|
||||
chat::add_info_msg_with_cmd(
|
||||
self,
|
||||
instance.chat_id,
|
||||
info.as_str(),
|
||||
SystemMessage::WebxdcInfoMessage,
|
||||
timestamp,
|
||||
None,
|
||||
Some(instance),
|
||||
Some(from_id),
|
||||
)
|
||||
.await?;
|
||||
let notify = if let Some(notify_list) = notify_list {
|
||||
if let Ok(self_addr) = self.get_primary_self_addr().await {
|
||||
notify_list.contains(&self_addr)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
info_msg_id = Some(
|
||||
chat::add_info_msg_with_importance(
|
||||
self,
|
||||
instance.chat_id,
|
||||
info.as_str(),
|
||||
SystemMessage::WebxdcInfoMessage,
|
||||
timestamp,
|
||||
None,
|
||||
Some(instance),
|
||||
Some(from_id),
|
||||
notify,
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(info_msg_id) = info_msg_id {
|
||||
let mut info_msg = Message::load_from_db(self, info_msg_id).await?;
|
||||
info_msg
|
||||
.param
|
||||
.set_int(Param::Arg, status_update_serial.to_u32() as i32);
|
||||
info_msg.update_param(self).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -906,6 +934,37 @@ impl Message {
|
||||
internet_access,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get deeplink attached to an info message.
|
||||
///
|
||||
/// The info message need to be of type SystemMessage::WebxdcInfoMessage.
|
||||
/// Typically, this is used to start the corresponding webxdc directly or indirectly
|
||||
// and passing the deeplink to `window.webxdc.deeplink` in JS land.
|
||||
pub async fn get_webxdc_deeplink(&self, context: &Context) -> Result<Option<String>> {
|
||||
let Some(serial) = self.param.get_int(Param::Arg) else {
|
||||
return Ok(None);
|
||||
};
|
||||
let serial = StatusUpdateSerial::new(serial as u32);
|
||||
let Some(instance) = self.parent(&context).await? else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let update_item_str: String = context
|
||||
.sql
|
||||
.query_get_value(
|
||||
"SELECT update_item FROM msgs_status_updates WHERE msg_id=? AND id=?",
|
||||
(instance.id, serial),
|
||||
)
|
||||
.await?
|
||||
.context("cannot read update item")?;
|
||||
let update_item = StatusUpdateItem {
|
||||
uid: None, // Erase UIDs, apps, bots and tests don't need to know them.
|
||||
..serde_json::from_str(&update_item_str)?
|
||||
};
|
||||
|
||||
let json = serde_json::to_string(&update_item)?;
|
||||
Ok(Some(json))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1422,6 +1481,7 @@ mod tests {
|
||||
document: None,
|
||||
summary: None,
|
||||
uid: Some("iecie2Ze".to_string()),
|
||||
notify: None,
|
||||
},
|
||||
1640178619,
|
||||
true,
|
||||
@@ -1446,6 +1506,7 @@ mod tests {
|
||||
document: None,
|
||||
summary: None,
|
||||
uid: Some("iecie2Ze".to_string()),
|
||||
notify: None,
|
||||
},
|
||||
1640178619,
|
||||
true,
|
||||
@@ -1479,6 +1540,7 @@ mod tests {
|
||||
document: None,
|
||||
summary: None,
|
||||
uid: None,
|
||||
notify: None,
|
||||
},
|
||||
1640178619,
|
||||
true,
|
||||
@@ -1498,6 +1560,7 @@ mod tests {
|
||||
document: None,
|
||||
summary: None,
|
||||
uid: None,
|
||||
notify: None,
|
||||
},
|
||||
1640178619,
|
||||
true,
|
||||
@@ -2871,4 +2934,139 @@ sth_for_the = "future""#
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn has_incoming_msg_event(t: &TestContext, msg: Message) -> bool {
|
||||
t.evtracker
|
||||
.get_matching_opt(t, |evt| {
|
||||
if let EventType::IncomingMsg { msg_id, .. } = evt {
|
||||
*msg_id == msg.id
|
||||
} 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 instance = send_webxdc_instance(&alice, grp_id).await?;
|
||||
let sent1 = alice.pop_sent_msg().await;
|
||||
|
||||
alice
|
||||
.send_webxdc_status_update(
|
||||
instance.id,
|
||||
r#"{"payload":7,"info": "your move!","notify":["bob@example.net"]}"#,
|
||||
"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_msg_event(&alice, info_msg).await);
|
||||
|
||||
bob.recv_msg(&sent1).await;
|
||||
bob.recv_msg_trash(&sent2).await;
|
||||
let info_msg = bob.get_last_msg().await;
|
||||
assert!(info_msg.is_info());
|
||||
assert!(has_incoming_msg_event(&bob, info_msg).await);
|
||||
|
||||
fiona.recv_msg(&sent1).await;
|
||||
fiona.recv_msg_trash(&sent2).await;
|
||||
let info_msg = fiona.get_last_msg().await;
|
||||
assert!(info_msg.is_info());
|
||||
assert!(!has_incoming_msg_event(&fiona, info_msg).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 instance = send_webxdc_instance(&alice, grp_id).await?;
|
||||
let sent1 = alice.pop_sent_msg().await;
|
||||
|
||||
alice
|
||||
.send_webxdc_status_update(
|
||||
instance.id,
|
||||
r#"{"payload":7,"info": "my move!","notify":["bob@example.net","fiona@example.net"]}"#,
|
||||
"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_msg_event(&alice, info_msg).await);
|
||||
|
||||
bob.recv_msg(&sent1).await;
|
||||
bob.recv_msg_trash(&sent2).await;
|
||||
let info_msg = bob.get_last_msg().await;
|
||||
assert!(info_msg.is_info());
|
||||
assert!(has_incoming_msg_event(&bob, info_msg).await);
|
||||
|
||||
fiona.recv_msg(&sent1).await;
|
||||
fiona.recv_msg_trash(&sent2).await;
|
||||
let info_msg = fiona.get_last_msg().await;
|
||||
assert!(info_msg.is_info());
|
||||
assert!(has_incoming_msg_event(&fiona, info_msg).await);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_webxdc_deeplink() -> 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 instance = send_webxdc_instance(&alice, grp_id).await?;
|
||||
let sent1 = alice.pop_sent_msg().await;
|
||||
|
||||
alice
|
||||
.send_webxdc_status_update(
|
||||
instance.id,
|
||||
r#"{"payload": "my deeplink data", "info": "my move!"}"#,
|
||||
"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_eq!(
|
||||
info_msg.get_webxdc_deeplink(&alice).await?.unwrap(),
|
||||
r#"{"payload":"my deeplink data","info":"my move!"}"#
|
||||
);
|
||||
|
||||
bob.recv_msg(&sent1).await;
|
||||
bob.recv_msg_trash(&sent2).await;
|
||||
let info_msg = bob.get_last_msg().await;
|
||||
assert!(info_msg.is_info());
|
||||
assert_eq!(
|
||||
info_msg.get_webxdc_deeplink(&bob).await?.unwrap(),
|
||||
r#"{"payload":"my deeplink data","info":"my move!"}"#
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user