Compare commits

...

25 Commits

Author SHA1 Message Date
bjoern
d1537095e4 chore(release): prepare for 1.151.2 (#6267)
following `RELEASE.md`, after merging, the following is needed:

6. Tag the release: `git tag --annotate v1.151.2`.
7. Push the release tag: `git push origin v1.151.2`.
8. Create a GitHub release: `gh release create v1.151.2 --notes ''`.
2024-11-27 13:37:01 +01:00
bjoern
ba68b87c58 feat: add href to IncomingWebxdcNotify event (#6266)
this PR adds the `href` from `update.href` to the IncomingWebxdcNotify
event (DC_EVENT_INCOMING_WEBXDC_NOTIFY in cffi)

purpose is to add a "Start" button to the notifications that allow
starting the app immediately with the given href
2024-11-26 18:21:09 +01:00
B. Petersen
b5f899540c change update.notify to a dict of addr:text_to_notify and allow to notify all using the special addr '*' 2024-11-26 14:10:00 +01:00
B. Petersen
c6dd03590c feat: add webxdc limits api 2024-11-26 14:09:40 +01:00
link2xt
ff3efafcfc fix: revert treating some transient SMTP errors as permanent 2024-11-26 03:08:40 +00:00
iequidoo
717c18ed0f test: Check that IncomingMsg isn't emitted for reactions 2024-11-25 20:58:45 -03:00
bjoern
4026c827be prefer long options in RELEASE.md (#6136)
using long options make things less mystical, it is clearer what
happens.

(apart from that i also disallowed `git -a` on my machine in general, as
`git commit -a` is considered harmful. as my approach to disallow that
is a bit greedy and disallows `-a` just for any git commands, this is
the only place where i regularly struggle :)
2024-11-25 17:34:20 +01:00
l
cd8cff7efb feat: do not use format=flowed in outgoing messages (#6256)
Text parts are using quoted-printable encoding
which takes care of wrapping long lines,
so using format=flowed is unnecessary.

This improves compatibility with receivers
which do not support format=flowed.

Receiving format=flowed messages is still possible, receiver side of
Delta Chat is unchanged.
2024-11-25 15:40:38 +00:00
Simon Laux
a319c1ea27 feat: add AccountsChanged and AccountsItemChanged events (#6118)
- **feat: add `AccountsChanged` and `AccountsItemChanged` events**
- **emit event and add tests**

closes #6106

TODO:
- [x] test receiving synced config from second device
- [x] bug: investigate how to delay the configuration event until it is
actually configured - because desktop gets the event but still shows
account as if it was unconfigured, maybe event is emitted before the
value is written to the database?
- [x] update node bindings constants
2024-11-25 13:34:33 +00:00
iequidoo
5db574b44f refactor: create_status_update_record: Get rid of notify var
It's used in the only place. Also this way `get_webxdc_self_addr()` which makes a db query is only
called when necessary.
2024-11-25 11:18:07 +01:00
iequidoo
8af90a1299 feat: AEAP: Check that the old peerstate verified key fingerprint hasn't changed when removing it 2024-11-24 15:51:19 -03:00
bjoern
a6db7ba1e3 api: deprecate webxdc descr parameter (#6255)
this PR removes most usages of the `descr` parameter.

- to avoid noise in different branches etc. (as annoying on similar, at
a first glance simple changes), i left the external API stable

- also, the effort to do a database migration seems to be over the top,
so the column is left and set to empty strings on future updates - maybe
we can recycle the column at some point ;)

closes #6245
2024-11-24 16:34:24 +00:00
link2xt
703cad970d chore(release): prepare for 1.151.1 2024-11-24 14:00:53 +00:00
link2xt
47757c3c7f ci: test building nix targets to avoid regressions
Otherwise build failure may only be detected during release.
2024-11-24 13:45:06 +00:00
link2xt
dca922b932 build(nix): fix deltachat-rpc-server-source installable 2024-11-24 13:45:06 +00:00
link2xt
bacdf8f8df chore(release): prepare for 1.151.0 2024-11-23 21:57:19 +00:00
link2xt
eed2320217 build: use underscores in deltachat-rpc-server source package filename 2024-11-23 21:49:20 +00:00
iequidoo
d22c29ab89 test: After AEAP, 1:1 chat isn't available for sending, but unprotected groups are (#6222) 2024-11-23 18:34:18 -03:00
bjoern
22b9308c9b feat: update.href api (#6248)
add `update.href` property option to update objects send via
`Context::send_webxdc_status_update()`.

when set together with `update.info`,
UI can implement the info message as a link that is passed to the webxdc
via `window.location.href`.
for that purpose, UI will read the link back from
`Message::get_webxdc_href()`.

Practically,
this allows e.g. an calendar.xdc
to emits clickable update messages
opening the calendar at the correct date.

closes #6219

documentation at https://github.com/webxdc/website/pull/90
2024-11-23 18:38:02 +01:00
bjoern
1f0a12a729 fix: never notify SELF (#6251)
it may be handy for an xdc to have only one list of all adresses, or
there may just be bugs.

in any case, do not notify SELF, e.g. in a multi-device setup; we're
also not doing this for other messages.

this is also a preparation for having an option to notify ALL.
2024-11-23 18:16:20 +01:00
dependabot[bot]
d06fa73e4f chore(deps): bump curve25519-dalek from 3.2.0 to 4.1.3 in /fuzz
Bumps [curve25519-dalek](https://github.com/dalek-cryptography/curve25519-dalek) from 3.2.0 to 4.1.3.
- [Release notes](https://github.com/dalek-cryptography/curve25519-dalek/releases)
- [Commits](https://github.com/dalek-cryptography/curve25519-dalek/compare/3.2.0...curve25519-4.1.3)

---
updated-dependencies:
- dependency-name: curve25519-dalek
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-23 15:53:12 +00:00
adb
407bc95ae5 remove imap_tools from dependencies (#6238) 2024-11-23 16:28:23 +01:00
Hocuri
daeeca3710 docs: Clarify DC_EVENT_INCOMING_WEBXDC_NOTIFY documentation (#6249)
I found the old documentation rather hard to understand. The new doc
string:
- uses whole sentences, leaving less space for misinterpretation
- explicitly mentions that it can happen that there is no
webxdc-info-message
- is clearly structured using bullet points.
2024-11-23 15:52:08 +01:00
bjoern
29de7c3603 feat: webxdc notify (#6230)
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

---------

Co-authored-by: adb <asieldbenitez@gmail.com>
Co-authored-by: l <link2xt@testrun.org>
2024-11-22 21:31:56 +01:00
link2xt
f669f43fe6 chore(cargo): update Rustls from 0.23.14 to 0.23.18 2024-11-22 18:50:25 +00:00
53 changed files with 2144 additions and 1800 deletions

View File

@@ -2,7 +2,13 @@ name: Test Nix flake
on:
pull_request:
paths:
- flake.nix
- flake.lock
push:
paths:
- flake.nix
- flake.lock
branches:
- main
@@ -20,3 +26,79 @@ jobs:
# Check that formatting does not change anything.
- run: git diff --exit-code
build:
name: nix build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
installable:
# Ensure `nix develop` will work.
- devShells.x86_64-linux.default
- deltachat-python
- deltachat-repl
- deltachat-repl-aarch64-linux
- deltachat-repl-arm64-v8a-android
- deltachat-repl-armeabi-v7a-android
- deltachat-repl-armv6l-linux
- deltachat-repl-armv7l-linux
- deltachat-repl-i686-linux
- deltachat-repl-win32
- deltachat-repl-win64
- deltachat-repl-x86_64-linux
- deltachat-rpc-client
- deltachat-rpc-server
- deltachat-rpc-server-aarch64-linux
- deltachat-rpc-server-aarch64-linux-wheel
- deltachat-rpc-server-arm64-v8a-android
- deltachat-rpc-server-armeabi-v7a-android
- deltachat-rpc-server-armv6l-linux
- deltachat-rpc-server-armv6l-linux-wheel
- deltachat-rpc-server-armv7l-linux
- deltachat-rpc-server-armv7l-linux-wheel
- deltachat-rpc-server-i686-linux
- deltachat-rpc-server-i686-linux-wheel
- deltachat-rpc-server-source
- deltachat-rpc-server-win32
- deltachat-rpc-server-win32-wheel
- deltachat-rpc-server-win64
- deltachat-rpc-server-win64-wheel
- deltachat-rpc-server-x86_64-linux
- deltachat-rpc-server-x86_64-linux-wheel
- docs
- libdeltachat
- python-docs
# Fails to build
#- deltachat-repl-x86_64-android
#- deltachat-repl-x86-android
#- deltachat-rpc-server-x86_64-android
#- deltachat-rpc-server-x86-android
steps:
- uses: actions/checkout@v4
with:
show-progress: false
- uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- run: nix build .#${{ matrix.installable }}
build-macos:
name: nix build on macOS
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
installable:
- deltachat-rpc-server-aarch64-darwin
# Fails to bulid
# - deltachat-rpc-server-x86_64-darwin
steps:
- uses: actions/checkout@v4
with:
show-progress: false
- uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- run: nix build .#${{ matrix.installable }}

View File

@@ -1,5 +1,70 @@
# Changelog
## [1.151.2] - 2024-11-26
### API-Changes
- Deprecate webxdc `descr` parameter ([#6255](https://github.com/deltachat/deltachat-core-rust/pull/6255)).
### Features / Changes
- AEAP: Check that the old peerstate verified key fingerprint hasn't changed when removing it.
- Add `AccountsChanged` and `AccountsItemChanged` events ([#6118](https://github.com/deltachat/deltachat-core-rust/pull/6118)).
- Do not use format=flowed in outgoing messages ([#6256](https://github.com/deltachat/deltachat-core-rust/pull/6256)).
- Add webxdc limits api.
- Add href to IncomingWebxdcNotify event ([#6266](https://github.com/deltachat/deltachat-core-rust/pull/6266)).
### Fixes
- Revert treating some transient SMTP errors as permanent.
### Refactor
- Create_status_update_record: Get rid of `notify` var.
### Tests
- Check that IncomingMsg isn't emitted for reactions.
## [1.151.1] - 2024-11-24
### Build system
- nix: Fix deltachat-rpc-server-source installable.
### CI
- Test building nix targets to avoid regressions.
## [1.151.0] - 2024-11-23
### Features / Changes
- Trim whitespace from scanned QR codes.
- Use privacy-preserving webxdc addresses ([#6237](https://github.com/deltachat/deltachat-core-rust/pull/6237)).
- Webxdc notify ([#6230](https://github.com/deltachat/deltachat-core-rust/pull/6230)).
- `update.href` api ([#6248](https://github.com/deltachat/deltachat-core-rust/pull/6248)).
### Fixes
- Never notify SELF ([#6251](https://github.com/deltachat/deltachat-core-rust/pull/6251)).
### Build system
- Use underscores in deltachat-rpc-server source package filename.
- Remove imap_tools from dependencies ([#6238](https://github.com/deltachat/deltachat-core-rust/pull/6238)).
- cargo: Update Rustls from 0.23.14 to 0.23.18.
- deps: Bump curve25519-dalek from 3.2.0 to 4.1.3 in /fuzz.
### Documentation
- Move style guide into a separate document.
- Clarify DC_EVENT_INCOMING_WEBXDC_NOTIFY documentation ([#6249](https://github.com/deltachat/deltachat-core-rust/pull/6249)).
### Tests
- After AEAP, 1:1 chat isn't available for sending, but unprotected groups are ([#6222](https://github.com/deltachat/deltachat-core-rust/pull/6222)).
## [1.150.0] - 2024-11-21
### API-Changes
@@ -5301,3 +5366,6 @@ https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed
[1.148.7]: https://github.com/deltachat/deltachat-core-rust/compare/v1.148.6..v1.148.7
[1.149.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.148.7..v1.149.0
[1.150.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.149.0..v1.150.0
[1.151.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.150.0..v1.151.0
[1.151.1]: https://github.com/deltachat/deltachat-core-rust/compare/v1.151.0..v1.151.1
[1.151.2]: https://github.com/deltachat/deltachat-core-rust/compare/v1.151.1..v1.151.2

14
Cargo.lock generated
View File

@@ -1306,7 +1306,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.150.0"
version = "1.151.2"
dependencies = [
"anyhow",
"async-broadcast",
@@ -1407,7 +1407,7 @@ dependencies = [
[[package]]
name = "deltachat-jsonrpc"
version = "1.150.0"
version = "1.151.2"
dependencies = [
"anyhow",
"async-channel 2.3.1",
@@ -1432,7 +1432,7 @@ dependencies = [
[[package]]
name = "deltachat-repl"
version = "1.150.0"
version = "1.151.2"
dependencies = [
"anyhow",
"deltachat",
@@ -1448,7 +1448,7 @@ dependencies = [
[[package]]
name = "deltachat-rpc-server"
version = "1.150.0"
version = "1.151.2"
dependencies = [
"anyhow",
"deltachat",
@@ -1477,7 +1477,7 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "1.150.0"
version = "1.151.2"
dependencies = [
"anyhow",
"deltachat",
@@ -5270,9 +5270,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.14"
version = "0.23.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8"
checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f"
dependencies = [
"log",
"once_cell",

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.150.0"
version = "1.151.2"
edition = "2021"
license = "MPL-2.0"
rust-version = "1.77"

View File

@@ -14,8 +14,8 @@ For example, to release version 1.116.0 of the core, do the following steps.
5. Commit the changes as `chore(release): prepare for 1.116.0`.
Optionally, use a separate branch like `prep-1.116.0` for this commit and open a PR for review.
6. Tag the release: `git tag -a v1.116.0`.
6. Tag the release: `git tag --annotate v1.116.0`.
7. Push the release tag: `git push origin v1.116.0`.
8. Create a GitHub release: `gh release create v1.116.0 -n ''`.
8. Create a GitHub release: `gh release create v1.116.0 --notes ''`.

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.150.0"
version = "1.151.2"
description = "Deltachat FFI"
edition = "2018"
readme = "README.md"

View File

@@ -1154,9 +1154,14 @@ 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 descr The user-visible description of JSON data,
* in case of a chess game, e.g. the move.
* @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 Deprecated, set to NULL
* @return 1=success, 0=error
*/
int dc_send_webxdc_status_update (dc_context_t* context, uint32_t msg_id, const char* json, const char* descr);
@@ -4198,6 +4203,10 @@ char* dc_msg_get_webxdc_blob (const dc_msg_t* msg, const char*
* 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.
* - 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.
+ Should be exposed to `webxdc.sendUpdateMaxSize` in JS land.
*
* @memberof dc_msg_t
* @param msg The webxdc instance.
@@ -4512,6 +4521,24 @@ 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 link attached to an webxdc info message.
* The info message needs to be of type DC_INFO_WEBXDC_INFO_MESSAGE.
*
* Typically, this is used to set `document.location.href` in JS land.
*
* Webxdc apps can define the link by setting `update.href` when sending and update,
* see dc_send_webxdc_status_update().
*
* @memberof dc_msg_t
* @param msg The info message object.
* Not: the webxdc instance.
* @return The link to be set to `document.location.href` in JS land.
* Returns NULL if there is no link attached to the info message and on errors.
*/
char* dc_msg_get_webxdc_href (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().
@@ -5874,15 +5901,26 @@ int dc_event_get_data2_int(dc_event_t* event);
/**
* Get data associated with an event object.
* The meaning of the data depends on the event ID
* returned as @ref DC_EVENT constants by dc_event_get_id().
* See also dc_event_get_data1_int() and dc_event_get_data2_int().
* The meaning of the data depends on the event ID returned as @ref DC_EVENT constants.
*
* @memberof dc_event_t
* @param event Event object as returned from dc_get_next_event().
* @return "data2" as a string or NULL.
* the meaning depends on the event type associated with this event.
* Once you're done with the string, you have to unref it using dc_unref_str().
* @return "data1" string or NULL.
* The meaning depends on the event type associated with this event.
* Must be freed using dc_str_unref().
*/
char* dc_event_get_data1_str(dc_event_t* event);
/**
* Get data associated with an event object.
* The meaning of the data depends on the event ID returned as @ref DC_EVENT constants.
*
* @memberof dc_event_t
* @param event Event object as returned from dc_get_next_event().
* @return "data2" string or NULL.
* The meaning depends on the event type associated with this event.
* Must be freed using dc_str_unref().
*/
char* dc_event_get_data2_str(dc_event_t* event);
@@ -6080,12 +6118,35 @@ 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 (int) contact_id ID _and_ (char*) href.
* - dc_event_get_data1_int() returns contact_id of the sending contact.
* - dc_event_get_data1_str() returns the href as set to `update.href`.
* @param data2 (int) msg_id _and_ (char*) text_to_notify.
* - dc_event_get_data2_int() returns the msg_id,
* referring to the webxdc-info-message, if there is any.
* Sometimes no webxdc-info-message is added to the chat
* and yet a notification is sent; in this case the msg_id
* of the webxdc instance is returned.
* - dc_event_get_data2_str() returns text_to_notify,
* the text that shall be shown in the notification.
* 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
*/
@@ -6369,6 +6430,25 @@ void dc_event_unref(dc_event_t* event);
#define DC_EVENT_CHATLIST_ITEM_CHANGED 2301
/**
* Inform that the list of accounts has changed (an account removed or added or (not yet implemented) the account order changes)
*
* This event is only emitted by the account manager.
*/
#define DC_EVENT_ACCOUNTS_CHANGED 2302
/**
* Inform that an account property that might be shown in the account list changed, namely:
* - is_configured (see dc_is_configured())
* - displayname
* - selfavatar
* - private_tag
*
* This event is emitted from the account whose property changed.
*/
#define DC_EVENT_ACCOUNTS_ITEM_CHANGED 2303
/**
* Inform that some events have been skipped due to event channel overflow.

View File

@@ -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,
@@ -568,6 +569,8 @@ pub unsafe extern "C" fn dc_event_get_id(event: *mut dc_event_t) -> libc::c_int
EventType::AccountsBackgroundFetchDone => 2200,
EventType::ChatlistChanged => 2300,
EventType::ChatlistItemChanged { .. } => 2301,
EventType::AccountsChanged => 2302,
EventType::AccountsItemChanged => 2303,
EventType::EventChannelOverflow { .. } => 2400,
#[allow(unreachable_patterns)]
#[cfg(test)]
@@ -600,9 +603,12 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc:
| EventType::ConfigSynced { .. }
| EventType::IncomingMsgBunch { .. }
| EventType::ErrorSelfNotInGroup(_)
| EventType::AccountsBackgroundFetchDone => 0,
EventType::ChatlistChanged => 0,
EventType::IncomingReaction { contact_id, .. } => contact_id.to_u32() as libc::c_int,
| EventType::AccountsBackgroundFetchDone
| EventType::ChatlistChanged
| EventType::AccountsChanged
| EventType::AccountsItemChanged => 0,
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, .. }
@@ -674,6 +680,8 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
| EventType::AccountsBackgroundFetchDone
| EventType::ChatlistChanged
| EventType::ChatlistItemChanged { .. }
| EventType::AccountsChanged
| EventType::AccountsItemChanged
| EventType::ConfigSynced { .. }
| EventType::ChatModified(_)
| EventType::WebxdcRealtimeAdvertisementReceived { .. }
@@ -681,6 +689,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, .. }
@@ -700,6 +709,27 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
}
}
#[no_mangle]
pub unsafe extern "C" fn dc_event_get_data1_str(event: *mut dc_event_t) -> *mut libc::c_char {
if event.is_null() {
eprintln!("ignoring careless call to dc_event_get_data1_str()");
return ptr::null_mut();
}
let event = &(*event).typ;
match event {
EventType::IncomingWebxdcNotify { href, .. } => {
if let Some(href) = href {
href.to_c_string().unwrap_or_default().into_raw()
} else {
ptr::null_mut()
}
}
_ => ptr::null_mut(),
}
}
#[no_mangle]
pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut libc::c_char {
if event.is_null() {
@@ -748,6 +778,8 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut
| EventType::IncomingMsgBunch { .. }
| EventType::ChatlistItemChanged { .. }
| EventType::ChatlistChanged
| EventType::AccountsChanged
| EventType::AccountsItemChanged
| EventType::WebxdcRealtimeAdvertisementReceived { .. }
| EventType::EventChannelOverflow { .. } => ptr::null_mut(),
EventType::ConfigureProgress { comment, .. } => {
@@ -775,6 +807,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"),
@@ -1059,7 +1094,7 @@ pub unsafe extern "C" fn dc_send_webxdc_status_update(
context: *mut dc_context_t,
msg_id: u32,
json: *const libc::c_char,
descr: *const libc::c_char,
_descr: *const libc::c_char,
) -> libc::c_int {
if context.is_null() {
eprintln!("ignoring careless call to dc_send_webxdc_status_update()");
@@ -1067,14 +1102,10 @@ pub unsafe extern "C" fn dc_send_webxdc_status_update(
}
let ctx = &*context;
block_on(ctx.send_webxdc_status_update(
MsgId::new(msg_id),
&to_string_lossy(json),
&to_string_lossy(descr),
))
.context("Failed to send webxdc update")
.log_err(ctx)
.is_ok() as libc::c_int
block_on(ctx.send_webxdc_status_update(MsgId::new(msg_id), &to_string_lossy(json)))
.context("Failed to send webxdc update")
.log_err(ctx)
.is_ok() as libc::c_int
}
#[no_mangle]
@@ -3681,6 +3712,17 @@ 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_href(msg: *mut dc_msg_t) -> *mut libc::c_char {
if msg.is_null() {
eprintln!("ignoring careless call to dc_msg_get_webxdc_href()");
return "".strdup();
}
let ffi_msg = &*msg;
ffi_msg.message.get_webxdc_href().strdup()
}
#[no_mangle]
pub unsafe extern "C" fn dc_msg_is_increation(msg: *mut dc_msg_t) -> libc::c_int {
if msg.is_null() {

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-jsonrpc"
version = "1.150.0"
version = "1.151.2"
description = "DeltaChat JSON-RPC API"
edition = "2021"
default-run = "deltachat-jsonrpc-server"

View File

@@ -1767,10 +1767,10 @@ impl CommandApi {
account_id: u32,
instance_msg_id: u32,
update_str: String,
description: String,
_descr: String,
) -> Result<()> {
let ctx = self.get_context(account_id).await?;
ctx.send_webxdc_status_update(MsgId::new(instance_msg_id), &update_str, &description)
ctx.send_webxdc_status_update(MsgId::new(instance_msg_id), &update_str)
.await
}

View File

@@ -106,6 +106,15 @@ 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,
href: Option<String>,
},
/// There is a fresh message. Typically, the user will show an notification
/// when receiving this message.
///
@@ -277,6 +286,20 @@ pub enum EventType {
#[serde(rename_all = "camelCase")]
ChatlistItemChanged { chat_id: Option<u32> },
/// Inform that the list of accounts has changed (an account removed or added or (not yet implemented) the account order changes)
///
/// This event is only emitted by the account manager
AccountsChanged,
/// Inform that an account property that might be shown in the account list changed, namely:
/// - is_configured (see is_configured())
/// - displayname
/// - selfavatar
/// - private_tag
///
/// This event is emitted from the account whose property changed.
AccountsItemChanged,
/// Inform than some events have been skipped due to event channel overflow.
EventChannelOverflow { n: u64 },
}
@@ -319,6 +342,17 @@ impl From<CoreEventType> for EventType {
msg_id: msg_id.to_u32(),
reaction: reaction.as_str().to_string(),
},
CoreEventType::IncomingWebxdcNotify {
contact_id,
msg_id,
text,
href,
} => IncomingWebxdcNotify {
contact_id: contact_id.to_u32(),
msg_id: msg_id.to_u32(),
text,
href,
},
CoreEventType::IncomingMsg { chat_id, msg_id } => IncomingMsg {
chat_id: chat_id.to_u32(),
msg_id: msg_id.to_u32(),
@@ -409,6 +443,8 @@ impl From<CoreEventType> for EventType {
},
CoreEventType::ChatlistChanged => ChatlistChanged,
CoreEventType::EventChannelOverflow { n } => EventChannelOverflow { n },
CoreEventType::AccountsChanged => AccountsChanged,
CoreEventType::AccountsItemChanged => AccountsItemChanged,
#[allow(unreachable_patterns)]
#[cfg(test)]
_ => unreachable!("This is just to silence a rust_analyzer false-positive"),

View File

@@ -37,6 +37,12 @@ pub struct WebxdcMessageInfo {
internet_access: bool,
/// Address to be used for `window.webxdc.selfAddr` in JS land.
self_addr: String,
/// Milliseconds to wait before calling `sendUpdate()` again since the last call.
/// Should be exposed to `window.sendUpdateInterval` in JS land.
send_update_interval: usize,
/// Maximum number of bytes accepted for a serialized update object.
/// Should be exposed to `window.sendUpdateMaxSize` in JS land.
send_update_max_size: usize,
}
impl WebxdcMessageInfo {
@@ -53,6 +59,8 @@ impl WebxdcMessageInfo {
source_code_url,
internet_access,
self_addr,
send_update_interval,
send_update_max_size,
} = message.get_webxdc_info(context).await?;
Ok(Self {
@@ -63,6 +71,8 @@ impl WebxdcMessageInfo {
source_code_url: maybe_empty_string_to_option(source_code_url),
internet_access,
self_addr,
send_update_interval,
send_update_max_size,
})
}
}

View File

@@ -58,5 +58,5 @@
},
"type": "module",
"types": "dist/deltachat.d.ts",
"version": "1.150.0"
"version": "1.151.2"
}

View File

@@ -90,6 +90,11 @@ impl Ratelimit {
pub fn until_can_send(&self) -> Duration {
self.until_can_send_at(SystemTime::now())
}
/// Returns minimum possible update interval in milliseconds.
pub fn update_interval(&self) -> usize {
(self.window.as_millis() as f64 / self.quota) as usize
}
}
#[cfg(test)]
@@ -102,6 +107,7 @@ mod tests {
let mut ratelimit = Ratelimit::new_at(Duration::new(60, 0), 3.0, now);
assert!(ratelimit.can_send_at(now));
assert_eq!(ratelimit.update_interval(), 20_000);
// Send burst of 3 messages.
ratelimit.send_at(now);

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-repl"
version = "1.150.0"
version = "1.151.2"
license = "MPL-2.0"
edition = "2021"
repository = "https://github.com/deltachat/deltachat-core-rust"

View File

@@ -969,9 +969,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
"Arguments <msg-id> <json status update> expected"
);
let msg_id = MsgId::new(arg1.parse()?);
context
.send_webxdc_status_update(msg_id, arg2, "this is a webxdc status update")
.await?;
context.send_webxdc_status_update(msg_id, arg2).await?;
}
"videochat" => {
ensure!(sel_chat.is_some(), "No chat selected.");

View File

@@ -25,7 +25,8 @@ $ pip install .
## Testing
1. Build `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`.
2. Run `CHATMAIL_DOMAIN=nine.testrun.org PATH="../target/debug:$PATH" tox`.
2. Install tox `pip install -U tox`
3. Run `CHATMAIL_DOMAIN=nine.testrun.org PATH="../target/debug:$PATH" tox`.
Additional arguments to `tox` are passed to pytest, e.g. `tox -- -s` does not capture test output.

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "deltachat-rpc-client"
version = "1.150.0"
version = "1.151.2"
description = "Python client for Delta Chat core JSON-RPC interface"
classifiers = [
"Development Status :: 5 - Production/Stable",
@@ -24,9 +24,6 @@ classifiers = [
"Topic :: Communications :: Email"
]
readme = "README.md"
dependencies = [
"imap-tools",
]
[tool.setuptools.package-data]
deltachat_rpc_client = [

View File

@@ -61,6 +61,8 @@ class EventType(str, Enum):
WEBXDC_INSTANCE_DELETED = "WebxdcInstanceDeleted"
CHATLIST_CHANGED = "ChatlistChanged"
CHATLIST_ITEM_CHANGED = "ChatlistItemChanged"
ACCOUNTS_CHANGED = "AccountsChanged"
ACCOUNTS_ITEM_CHANGED = "AccountsItemChanged"
CONFIG_SYNCED = "ConfigSynced"
WEBXDC_REALTIME_DATA = "WebxdcRealtimeData"
WEBXDC_REALTIME_ADVERTISEMENT_RECEIVED = "WebxdcRealtimeAdvertisementReceived"

View File

@@ -1,7 +1,3 @@
"""
Internal Python-level IMAP handling used by the tests.
"""
from __future__ import annotations
import imaplib
@@ -11,17 +7,11 @@ import ssl
from contextlib import contextmanager
from typing import TYPE_CHECKING
from imap_tools import (
AND,
Header,
MailBox,
MailMessage,
MailMessageFlags,
errors,
)
import pytest
from imap_tools import AND, Header, MailBox, MailMessage, MailMessageFlags, errors
if TYPE_CHECKING:
from . import Account
from deltachat_rpc_client import Account
FLAGS = b"FLAGS"
FETCH = b"FETCH"
@@ -29,6 +19,8 @@ ALL = "1:*"
class DirectImap:
"""Internal Python-level IMAP handling."""
def __init__(self, account: Account) -> None:
self.account = account
self.logid = account.get_config("displayname") or id(account)
@@ -212,3 +204,8 @@ class IdleManager:
def done(self):
"""send idle-done to server if we are currently in idle mode."""
return self.direct_imap.conn.idle.stop()
@pytest.fixture
def direct_imap():
return DirectImap

View File

@@ -0,0 +1,29 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from deltachat_rpc_client import EventType
if TYPE_CHECKING:
from deltachat_rpc_client.pytestplugin import ACFactory
def test_event_on_configuration(acfactory: ACFactory) -> None:
"""
Test if ACCOUNTS_ITEM_CHANGED event is emitted on configure
"""
account = acfactory.new_preconfigured_account()
account.clear_all_events()
assert not account.is_configured()
future = account.configure.future()
while True:
event = account.wait_for_event()
if event.kind == EventType.ACCOUNTS_ITEM_CHANGED:
break
assert account.is_configured()
future()
# other tests are written in rust: src/tests/account_events.rs

View File

@@ -12,7 +12,6 @@ import pytest
from deltachat_rpc_client import Contact, EventType, Message, events
from deltachat_rpc_client.const import DownloadState, MessageState
from deltachat_rpc_client.direct_imap import DirectImap
from deltachat_rpc_client.rpc import JsonRpcError
@@ -536,7 +535,7 @@ def test_reaction_to_partially_fetched_msg(acfactory, tmp_path):
assert list(reactions.reactions_by_contact.values())[0] == [react_str]
def test_reactions_for_a_reordering_move(acfactory):
def test_reactions_for_a_reordering_move(acfactory, direct_imap):
"""When a batch of messages is moved from Inbox to DeltaChat folder with a single MOVE command,
their UIDs may be reordered (e.g. Gmail is known for that) which led to that messages were
processed by receive_imf in the wrong order, and, particularly, reactions were processed before
@@ -560,7 +559,7 @@ def test_reactions_for_a_reordering_move(acfactory):
msg1.send_reaction(react_str).wait_until_delivered()
logging.info("moving messages to ac2's DeltaChat folder in the reverse order")
ac2_direct_imap = DirectImap(ac2)
ac2_direct_imap = direct_imap(ac2)
ac2_direct_imap.connect()
for uid in sorted([m.uid for m in ac2_direct_imap.get_all_messages()], reverse=True):
ac2_direct_imap.conn.move(uid, "DeltaChat")

View File

@@ -25,6 +25,8 @@ def test_webxdc(acfactory) -> None:
"sourceCodeUrl": None,
"summary": None,
"selfAddr": webxdc_info["selfAddr"],
"sendUpdateInterval": 1000,
"sendUpdateMaxSize": 18874368,
}
status_updates = message.get_webxdc_status_updates()

View File

@@ -16,6 +16,7 @@ deps =
pytest
pytest-timeout
pytest-xdist
imap-tools
[testenv:lint]
skipsdist = True

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-rpc-server"
version = "1.150.0"
version = "1.151.2"
description = "DeltaChat JSON-RPC server"
edition = "2021"
readme = "README.md"

View File

@@ -15,5 +15,5 @@
},
"type": "module",
"types": "index.d.ts",
"version": "1.150.0"
"version": "1.151.2"
}

View File

@@ -363,6 +363,8 @@
mkRustPackages "x86_64-linux" //
mkRustPackages "armv7l-linux" //
mkRustPackages "armv6l-linux" //
mkRustPackages "x86_64-darwin" //
mkRustPackages "aarch64-darwin" //
mkAndroidPackages "armeabi-v7a" //
mkAndroidPackages "arm64-v8a" //
mkAndroidPackages "x86" //
@@ -479,8 +481,8 @@
pkgs.python3
pkgs.python3Packages.wheel
];
buildPhase = ''python3 scripts/wheel-rpc-server.py source deltachat-rpc-server-${manifest.version}.tar.gz'';
installPhase = ''mkdir -p $out; cp -av deltachat-rpc-server-${manifest.version}.tar.gz $out'';
buildPhase = ''python3 scripts/wheel-rpc-server.py source deltachat_rpc_server-${manifest.version}.tar.gz'';
installPhase = ''mkdir -p $out; cp -av deltachat_rpc_server-${manifest.version}.tar.gz $out'';
};
deltachat-rpc-client =

2453
fuzz/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -31,6 +31,8 @@ module.exports = {
DC_DOWNLOAD_IN_PROGRESS: 1000,
DC_DOWNLOAD_UNDECIPHERABLE: 30,
DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE: 2200,
DC_EVENT_ACCOUNTS_CHANGED: 2302,
DC_EVENT_ACCOUNTS_ITEM_CHANGED: 2303,
DC_EVENT_CHANNEL_OVERFLOW: 2400,
DC_EVENT_CHATLIST_CHANGED: 2300,
DC_EVENT_CHATLIST_ITEM_CHANGED: 2301,
@@ -52,6 +54,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,

View File

@@ -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',
@@ -43,5 +44,7 @@ module.exports = {
2200: 'DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE',
2300: 'DC_EVENT_CHATLIST_CHANGED',
2301: 'DC_EVENT_CHATLIST_ITEM_CHANGED',
2302: 'DC_EVENT_ACCOUNTS_CHANGED',
2303: 'DC_EVENT_ACCOUNTS_ITEM_CHANGED',
2400: 'DC_EVENT_CHANNEL_OVERFLOW'
}

View File

@@ -31,6 +31,8 @@ export enum C {
DC_DOWNLOAD_IN_PROGRESS = 1000,
DC_DOWNLOAD_UNDECIPHERABLE = 30,
DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE = 2200,
DC_EVENT_ACCOUNTS_CHANGED = 2302,
DC_EVENT_ACCOUNTS_ITEM_CHANGED = 2303,
DC_EVENT_CHANNEL_OVERFLOW = 2400,
DC_EVENT_CHATLIST_CHANGED = 2300,
DC_EVENT_CHATLIST_ITEM_CHANGED = 2301,
@@ -52,6 +54,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 +328,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',
@@ -351,5 +355,7 @@ export const EventId2EventName: { [key: number]: string } = {
2200: 'DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE',
2300: 'DC_EVENT_CHATLIST_CHANGED',
2301: 'DC_EVENT_CHATLIST_ITEM_CHANGED',
2302: 'DC_EVENT_ACCOUNTS_CHANGED',
2303: 'DC_EVENT_ACCOUNTS_ITEM_CHANGED',
2400: 'DC_EVENT_CHANNEL_OVERFLOW',
}

View File

@@ -55,5 +55,5 @@
"test:mocha": "mocha node/test/test.mjs --growl --reporter=spec --bail --exit"
},
"types": "node/dist/index.d.ts",
"version": "1.150.0"
"version": "1.151.2"
}

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "deltachat"
version = "1.150.0"
version = "1.151.2"
description = "Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat"
readme = "README.rst"
requires-python = ">=3.7"

View File

@@ -1 +1 @@
2024-11-21
2024-11-26

View File

@@ -25,7 +25,7 @@ def build_source_package(version, filename):
def pack(name, contents):
contents = contents.encode()
tar_info = tarfile.TarInfo(f"deltachat-rpc-server-{version}/{name}")
tar_info = tarfile.TarInfo(f"deltachat_rpc_server-{version}/{name}")
tar_info.mode = 0o644
tar_info.size = len(contents)
pkg.addfile(tar_info, BytesIO(contents))
@@ -167,7 +167,7 @@ def main():
cargo_manifest = tomllib.load(fp)
version = cargo_manifest["package"]["version"]
if sys.argv[1] == "source":
filename = f"deltachat-rpc-server-{version}.tar.gz"
filename = f"deltachat_rpc_server-{version}.tar.gz"
build_source_package(version, filename)
else:
arch = sys.argv[1]

View File

@@ -139,6 +139,7 @@ impl Accounts {
ctx.open("".to_string()).await?;
self.accounts.insert(account_config.id, ctx);
self.emit_event(EventType::AccountsChanged);
Ok(account_config.id)
}
@@ -156,6 +157,7 @@ impl Accounts {
.build()
.await?;
self.accounts.insert(account_config.id, ctx);
self.emit_event(EventType::AccountsChanged);
Ok(account_config.id)
}
@@ -190,6 +192,7 @@ impl Accounts {
.context("failed to remove account data")?;
}
self.config.remove_account(id).await?;
self.emit_event(EventType::AccountsChanged);
Ok(())
}

View File

@@ -791,6 +791,12 @@ impl Context {
self.sql.set_raw_config(key.as_ref(), value).await?;
}
}
if matches!(
key,
Config::Displayname | Config::Selfavatar | Config::PrivateTag
) {
self.emit_event(EventType::AccountsItemChanged);
}
if key.is_synced() {
self.emit_event(EventType::ConfigSynced { key });
}

View File

@@ -36,10 +36,10 @@ use crate::message::Message;
use crate::oauth2::get_oauth2_addr;
use crate::provider::{Protocol, Socket, UsernamePattern};
use crate::smtp::Smtp;
use crate::stock_str;
use crate::sync::Sync::*;
use crate::tools::time;
use crate::{chat, e2ee, provider};
use crate::{stock_str, EventType};
use deltachat_contact_tools::addr_cmp;
macro_rules! progress {
@@ -486,6 +486,7 @@ async fn configure(ctx: &Context, param: &EnteredLoginParam) -> Result<Configure
update_device_chats_handle.await??;
ctx.sql.set_raw_config_bool("configured", true).await?;
ctx.emit_event(EventType::AccountsItemChanged);
Ok(configured_param)
}

View File

@@ -60,9 +60,11 @@ pub async fn debug_logging_loop(context: &Context, events: Receiver<DebugEventLo
"time": time,
}),
info: None,
href: None,
summary: None,
document: None,
uid: None,
notify: None,
},
time,
)

View File

@@ -440,7 +440,7 @@ mod tests {
let _sent1 = alice.send_msg(chat_id, &mut instance).await;
alice
.send_webxdc_status_update(instance.id, r#"{"payload":7}"#, "d")
.send_webxdc_status_update(instance.id, r#"{"payload":7}"#)
.await?;
alice.flush_status_updates().await?;
let sent2 = alice.pop_sent_msg().await;

View File

@@ -107,6 +107,21 @@ pub enum EventType {
reaction: Reaction,
},
/// A webxdc wants an info message or a changed summary to be notified.
IncomingWebxdcNotify {
/// ID of the contact sending.
contact_id: ContactId,
/// ID of the added info message or webxdc instance in case of summary change.
msg_id: MsgId,
/// Text to notify.
text: String,
/// Link assigned to this notification, if any.
href: Option<String>,
},
/// There is a fresh message. Typically, the user will show an notification
/// when receiving this message.
///
@@ -332,6 +347,20 @@ pub enum EventType {
chat_id: Option<ChatId>,
},
/// Inform that the list of accounts has changed (an account removed or added or (not yet implemented) the account order changes)
///
/// This event is only emitted by the account manager
AccountsChanged,
/// Inform that an account property that might be shown in the account list changed, namely:
/// - is_configured (see [crate::context::Context::is_configured])
/// - displayname
/// - selfavatar
/// - private_tag
///
/// This event is emitted from the account whose property changed.
AccountsItemChanged,
/// Event for using in tests, e.g. as a fence between normally generated events.
#[cfg(test)]
Test,

View File

@@ -426,6 +426,7 @@ async fn import_backup_stream_inner<R: tokio::io::AsyncRead + Unpin>(
if res.is_ok() {
context.emit_event(EventType::ImexProgress(999));
res = context.sql.run_migrations(context).await;
context.emit_event(EventType::AccountsItemChanged);
}
if res.is_ok() {
delete_and_reset_all_device_msgs(context)

View File

@@ -6,7 +6,6 @@ use anyhow::{bail, Context as _, Result};
use base64::Engine as _;
use chrono::TimeZone;
use email::Mailbox;
use format_flowed::{format_flowed, format_flowed_quote};
use lettre_email::{Address, Header, MimeMultipartType, PartBuilder};
use tokio::fs;
@@ -1300,9 +1299,18 @@ impl MimeFactory {
let final_text = placeholdertext.as_deref().unwrap_or(&msg.text);
let mut quoted_text = msg
.quoted_text()
.map(|quote| format_flowed_quote(&quote) + "\r\n\r\n");
let mut quoted_text = None;
if let Some(msg_quoted_text) = msg.quoted_text() {
let mut some_quoted_text = String::new();
for quoted_line in msg_quoted_text.split('\n') {
some_quoted_text += "> ";
some_quoted_text += quoted_line;
some_quoted_text += "\r\n";
}
some_quoted_text += "\r\n";
quoted_text = Some(some_quoted_text)
}
if !is_encrypted && msg.param.get_bool(Param::ProtectQuote).unwrap_or_default() {
// Message is not encrypted but quotes encrypted message.
quoted_text = Some("> ...\r\n\r\n".to_string());
@@ -1312,7 +1320,6 @@ impl MimeFactory {
// Delta Chat.
quoted_text = Some("\r\n".to_string());
}
let flowed_text = format_flowed(final_text);
let is_reaction = msg.param.get_int(Param::Reaction).unwrap_or_default() != 0;
@@ -1322,7 +1329,7 @@ impl MimeFactory {
"{}{}{}{}{}{}",
fwdhint.unwrap_or_default(),
quoted_text.unwrap_or_default(),
escape_message_footer_marks(&flowed_text),
escape_message_footer_marks(final_text),
if !final_text.is_empty() && !footer.is_empty() {
"\r\n\r\n"
} else {
@@ -1332,10 +1339,8 @@ impl MimeFactory {
footer
);
let mut main_part = PartBuilder::new().header((
"Content-Type",
"text/plain; charset=utf-8; format=flowed; delsp=no",
));
let mut main_part =
PartBuilder::new().header(("Content-Type", "text/plain; charset=utf-8"));
main_part = self.add_message_text(main_part, message_text);
if is_reaction {

View File

@@ -542,6 +542,8 @@ impl Peerstate {
/// * `old_addr`: Old address of the peerstate in case of an AEAP transition.
pub(crate) async fn save_to_db_ex(&self, sql: &Sql, old_addr: Option<&str>) -> Result<()> {
let trans_fn = |t: &mut rusqlite::Transaction| {
let verified_key_fingerprint =
self.verified_key_fingerprint.as_ref().map(|fp| fp.hex());
if let Some(old_addr) = old_addr {
// We are doing an AEAP transition to the new address and the SQL INSERT below will
// save the existing peerstate as belonging to this new address. We now need to
@@ -551,11 +553,14 @@ impl Peerstate {
// existing peerstate as this would break encryption to it. This is critical for
// non-verified groups -- if we can't encrypt to the old address, we can't securely
// remove it from the group (to add the new one instead).
//
// NB: We check that `verified_key_fingerprint` hasn't changed to protect from
// possible races.
t.execute(
"UPDATE acpeerstates \
SET verified_key=NULL, verified_key_fingerprint='', verifier='' \
WHERE addr=?",
(old_addr,),
"UPDATE acpeerstates
SET verified_key=NULL, verified_key_fingerprint='', verifier=''
WHERE addr=? AND verified_key_fingerprint=?",
(old_addr, &verified_key_fingerprint),
)?;
}
t.execute(
@@ -604,7 +609,7 @@ impl Peerstate {
self.public_key_fingerprint.as_ref().map(|fp| fp.hex()),
self.gossip_key_fingerprint.as_ref().map(|fp| fp.hex()),
self.verified_key.as_ref().map(|k| k.to_bytes()),
self.verified_key_fingerprint.as_ref().map(|fp| fp.hex()),
&verified_key_fingerprint,
self.verifier.as_deref().unwrap_or(""),
self.secondary_verified_key.as_ref().map(|k| k.to_bytes()),
self.secondary_verified_key_fingerprint

View File

@@ -558,7 +558,12 @@ Here's my footer -- bob@example.net"
) -> Result<()> {
let event = t
.evtracker
.get_matching(|evt| matches!(evt, EventType::ReactionsChanged { .. }))
.get_matching(|evt| {
matches!(
evt,
EventType::ReactionsChanged { .. } | EventType::IncomingMsg { .. }
)
})
.await;
match event {
EventType::ReactionsChanged {
@@ -570,7 +575,7 @@ Here's my footer -- bob@example.net"
assert_eq!(msg_id, expected_msg_id);
assert_eq!(contact_id, expected_contact_id);
}
_ => unreachable!(),
_ => panic!("Unexpected event {event:?}."),
}
Ok(())
}
@@ -583,7 +588,14 @@ Here's my footer -- bob@example.net"
) -> Result<()> {
let event = t
.evtracker
.get_matching(|evt| matches!(evt, EventType::IncomingReaction { .. }))
// Check for absence of `IncomingMsg` events -- it appeared that it's quite easy to make
// bugs when `IncomingMsg` is issued for reactions.
.get_matching(|evt| {
matches!(
evt,
EventType::IncomingReaction { .. } | EventType::IncomingMsg { .. }
)
})
.await;
match event {
EventType::IncomingReaction {
@@ -595,16 +607,25 @@ Here's my footer -- bob@example.net"
assert_eq!(contact_id, expected_contact_id);
assert_eq!(reaction, Reaction::from(expected_reaction));
}
_ => unreachable!(),
_ => panic!("Unexpected event {event:?}."),
}
Ok(())
}
async fn has_incoming_reactions_event(t: &TestContext) -> bool {
t.evtracker
.get_matching_opt(t, |evt| matches!(evt, EventType::IncomingReaction { .. }))
.await
.is_some()
/// Checks that no unwanted events remain after expecting "wanted" reaction events.
async fn expect_no_unwanted_events(t: &TestContext) {
let ev = t
.evtracker
.get_matching_opt(t, |evt| {
matches!(
evt,
EventType::IncomingReaction { .. } | EventType::IncomingMsg { .. }
)
})
.await;
if let Some(ev) = ev {
panic!("Unwanted event {ev:?}.")
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
@@ -635,9 +656,10 @@ Here's my footer -- bob@example.net"
bob_msg.chat_id.accept(&bob).await?;
bob.evtracker.clear_events();
send_reaction(&bob, bob_msg.id, "👍").await.unwrap();
expect_reactions_changed_event(&bob, bob_msg.chat_id, bob_msg.id, ContactId::SELF).await?;
assert!(!has_incoming_reactions_event(&bob).await);
expect_no_unwanted_events(&bob).await;
assert_eq!(get_chat_msgs(&bob, bob_msg.chat_id).await?.len(), 2);
let bob_reaction_msg = bob.pop_sent_msg().await;
@@ -656,6 +678,7 @@ Here's my footer -- bob@example.net"
expect_reactions_changed_event(&alice, chat_alice.id, alice_msg.sender_msg_id, *bob_id)
.await?;
expect_incoming_reactions_event(&alice, alice_msg.sender_msg_id, *bob_id, "👍").await?;
expect_no_unwanted_events(&alice).await;
// Alice reacts to own message.
send_reaction(&alice, alice_msg.sender_msg_id, "👍 😀")
@@ -684,6 +707,7 @@ Here's my footer -- bob@example.net"
let bob = TestContext::new_bob().await;
alice.set_config(Config::Displayname, Some("ALICE")).await?;
bob.set_config(Config::Displayname, Some("BOB")).await?;
let alice_bob_id = alice.add_or_lookup_contact_id(&bob).await;
// Alice sends message to Bob
let alice_chat = alice.create_chat(&bob).await;
@@ -696,7 +720,9 @@ Here's my footer -- bob@example.net"
send_reaction(&bob, bob_msg1.id, "👍").await?;
let bob_send_reaction = bob.pop_sent_msg().await;
alice.recv_msg_trash(&bob_send_reaction).await;
assert!(has_incoming_reactions_event(&alice).await);
expect_incoming_reactions_event(&alice, alice_msg1.sender_msg_id, alice_bob_id, "👍")
.await?;
expect_no_unwanted_events(&alice).await;
let chatlist = Chatlist::try_load(&bob, 0, None, None).await?;
let summary = chatlist.get_summary(&bob, 0, None).await?;
@@ -711,8 +737,9 @@ Here's my footer -- bob@example.net"
SystemTime::shift(Duration::from_secs(10));
send_reaction(&alice, alice_msg1.sender_msg_id, "🍿").await?;
let alice_send_reaction = alice.pop_sent_msg().await;
bob.evtracker.clear_events();
bob.recv_msg_opt(&alice_send_reaction).await;
assert!(!has_incoming_reactions_event(&bob).await);
expect_no_unwanted_events(&bob).await;
assert_summary(&alice, "You reacted 🍿 to \"Party?\"").await;
assert_summary(&bob, "ALICE reacted 🍿 to \"Party?\"").await;
@@ -934,7 +961,9 @@ Here's my footer -- bob@example.net"
expect_reactions_changed_event(&alice0, chat_id, alice0_msg_id, ContactId::SELF).await?;
expect_reactions_changed_event(&alice1, alice1_msg.chat_id, alice1_msg.id, ContactId::SELF)
.await?;
for a in [&alice0, &alice1] {
expect_no_unwanted_events(a).await;
}
Ok(())
}
}

View File

@@ -1068,7 +1068,7 @@ async fn test_classic_mailing_list() -> Result<()> {
let mime = sent.payload();
println!("Sent mime message is:\n\n{mime}\n\n");
assert!(mime.contains("Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no\r\n"));
assert!(mime.contains("Content-Type: text/plain; charset=utf-8\r\n"));
assert!(mime.contains("Subject: =?utf-8?q?Re=3A_=5Bdelta-dev=5D_What=27s_up=3F?=\r\n"));
assert!(mime.contains("MIME-Version: 1.0\r\n"));
assert!(mime.contains("In-Reply-To: <38942@posteo.org>\r\n"));

View File

@@ -244,32 +244,27 @@ pub(crate) async fn smtp_send(
async_smtp::error::Error::Transient(ref response) => {
// We got a transient 4xx response from SMTP server.
// Give some time until the server-side error maybe goes away.
if let Some(first_word) = response.first_word() {
if first_word.ends_with(".1.1")
|| first_word.ends_with(".1.2")
|| first_word.ends_with(".1.3")
{
// Sometimes we receive transient errors that should be permanent.
// Any extended smtp status codes like x.1.1, x.1.2 or x.1.3 that we
// receive as a transient error are misconfigurations of the smtp server.
// See <https://tools.ietf.org/html/rfc3463#section-3.2>
info!(context, "Received extended status code {first_word} for a transient error. This looks like a misconfigured SMTP server, let's fail immediately.");
SendResult::Failure(format_err!("Permanent SMTP error: {}", err))
} else {
info!(
context,
"Transient error with status code {first_word}, postponing retry for later."
);
SendResult::Retry
}
} else {
info!(
context,
"Transient error without status code, postponing retry for later."
);
SendResult::Retry
}
//
// One particular case is
// `450 4.1.2 <alice@example.org>: Recipient address rejected: Domain not found`.
// known to be returned by Postfix.
//
// [RFC 3463](https://tools.ietf.org/html/rfc3463#section-3.2)
// says "This code is only useful for permanent failures."
// in X.1.1, X.1.2 and X.1.3 descriptions.
//
// Previous Delta Chat core versions
// from 1.51.0 to 1.151.1
// were treating such errors as permanent.
//
// This was later reverted because such errors were observed
// for existing domains and turned out to be actually transient,
// likely caused by nameserver downtime.
info!(
context,
"Transient error {response:?}, postponing retry for later."
);
SendResult::Retry
}
_ => {
info!(

View File

@@ -391,7 +391,7 @@ impl TestContext {
Self {
ctx,
dir,
evtracker: EventTracker(evtracker_receiver),
evtracker: EventTracker::new(evtracker_receiver),
_log_sink,
}
}
@@ -655,8 +655,8 @@ impl TestContext {
.expect("failed to load msg")
}
/// Returns the [`Contact`] for the other [`TestContext`], creating it if necessary.
pub async fn add_or_lookup_contact(&self, other: &TestContext) -> Contact {
/// Returns the [`ContactId`] for the other [`TestContext`], creating a contact if necessary.
pub async fn add_or_lookup_contact_id(&self, other: &TestContext) -> ContactId {
let primary_self_addr = other.ctx.get_primary_self_addr().await.unwrap();
let addr = ContactAddress::new(&primary_self_addr).unwrap();
// MailinglistAddress is the lowest allowed origin, we'd prefer to not modify the
@@ -670,6 +670,12 @@ impl TestContext {
Modifier::Modified => warn!(&self.ctx, "Contact {} modified by TestContext", &addr),
Modifier::Created => warn!(&self.ctx, "Contact {} created by TestContext", &addr),
}
contact_id
}
/// Returns the [`Contact`] for the other [`TestContext`], creating it if necessary.
pub async fn add_or_lookup_contact(&self, other: &TestContext) -> Contact {
let contact_id = self.add_or_lookup_contact_id(other).await;
Contact::get_by_id(&self.ctx, contact_id).await.unwrap()
}
@@ -1087,6 +1093,10 @@ impl DerefMut for EventTracker {
}
impl EventTracker {
pub fn new(emitter: EventEmitter) -> Self {
Self(emitter)
}
/// Consumes emitted events returning the first matching one.
///
/// If no matching events are ready this will wait for new events to arrive and time out

View File

@@ -1,2 +1,3 @@
mod account_events;
mod aeap;
mod verified_chats;

170
src/tests/account_events.rs Normal file
View File

@@ -0,0 +1,170 @@
//! contains tests for account (list) events
use std::time::Duration;
use anyhow::Result;
use tempfile::tempdir;
use crate::accounts::Accounts;
use crate::config::Config;
use crate::imex::{get_backup, has_backup, imex, BackupProvider, ImexMode};
use crate::test_utils::{sync, EventTracker, TestContext, TestContextManager};
use crate::EventType;
async fn wait_for_item_changed(context: &TestContext) {
context
.evtracker
.get_matching(|evt| matches!(evt, EventType::AccountsItemChanged))
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_account_event() -> Result<()> {
let dir = tempdir().unwrap();
let mut manager = Accounts::new(dir.path().join("accounts"), true).await?;
let tracker = EventTracker::new(manager.get_event_emitter());
// create account
tracker.clear_events();
let account_id = manager.add_account().await?;
tracker
.get_matching(|evt| matches!(evt, EventType::AccountsChanged))
.await;
// remove account
tracker.clear_events();
manager.remove_account(account_id).await?;
tracker
.get_matching(|evt| matches!(evt, EventType::AccountsChanged))
.await;
// create closed account
tracker.clear_events();
manager.add_closed_account().await?;
tracker
.get_matching(|evt| matches!(evt, EventType::AccountsChanged))
.await;
Ok(())
}
// configuration is tested by python tests in deltachat-rpc-client/tests/test_account_events.py
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_set_displayname() -> Result<()> {
let mut tcm = TestContextManager::new();
let context = tcm.alice().await;
context.evtracker.clear_events();
context
.set_config(crate::config::Config::Displayname, Some("🐰 Alice"))
.await?;
wait_for_item_changed(&context).await;
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_set_selfavatar() -> Result<()> {
let mut tcm = TestContextManager::new();
let context = tcm.alice().await;
let file = context.dir.path().join("avatar.jpg");
let bytes = include_bytes!("../../test-data/image/avatar1000x1000.jpg");
tokio::fs::write(&file, bytes).await?;
context.evtracker.clear_events();
context
.set_config(
crate::config::Config::Selfavatar,
Some(file.to_str().unwrap()),
)
.await?;
wait_for_item_changed(&context).await;
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_set_private_tag() -> Result<()> {
let mut tcm = TestContextManager::new();
let context = tcm.alice().await;
context.evtracker.clear_events();
context
.set_config(crate::config::Config::PrivateTag, Some("Wonderland"))
.await?;
wait_for_item_changed(&context).await;
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_import_backup() -> Result<()> {
let mut tcm = TestContextManager::new();
let context1 = tcm.alice().await;
let backup_dir = tempfile::tempdir().unwrap();
assert!(
imex(&context1, ImexMode::ExportBackup, backup_dir.path(), None)
.await
.is_ok()
);
let context2 = TestContext::new().await;
assert!(!context2.is_configured().await?);
context2.evtracker.clear_events();
let backup = has_backup(&context2, backup_dir.path()).await?;
imex(&context2, ImexMode::ImportBackup, backup.as_ref(), None).await?;
assert!(context2.is_configured().await?);
wait_for_item_changed(&context2).await;
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_receive_backup() {
let mut tcm = TestContextManager::new();
// Create first device.
let ctx0 = tcm.alice().await;
// Prepare to transfer backup.
let provider = BackupProvider::prepare(&ctx0).await.unwrap();
// Set up second device.
let ctx1 = tcm.unconfigured().await;
ctx1.evtracker.clear_events();
get_backup(&ctx1, provider.qr()).await.unwrap();
// Make sure the provider finishes without an error.
tokio::time::timeout(Duration::from_secs(30), provider)
.await
.expect("timed out")
.expect("error in provider");
wait_for_item_changed(&ctx1).await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_sync() -> Result<()> {
let alice0 = TestContext::new_alice().await;
let alice1 = TestContext::new_alice().await;
for a in [&alice0, &alice1] {
a.set_config_bool(Config::SyncMsgs, true).await?;
}
let new_name = "new name";
alice0
.set_config(Config::Displayname, Some(new_name))
.await?;
alice1.evtracker.clear_events();
sync(&alice0, &alice1).await;
wait_for_item_changed(&alice1).await;
assert_eq!(
alice1.get_config(Config::Displayname).await?,
Some(new_name.to_owned())
);
assert!(alice0.get_config(Config::Selfavatar).await?.is_none());
let file = alice0.dir.path().join("avatar.png");
let bytes = include_bytes!("../../test-data/image/avatar64x64.png");
tokio::fs::write(&file, bytes).await?;
alice0
.set_config(Config::Selfavatar, Some(file.to_str().unwrap()))
.await?;
alice1.evtracker.clear_events();
sync(&alice0, &alice1).await;
wait_for_item_changed(&alice1).await;
Ok(())
}

View File

@@ -1,13 +1,13 @@
use anyhow::Result;
use crate::chat;
use crate::chat::ChatId;
use crate::chat::{self, Chat, ChatId, ProtectionStatus};
use crate::contact;
use crate::contact::Contact;
use crate::contact::ContactId;
use crate::message::Message;
use crate::peerstate::Peerstate;
use crate::receive_imf::receive_imf;
use crate::securejoin::get_securejoin_qr;
use crate::stock_str;
use crate::test_utils::mark_as_verified;
use crate::test_utils::TestContext;
@@ -394,3 +394,40 @@ async fn test_aeap_replay_attack() -> Result<()> {
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_write_to_alice_after_aeap() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = &tcm.alice().await;
let bob = &tcm.bob().await;
let alice_grp_id = chat::create_group_chat(alice, ProtectionStatus::Protected, "Group").await?;
let qr = get_securejoin_qr(alice, Some(alice_grp_id)).await?;
tcm.exec_securejoin_qr(bob, alice, &qr).await;
let bob_alice_contact = bob.add_or_lookup_contact(alice).await;
assert!(bob_alice_contact.is_verified(bob).await?);
let bob_alice_chat = bob.create_chat(alice).await;
assert!(bob_alice_chat.is_protected());
let bob_unprotected_grp_id = bob
.create_group_with_members(ProtectionStatus::Unprotected, "Group", &[alice])
.await;
tcm.change_addr(alice, "alice@someotherdomain.xyz").await;
let sent = alice.send_text(alice_grp_id, "Hello!").await;
bob.recv_msg(&sent).await;
assert!(!bob_alice_contact.is_verified(bob).await?);
let bob_alice_chat = Chat::load_from_db(bob, bob_alice_chat.id).await?;
assert!(bob_alice_chat.is_protected());
let mut msg = Message::new_text("hi".to_string());
assert!(chat::send_msg(bob, bob_alice_chat.id, &mut msg)
.await
.is_err());
// But encrypted communication is still possible in unprotected groups with old Alice.
let sent = bob
.send_text(bob_unprotected_grp_id, "Alice, how is your address change?")
.await;
let msg = Message::load_from_db(bob, sent.sender_msg_id).await?;
assert!(msg.get_showpadlock());
Ok(())
}

View File

@@ -13,12 +13,13 @@
//! - `msg_id` - ID of the message in the `msgs` table
//! - `first_serial` - serial number of the first status update to send
//! - `last_serial` - serial number of the last status update to send
//! - `descr` - text to send along with the updates
//! - `descr` - not used, set to empty string
mod integration;
mod maps_integration;
use std::cmp::max;
use std::collections::HashMap;
use std::path::Path;
use anyhow::{anyhow, bail, ensure, format_err, Context as _, Result};
@@ -40,6 +41,7 @@ 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::mimefactory::RECOMMENDED_FILE_SIZE;
use crate::mimeparser::SystemMessage;
use crate::param::Param;
use crate::param::Params;
@@ -56,6 +58,9 @@ const WEBXDC_API_VERSION: u32 = 1;
pub const WEBXDC_SUFFIX: &str = "xdc";
const WEBXDC_DEFAULT_ICON: &str = "__webxdc__/default-icon.png";
/// Text shown to classic e-mail users in the visible e-mail body.
const BODY_DESCR: &str = "Webxdc Status Update";
/// Raw information read from manifest.toml
#[derive(Debug, Deserialize, Default)]
#[non_exhaustive]
@@ -102,6 +107,14 @@ pub struct WebxdcInfo {
/// Address to be used for `window.webxdc.selfAddr` in JS land.
pub self_addr: String,
/// 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,
/// Maximum number of bytes accepted for a serialized update object.
/// Should be exposed to `window.sendUpdateMaxSize` in JS land.
pub send_update_max_size: usize,
}
/// Status Update ID.
@@ -165,6 +178,11 @@ pub struct StatusUpdateItem {
#[serde(skip_serializing_if = "Option::is_none")]
pub info: Option<String>,
/// Optional link the info message will point to.
/// Used to set `window.location.href` in JS land.
#[serde(skip_serializing_if = "Option::is_none")]
pub href: Option<String>,
/// The new name of the editing document.
/// This is not needed if the webxdc doesn't edit documents.
#[serde(skip_serializing_if = "Option::is_none")]
@@ -182,6 +200,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 other users `selfAddr` that should be notified about this update.
#[serde(skip_serializing_if = "Option::is_none")]
pub notify: Option<HashMap<String, String>>,
}
/// Update items as passed to the UIs.
@@ -314,35 +336,7 @@ impl Context {
return Ok(None);
};
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?;
} 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 mut notify_msg_id = instance.id;
let mut param_changed = false;
let mut instance = instance.clone();
@@ -361,13 +355,52 @@ impl Context {
.param
.update_timestamp(Param::WebxdcSummaryTimestamp, timestamp)?
{
instance
.param
.set(Param::WebxdcSummary, sanitize_bidi_characters(summary));
let summary = sanitize_bidi_characters(summary);
instance.param.set(Param::WebxdcSummary, summary.clone());
param_changed = true;
}
}
if can_info_msg {
if let Some(ref info) = status_update_item.info {
let info_msg_id = self
.get_overwritable_info_msg_id(&instance, from_id)
.await?;
if info_msg_id.is_some() && status_update_item.href.is_none() {
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?;
notify_msg_id = info_msg_id;
}
} else {
notify_msg_id = chat::add_info_msg_with_cmd(
self,
instance.chat_id,
info.as_str(),
SystemMessage::WebxdcInfoMessage,
timestamp,
None,
Some(&instance),
Some(from_id),
)
.await?;
}
if let Some(ref href) = status_update_item.href {
let mut notify_msg = Message::load_from_db(self, notify_msg_id).await?;
notify_msg.param.set(Param::Arg, href);
notify_msg.update_param(self).await?;
}
}
}
if param_changed {
instance.update_param(self).await?;
self.emit_msgs_changed(instance.chat_id, instance.id);
@@ -380,6 +413,27 @@ impl Context {
});
}
if from_id != ContactId::SELF {
if let Some(notify_list) = status_update_item.notify {
let self_addr = instance.get_webxdc_self_addr(self).await?;
if let Some(notify_text) = notify_list.get(&self_addr) {
self.emit_event(EventType::IncomingWebxdcNotify {
contact_id: from_id,
msg_id: notify_msg_id,
text: notify_text.clone(),
href: status_update_item.href,
});
} else if let Some(notify_text) = notify_list.get("*") {
self.emit_event(EventType::IncomingWebxdcNotify {
contact_id: from_id,
msg_id: notify_msg_id,
text: notify_text.clone(),
href: status_update_item.href,
});
}
}
}
Ok(Some(status_update_serial))
}
@@ -446,11 +500,10 @@ impl Context {
&self,
instance_msg_id: MsgId,
update_str: &str,
descr: &str,
) -> Result<()> {
let status_update_item: StatusUpdateItem = serde_json::from_str(update_str)
.with_context(|| format!("Failed to parse webxdc update item from {update_str:?}"))?;
self.send_webxdc_status_update_struct(instance_msg_id, status_update_item, descr)
self.send_webxdc_status_update_struct(instance_msg_id, status_update_item)
.await?;
Ok(())
}
@@ -461,7 +514,6 @@ impl Context {
&self,
instance_msg_id: MsgId,
mut status_update: StatusUpdateItem,
descr: &str,
) -> Result<()> {
let instance = Message::load_from_db(self, instance_msg_id)
.await
@@ -509,10 +561,10 @@ impl Context {
if send_now {
self.sql.insert(
"INSERT INTO smtp_status_updates (msg_id, first_serial, last_serial, descr) VALUES(?, ?, ?, ?)
"INSERT INTO smtp_status_updates (msg_id, first_serial, last_serial, descr) VALUES(?, ?, ?, '')
ON CONFLICT(msg_id)
DO UPDATE SET last_serial=excluded.last_serial, descr=excluded.descr",
(instance.id, status_update_serial, status_update_serial, descr),
DO UPDATE SET last_serial=excluded.last_serial",
(instance.id, status_update_serial, status_update_serial),
).await.context("Failed to insert webxdc update into SMTP queue")?;
self.scheduler.interrupt_smtp().await;
}
@@ -520,21 +572,18 @@ impl Context {
}
/// Returns one record of the queued webxdc status updates.
async fn smtp_status_update_get(
&self,
) -> Result<Option<(MsgId, i64, StatusUpdateSerial, String)>> {
async fn smtp_status_update_get(&self) -> Result<Option<(MsgId, i64, StatusUpdateSerial)>> {
let res = self
.sql
.query_row_optional(
"SELECT msg_id, first_serial, last_serial, descr \
"SELECT msg_id, first_serial, last_serial \
FROM smtp_status_updates LIMIT 1",
(),
|row| {
let instance_id: MsgId = row.get(0)?;
let first_serial: i64 = row.get(1)?;
let last_serial: StatusUpdateSerial = row.get(2)?;
let descr: String = row.get(3)?;
Ok((instance_id, first_serial, last_serial, descr))
Ok((instance_id, first_serial, last_serial))
},
)
.await?;
@@ -572,7 +621,7 @@ impl Context {
/// Attempts to send queued webxdc status updates.
pub(crate) async fn flush_status_updates(&self) -> Result<()> {
loop {
let (instance_id, first, last, descr) = match self.smtp_status_update_get().await? {
let (instance_id, first, last) = match self.smtp_status_update_get().await? {
Some(res) => res,
None => return Ok(()),
};
@@ -589,7 +638,7 @@ impl Context {
let mut status_update = Message {
chat_id: instance.chat_id,
viewtype: Viewtype::Text,
text: descr.to_string(),
text: BODY_DESCR.to_string(),
hidden: true,
..Default::default()
};
@@ -912,6 +961,8 @@ impl Message {
},
internet_access,
self_addr,
send_update_interval: context.ratelimit.read().await.update_interval(),
send_update_max_size: RECOMMENDED_FILE_SIZE as usize,
})
}
@@ -921,6 +972,15 @@ impl Message {
let hash = Sha256::digest(data.as_bytes());
Ok(format!("{:x}", hash))
}
/// Get link attached to an info message.
///
/// The info message needs to be of type SystemMessage::WebxdcInfoMessage.
/// Typically, this is used to start the corresponding webxdc app
/// with `window.location.href` set in JS land.
pub fn get_webxdc_href(&self) -> Option<String> {
self.param.get(Param::Arg).map(|href| href.to_string())
}
}
#[cfg(test)]
@@ -1087,7 +1147,6 @@ mod tests {
t.send_webxdc_status_update(
instance.id,
r#"{"info": "foo", "summary":"bar", "document":"doc", "payload": 42}"#,
"descr",
)
.await?;
assert!(!instance.is_forwarded());
@@ -1138,7 +1197,6 @@ mod tests {
.send_webxdc_status_update(
alice_instance.id,
r#"{"payload":7,"info": "i","summary":"s"}"#,
"d",
)
.await?;
assert_eq!(alice_grp.get_msg_cnt(&alice).await?, 2);
@@ -1221,7 +1279,7 @@ mod tests {
.await
.is_ok());
assert!(bob
.send_webxdc_status_update(bob_instance.id, r#"{"payload":42}"#, "descr")
.send_webxdc_status_update(bob_instance.id, r#"{"payload":42}"#)
.await
.is_err());
assert_eq!(
@@ -1233,7 +1291,7 @@ mod tests {
// Once the contact request is accepted, Bob can send updates
bob_chat.id.accept(&bob).await?;
assert!(bob
.send_webxdc_status_update(bob_instance.id, r#"{"payload":42}"#, "descr")
.send_webxdc_status_update(bob_instance.id, r#"{"payload":42}"#)
.await
.is_ok());
assert_eq!(
@@ -1264,7 +1322,6 @@ mod tests {
.send_webxdc_status_update(
alice_instance.id,
r#"{"payload": 7, "summary":"sum", "document":"doc"}"#,
"bla",
)
.await?;
alice.flush_status_updates().await?;
@@ -1391,7 +1448,7 @@ mod tests {
.await?;
chat_id.set_draft(&t, Some(&mut instance)).await?;
let instance = chat_id.get_draft(&t).await?.unwrap();
t.send_webxdc_status_update(instance.id, r#"{"payload": 42}"#, "descr")
t.send_webxdc_status_update(instance.id, r#"{"payload": 42}"#)
.await?;
assert_eq!(
t.get_webxdc_status_updates(instance.id, StatusUpdateSerial(0))
@@ -1434,9 +1491,11 @@ mod tests {
StatusUpdateItem {
payload: json!({"foo": "bar"}),
info: None,
href: None,
document: None,
summary: None,
uid: Some("iecie2Ze".to_string()),
notify: None,
},
1640178619,
true,
@@ -1458,9 +1517,11 @@ mod tests {
StatusUpdateItem {
payload: json!({"nothing": "this should be ignored"}),
info: None,
href: None,
document: None,
summary: None,
uid: Some("iecie2Ze".to_string()),
notify: None,
},
1640178619,
true,
@@ -1470,12 +1531,12 @@ mod tests {
assert_eq!(update_id1_duplicate, None);
assert!(t
.send_webxdc_status_update(instance.id, "\n\n\n", "")
.send_webxdc_status_update(instance.id, "\n\n\n")
.await
.is_err());
assert!(t
.send_webxdc_status_update(instance.id, "bad json", "")
.send_webxdc_status_update(instance.id, "bad json")
.await
.is_err());
@@ -1491,9 +1552,11 @@ mod tests {
StatusUpdateItem {
payload: json!({"foo2": "bar2"}),
info: None,
href: None,
document: None,
summary: None,
uid: None,
notify: None,
},
1640178619,
true,
@@ -1510,9 +1573,11 @@ mod tests {
StatusUpdateItem {
payload: Value::Bool(true),
info: None,
href: None,
document: None,
summary: None,
uid: None,
notify: None,
},
1640178619,
true,
@@ -1530,7 +1595,6 @@ mod tests {
t.send_webxdc_status_update(
instance.id,
r#"{"payload" : 1, "sender": "that is not used"}"#,
"",
)
.await?;
assert_eq!(
@@ -1665,11 +1729,7 @@ mod tests {
assert!(!sent1.payload().contains("report-type=status-update"));
alice
.send_webxdc_status_update(
alice_instance.id,
r#"{"payload" : {"foo":"bar"}}"#,
"descr text",
)
.send_webxdc_status_update(alice_instance.id, r#"{"payload" : {"foo":"bar"}}"#)
.await?;
alice.flush_status_updates().await?;
expect_status_update_event(&alice, alice_instance.id).await?;
@@ -1678,7 +1738,7 @@ mod tests {
assert!(alice_update.hidden);
assert_eq!(alice_update.viewtype, Viewtype::Text);
assert_eq!(alice_update.get_filename(), None);
assert_eq!(alice_update.text, "descr text".to_string());
assert_eq!(alice_update.text, BODY_DESCR.to_string());
assert_eq!(alice_update.chat_id, alice_instance.chat_id);
assert_eq!(
alice_update.parent(&alice).await?.unwrap().id,
@@ -1686,7 +1746,7 @@ mod tests {
);
assert_eq!(alice_chat.id.get_msg_cnt(&alice).await?, 1);
assert!(sent2.payload().contains("report-type=status-update"));
assert!(sent2.payload().contains("descr text"));
assert!(sent2.payload().contains(BODY_DESCR));
assert_eq!(
alice
.get_webxdc_status_updates(alice_instance.id, StatusUpdateSerial(0))
@@ -1695,11 +1755,7 @@ mod tests {
);
alice
.send_webxdc_status_update(
alice_instance.id,
r#"{"payload":{"snipp":"snapp"}}"#,
"bla text",
)
.send_webxdc_status_update(alice_instance.id, r#"{"payload":{"snipp":"snapp"}}"#)
.await?;
assert_eq!(
alice
@@ -1768,31 +1824,23 @@ mod tests {
+ &String::from_utf8(vec![b'a'; STATUS_UPDATE_SIZE_MAX])?
+ r#""}"#;
alice
.send_webxdc_status_update(alice_instance.id, &(update1_str.clone() + "}"), "descr1")
.send_webxdc_status_update(alice_instance.id, &(update1_str.clone() + "}"))
.await?;
alice
.send_webxdc_status_update(
alice_instance.id,
r#"{"payload" : {"foo":"bar2"}}"#,
"descr2",
)
.send_webxdc_status_update(alice_instance.id, r#"{"payload" : {"foo":"bar2"}}"#)
.await?;
alice
.send_webxdc_status_update(
alice_instance.id,
r#"{"payload" : {"foo":"bar3"}}"#,
"descr3",
)
.send_webxdc_status_update(alice_instance.id, r#"{"payload" : {"foo":"bar3"}}"#)
.await?;
alice.flush_status_updates().await?;
// There's the message stack, so we pop messages in the reverse order.
let sent3 = &alice.pop_sent_msg().await;
let alice_update = sent3.load_from_db().await;
assert_eq!(alice_update.text, "descr3".to_string());
assert_eq!(alice_update.text, BODY_DESCR.to_string());
let sent2 = &alice.pop_sent_msg().await;
let alice_update = sent2.load_from_db().await;
assert_eq!(alice_update.text, "descr3".to_string());
assert_eq!(alice_update.text, BODY_DESCR.to_string());
assert_eq!(alice_chat.id.get_msg_cnt(&alice).await?, 1);
// Bob receives the instance.
@@ -1843,7 +1891,7 @@ mod tests {
(None, StatusUpdateSerial(u32::MAX))
);
t.send_webxdc_status_update(instance.id, r#"{"payload": 1}"#, "bla")
t.send_webxdc_status_update(instance.id, r#"{"payload": 1}"#)
.await?;
let (object, first_new) = t
.render_webxdc_status_update_object(instance.id, first, last, None)
@@ -1859,13 +1907,13 @@ mod tests {
let t = TestContext::new_alice().await;
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "a chat").await?;
let instance = send_webxdc_instance(&t, chat_id).await?;
t.send_webxdc_status_update(instance.id, r#"{"payload": 1}"#, "d")
t.send_webxdc_status_update(instance.id, r#"{"payload": 1}"#)
.await?;
t.send_webxdc_status_update(instance.id, r#"{"payload": 2}"#, "d")
t.send_webxdc_status_update(instance.id, r#"{"payload": 2}"#)
.await?;
t.send_webxdc_status_update(instance.id, r#"{"payload": 3}"#, "d")
t.send_webxdc_status_update(instance.id, r#"{"payload": 3}"#)
.await?;
t.send_webxdc_status_update(instance.id, r#"{"payload": 4}"#, "d")
t.send_webxdc_status_update(instance.id, r#"{"payload": 4}"#)
.await?;
let (json, first_new) = t
.render_webxdc_status_update_object(
@@ -1910,17 +1958,17 @@ mod tests {
let instance3 = send_webxdc_instance(&t, chat_id).await?;
assert!(t.smtp_status_update_get().await?.is_none());
t.send_webxdc_status_update(instance1.id, r#"{"payload": "1a"}"#, "descr1a")
t.send_webxdc_status_update(instance1.id, r#"{"payload": "1a"}"#)
.await?;
t.send_webxdc_status_update(instance2.id, r#"{"payload": "2a"}"#, "descr2a")
t.send_webxdc_status_update(instance2.id, r#"{"payload": "2a"}"#)
.await?;
t.send_webxdc_status_update(instance2.id, r#"{"payload": "2b"}"#, "descr2b")
t.send_webxdc_status_update(instance2.id, r#"{"payload": "2b"}"#)
.await?;
t.send_webxdc_status_update(instance3.id, r#"{"payload": "3a"}"#, "descr3a")
t.send_webxdc_status_update(instance3.id, r#"{"payload": "3a"}"#)
.await?;
t.send_webxdc_status_update(instance3.id, r#"{"payload": "3b"}"#, "descr3b")
t.send_webxdc_status_update(instance3.id, r#"{"payload": "3b"}"#)
.await?;
t.send_webxdc_status_update(instance3.id, r#"{"payload": "3c"}"#, "descr3c")
t.send_webxdc_status_update(instance3.id, r#"{"payload": "3c"}"#)
.await?;
assert_eq!(
t.sql
@@ -1932,7 +1980,7 @@ mod tests {
// order of smtp_status_update_get() is not defined, therefore the more complicated test
let mut instances_checked = 0;
for i in 0..3 {
let (instance, min_ser, max_ser, descr) = t.smtp_status_update_get().await?.unwrap();
let (instance, min_ser, max_ser) = t.smtp_status_update_get().await?.unwrap();
t.smtp_status_update_pop_serials(
instance,
min_ser,
@@ -1942,15 +1990,14 @@ mod tests {
let min_ser: u32 = min_ser.try_into()?;
if instance == instance1.id {
assert_eq!(min_ser, max_ser.to_u32());
assert_eq!(descr, "descr1a");
instances_checked += 1;
} else if instance == instance2.id {
assert_eq!(min_ser, max_ser.to_u32() - 1);
assert_eq!(descr, "descr2b");
instances_checked += 1;
} else if instance == instance3.id {
assert_eq!(min_ser, max_ser.to_u32() - 2);
assert_eq!(descr, "descr3c");
instances_checked += 1;
} else {
bail!("unexpected instance");
@@ -1988,11 +2035,11 @@ mod tests {
let mut alice_instance = alice_chat_id.get_draft(&alice).await?.unwrap();
alice
.send_webxdc_status_update(alice_instance.id, r#"{"payload": {"foo":"bar"}}"#, "descr")
.send_webxdc_status_update(alice_instance.id, r#"{"payload": {"foo":"bar"}}"#)
.await?;
expect_status_update_event(&alice, alice_instance.id).await?;
alice
.send_webxdc_status_update(alice_instance.id, r#"{"payload":42, "info":"i"}"#, "descr")
.send_webxdc_status_update(alice_instance.id, r#"{"payload":42, "info":"i"}"#)
.await?;
expect_status_update_event(&alice, alice_instance.id).await?;
assert_eq!(
@@ -2039,7 +2086,7 @@ mod tests {
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
let msg_id = send_text_msg(&t, chat_id, "ho!".to_string()).await?;
assert!(t
.send_webxdc_status_update(msg_id, r#"{"foo":"bar"}"#, "descr")
.send_webxdc_status_update(msg_id, r#"{"foo":"bar"}"#)
.await
.is_err());
Ok(())
@@ -2228,6 +2275,8 @@ sth_for_the = "future""#
let info = instance.get_webxdc_info(&t).await?;
assert_eq!(info.name, "minimal.xdc");
assert_eq!(info.icon, WEBXDC_DEFAULT_ICON.to_string());
assert_eq!(info.send_update_interval, 10000);
assert_eq!(info.send_update_max_size, RECOMMENDED_FILE_SIZE as usize);
let mut instance = create_webxdc_instance(
&t,
@@ -2333,11 +2382,7 @@ sth_for_the = "future""#
assert_eq!(info.summary, "".to_string());
alice
.send_webxdc_status_update(
alice_instance.id,
r#"{"summary":"sum: 1", "payload":1}"#,
"descr",
)
.send_webxdc_status_update(alice_instance.id, r#"{"summary":"sum: 1", "payload":1}"#)
.await?;
alice.flush_status_updates().await?;
let sent_update1 = &alice.pop_sent_msg().await;
@@ -2348,11 +2393,7 @@ sth_for_the = "future""#
assert_eq!(info.summary, "sum: 1".to_string());
alice
.send_webxdc_status_update(
alice_instance.id,
r#"{"summary":"sum: 2", "payload":2}"#,
"descr",
)
.send_webxdc_status_update(alice_instance.id, r#"{"summary":"sum: 2", "payload":2}"#)
.await?;
alice.flush_status_updates().await?;
let sent_update2 = &alice.pop_sent_msg().await;
@@ -2403,7 +2444,6 @@ sth_for_the = "future""#
.send_webxdc_status_update(
alice_instance.id,
r#"{"document":"my file", "payload":1337}"#,
"descr",
)
.await?;
alice.flush_status_updates().await?;
@@ -2443,7 +2483,6 @@ sth_for_the = "future""#
.send_webxdc_status_update(
alice_instance.id,
r#"{"info":"this appears in-chat", "payload":"sth. else"}"#,
"descr text",
)
.await?;
alice.flush_status_updates().await?;
@@ -2521,13 +2560,13 @@ sth_for_the = "future""#
// Alice sends two info messages in a row;
// the second one removes the first one as there is nothing in between
alice
.send_webxdc_status_update(alice_instance.id, r#"{"info":"i1", "payload":1}"#, "d")
.send_webxdc_status_update(alice_instance.id, r#"{"info":"i1", "payload":1}"#)
.await?;
alice.flush_status_updates().await?;
let sent2 = &alice.pop_sent_msg().await;
assert_eq!(alice_chat.id.get_msg_cnt(&alice).await?, 2);
alice
.send_webxdc_status_update(alice_instance.id, r#"{"info":"i2", "payload":2}"#, "d")
.send_webxdc_status_update(alice_instance.id, r#"{"info":"i2", "payload":2}"#)
.await?;
alice.flush_status_updates().await?;
let sent3 = &alice.pop_sent_msg().await;
@@ -2554,12 +2593,12 @@ sth_for_the = "future""#
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "c").await?;
let instance = send_webxdc_instance(&t, chat_id).await?;
t.send_webxdc_status_update(instance.id, r#"{"info":"i1", "payload":1}"#, "d")
t.send_webxdc_status_update(instance.id, r#"{"info":"i1", "payload":1}"#)
.await?;
assert_eq!(chat_id.get_msg_cnt(&t).await?, 2);
send_text_msg(&t, chat_id, "msg between info".to_string()).await?;
assert_eq!(chat_id.get_msg_cnt(&t).await?, 3);
t.send_webxdc_status_update(instance.id, r#"{"info":"i2", "payload":2}"#, "d")
t.send_webxdc_status_update(instance.id, r#"{"info":"i2", "payload":2}"#)
.await?;
assert_eq!(chat_id.get_msg_cnt(&t).await?, 4);
@@ -2588,7 +2627,7 @@ sth_for_the = "future""#
let alice_instance = send_webxdc_instance(&alice, alice_chat_id).await?;
let sent1 = &alice.pop_sent_msg().await;
alice
.send_webxdc_status_update(alice_instance.id, r#"{"payload":42}"#, "descr")
.send_webxdc_status_update(alice_instance.id, r#"{"payload":42}"#)
.await?;
alice.flush_status_updates().await?;
let sent2 = &alice.pop_sent_msg().await;
@@ -2608,7 +2647,7 @@ sth_for_the = "future""#
Contact::create(&bob, "", "claire@example.org").await?,
)
.await?;
bob.send_webxdc_status_update(bob_instance.id, r#"{"payload":43}"#, "descr")
bob.send_webxdc_status_update(bob_instance.id, r#"{"payload":43}"#)
.await?;
bob.flush_status_updates().await?;
let sent3 = bob.pop_sent_msg().await;
@@ -2646,7 +2685,6 @@ sth_for_the = "future""#
t.send_webxdc_status_update(
instance_id,
r#"{"summary":"real summary", "payload": 42}"#,
"descr",
)
.await?;
let instance = Message::load_from_db(&t, instance_id).await?;
@@ -2724,7 +2762,6 @@ sth_for_the = "future""#
bob.send_webxdc_status_update(
bob_instance.id,
r#"{"payload":7,"info": "i","summary":"s"}"#,
"",
)
.await?;
bob.flush_status_updates().await?;
@@ -2844,7 +2881,6 @@ sth_for_the = "future""#
.send_webxdc_status_update(
alice_instance.id,
r#"{"payload":"p","info":"i","aNewUnknownProperty":"x","max_serial":123}"#,
"Some description",
)
.await?;
alice.flush_status_updates().await?;
@@ -2903,4 +2939,293 @@ 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\":{{\"{}\": \"Your move!\"}} }}",
bob_instance.get_webxdc_self_addr(&bob).await?
),
)
.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.text, "Alice moved");
assert!(!has_incoming_webxdc_event(&alice, info_msg, "").await);
bob.recv_msg_trash(&sent2).await;
let info_msg = bob.get_last_msg().await;
assert!(info_msg.is_info());
assert_eq!(info_msg.text, "Alice moved");
assert!(has_incoming_webxdc_event(&bob, info_msg, "Your move!").await);
fiona.recv_msg_trash(&sent2).await;
let info_msg = fiona.get_last_msg().await;
assert!(info_msg.is_info());
assert_eq!(info_msg.text, "Alice moved");
assert!(!has_incoming_webxdc_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 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\": \"move summary\", \"notify\":{{\"{}\":\"move, Bob\",\"{}\":\"move, Fiona\"}} }}",
bob_instance.get_webxdc_self_addr(&bob).await?,
fiona_instance.get_webxdc_self_addr(&fiona).await?
),
)
.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, "").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, "move, Bob").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, "move, Fiona").await);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_webxdc_no_notify_self() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let alice2 = tcm.alice().await;
let grp_id = alice
.create_group_with_members(ProtectionStatus::Unprotected, "grp", &[])
.await;
let alice_instance = send_webxdc_instance(&alice, grp_id).await?;
let sent1 = alice.pop_sent_msg().await;
let alice2_instance = alice2.recv_msg(&sent1).await;
assert_eq!(
alice_instance.get_webxdc_self_addr(&alice).await?,
alice2_instance.get_webxdc_self_addr(&alice2).await?
);
alice
.send_webxdc_status_update(
alice_instance.id,
&format!(
"{{\"payload\":7,\"info\": \"moved\", \"notify\":{{\"{}\": \"bla\"}} }}",
alice2_instance.get_webxdc_self_addr(&alice2).await?
),
)
.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, "").await);
alice2.recv_msg_trash(&sent2).await;
let info_msg = alice2.get_last_msg().await;
assert!(info_msg.is_info());
assert!(!has_incoming_webxdc_event(&alice2, info_msg, "").await);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_webxdc_notify_all() -> 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;
bob.recv_msg(&sent1).await;
fiona.recv_msg(&sent1).await;
alice
.send_webxdc_status_update(
alice_instance.id,
"{\"payload\":7,\"info\": \"go\", \"notify\":{\"*\":\"notify all\"} }",
)
.await?;
alice.flush_status_updates().await?;
let sent2 = alice.pop_sent_msg().await;
let info_msg = alice.get_last_msg().await;
assert_eq!(info_msg.text, "go");
assert!(!has_incoming_webxdc_event(&alice, info_msg, "").await);
bob.recv_msg_trash(&sent2).await;
let info_msg = bob.get_last_msg().await;
assert_eq!(info_msg.text, "go");
assert!(has_incoming_webxdc_event(&bob, info_msg, "notify all").await);
fiona.recv_msg_trash(&sent2).await;
let info_msg = fiona.get_last_msg().await;
assert_eq!(info_msg.text, "go");
assert!(has_incoming_webxdc_event(&fiona, info_msg, "notify all").await);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_webxdc_notify_bob_and_all() -> 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, \"notify\":{{\"{}\": \"notify bob\",\"*\": \"notify all\"}} }}",
bob_instance.get_webxdc_self_addr(&bob).await?
),
)
.await?;
alice.flush_status_updates().await?;
let sent2 = alice.pop_sent_msg().await;
bob.recv_msg_trash(&sent2).await;
fiona.recv_msg_trash(&sent2).await;
assert!(has_incoming_webxdc_event(&bob, bob_instance, "notify bob").await);
assert!(has_incoming_webxdc_event(&fiona, fiona_instance, "notify all").await);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_webxdc_notify_all_and_bob() -> 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, \"notify\":{{\"*\": \"notify all\", \"{}\": \"notify bob\"}} }}",
bob_instance.get_webxdc_self_addr(&bob).await?
),
)
.await?;
alice.flush_status_updates().await?;
let sent2 = alice.pop_sent_msg().await;
bob.recv_msg_trash(&sent2).await;
fiona.recv_msg_trash(&sent2).await;
assert!(has_incoming_webxdc_event(&bob, bob_instance, "notify bob").await);
assert!(has_incoming_webxdc_event(&fiona, fiona_instance, "notify all").await);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_webxdc_href() -> 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!", "href": "#foobar"}"##,
)
.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_href(), Some("#foobar".to_string()));
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_href(), Some("#foobar".to_string()));
Ok(())
}
}

View File

@@ -146,9 +146,11 @@ pub(crate) async fn intercept_get_updates(
item: StatusUpdateItem {
payload: serde_json::to_value(location_item)?,
info: None,
href: None,
document: None,
summary: None,
uid: None,
notify: None,
},
serial: StatusUpdateSerial(location.location_id),
max_serial: StatusUpdateSerial(location.location_id),
@@ -203,7 +205,6 @@ mod tests {
t.send_webxdc_status_update(
integration_id,
r#"{"payload": {"action": "pos", "lat": 11.0, "lng": 12.0, "label": "poi #1"}}"#,
"descr",
)
.await?;
t.evtracker
@@ -237,7 +238,6 @@ mod tests {
t.send_webxdc_status_update(
integration_id,
r#"{"payload": {"action": "pos", "lat": 22.0, "lng": 23.0, "label": "poi #2"}}"#,
"descr",
)
.await?;
let updates = t