remove getAllUpdates() and add a typical replicatio API for the update call (#3081)

* (r10s, adb, hpk) remove getAllUpdates() and add a typical replica-API that works with increasing serials.  Streamline docs a bit.

* adapt ffi to new api

* documentation: updates serials may have gaps

* get_webxdc_status_updates() return updates larger than a given serial

* remove status_update_id from status-update-event; it is not needed (ui should update from the last known serial) and easily gets confused with last_serial

* unify wording to 'StatusUpdateSerial'

* remove legacy payload format, all known webxdc should be adapted

* add serial and max_serial to status updates

* avoid races when getting max_serial by avoiding two SQL calls

* update changelog

Co-authored-by: B. Petersen <r10s@b44t.com>
This commit is contained in:
holger krekel
2022-03-04 20:22:48 +01:00
committed by GitHub
parent ef841b1aa3
commit 63688a2f95
6 changed files with 219 additions and 179 deletions

View File

@@ -2,6 +2,10 @@
## Unreleased ## Unreleased
### API changes
- change semantics of `dc_get_webxdc_status_updates()` second parameter
and remove update-id from `DC_EVENT_WEBXDC_STATUS_UPDATE` #3081
### Changes ### Changes
- add more SMTP logging #3093 - add more SMTP logging #3093
- place common headers like `From:` before the large `Autocrypt:` header #3079 - place common headers like `From:` before the large `Autocrypt:` header #3079

View File

@@ -1037,14 +1037,18 @@ int dc_send_webxdc_status_update (dc_context_t* context, uint32_t msg_id, const
* @memberof dc_context_t * @memberof dc_context_t
* @param context The context object * @param context The context object
* @param msg_id id of the message with the webxdc instance * @param msg_id id of the message with the webxdc instance
* @param status_update_id Can be used to filter out only a concrete status update. * @param serial The last known serial. Pass 0 if there are no known serials to receive all updates.
* When set to 0, all known status updates are returned. * @return JSON-array containing the requested updates.
* @return JSON-array containing the requested updates, * Each `update` comes with the following properties:
* each element was created by dc_send_webxdc_status_update() * - `update.payload`: equals the payload given to dc_send_webxdc_status_update()
* on this or other devices. * - `update.serial`: the serial number of this update. Serials are larger `0` and newer serials have higher numbers.
* - `update.max_serial`: the maximum serial currently known.
* If `max_serial` equals `serial` this update is the last update (until new network messages arrive).
* - `update.info`: optional, short, informational message.
* - `update.summary`: optional, short text, shown beside app icon.
* If there are no updates, an empty JSON-array is returned. * If there are no updates, an empty JSON-array is returned.
*/ */
char* dc_get_webxdc_status_updates (dc_context_t* context, uint32_t msg_id, uint32_t status_update_id); char* dc_get_webxdc_status_updates (dc_context_t* context, uint32_t msg_id, uint32_t serial);
/** /**
* Save a draft for a chat in the database. * Save a draft for a chat in the database.
@@ -5603,15 +5607,12 @@ void dc_event_unref(dc_event_t* event);
/** /**
* webxdc status update received. * webxdc status update received.
* To get the received status update, use dc_get_webxdc_status_updates(). * To get the received status update, use dc_get_webxdc_status_updates() with
* `serial` set to the last known update.
* To send status updates, use dc_send_webxdc_status_update(). * To send status updates, use dc_send_webxdc_status_update().
* *
* Note, that you do not get events that arrive when the app is not running;
* instead, you can use dc_get_webxdc_status_updates() to get all status updates
* and catch up that way.
*
* @param data1 (int) msg_id * @param data1 (int) msg_id
* @param data2 (int) status_update_id * @param data2 (int) 0
*/ */
#define DC_EVENT_WEBXDC_STATUS_UPDATE 2120 #define DC_EVENT_WEBXDC_STATUS_UPDATE 2120

View File

@@ -37,7 +37,7 @@ use deltachat::ephemeral::Timer as EphemeralTimer;
use deltachat::key::DcKey; use deltachat::key::DcKey;
use deltachat::message::MsgId; use deltachat::message::MsgId;
use deltachat::stock_str::StockMessage; use deltachat::stock_str::StockMessage;
use deltachat::webxdc::StatusUpdateId; use deltachat::webxdc::StatusUpdateSerial;
use deltachat::*; use deltachat::*;
use deltachat::{accounts::Accounts, log::LogExt}; use deltachat::{accounts::Accounts, log::LogExt};
@@ -501,7 +501,7 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc:
EventType::ImexFileWritten(_) => 0, EventType::ImexFileWritten(_) => 0,
EventType::SecurejoinInviterProgress { contact_id, .. } EventType::SecurejoinInviterProgress { contact_id, .. }
| EventType::SecurejoinJoinerProgress { contact_id, .. } => *contact_id as libc::c_int, | EventType::SecurejoinJoinerProgress { contact_id, .. } => *contact_id as libc::c_int,
EventType::WebxdcStatusUpdate { msg_id, .. } => msg_id.to_u32() as libc::c_int, EventType::WebxdcStatusUpdate(msg_id) => msg_id.to_u32() as libc::c_int,
} }
} }
@@ -534,6 +534,7 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
| EventType::MsgsNoticed(_) | EventType::MsgsNoticed(_)
| EventType::ConnectivityChanged | EventType::ConnectivityChanged
| EventType::SelfavatarChanged | EventType::SelfavatarChanged
| EventType::WebxdcStatusUpdate(_)
| EventType::ChatModified(_) => 0, | EventType::ChatModified(_) => 0,
EventType::MsgsChanged { msg_id, .. } EventType::MsgsChanged { msg_id, .. }
| EventType::IncomingMsg { msg_id, .. } | EventType::IncomingMsg { msg_id, .. }
@@ -543,9 +544,6 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
EventType::SecurejoinInviterProgress { progress, .. } EventType::SecurejoinInviterProgress { progress, .. }
| EventType::SecurejoinJoinerProgress { progress, .. } => *progress as libc::c_int, | EventType::SecurejoinJoinerProgress { progress, .. } => *progress as libc::c_int,
EventType::ChatEphemeralTimerModified { timer, .. } => timer.to_u32() as libc::c_int, EventType::ChatEphemeralTimerModified { timer, .. } => timer.to_u32() as libc::c_int,
EventType::WebxdcStatusUpdate {
status_update_id, ..
} => status_update_id.to_u32() as libc::c_int,
} }
} }
@@ -587,7 +585,7 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut
| EventType::SecurejoinJoinerProgress { .. } | EventType::SecurejoinJoinerProgress { .. }
| EventType::ConnectivityChanged | EventType::ConnectivityChanged
| EventType::SelfavatarChanged | EventType::SelfavatarChanged
| EventType::WebxdcStatusUpdate { .. } | EventType::WebxdcStatusUpdate(_)
| EventType::ChatEphemeralTimerModified { .. } => ptr::null_mut(), | EventType::ChatEphemeralTimerModified { .. } => ptr::null_mut(),
EventType::ConfigureProgress { comment, .. } => { EventType::ConfigureProgress { comment, .. } => {
if let Some(comment) = comment { if let Some(comment) = comment {
@@ -903,7 +901,7 @@ pub unsafe extern "C" fn dc_send_webxdc_status_update(
pub unsafe extern "C" fn dc_get_webxdc_status_updates( pub unsafe extern "C" fn dc_get_webxdc_status_updates(
context: *mut dc_context_t, context: *mut dc_context_t,
msg_id: u32, msg_id: u32,
status_update_id: u32, last_known_serial: u32,
) -> *mut libc::c_char { ) -> *mut libc::c_char {
if context.is_null() { if context.is_null() {
eprintln!("ignoring careless call to dc_get_webxdc_status_updates()"); eprintln!("ignoring careless call to dc_get_webxdc_status_updates()");
@@ -913,11 +911,7 @@ pub unsafe extern "C" fn dc_get_webxdc_status_updates(
block_on(ctx.get_webxdc_status_updates( block_on(ctx.get_webxdc_status_updates(
MsgId::new(msg_id), MsgId::new(msg_id),
if status_update_id == 0 { StatusUpdateSerial::new(last_known_serial),
None
} else {
Some(StatusUpdateId::new(status_update_id))
},
)) ))
.unwrap_or_else(|_| "".to_string()) .unwrap_or_else(|_| "".to_string())
.strdup() .strdup()

View File

@@ -29,7 +29,7 @@ window.webxdc.sendUpdate(update, descr);
Webxdc apps are usually shared in a chat and run independently on each peer. Webxdc apps are usually shared in a chat and run independently on each peer.
To get a shared state, the peers use `sendUpdate()` to send updates to each other. To get a shared state, the peers use `sendUpdate()` to send updates to each other.
- `update`: an object with the following fields: - `update`: an object with the following properties:
- `update.payload`: any javascript primitive, array or object. - `update.payload`: any javascript primitive, array or object.
- `update.info`: optional, short, informational message that will be added to the chat, - `update.info`: optional, short, informational message that will be added to the chat,
eg. "Alice voted" or "Bob scored 123 in MyGame"; eg. "Alice voted" or "Bob scored 123 in MyGame";
@@ -45,48 +45,37 @@ All peers, including the sending one,
will receive the update by the callback given to `setUpdateListener()`. will receive the update by the callback given to `setUpdateListener()`.
There are situations where the user cannot send messages to a chat, There are situations where the user cannot send messages to a chat,
eg. contact requests or if the user has left a group. eg. if the webxdc instance comes as a contact request or if the user has left a group.
In these cases, you can still call `sendUpdate()`, In these cases, you can still call `sendUpdate()`,
however, the update won't be sent to other peers however, the update won't be sent to other peers
and you won't get the update by `setUpdateListener()` nor by `getAllUpdates()`. and you won't get the update by `setUpdateListener()`.
### setUpdateListener() ### setUpdateListener()
```js ```js
window.webxdc.setUpdateListener((update) => {}); window.webxdc.setUpdateListener((update) => {}, serial);
``` ```
With `setUpdateListener()` you define a callback that receives the updates With `setUpdateListener()` you define a callback that receives the updates
sent by `sendUpdate()`. sent by `sendUpdate()`. The callback is called for updates sent by you or other peers.
The `serial` specifies the last serial that you know about (defaults to 0).
- `update`: passed to the callback on updates with the following fields: Each `update` which is passed to the callback comes with the following properties:
`update.payload`: equals the payload given to `sendUpdate()`
The callback is called for updates sent by you or other peers. - `update.payload`: equals the payload given to `sendUpdate()`
- `update.serial`: the serial number of this update.
Serials are larger `0` and newer serials have higher numbers.
There may be gaps in the serials
and it is not guaranteed that the next serial is exactly incremented by one.
### getAllUpdates() - `update.max_serial`: the maximum serial currently known.
If `max_serial` equals `serial` this update is the last update (until new network messages arrive).
```js - `update.info`: optional, short, informational message (see `send_update`)
updates = await window.webxdc.getAllUpdates();
```
In case your Webxdc was just started, - `update.summary`: optional, short text, shown beside app icon (see `send_update`)
you may want to reconstruct the state from the last run -
and also incorporate updates that may have arrived while the app was not running.
- `updates`: All previous updates in an array,
eg. `[{payload: "foo"},{payload: "bar"}]`
if `webxdc.sendUpdate({payload: "foo"}); webxdc.sendUpdate({payload: "bar"};` was called on the last run.
The updates are wrapped into a Promise that you can `await` for.
If you are not in an async function and cannot use `await` therefore,
you can get the updates with `then()`:
```js
window.webxdc.getAllUpdates().then(updates => {});
```
### selfAddr ### selfAddr
@@ -162,9 +151,7 @@ The following example shows an input field and every input is show on all peers
document.getElementById('output').innerHTML += update.payload + "<br>"; document.getElementById('output').innerHTML += update.payload + "<br>";
} }
window.webxdc.setUpdateListener(receiveUpdate); window.webxdc.setUpdateListener(receiveUpdate, 0);
window.webxdc.getAllUpdates().then(updates => updates.forEach(receiveUpdate));
</script> </script>
</body> </body>
</html> </html>

View File

@@ -9,7 +9,6 @@ use strum::EnumProperty;
use crate::chat::ChatId; use crate::chat::ChatId;
use crate::ephemeral::Timer as EphemeralTimer; use crate::ephemeral::Timer as EphemeralTimer;
use crate::message::MsgId; use crate::message::MsgId;
use crate::webxdc::StatusUpdateId;
#[derive(Debug)] #[derive(Debug)]
pub struct Events { pub struct Events {
@@ -329,8 +328,5 @@ pub enum EventType {
SelfavatarChanged, SelfavatarChanged,
#[strum(props(id = "2120"))] #[strum(props(id = "2120"))]
WebxdcStatusUpdate { WebxdcStatusUpdate(MsgId),
msg_id: MsgId,
status_update_id: StatusUpdateId,
},
} }

View File

@@ -10,6 +10,7 @@ use crate::param::Param;
use crate::{chat, EventType}; use crate::{chat, EventType};
use anyhow::{bail, ensure, format_err, Result}; use anyhow::{bail, ensure, format_err, Result};
use async_std::path::PathBuf; use async_std::path::PathBuf;
use deltachat_derive::FromSql;
use lettre_email::mime::{self}; use lettre_email::mime::{self};
use lettre_email::PartBuilder; use lettre_email::PartBuilder;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -56,14 +57,26 @@ pub struct WebxdcInfo {
/// Status Update ID. /// Status Update ID.
#[derive( #[derive(
Debug, Copy, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, Debug,
Copy,
Clone,
Default,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
Serialize,
Deserialize,
FromSql,
FromPrimitive,
)] )]
pub struct StatusUpdateId(u32); pub struct StatusUpdateSerial(u32);
impl StatusUpdateId { impl StatusUpdateSerial {
/// Create a new [MsgId]. /// Create a new [MsgId].
pub fn new(id: u32) -> StatusUpdateId { pub fn new(id: u32) -> StatusUpdateSerial {
StatusUpdateId(id) StatusUpdateSerial(id)
} }
/// Gets StatusUpdateId as untyped integer. /// Gets StatusUpdateId as untyped integer.
@@ -73,7 +86,7 @@ impl StatusUpdateId {
} }
} }
impl rusqlite::types::ToSql for StatusUpdateId { impl rusqlite::types::ToSql for StatusUpdateSerial {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> { fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
let val = rusqlite::types::Value::Integer(i64::from(self.0)); let val = rusqlite::types::Value::Integer(i64::from(self.0));
let out = rusqlite::types::ToSqlOutput::Owned(val); let out = rusqlite::types::ToSqlOutput::Owned(val);
@@ -99,6 +112,16 @@ pub(crate) struct StatusUpdateItem {
summary: Option<String>, summary: Option<String>,
} }
/// Update items as passed to the UIs.
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct StatusUpdateItemAndSerial {
#[serde(flatten)]
item: StatusUpdateItem,
serial: StatusUpdateSerial,
max_serial: StatusUpdateSerial,
}
impl Context { impl Context {
/// check if a file is an acceptable webxdc for sending or receiving. /// check if a file is an acceptable webxdc for sending or receiving.
pub(crate) async fn is_webxdc_file(&self, filename: &str, buf: &[u8]) -> Result<bool> { pub(crate) async fn is_webxdc_file(&self, filename: &str, buf: &[u8]) -> Result<bool> {
@@ -157,7 +180,7 @@ impl Context {
instance: &mut Message, instance: &mut Message,
update_str: &str, update_str: &str,
timestamp: i64, timestamp: i64,
) -> Result<StatusUpdateId> { ) -> Result<StatusUpdateSerial> {
let update_str = update_str.trim(); let update_str = update_str.trim();
if update_str.is_empty() { if update_str.is_empty() {
bail!("create_status_update_record: empty update."); bail!("create_status_update_record: empty update.");
@@ -176,13 +199,7 @@ impl Context {
_ => item, _ => item,
} }
} else { } else {
// TODO: this fallback (legacy `PAYLOAD`) should be deleted soon, together with the test below bail!("create_status_update_record: no valid update item.");
let payload: Value = serde_json::from_str(update_str)?; // checks if input data are valid json
StatusUpdateItem {
payload,
info: None,
summary: None,
}
} }
}; };
@@ -219,14 +236,10 @@ impl Context {
paramsv![instance.id, serde_json::to_string(&status_update_item)?], paramsv![instance.id, serde_json::to_string(&status_update_item)?],
) )
.await?; .await?;
let status_update_id = StatusUpdateId(u32::try_from(rowid)?);
self.emit_event(EventType::WebxdcStatusUpdate { self.emit_event(EventType::WebxdcStatusUpdate(instance.id));
msg_id: instance.id,
status_update_id,
});
Ok(status_update_id) Ok(StatusUpdateSerial(u32::try_from(rowid)?))
} }
/// Sends a status update for an webxdc instance. /// Sends a status update for an webxdc instance.
@@ -250,7 +263,7 @@ impl Context {
let chat = Chat::load_from_db(self, instance.chat_id).await?; let chat = Chat::load_from_db(self, instance.chat_id).await?;
ensure!(chat.can_send(self).await?, "cannot send to {}", chat.id); ensure!(chat.can_send(self).await?, "cannot send to {}", chat.id);
let status_update_id = self let status_update_serial = self
.create_status_update_record( .create_status_update_record(
&mut instance, &mut instance,
update_str, update_str,
@@ -279,7 +292,7 @@ impl Context {
Param::Arg, Param::Arg,
self.render_webxdc_status_update_object( self.render_webxdc_status_update_object(
instance_msg_id, instance_msg_id,
Some(status_update_id), Some(status_update_serial),
) )
.await? .await?
.ok_or_else(|| format_err!("Status object expected."))?, .ok_or_else(|| format_err!("Status object expected."))?,
@@ -338,24 +351,79 @@ impl Context {
Ok(()) Ok(())
} }
/// Returns status updates as an JSON-array. /// Returns status updates as an JSON-array, ready to be consumed by a webxdc.
/// ///
/// Example: `[{"payload":"any update data"},{"payload":"another update data"}]` /// Example: `[{"serial":1, "max_serial":3, "payload":"any update data"},
/// The updates may be filtered by a given status_update_id; /// {"serial":3, "max_serial":3, "payload":"another update data"}]`
/// if no updates are available, an empty JSON-array is returned. /// Updates with serials larger than `last_known_serial` are returned.
/// If no last serial is known, set `last_known_serial` to 0.
/// If no updates are available, an empty JSON-array is returned.
pub async fn get_webxdc_status_updates( pub async fn get_webxdc_status_updates(
&self, &self,
instance_msg_id: MsgId, instance_msg_id: MsgId,
status_update_id: Option<StatusUpdateId>, last_known_serial: StatusUpdateSerial,
) -> Result<String> { ) -> Result<String> {
let json = self let json = self
.sql .sql
.query_map( .query_map(
"SELECT update_item FROM msgs_status_updates WHERE msg_id=? AND (1=? OR id=?)", "SELECT update_item, id FROM msgs_status_updates WHERE msg_id=? AND id>? ORDER BY id",
paramsv![instance_msg_id, last_known_serial],
|row| {
let update_item_str = row.get::<_, String>(0)?;
let serial = row.get::<_, StatusUpdateSerial>(1)?;
Ok((update_item_str, serial))
},
|rows| {
let mut rows_copy : Vec<(String, StatusUpdateSerial)> = Vec::new(); // `rows_copy` needed as `rows` cannot be iterated twice.
let mut max_serial = StatusUpdateSerial(0);
for row in rows {
let row = row?;
if row.1 > max_serial {
max_serial = row.1;
}
rows_copy.push(row);
}
let mut json = String::default();
for row in rows_copy {
let (update_item_str, serial) = row;
let update_item = StatusUpdateItemAndSerial
{
item: serde_json::from_str(&*update_item_str)?,
serial,
max_serial,
};
if !json.is_empty() {
json.push_str(",\n");
}
json.push_str(&*serde_json::to_string(&update_item)?);
}
Ok(json)
},
)
.await?;
Ok(format!("[{}]", json))
}
/// Renders JSON-object for status updates as used on the wire.
///
/// Example: `{"updates": [{"payload":"any update data"},
/// {"payload":"another update data"}]}`
/// If `status_update_serial` is set, exactly that update is rendered, otherwise all updates are rendered.
pub(crate) async fn render_webxdc_status_update_object(
&self,
instance_msg_id: MsgId,
status_update_serial: Option<StatusUpdateSerial>,
) -> Result<Option<String>> {
let json = self
.sql
.query_map(
"SELECT update_item FROM msgs_status_updates WHERE msg_id=? AND (1=? OR id=?) ORDER BY id",
paramsv![ paramsv![
instance_msg_id, instance_msg_id,
if status_update_id.is_some() { 0 } else { 1 }, if status_update_serial.is_some() { 0 } else { 1 },
status_update_id.unwrap_or(StatusUpdateId(0)) status_update_serial.unwrap_or(StatusUpdateSerial(0))
], ],
|row| row.get::<_, String>(0), |row| row.get::<_, String>(0),
|rows| { |rows| {
@@ -371,22 +439,10 @@ impl Context {
}, },
) )
.await?; .await?;
Ok(format!("[{}]", json)) if json.is_empty() {
}
/// Render JSON-object for status updates as used on the wire.
pub(crate) async fn render_webxdc_status_update_object(
&self,
instance_msg_id: MsgId,
status_update_id: Option<StatusUpdateId>,
) -> Result<Option<String>> {
let updates_array = self
.get_webxdc_status_updates(instance_msg_id, status_update_id)
.await?;
if updates_array == "[]" {
Ok(None) Ok(None)
} else { } else {
Ok(Some(format!(r#"{{"updates":{}}}"#, updates_array))) Ok(Some(format!(r#"{{"updates":[{}]}}"#, json)))
} }
} }
} }
@@ -635,8 +691,9 @@ mod tests {
.await?; .await?;
assert!(!instance.is_forwarded()); assert!(!instance.is_forwarded());
assert_eq!( assert_eq!(
t.get_webxdc_status_updates(instance.id, None).await?, t.get_webxdc_status_updates(instance.id, StatusUpdateSerial(0))
r#"[{"payload":42,"info":"foo","summary":"bar"}]"# .await?,
r#"[{"payload":42,"info":"foo","summary":"bar","serial":1,"max_serial":1}]"#
); );
assert_eq!(chat_id.get_msg_cnt(&t).await?, 2); // instance and info assert_eq!(chat_id.get_msg_cnt(&t).await?, 2); // instance and info
let info = Message::load_from_db(&t, instance.id) let info = Message::load_from_db(&t, instance.id)
@@ -649,7 +706,11 @@ mod tests {
forward_msgs(&t, &[instance.get_id()], chat_id).await?; forward_msgs(&t, &[instance.get_id()], chat_id).await?;
let instance2 = t.get_last_msg_in(chat_id).await; let instance2 = t.get_last_msg_in(chat_id).await;
assert!(instance2.is_forwarded()); assert!(instance2.is_forwarded());
assert_eq!(t.get_webxdc_status_updates(instance2.id, None).await?, "[]"); assert_eq!(
t.get_webxdc_status_updates(instance2.id, StatusUpdateSerial(0))
.await?,
"[]"
);
assert_eq!(chat_id.get_msg_cnt(&t).await?, 3); // two instances, only one info assert_eq!(chat_id.get_msg_cnt(&t).await?, 3); // two instances, only one info
let info = Message::load_from_db(&t, instance2.id) let info = Message::load_from_db(&t, instance2.id)
.await? .await?
@@ -712,7 +773,8 @@ mod tests {
.await .await
.is_err()); .is_err());
assert_eq!( assert_eq!(
bob.get_webxdc_status_updates(bob_instance.id, None).await?, bob.get_webxdc_status_updates(bob_instance.id, StatusUpdateSerial(0))
.await?,
"[]" "[]"
); );
@@ -723,8 +785,9 @@ mod tests {
.await .await
.is_ok()); .is_ok());
assert_eq!( assert_eq!(
bob.get_webxdc_status_updates(bob_instance.id, None).await?, bob.get_webxdc_status_updates(bob_instance.id, StatusUpdateSerial(0))
r#"[{"payload":42}]"# .await?,
r#"[{"payload":42,"serial":1,"max_serial":1}]"#
); );
Ok(()) Ok(())
@@ -746,14 +809,16 @@ mod tests {
t.send_webxdc_status_update(instance.id, r#"{"payload": 42}"#, "descr") t.send_webxdc_status_update(instance.id, r#"{"payload": 42}"#, "descr")
.await?; .await?;
assert_eq!( assert_eq!(
t.get_webxdc_status_updates(instance.id, None).await?, t.get_webxdc_status_updates(instance.id, StatusUpdateSerial(0))
r#"[{"payload":42}]"#.to_string() .await?,
r#"[{"payload":42,"serial":1,"max_serial":1}]"#.to_string()
); );
// set_draft(None) deletes the message without the need to simulate network // set_draft(None) deletes the message without the need to simulate network
chat_id.set_draft(&t, None).await?; chat_id.set_draft(&t, None).await?;
assert_eq!( assert_eq!(
t.get_webxdc_status_updates(instance.id, None).await?, t.get_webxdc_status_updates(instance.id, StatusUpdateSerial(0))
.await?,
"[]".to_string() "[]".to_string()
); );
assert_eq!( assert_eq!(
@@ -772,9 +837,13 @@ mod tests {
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?; let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
let mut instance = send_webxdc_instance(&t, chat_id).await?; let mut instance = send_webxdc_instance(&t, chat_id).await?;
assert_eq!(t.get_webxdc_status_updates(instance.id, None).await?, "[]"); assert_eq!(
t.get_webxdc_status_updates(instance.id, StatusUpdateSerial(0))
.await?,
"[]"
);
let id = t let update_id1 = t
.create_status_update_record( .create_status_update_record(
&mut instance, &mut instance,
"\n\n{\"payload\": {\"foo\":\"bar\"}}\n", "\n\n{\"payload\": {\"foo\":\"bar\"}}\n",
@@ -782,8 +851,9 @@ mod tests {
) )
.await?; .await?;
assert_eq!( assert_eq!(
t.get_webxdc_status_updates(instance.id, Some(id)).await?, t.get_webxdc_status_updates(instance.id, StatusUpdateSerial(0))
r#"[{"payload":{"foo":"bar"}}]"# .await?,
r#"[{"payload":{"foo":"bar"},"serial":1,"max_serial":1}]"#
); );
assert!(t assert!(t
@@ -795,15 +865,12 @@ mod tests {
.await .await
.is_err()); .is_err());
assert_eq!( assert_eq!(
t.get_webxdc_status_updates(instance.id, Some(id)).await?, t.get_webxdc_status_updates(instance.id, StatusUpdateSerial(0))
r#"[{"payload":{"foo":"bar"}}]"# .await?,
); r#"[{"payload":{"foo":"bar"},"serial":1,"max_serial":1}]"#
assert_eq!(
t.get_webxdc_status_updates(instance.id, None).await?,
r#"[{"payload":{"foo":"bar"}}]"#
); );
let id = t let update_id2 = t
.create_status_update_record( .create_status_update_record(
&mut instance, &mut instance,
r#"{"payload" : { "foo2":"bar2"}}"#, r#"{"payload" : { "foo2":"bar2"}}"#,
@@ -811,19 +878,20 @@ mod tests {
) )
.await?; .await?;
assert_eq!( assert_eq!(
t.get_webxdc_status_updates(instance.id, Some(id)).await?, t.get_webxdc_status_updates(instance.id, update_id1).await?,
r#"[{"payload":{"foo2":"bar2"}}]"# r#"[{"payload":{"foo2":"bar2"},"serial":2,"max_serial":2}]"#
); );
t.create_status_update_record(&mut instance, r#"{"payload":true}"#, 1640178619) t.create_status_update_record(&mut instance, r#"{"payload":true}"#, 1640178619)
.await?; .await?;
assert_eq!( assert_eq!(
t.get_webxdc_status_updates(instance.id, None).await?, t.get_webxdc_status_updates(instance.id, StatusUpdateSerial(0))
r#"[{"payload":{"foo":"bar"}}, .await?,
{"payload":{"foo2":"bar2"}}, r#"[{"payload":{"foo":"bar"},"serial":1,"max_serial":3},
{"payload":true}]"# {"payload":{"foo2":"bar2"},"serial":2,"max_serial":3},
{"payload":true,"serial":3,"max_serial":3}]"#
); );
let id = t let _update_id3 = t
.create_status_update_record( .create_status_update_record(
&mut instance, &mut instance,
r#"{"payload" : 1, "sender": "that is not used"}"#, r#"{"payload" : 1, "sender": "that is not used"}"#,
@@ -831,17 +899,9 @@ mod tests {
) )
.await?; .await?;
assert_eq!( assert_eq!(
t.get_webxdc_status_updates(instance.id, Some(id)).await?, t.get_webxdc_status_updates(instance.id, update_id2).await?,
r#"[{"payload":1}]"# r#"[{"payload":true,"serial":3,"max_serial":4},
); {"payload":1,"serial":4,"max_serial":4}]"#
// TODO: legacy `PAYLOAD` support should be deleted soon
let id = t
.create_status_update_record(&mut instance, r#"{"foo" : 1}"#, 1640178619)
.await?;
assert_eq!(
t.get_webxdc_status_updates(instance.id, Some(id)).await?,
r#"[{"payload":{"foo":1}}]"#
); );
Ok(()) Ok(())
@@ -873,8 +933,9 @@ mod tests {
t.receive_status_update(instance.id, r#"{"updates":[{"payload":{"foo":"bar"}}]}"#) t.receive_status_update(instance.id, r#"{"updates":[{"payload":{"foo":"bar"}}]}"#)
.await?; .await?;
assert_eq!( assert_eq!(
t.get_webxdc_status_updates(instance.id, None).await?, t.get_webxdc_status_updates(instance.id, StatusUpdateSerial(0))
r#"[{"payload":{"foo":"bar"}}]"# .await?,
r#"[{"payload":{"foo":"bar"},"serial":1,"max_serial":1}]"#
); );
t.receive_status_update( t.receive_status_update(
@@ -883,10 +944,11 @@ mod tests {
) )
.await?; .await?;
assert_eq!( assert_eq!(
t.get_webxdc_status_updates(instance.id, None).await?, t.get_webxdc_status_updates(instance.id, StatusUpdateSerial(0))
r#"[{"payload":{"foo":"bar"}}, .await?,
{"payload":42}, r#"[{"payload":{"foo":"bar"},"serial":1,"max_serial":3},
{"payload":23}]"# {"payload":42,"serial":2,"max_serial":3},
{"payload":23,"serial":3,"max_serial":3}]"#
); );
t.receive_status_update( t.receive_status_update(
@@ -895,11 +957,12 @@ mod tests {
) )
.await?; // ignore members that may be added in the future .await?; // ignore members that may be added in the future
assert_eq!( assert_eq!(
t.get_webxdc_status_updates(instance.id, None).await?, t.get_webxdc_status_updates(instance.id, StatusUpdateSerial(0))
r#"[{"payload":{"foo":"bar"}}, .await?,
{"payload":42}, r#"[{"payload":{"foo":"bar"},"serial":1,"max_serial":4},
{"payload":23}, {"payload":42,"serial":2,"max_serial":4},
{"payload":"ok"}]"# {"payload":23,"serial":3,"max_serial":4},
{"payload":"ok","serial":4,"max_serial":4}]"#
); );
Ok(()) Ok(())
@@ -911,15 +974,7 @@ mod tests {
.get_matching(|evt| matches!(evt, EventType::WebxdcStatusUpdate { .. })) .get_matching(|evt| matches!(evt, EventType::WebxdcStatusUpdate { .. }))
.await; .await;
match event { match event {
EventType::WebxdcStatusUpdate { EventType::WebxdcStatusUpdate(msg_id) => {
msg_id,
status_update_id,
} => {
assert_eq!(
t.get_webxdc_status_updates(msg_id, Some(status_update_id))
.await?,
r#"[{"payload":{"foo":"bar"}}]"#
);
assert_eq!(msg_id, instance_id); assert_eq!(msg_id, instance_id);
} }
_ => unreachable!(), _ => unreachable!(),
@@ -964,9 +1019,9 @@ mod tests {
assert!(sent2.payload().contains("descr text")); assert!(sent2.payload().contains("descr text"));
assert_eq!( assert_eq!(
alice alice
.get_webxdc_status_updates(alice_instance.id, None) .get_webxdc_status_updates(alice_instance.id, StatusUpdateSerial(0))
.await?, .await?,
r#"[{"payload":{"foo":"bar"}}]"# r#"[{"payload":{"foo":"bar"},"serial":1,"max_serial":1}]"#
); );
alice alice
@@ -979,10 +1034,10 @@ mod tests {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
alice alice
.get_webxdc_status_updates(alice_instance.id, None) .get_webxdc_status_updates(alice_instance.id, StatusUpdateSerial(0))
.await?, .await?,
r#"[{"payload":{"foo":"bar"}}, r#"[{"payload":{"foo":"bar"},"serial":1,"max_serial":2},
{"payload":{"snipp":"snapp"}}]"# {"payload":{"snipp":"snapp"},"serial":2,"max_serial":2}]"#
); );
// Bob receives all messages // Bob receives all messages
@@ -998,8 +1053,9 @@ mod tests {
assert_eq!(bob_chat_id.get_msg_cnt(&bob).await?, 1); assert_eq!(bob_chat_id.get_msg_cnt(&bob).await?, 1);
assert_eq!( assert_eq!(
bob.get_webxdc_status_updates(bob_instance.id, None).await?, bob.get_webxdc_status_updates(bob_instance.id, StatusUpdateSerial(0))
r#"[{"payload":{"foo":"bar"}}]"# .await?,
r#"[{"payload":{"foo":"bar"},"serial":1,"max_serial":1}]"#
); );
// Alice has a second device and also receives messages there // Alice has a second device and also receives messages there
@@ -1091,9 +1147,10 @@ mod tests {
assert!(sent1.payload().contains("status-update.json")); assert!(sent1.payload().contains("status-update.json"));
assert!(sent1.payload().contains(r#""payload":{"foo":"bar"}"#)); assert!(sent1.payload().contains(r#""payload":{"foo":"bar"}"#));
assert_eq!( assert_eq!(
bob.get_webxdc_status_updates(bob_instance.id, None).await?, bob.get_webxdc_status_updates(bob_instance.id, StatusUpdateSerial(0))
r#"[{"payload":{"foo":"bar"}}, .await?,
{"payload":42}]"# // 'info: "i"' ignored as sent in draft mode r#"[{"payload":{"foo":"bar"},"serial":1,"max_serial":2},
{"payload":42,"serial":2,"max_serial":2}]"# // 'info: "i"' ignored as sent in draft mode
); );
Ok(()) Ok(())
@@ -1411,9 +1468,9 @@ sth_for_the = "future""#
assert!(info_msg.quoted_message(&alice).await?.is_none()); assert!(info_msg.quoted_message(&alice).await?.is_none());
assert_eq!( assert_eq!(
alice alice
.get_webxdc_status_updates(alice_instance.id, None) .get_webxdc_status_updates(alice_instance.id, StatusUpdateSerial(0))
.await?, .await?,
r#"[{"payload":"sth. else","info":"this appears in-chat"}]"# r#"[{"payload":"sth. else","info":"this appears in-chat","serial":1,"max_serial":1}]"#
); );
// Bob receives all messages // Bob receives all messages
@@ -1431,8 +1488,9 @@ sth_for_the = "future""#
assert_eq!(info_msg.parent(&bob).await?.unwrap().id, bob_instance.id); assert_eq!(info_msg.parent(&bob).await?.unwrap().id, bob_instance.id);
assert!(info_msg.quoted_message(&bob).await?.is_none()); assert!(info_msg.quoted_message(&bob).await?.is_none());
assert_eq!( assert_eq!(
bob.get_webxdc_status_updates(bob_instance.id, None).await?, bob.get_webxdc_status_updates(bob_instance.id, StatusUpdateSerial(0))
r#"[{"payload":"sth. else","info":"this appears in-chat"}]"# .await?,
r#"[{"payload":"sth. else","info":"this appears in-chat","serial":1,"max_serial":1}]"#
); );
// Alice has a second device and also receives the info message there // Alice has a second device and also receives the info message there
@@ -1455,9 +1513,9 @@ sth_for_the = "future""#
assert!(info_msg.quoted_message(&alice2).await?.is_none()); assert!(info_msg.quoted_message(&alice2).await?.is_none());
assert_eq!( assert_eq!(
alice2 alice2
.get_webxdc_status_updates(alice2_instance.id, None) .get_webxdc_status_updates(alice2_instance.id, StatusUpdateSerial(0))
.await?, .await?,
r#"[{"payload":"sth. else","info":"this appears in-chat"}]"# r#"[{"payload":"sth. else","info":"this appears in-chat","serial":1,"max_serial":1}]"#
); );
Ok(()) Ok(())