Compare commits

..

45 Commits

Author SHA1 Message Date
Nico de Haen
7e8b5fe3d2 Refactor parent_message 2024-12-02 09:25:58 +01:00
Nico de Haen
70fe196220 Add webxdc_info to jsonrpc WebxdcInfoMessage 2024-12-02 09:25:58 +01:00
Nico de Haen
3ad9cf3c74 Add getWebxdcHref to json api (#6281) 2024-12-02 06:58:43 +01:00
dependabot[bot]
8ffe864812 Merge pull request #6296 from deltachat/dependabot/cargo/image-0.25.5 2024-12-02 02:31:19 +00:00
dependabot[bot]
df8c4cc3e9 chore(cargo): bump quick-xml from 0.37.0 to 0.37.1
Bumps [quick-xml](https://github.com/tafia/quick-xml) from 0.37.0 to 0.37.1.
- [Release notes](https://github.com/tafia/quick-xml/releases)
- [Changelog](https://github.com/tafia/quick-xml/blob/master/Changelog.md)
- [Commits](https://github.com/tafia/quick-xml/compare/v0.37.0...v0.37.1)

---
updated-dependencies:
- dependency-name: quick-xml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 01:47:58 +00:00
dependabot[bot]
5a353a206b chore(cargo): bump tempfile from 3.13.0 to 3.14.0
Bumps [tempfile](https://github.com/Stebalien/tempfile) from 3.13.0 to 3.14.0.
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.13.0...v3.14.0)

---
updated-dependencies:
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 01:19:54 +00:00
dependabot[bot]
8ddd28d08c chore(cargo): bump futures-lite from 2.4.0 to 2.5.0
Bumps [futures-lite](https://github.com/smol-rs/futures-lite) from 2.4.0 to 2.5.0.
- [Release notes](https://github.com/smol-rs/futures-lite/releases)
- [Changelog](https://github.com/smol-rs/futures-lite/blob/master/CHANGELOG.md)
- [Commits](https://github.com/smol-rs/futures-lite/compare/v2.4.0...v2.5.0)

---
updated-dependencies:
- dependency-name: futures-lite
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 01:19:04 +00:00
dependabot[bot]
e07e9aec17 Merge pull request #6297 from deltachat/dependabot/cargo/bytes-1.9.0 2024-12-02 01:18:25 +00:00
dependabot[bot]
0c35360b9f Merge pull request #6301 from deltachat/dependabot/cargo/webpki-roots-0.26.7 2024-12-02 00:50:22 +00:00
dependabot[bot]
c356dbff06 chore(cargo): bump image from 0.25.4 to 0.25.5
Bumps [image](https://github.com/image-rs/image) from 0.25.4 to 0.25.5.
- [Changelog](https://github.com/image-rs/image/blob/main/CHANGES.md)
- [Commits](https://github.com/image-rs/image/compare/v0.25.4...v0.25.5)

---
updated-dependencies:
- dependency-name: image
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 00:29:39 +00:00
dependabot[bot]
d4a6484b0c Merge pull request #6290 from deltachat/dependabot/cargo/rustls-0.23.19 2024-12-02 00:29:37 +00:00
dependabot[bot]
5aa8ffaf5e Merge pull request #6294 from deltachat/dependabot/cargo/anyhow-1.0.93 2024-12-02 00:28:08 +00:00
dependabot[bot]
85de1ad538 Merge pull request #6288 from deltachat/dependabot/cargo/kamadak-exif-0.6.1 2024-12-02 00:27:17 +00:00
dependabot[bot]
913203fbad Merge pull request #6286 from deltachat/dependabot/cargo/thiserror-1.0.69 2024-12-02 00:26:07 +00:00
dependabot[bot]
a42cd5450b chore(cargo): bump webpki-roots from 0.26.6 to 0.26.7
Bumps [webpki-roots](https://github.com/rustls/webpki-roots) from 0.26.6 to 0.26.7.
- [Release notes](https://github.com/rustls/webpki-roots/releases)
- [Commits](https://github.com/rustls/webpki-roots/compare/v/0.26.6...v/0.26.7)

---
updated-dependencies:
- dependency-name: webpki-roots
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-01 21:14:55 +00:00
dependabot[bot]
92a68ceb48 chore(cargo): bump bytes from 1.8.0 to 1.9.0
Bumps [bytes](https://github.com/tokio-rs/bytes) from 1.8.0 to 1.9.0.
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v1.8.0...v1.9.0)

---
updated-dependencies:
- dependency-name: bytes
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-01 21:13:34 +00:00
dependabot[bot]
ada5368b9c chore(cargo): bump anyhow from 1.0.92 to 1.0.93
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.92 to 1.0.93.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.92...1.0.93)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-01 21:12:41 +00:00
dependabot[bot]
f3332fa7a6 chore(cargo): bump rustls from 0.23.18 to 0.23.19
Bumps [rustls](https://github.com/rustls/rustls) from 0.23.18 to 0.23.19.
- [Release notes](https://github.com/rustls/rustls/releases)
- [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustls/rustls/compare/v/0.23.18...v/0.23.19)

---
updated-dependencies:
- dependency-name: rustls
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-01 21:11:13 +00:00
dependabot[bot]
f03d56143c chore(cargo): bump kamadak-exif from 0.6.0 to 0.6.1
Bumps [kamadak-exif](https://github.com/kamadak/exif-rs) from 0.6.0 to 0.6.1.
- [Changelog](https://github.com/kamadak/exif-rs/blob/master/NEWS)
- [Commits](https://github.com/kamadak/exif-rs/compare/0.6...0.6.1)

---
updated-dependencies:
- dependency-name: kamadak-exif
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-01 21:10:35 +00:00
dependabot[bot]
d21756812b chore(cargo): bump thiserror from 1.0.66 to 1.0.69
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.66 to 1.0.69.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.66...1.0.69)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-01 21:10:01 +00:00
iequidoo
cbe5c38705 fix: Sync chat action even if sync message arrives before first one from contact (#6259)
A sync message for accepting or blocking a 1:1 chat may arrive before the first message from the
contact, when it does not exist yet. This frequently happens in non-chatmail accounts that have
moving to the DeltaChat folder disabled because Delta Chat unconditionally uploads sync messages to
the DeltaChat folder. Let's create a hidden contact in this case and a 1:1 chat for it.
2024-12-01 13:49:10 -03:00
link2xt
755b245495 fix: mark Saved Messages chat as protected if it exists
Saved Messages chat is created as protected,
but for existing accounts we need to do this in a migration.
2024-12-01 07:18:38 +00:00
link2xt
dc5fcdf425 ci: update Rust to 1.83.0 2024-11-30 01:11:44 +00:00
iequidoo
45e55c963e refactor: Use Option::or_else() to dedup emitting IncomingWebxdcNotify 2024-11-29 16:39:14 -03:00
link2xt
8967d7748c docs: fix references to iroh-related headers in peer_channels docs 2024-11-29 18:14:03 +00:00
link2xt
948cefa3ef fix: do not add protection messages to Saved Messages chat
This causes troubles such as adding this message
the first time a sync message is sent.
2024-11-29 17:54:39 +00:00
link2xt
9ec1401a37 feat: mark saved messages chat as protected 2024-11-29 17:54:39 +00:00
B. Petersen
170b7e2ded api: remove experimental request_internet_access option from webxdc's manifest.toml
this partly reverts experimental #3516
that allowed any .xdc sent to "Saved Messages" to request internet.
this helped on pushing map integration forward.

meanwhile, however, we have that map integration (#5461 and #5678),
that implies `info.internet_access` being set.
experimental `manifest.request_internet_access` is no longer needed therefore.

future will tell, if we revive the option at some point or
go for more intrations ('sending' is discussed often :) -
but currently it is not needed.
2024-11-29 18:02:50 +01:00
bjoern
d63a2b39aa feat: allow the user to replace maps integration (#5678)
with this PR, when an `.xdc` with `request_integration = map` in the
manifest is added to the "Saved Messages" chat, it is used _locally_ as
an replacement for the shipped maps.xdc (other devices will see the
`.xdc` but not use it)

this allows easy development and adapting the map to use services that
work better in some area.

there are lots of known discussions and ideas about adding more barriers
of safety. however, after internal discussions, we decided to move
forward and also to allow internet, if requested by an integration (as
discussed at
https://github.com/deltachat/deltachat-core-rust/pull/3516).
the gist is to ease development and to make users who want to adapt,
actionable _now_, without making things too hard and adding too high
barriers or stressing our own resources/power too much.
note, that things are still experimental and will be the next time -
without the corresponding switch being enabled, nothing will work at
all, so we can be quite relaxed here :)

for android/ios, things will work directly. for desktop, allow_internet
needs to be accepted unconditionally from core. for the future, we might
add a question before using an integration and/or add signing. or sth.
completely different - but for now, the thing is to get started.

nb: "integration" field in the webxdc-info is experimental as well and
should not be used in UIs at all currently, it may vanish again and is
there mainly for simplicity of the code; therefore, no need to document
that.

successor of https://github.com/deltachat/deltachat-core-rust/pull/5461

this is how it looks like currently - again, please note that all that
is an experiment!

<img width=320
src=https://github.com/deltachat/deltachat-core-rust/assets/9800740/f659c891-f46a-4e28-9d0a-b6783d69be8d>
&nbsp; &nbsp; <img width=320
src=https://github.com/deltachat/deltachat-core-rust/assets/9800740/54549b3c-a894-4568-9e27-d5f1caea2d22>

... when going out of experimental, there are loots of ideas, eg.
changing "Start" to "integrate"
2024-11-29 14:18:35 +00:00
iequidoo
167948e62a refactor: create_status_update_record: Remove double check of info_msg_id 2024-11-28 14:59:24 -03:00
link2xt
4edade225c fix: close iroh endpoint when I/O is stopped 2024-11-28 17:06:15 +00:00
bjoern
da546d3526 docs: update dc_msg_get_info_type() and dc_get_securejoin_qr() (#6269)
this was partly missing at
https://github.com/deltachat/deltachat-core-rust/pull/6223

this is not meant as being exhaustive :)

---------

Co-authored-by: iequidoo <117991069+iequidoo@users.noreply.github.com>
2024-11-28 07:19:48 +00:00
link2xt
6be96d3eba refactor: remove some .unwrap() calls 2024-11-27 23:57:23 +00:00
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
58 changed files with 1216 additions and 443 deletions

View File

@@ -24,7 +24,7 @@ jobs:
name: Lint Rust
runs-on: ubuntu-latest
env:
RUSTUP_TOOLCHAIN: 1.82.0
RUSTUP_TOOLCHAIN: 1.83.0
steps:
- uses: actions/checkout@v4
with:
@@ -95,11 +95,11 @@ jobs:
matrix:
include:
- os: ubuntu-latest
rust: 1.82.0
rust: 1.83.0
- os: windows-latest
rust: 1.82.0
rust: 1.83.0
- os: macos-latest
rust: 1.82.0
rust: 1.83.0
# Minimum Supported Rust Version = 1.77.0
- os: ubuntu-latest

View File

@@ -1,5 +1,31 @@
# 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
@@ -5342,3 +5368,4 @@ https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed
[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

160
Cargo.lock generated
View File

@@ -169,9 +169,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.92"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13"
checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
dependencies = [
"backtrace",
]
@@ -230,7 +230,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
"synstructure",
]
@@ -242,7 +242,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -347,7 +347,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -375,7 +375,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -386,7 +386,7 @@ checksum = "00b9f7252833d5ed4b00aa9604b563529dd5e11de9c23615de2dcdf91eb87b52"
dependencies = [
"async-compression",
"crc32fast",
"futures-lite 2.4.0",
"futures-lite 2.5.0",
"pin-project",
"thiserror",
"tokio",
@@ -688,9 +688,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "bytes"
version = "1.8.0"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
dependencies = [
"serde",
]
@@ -1238,7 +1238,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -1262,7 +1262,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -1273,7 +1273,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -1306,7 +1306,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.151.1"
version = "1.151.2"
dependencies = [
"anyhow",
"async-broadcast",
@@ -1330,7 +1330,7 @@ dependencies = [
"fd-lock",
"format-flowed",
"futures",
"futures-lite 2.4.0",
"futures-lite 2.5.0",
"hex",
"hickory-resolver",
"http-body-util",
@@ -1407,7 +1407,7 @@ dependencies = [
[[package]]
name = "deltachat-jsonrpc"
version = "1.151.1"
version = "1.151.2"
dependencies = [
"anyhow",
"async-channel 2.3.1",
@@ -1432,7 +1432,7 @@ dependencies = [
[[package]]
name = "deltachat-repl"
version = "1.151.1"
version = "1.151.2"
dependencies = [
"anyhow",
"deltachat",
@@ -1448,12 +1448,12 @@ dependencies = [
[[package]]
name = "deltachat-rpc-server"
version = "1.151.1"
version = "1.151.2"
dependencies = [
"anyhow",
"deltachat",
"deltachat-jsonrpc",
"futures-lite 2.4.0",
"futures-lite 2.5.0",
"log",
"serde",
"serde_json",
@@ -1472,12 +1472,12 @@ name = "deltachat_derive"
version = "2.0.0"
dependencies = [
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
name = "deltachat_ffi"
version = "1.151.1"
version = "1.151.2"
dependencies = [
"anyhow",
"deltachat",
@@ -1527,7 +1527,7 @@ checksum = "5fe87ce4529967e0ba1dcf8450bab64d97dfd5010a6256187ffe2e43e6f0e049"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -1558,7 +1558,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -1568,7 +1568,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b"
dependencies = [
"derive_builder_core",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -1588,7 +1588,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
"unicode-xid",
]
@@ -1654,7 +1654,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -1936,7 +1936,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -1956,7 +1956,7 @@ checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -2324,9 +2324,9 @@ dependencies = [
[[package]]
name = "futures-lite"
version = "2.4.0"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f1fa2f9765705486b33fd2acf1577f8ec449c2ba1f318ae5447697b7c08d210"
checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1"
dependencies = [
"fastrand 2.1.1",
"futures-core",
@@ -2343,7 +2343,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -2911,9 +2911,9 @@ dependencies = [
[[package]]
name = "image"
version = "0.25.4"
version = "0.25.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc144d44a31d753b02ce64093d532f55ff8dc4ebf2ffb8a63c0dda691385acae"
checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b"
dependencies = [
"bytemuck",
"byteorder-lite",
@@ -3043,7 +3043,7 @@ dependencies = [
"derive_more",
"ed25519-dalek",
"futures-concurrency",
"futures-lite 2.4.0",
"futures-lite 2.5.0",
"futures-util",
"indexmap",
"iroh-base",
@@ -3097,7 +3097,7 @@ dependencies = [
"duct",
"futures-buffered",
"futures-concurrency",
"futures-lite 2.4.0",
"futures-lite 2.5.0",
"futures-sink",
"futures-util",
"genawaiter",
@@ -3218,7 +3218,7 @@ checksum = "c1fd18ec6325dd3f01625f12c01acff50a4374ee1ab708e7b2078885fd63ad30"
dependencies = [
"anyhow",
"futures-buffered",
"futures-lite 2.4.0",
"futures-lite 2.5.0",
"futures-util",
"iroh-net",
"tokio",
@@ -3303,9 +3303,9 @@ dependencies = [
[[package]]
name = "kamadak-exif"
version = "0.6.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99e7b00ff45df279c3e40f7fee99fad4f7eddbf9ed2d24e99133e8683330f0c7"
checksum = "1130d80c7374efad55a117d715a3af9368f0fa7a2c54573afc15a188cd984837"
dependencies = [
"mutate_once",
]
@@ -3693,7 +3693,7 @@ dependencies = [
"anyhow",
"bytes",
"derive_more",
"futures-lite 2.4.0",
"futures-lite 2.5.0",
"futures-sink",
"futures-util",
"libc",
@@ -3834,7 +3834,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -3916,7 +3916,7 @@ dependencies = [
"proc-macro-crate 3.2.0",
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -3990,7 +3990,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -4194,7 +4194,7 @@ dependencies = [
"pest_meta",
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -4292,7 +4292,7 @@ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -4406,7 +4406,7 @@ dependencies = [
"proc-macro2",
"quote",
"regex",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -4482,7 +4482,7 @@ dependencies = [
"base64 0.22.1",
"bytes",
"derive_more",
"futures-lite 2.4.0",
"futures-lite 2.5.0",
"futures-util",
"igd-next",
"iroh-metrics",
@@ -4654,7 +4654,7 @@ dependencies = [
"proc-macro-error-attr2",
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -4692,7 +4692,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -4762,9 +4762,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quick-xml"
version = "0.37.0"
version = "0.37.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffbfb3ddf5364c9cfcd65549a1e7b801d0e8d1b14c1a1590a6408aa93cfbfa84"
checksum = "f22f29bdff3987b4d8632ef95fd6424ec7e4e0a57e2f4fc63e489e75357f6a03"
dependencies = [
"memchr",
]
@@ -5257,9 +5257,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.38.37"
version = "0.38.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811"
checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6"
dependencies = [
"bitflags 2.6.0",
"errno",
@@ -5270,9 +5270,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.18"
version = "0.23.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f"
checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1"
dependencies = [
"log",
"once_cell",
@@ -5442,7 +5442,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -5565,7 +5565,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -5576,7 +5576,7 @@ checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -5943,7 +5943,7 @@ dependencies = [
"proc-macro2",
"quote",
"struct_iterable_internal",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -5971,7 +5971,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -6033,9 +6033,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.86"
version = "2.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89275301d38033efb81a6e60e3497e734dfcc62571f2854bf4b16690398824c"
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
dependencies = [
"proc-macro2",
"quote",
@@ -6073,7 +6073,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -6119,9 +6119,9 @@ checksum = "094c9f64d6de9a8506b1e49b63a29333b37ed9e821ee04be694d431b3264c3c5"
[[package]]
name = "tempfile"
version = "3.13.0"
version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b"
checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c"
dependencies = [
"cfg-if",
"fastrand 2.1.1",
@@ -6157,22 +6157,22 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.66"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d171f59dbaa811dbbb1aee1e73db92ec2b122911a48e1390dfe327a821ddede"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.66"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b08be0f17bd307950653ce45db00cd31200d82b624b36e181337d9c7d92765b5"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -6288,7 +6288,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -6484,7 +6484,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -6596,7 +6596,7 @@ dependencies = [
"proc-macro-error2",
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -6818,7 +6818,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
"wasm-bindgen-shared",
]
@@ -6852,7 +6852,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -6887,9 +6887,9 @@ dependencies = [
[[package]]
name = "webpki-roots"
version = "0.26.6"
version = "0.26.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958"
checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e"
dependencies = [
"rustls-pki-types",
]
@@ -7005,7 +7005,7 @@ checksum = "12168c33176773b86799be25e2a2ba07c7aab9968b37541f1094dbd7a60c8946"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -7016,7 +7016,7 @@ checksum = "9d8dc32e0095a7eeccebd0e3f09e9509365ecb3fc6ac4d6f5f14a3f6392942d1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -7333,7 +7333,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -7359,7 +7359,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]
@@ -7379,7 +7379,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
"syn 2.0.87",
]
[[package]]

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.151.1"
version = "1.151.2"
edition = "2021"
license = "MPL-2.0"
rust-version = "1.77"
@@ -62,10 +62,10 @@ http-body-util = "0.1.2"
humansize = "2"
hyper = "1"
hyper-util = "0.1.10"
image = { version = "0.25.4", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
image = { version = "0.25.5", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
iroh-gossip = { version = "0.28.1", default-features = false, features = ["net"] }
iroh-net = { version = "0.28.1", default-features = false }
kamadak-exif = "0.6.0"
kamadak-exif = "0.6.1"
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
libc = { workspace = true }
mailparse = "0.15"
@@ -86,7 +86,7 @@ regex = { workspace = true }
rusqlite = { workspace = true, features = ["sqlcipher"] }
rust-hsluv = "0.1"
rustls-pki-types = "1.10.0"
rustls = { version = "0.23.14", default-features = false }
rustls = { version = "0.23.19", default-features = false }
sanitize-filename = { workspace = true }
serde_json = { workspace = true }
serde_urlencoded = "0.7.1"
@@ -109,7 +109,7 @@ tokio = { workspace = true, features = ["fs", "rt-multi-thread", "macros"] }
toml = "0.8"
url = "2"
uuid = { version = "1", features = ["serde", "v4"] }
webpki-roots = "0.26.6"
webpki-roots = "0.26.7"
[dev-dependencies]
anyhow = { workspace = true, features = ["backtrace"] } # Enable `backtrace` feature in tests.
@@ -173,7 +173,7 @@ deltachat-contact-tools = { path = "deltachat-contact-tools" }
deltachat-jsonrpc = { path = "deltachat-jsonrpc", default-features = false }
deltachat = { path = ".", default-features = false }
futures = "0.3.31"
futures-lite = "2.4.0"
futures-lite = "2.5.0"
libc = "0.2"
log = "0.4"
nu-ansi-term = "0.46"
@@ -185,7 +185,7 @@ rusqlite = "0.32"
sanitize-filename = "0.5"
serde = "1.0"
serde_json = "1"
tempfile = "3.13.0"
tempfile = "3.14.0"
thiserror = "1"
tokio = "1"
tokio-util = "0.7.11"

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.151.1"
version = "1.151.2"
description = "Deltachat FFI"
edition = "2018"
readme = "README.md"

View File

@@ -1161,8 +1161,7 @@ uint32_t dc_send_videochat_invitation (dc_context_t* context, uint32_t chat_id);
* - `document`: optional document name. shown eg. in title bar.
* - `summary`: optional summary. shown beside app icon.
* - `notify`: optional array of other users `selfAddr` to be notified e.g. by a sound about `info` or `summary`.
* @param descr The user-visible description of JSON data,
* in case of a chess game, e.g. the move.
* @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);
@@ -2598,13 +2597,15 @@ dc_lot_t* dc_check_qr (dc_context_t* context, const char*
/**
* Get QR code text that will offer an Setup-Contact or Verified-Group invitation.
* The QR code is compatible to the OPENPGP4FPR format
* so that a basic fingerprint comparison also works e.g. with OpenKeychain.
*
* The scanning device will pass the scanned content to dc_check_qr() then;
* if dc_check_qr() returns DC_QR_ASK_VERIFYCONTACT or DC_QR_ASK_VERIFYGROUP
* an out-of-band-verification can be joined using dc_join_securejoin()
*
* The returned text will also work as a normal https:-link,
* so that the QR code is useful also without Delta Chat being installed
* or can be passed to contacts through other channels.
*
* @memberof dc_context_t
* @param context The context object.
* @param chat_id If set to a group-chat-id,
@@ -4204,6 +4205,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.
@@ -4489,6 +4494,7 @@ int dc_msg_is_info (const dc_msg_t* msg);
* - DC_INFO_INVALID_UNENCRYPTED_MAIL (13) - Info-message for "Provider requires end-to-end encryption which is not setup yet",
* the UI should change the corresponding string using #DC_STR_INVALID_UNENCRYPTED_MAIL
* and also offer a way to fix the encryption, eg. by a button offering a QR scan
* - DC_INFO_WEBXDC_INFO_MESSAGE (32) - Info-message created by webxdc app sending `update.info`
*
* Even when you display an icon,
* you should still display the text of the informational message using dc_msg_get_text()
@@ -5898,15 +5904,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);
@@ -6108,7 +6125,9 @@ void dc_event_unref(dc_event_t* event);
/**
* A webxdc wants an info message or a changed summary to be notified.
*
* @param data1 contact_id ID of the contact sending.
* @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.
@@ -6414,6 +6433,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

@@ -569,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)]
@@ -601,8 +603,10 @@ 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::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, .. }
@@ -676,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 { .. }
@@ -703,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() {
@@ -751,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, .. } => {
@@ -1065,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()");
@@ -1073,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]

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-jsonrpc"
version = "1.151.1"
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: Option<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
}
@@ -1829,6 +1829,18 @@ impl CommandApi {
WebxdcMessageInfo::get_for_message(&ctx, MsgId::new(instance_msg_id)).await
}
/// Get href from a WebxdcInfoMessage which might include a hash holding
/// information about a specific position or state in a webxdc app (optional)
async fn get_webxdc_href(
&self,
account_id: u32,
instance_msg_id: u32,
) -> Result<Option<String>> {
let ctx = self.get_context(account_id).await?;
let message = Message::load_from_db(&ctx, MsgId::new(instance_msg_id)).await?;
Ok(message.get_webxdc_href())
}
/// Get blob encoded as base64 from a webxdc message
///
/// path is the path of the file within webxdc archive

View File

@@ -112,6 +112,7 @@ pub enum EventType {
contact_id: u32,
msg_id: u32,
text: String,
href: Option<String>,
},
/// There is a fresh message. Typically, the user will show an notification
@@ -285,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 },
}
@@ -331,10 +346,12 @@ impl From<CoreEventType> for EventType {
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(),
@@ -426,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

@@ -85,6 +85,8 @@ pub struct MessageObject {
webxdc_info: Option<WebxdcMessageInfo>,
webxdc_href: Option<String>,
download_state: DownloadState,
reactions: Option<JSONRPCReactions>,
@@ -128,6 +130,11 @@ impl MessageObject {
let webxdc_info = if message.get_viewtype() == Viewtype::Webxdc {
Some(WebxdcMessageInfo::get_for_message(context, msg_id).await?)
} else if let (deltachat::mimeparser::SystemMessage::WebxdcInfoMessage, Some(parent_msg)) =
(message.get_info_type(), message.parent(context).await?)
{
// get webcdx info from parent message
Some(WebxdcMessageInfo::get_for_message(context, parent_msg.get_id()).await?)
} else {
None
};
@@ -241,6 +248,10 @@ impl MessageObject {
file_name: message.get_filename(),
webxdc_info,
// On a WebxdcInfoMessage this might include a hash holding
// information about a specific position or state in a webxdc app
webxdc_href: message.get_webxdc_href(),
download_state,
reactions,

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 {
@@ -51,8 +57,11 @@ impl WebxdcMessageInfo {
document,
summary,
source_code_url,
request_integration: _,
internet_access,
self_addr,
send_update_interval,
send_update_max_size,
} = message.get_webxdc_info(context).await?;
Ok(Self {
@@ -63,6 +72,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.151.1"
"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.151.1"
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

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "deltachat-rpc-client"
version = "1.151.1"
version = "1.151.2"
description = "Python client for Delta Chat core JSON-RPC interface"
classifiers = [
"Development Status :: 5 - Production/Stable",

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

@@ -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

@@ -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

@@ -1,6 +1,6 @@
[package]
name = "deltachat-rpc-server"
version = "1.151.1"
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.151.1"
"version": "1.151.2"
}

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,

View File

@@ -44,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,
@@ -353,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.151.1"
"version": "1.151.2"
}

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "deltachat"
version = "1.151.1"
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-24
2024-11-26

View File

@@ -7,7 +7,7 @@ set -euo pipefail
#
# Avoid using rustup here as it depends on reading /proc/self/exe and
# has problems running under QEMU.
RUST_VERSION=1.82.0
RUST_VERSION=1.83.0
ARCH="$(uname -m)"
test -f "/lib/libc.musl-$ARCH.so.1" && LIBC=musl || LIBC=gnu

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

@@ -567,6 +567,14 @@ impl ChatId {
contact_id: Option<ContactId>,
timestamp_sort: i64,
) -> Result<()> {
if contact_id == Some(ContactId::SELF) {
// Do not add protection messages to Saved Messages chat.
// This chat never gets protected and unprotected,
// we do not want the first message
// to be a protection message with an arbitrary timestamp.
return Ok(());
}
let text = context.stock_protection_msg(protect, contact_id).await;
let cmd = match protect {
ProtectionStatus::Protected => SystemMessage::ChatProtectionEnabled,
@@ -2202,7 +2210,9 @@ impl Chat {
msg.id = MsgId::new(u32::try_from(raw_id)?);
maybe_set_logging_xdc(context, msg, self.id).await?;
context.update_webxdc_integration_database(msg).await?;
context
.update_webxdc_integration_database(msg, context)
.await?;
}
context.scheduler.interrupt_ephemeral_task().await;
Ok(msg.id)
@@ -2591,10 +2601,12 @@ impl ChatIdBlocked {
_ => (),
}
let peerstate = Peerstate::from_addr(context, contact.get_addr()).await?;
let protected = peerstate.map_or(false, |p| {
p.is_using_verified_key() && p.prefer_encrypt == EncryptPreference::Mutual
});
let protected = contact_id == ContactId::SELF || {
let peerstate = Peerstate::from_addr(context, contact.get_addr()).await?;
peerstate.is_some_and(|p| {
p.is_using_verified_key() && p.prefer_encrypt == EncryptPreference::Mutual
})
};
let smeared_time = create_smeared_timestamp(context);
let chat_id = context
@@ -2977,8 +2989,8 @@ pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -
recipients.push(from);
}
// Webxdc integrations are messages, however, shipped with main app and must not be sent out
if msg.param.get_int(Param::WebxdcIntegration).is_some() {
// Default Webxdc integrations are hidden messages and must not be sent out
if msg.param.get_int(Param::WebxdcIntegration).is_some() && msg.hidden {
recipients.clear();
}
@@ -4645,9 +4657,9 @@ impl Context {
Contact::create_ex(self, Nosync, to, addr).await?;
return Ok(());
}
let contact_id = Contact::lookup_id_by_addr_ex(self, addr, Origin::Unknown, None)
.await?
.with_context(|| format!("No contact for addr '{addr}'"))?;
let addr = ContactAddress::new(addr).context("Invalid address")?;
let (contact_id, _) =
Contact::add_or_lookup(self, "", &addr, Origin::Hidden).await?;
match action {
SyncAction::Block => {
return contact::set_blocked(self, Nosync, contact_id, true).await
@@ -4657,9 +4669,10 @@ impl Context {
}
_ => (),
}
ChatIdBlocked::lookup_by_contact(self, contact_id)
// Use `Request` so that even if the program crashes, the user doesn't have to look
// into the blocked contacts.
ChatIdBlocked::get_for_contact(self, contact_id, Blocked::Request)
.await?
.with_context(|| format!("No chat for addr '{addr}'"))?
.id
}
SyncId::Grpid(grpid) => {
@@ -7465,6 +7478,71 @@ mod tests {
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_sync_accept_before_first_msg() -> 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 bob = TestContext::new_bob().await;
let ba_chat = bob.create_chat(alice0).await;
let sent_msg = bob.send_text(ba_chat.id, "hi").await;
let a0b_chat_id = alice0.recv_msg(&sent_msg).await.chat_id;
assert_eq!(alice0.get_chat(&bob).await.blocked, Blocked::Request);
a0b_chat_id.accept(alice0).await?;
let a0b_contact = alice0.add_or_lookup_contact(&bob).await;
assert_eq!(a0b_contact.origin, Origin::CreateChat);
assert_eq!(alice0.get_chat(&bob).await.blocked, Blocked::Not);
sync(alice0, alice1).await;
let a1b_contact = alice1.add_or_lookup_contact(&bob).await;
assert_eq!(a1b_contact.origin, Origin::CreateChat);
let a1b_chat = alice1.get_chat(&bob).await;
assert_eq!(a1b_chat.blocked, Blocked::Not);
let chats = Chatlist::try_load(alice1, 0, None, None).await?;
assert_eq!(chats.len(), 1);
let rcvd_msg = alice1.recv_msg(&sent_msg).await;
assert_eq!(rcvd_msg.chat_id, a1b_chat.id);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_sync_block_before_first_msg() -> 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 bob = TestContext::new_bob().await;
let ba_chat = bob.create_chat(alice0).await;
let sent_msg = bob.send_text(ba_chat.id, "hi").await;
let a0b_chat_id = alice0.recv_msg(&sent_msg).await.chat_id;
assert_eq!(alice0.get_chat(&bob).await.blocked, Blocked::Request);
a0b_chat_id.block(alice0).await?;
let a0b_contact = alice0.add_or_lookup_contact(&bob).await;
assert_eq!(a0b_contact.origin, Origin::IncomingUnknownFrom);
assert_eq!(alice0.get_chat(&bob).await.blocked, Blocked::Yes);
sync(alice0, alice1).await;
let a1b_contact = alice1.add_or_lookup_contact(&bob).await;
assert_eq!(a1b_contact.origin, Origin::Hidden);
assert!(ChatIdBlocked::lookup_by_contact(alice1, a1b_contact.id)
.await?
.is_none());
let rcvd_msg = alice1.recv_msg(&sent_msg).await;
let a1b_contact = alice1.add_or_lookup_contact(&bob).await;
assert_eq!(a1b_contact.origin, Origin::IncomingUnknownFrom);
let a1b_chat = alice1.get_chat(&bob).await;
assert_eq!(a1b_chat.blocked, Blocked::Yes);
assert_eq!(rcvd_msg.chat_id, a1b_chat.id);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_sync_adhoc_grp() -> Result<()> {
let alice0 = &TestContext::new_alice().await;

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

@@ -67,19 +67,15 @@ fn parse_server<B: BufRead>(
let typ = server_event
.attributes()
.find(|attr| {
attr.as_ref()
.map(|a| {
String::from_utf8_lossy(a.key.as_ref())
.trim()
.to_lowercase()
== "type"
})
.unwrap_or_default()
.find_map(|attr| {
attr.ok().filter(|a| {
String::from_utf8_lossy(a.key.as_ref())
.trim()
.eq_ignore_ascii_case("type")
})
})
.map(|typ| {
typ.unwrap()
.decode_and_unescape_value(reader.decoder())
typ.decode_and_unescape_value(reader.decoder())
.unwrap_or_default()
.to_lowercase()
})

View File

@@ -3244,4 +3244,20 @@ Until the false-positive is fixed:
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_self_is_verified() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let contact = Contact::get_by_id(&alice, ContactId::SELF).await?;
assert_eq!(contact.is_verified(&alice).await?, true);
assert!(contact.is_profile_verified(&alice).await?);
assert!(contact.get_verifier_id(&alice).await?.is_none());
let chat_id = ChatId::get_for_contact(&alice, ContactId::SELF).await?;
assert!(chat_id.is_protected(&alice).await.unwrap() == ProtectionStatus::Protected);
Ok(())
}
}

View File

@@ -13,7 +13,7 @@ use async_channel::{self as channel, Receiver, Sender};
use pgp::types::PublicKeyTrait;
use pgp::SignedPublicKey;
use ratelimit::Ratelimit;
use tokio::sync::{Mutex, Notify, OnceCell, RwLock};
use tokio::sync::{Mutex, Notify, RwLock};
use crate::aheader::EncryptPreference;
use crate::chat::{get_chat_cnt, ChatId, ProtectionStatus};
@@ -276,7 +276,7 @@ pub struct InnerContext {
/// The text of the last error logged and emitted as an event.
/// If the ui wants to display an error after a failure,
/// `last_error` should be used to avoid races with the event thread.
pub(crate) last_error: std::sync::RwLock<String>,
pub(crate) last_error: parking_lot::RwLock<String>,
/// If debug logging is enabled, this contains all necessary information
///
@@ -292,7 +292,7 @@ pub struct InnerContext {
pub(crate) push_subscribed: AtomicBool,
/// Iroh for realtime peer channels.
pub(crate) iroh: OnceCell<Iroh>,
pub(crate) iroh: Arc<RwLock<Option<Iroh>>>,
}
/// The state of ongoing process.
@@ -446,11 +446,11 @@ impl Context {
metadata: RwLock::new(None),
creation_time: tools::Time::now(),
last_full_folder_scan: Mutex::new(None),
last_error: std::sync::RwLock::new("".to_string()),
last_error: parking_lot::RwLock::new("".to_string()),
debug_logging: std::sync::RwLock::new(None),
push_subscriber,
push_subscribed: AtomicBool::new(false),
iroh: OnceCell::new(),
iroh: Arc::new(RwLock::new(None)),
};
let ctx = Context {
@@ -486,6 +486,19 @@ impl Context {
/// Stops the IO scheduler.
pub async fn stop_io(&self) {
self.scheduler.stop(self).await;
if let Some(iroh) = self.iroh.write().await.take() {
// Close all QUIC connections.
// Spawn into a separate task,
// because Iroh calls `wait_idle()` internally
// and it may take time, especially if the network
// has become unavailable.
tokio::spawn(async move {
// We do not log the error because we do not want the task
// to hold the reference to Context.
let _ = tokio::time::timeout(Duration::from_secs(60), iroh.close()).await;
});
}
}
/// Restarts the IO scheduler if it was running before
@@ -496,7 +509,7 @@ impl Context {
/// Indicate that the network likely has come back.
pub async fn maybe_network(&self) {
if let Some(iroh) = self.iroh.get() {
if let Some(ref iroh) = *self.iroh.read().await {
iroh.network_change().await;
}
self.scheduler.maybe_network().await;

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

@@ -117,6 +117,9 @@ pub enum EventType {
/// 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
@@ -344,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

@@ -244,18 +244,14 @@ impl Kml {
self.tag = KmlTag::PlacemarkPoint;
} else if tag == "coordinates" && self.tag == KmlTag::PlacemarkPoint {
self.tag = KmlTag::PlacemarkPointCoordinates;
if let Some(acc) = event.attributes().find(|attr| {
attr.as_ref()
.map(|a| {
String::from_utf8_lossy(a.key.as_ref())
.trim()
.to_lowercase()
== "accuracy"
})
.unwrap_or_default()
if let Some(acc) = event.attributes().find_map(|attr| {
attr.ok().filter(|a| {
String::from_utf8_lossy(a.key.as_ref())
.trim()
.eq_ignore_ascii_case("accuracy")
})
}) {
let v = acc
.unwrap()
.decode_and_unescape_value(reader.decoder())
.unwrap_or_default();

View File

@@ -50,13 +50,13 @@ impl Context {
/// Set last error string.
/// Implemented as blocking as used from macros in different, not always async blocks.
pub fn set_last_error(&self, error: &str) {
let mut last_error = self.last_error.write().unwrap();
let mut last_error = self.last_error.write();
*last_error = error.to_string();
}
/// Get last error string.
pub fn get_last_error(&self) -> String {
let last_error = &*self.last_error.read().unwrap();
let last_error = &*self.last_error.read();
last_error.clone()
}
}

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

@@ -1667,10 +1667,11 @@ impl MimeMessage {
{
let mut to_list =
get_all_addresses_from_header(&report.headers, "x-failed-recipients");
let to = if to_list.len() == 1 {
Some(to_list.pop().unwrap())
let to = if to_list.len() != 1 {
// We do not know which recipient failed
None
} else {
None // We do not know which recipient failed
to_list.pop()
};
return Ok(Some(DeliveryReport {

View File

@@ -10,12 +10,12 @@
//! Adding peer channels to webxdc needs upfront negotation of a topic and sharing of public keys so that
//! nodes can connect to each other. The explicit approach is as follows:
//!
//! 1. We introduce a new [GossipTopic](crate::headerdef::HeaderDef::IrohGossipTopic) message header with a random 32-byte TopicId,
//! 1. We introduce a new [`IrohGossipTopic`](crate::headerdef::HeaderDef::IrohGossipTopic) message header with a random 32-byte TopicId,
//! securely generated on the initial webxdc sender's device. This message header is encrypted
//! and sent in the same message as the webxdc application.
//! 2. Whenever `joinRealtimeChannel().setListener()` or `joinRealtimeChannel().send()` is called by the webxdc application,
//! we start a routine to establish p2p connectivity and join the gossip swarm with Iroh.
//! 3. The first step of this routine is to introduce yourself with a regular message containing the `IrohPublicKey`.
//! 3. The first step of this routine is to introduce yourself with a regular message containing the [`IrohNodeAddr`](crate::headerdef::HeaderDef::IrohNodeAddr).
//! This message contains the users relay-server and public key.
//! Direct IP address is not included as this information can be persisted by email providers.
//! 4. After the announcement, the sending peer joins the gossip swarm with an empty list of peer IDs (as they don't know anyone yet).
@@ -78,6 +78,14 @@ impl Iroh {
self.endpoint.network_change().await
}
/// Closes the QUIC endpoint.
pub(crate) async fn close(self) -> Result<()> {
self.endpoint
.close(0u32.into(), b"")
.await
.context("Closing iroh endpoint failed")
}
/// Join a topic and create the subscriber loop for it.
///
/// If there is no gossip, create it.
@@ -285,15 +293,36 @@ impl Context {
}
/// Get or initialize the iroh peer channel.
pub async fn get_or_try_init_peer_channel(&self) -> Result<&Iroh> {
pub async fn get_or_try_init_peer_channel(
&self,
) -> Result<tokio::sync::RwLockReadGuard<'_, Iroh>> {
if !self.get_config_bool(Config::WebxdcRealtimeEnabled).await? {
bail!("Attempt to get Iroh when realtime is disabled");
}
let ctx = self.clone();
self.iroh
.get_or_try_init(|| async { ctx.init_peer_channels().await })
.await
if let Ok(lock) = tokio::sync::RwLockReadGuard::<'_, std::option::Option<Iroh>>::try_map(
self.iroh.read().await,
|opt_iroh| opt_iroh.as_ref(),
) {
return Ok(lock);
}
let lock = self.iroh.write().await;
match tokio::sync::RwLockWriteGuard::<'_, std::option::Option<Iroh>>::try_downgrade_map(
lock,
|opt_iroh| opt_iroh.as_ref(),
) {
Ok(lock) => Ok(lock),
Err(mut lock) => {
let iroh = self.init_peer_channels().await?;
*lock = Some(iroh);
tokio::sync::RwLockWriteGuard::<'_, std::option::Option<Iroh>>::try_downgrade_map(
lock,
|opt_iroh| opt_iroh.as_ref(),
)
.map_err(|_| anyhow!("Downgrade should succeed as we just stored `Some` value"))
}
}
}
}
@@ -626,7 +655,6 @@ mod tests {
break;
}
}
let bob_iroh = bob.get_or_try_init_peer_channel().await.unwrap();
// Bob adds alice to gossip peers.
let members = get_iroh_gossip_peers(bob, bob_webxdc.id)
@@ -636,13 +664,23 @@ mod tests {
.map(|addr| addr.node_id)
.collect::<Vec<_>>();
let alice_iroh = alice.get_or_try_init_peer_channel().await.unwrap();
assert_eq!(
members,
vec![alice_iroh.get_node_addr().await.unwrap().node_id]
vec![
alice
.get_or_try_init_peer_channel()
.await
.unwrap()
.get_node_addr()
.await
.unwrap()
.node_id
]
);
bob_iroh
bob.get_or_try_init_peer_channel()
.await
.unwrap()
.join_and_subscribe_gossip(bob, bob_webxdc.id)
.await
.unwrap()
@@ -651,7 +689,10 @@ mod tests {
.unwrap();
// Alice sends ephemeral message
alice_iroh
alice
.get_or_try_init_peer_channel()
.await
.unwrap()
.send_webxdc_realtime_data(alice, alice_webxdc.id, "alice -> bob".as_bytes().to_vec())
.await
.unwrap();
@@ -670,7 +711,9 @@ mod tests {
}
}
// Bob sends ephemeral message
bob_iroh
bob.get_or_try_init_peer_channel()
.await
.unwrap()
.send_webxdc_realtime_data(bob, bob_webxdc.id, "bob -> alice".as_bytes().to_vec())
.await
.unwrap();
@@ -699,10 +742,20 @@ mod tests {
assert_eq!(
members,
vec![bob_iroh.get_node_addr().await.unwrap().node_id]
vec![
bob.get_or_try_init_peer_channel()
.await
.unwrap()
.get_node_addr()
.await
.unwrap()
.node_id
]
);
bob_iroh
bob.get_or_try_init_peer_channel()
.await
.unwrap()
.send_webxdc_realtime_data(bob, bob_webxdc.id, "bob -> alice 2".as_bytes().to_vec())
.await
.unwrap();
@@ -720,6 +773,12 @@ mod tests {
}
}
}
// Calling stop_io() closes iroh endpoint as well,
// even though I/O was not started in this test.
assert!(alice.iroh.read().await.is_some());
alice.stop_io().await;
assert!(alice.iroh.read().await.is_none());
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
@@ -761,7 +820,6 @@ mod tests {
.unwrap();
bob.recv_msg_trash(&alice.pop_sent_msg().await).await;
let bob_iroh = bob.get_or_try_init_peer_channel().await.unwrap();
// Bob adds alice to gossip peers.
let members = get_iroh_gossip_peers(bob, bob_webxdc.id)
@@ -771,13 +829,23 @@ mod tests {
.map(|addr| addr.node_id)
.collect::<Vec<_>>();
let alice_iroh = alice.get_or_try_init_peer_channel().await.unwrap();
assert_eq!(
members,
vec![alice_iroh.get_node_addr().await.unwrap().node_id]
vec![
alice
.get_or_try_init_peer_channel()
.await
.unwrap()
.get_node_addr()
.await
.unwrap()
.node_id
]
);
bob_iroh
bob.get_or_try_init_peer_channel()
.await
.unwrap()
.join_and_subscribe_gossip(bob, bob_webxdc.id)
.await
.unwrap()
@@ -786,7 +854,10 @@ mod tests {
.unwrap();
// Alice sends ephemeral message
alice_iroh
alice
.get_or_try_init_peer_channel()
.await
.unwrap()
.send_webxdc_realtime_data(alice, alice_webxdc.id, "alice -> bob".as_bytes().to_vec())
.await
.unwrap();
@@ -811,7 +882,9 @@ mod tests {
.unwrap();
let bob_sequence_number = bob
.iroh
.get()
.read()
.await
.as_ref()
.unwrap()
.sequence_numbers
.lock()
@@ -820,7 +893,9 @@ mod tests {
leave_webxdc_realtime(bob, bob_webxdc.id).await.unwrap();
let bob_sequence_number_after = bob
.iroh
.get()
.read()
.await
.as_ref()
.unwrap()
.sequence_numbers
.lock()
@@ -829,7 +904,9 @@ mod tests {
// Check that sequence number is persisted when leaving the channel.
assert_eq!(bob_sequence_number, bob_sequence_number_after);
bob_iroh
bob.get_or_try_init_peer_channel()
.await
.unwrap()
.join_and_subscribe_gossip(bob, bob_webxdc.id)
.await
.unwrap()
@@ -837,7 +914,9 @@ mod tests {
.await
.unwrap();
bob_iroh
bob.get_or_try_init_peer_channel()
.await
.unwrap()
.send_webxdc_realtime_data(bob, bob_webxdc.id, "bob -> alice".as_bytes().to_vec())
.await
.unwrap();
@@ -860,7 +939,16 @@ mod tests {
// bob for example does not change the channels because he never sends an
// advertisement
assert_eq!(
alice.iroh.get().unwrap().iroh_channels.read().await.len(),
alice
.iroh
.read()
.await
.as_ref()
.unwrap()
.iroh_channels
.read()
.await
.len(),
1
);
leave_webxdc_realtime(alice, alice_webxdc.id).await.unwrap();
@@ -870,7 +958,9 @@ mod tests {
.unwrap();
assert!(alice
.iroh
.get()
.read()
.await
.as_ref()
.unwrap()
.iroh_channels
.read()
@@ -963,19 +1053,19 @@ mod tests {
.await
.unwrap();
assert!(alice.ctx.iroh.get().is_none());
assert!(alice.ctx.iroh.read().await.is_none());
// creates iroh endpoint as side effect
send_webxdc_realtime_data(alice, MsgId::new(1), vec![])
.await
.unwrap();
assert!(alice.ctx.iroh.get().is_none());
assert!(alice.ctx.iroh.read().await.is_none());
// creates iroh endpoint as side effect
leave_webxdc_realtime(alice, MsgId::new(1)).await.unwrap();
assert!(alice.ctx.iroh.get().is_none());
assert!(alice.ctx.iroh.read().await.is_none());
// This internal function should return error
// if accidentally called with the setting disabled.

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

@@ -1070,6 +1070,24 @@ CREATE INDEX msgs_status_updates_index2 ON msgs_status_updates (uid);
.await?;
}
inc_and_check(&mut migration_version, 124)?;
if dbversion < migration_version {
// Mark Saved Messages chat as protected if it already exists.
sql.execute_migration(
"UPDATE chats
SET protected=1 -- ProtectionStatus::Protected
WHERE type==100 -- Chattype::Single
AND EXISTS (
SELECT 1 FROM chats_contacts cc
WHERE cc.chat_id==chats.id
AND cc.contact_id=1
)
",
migration_version,
)
.await?;
}
let new_version = sql
.get_raw_config_int(VERSION_CFG)
.await?

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

@@ -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]
@@ -69,8 +74,8 @@ pub struct WebxdcManifest {
/// Optional URL of webxdc source code.
pub source_code_url: Option<String>,
/// If the webxdc requests network access.
pub request_internet_access: Option<bool>,
/// Set to "map" to request integration.
pub request_integration: Option<String>,
}
/// Parsed information from WebxdcManifest and fallbacks.
@@ -95,6 +100,9 @@ pub struct WebxdcInfo {
/// URL of webxdc source code or an empty string.
pub source_code_url: String,
/// Set to "map" to request integration, otherwise an empty string.
pub request_integration: String,
/// If the webxdc is allowed to access the network.
/// It should request access, be encrypted
/// and sent to self for this.
@@ -102,6 +110,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.
@@ -190,7 +206,7 @@ pub struct StatusUpdateItem {
/// Array of other users `selfAddr` that should be notified about this update.
#[serde(skip_serializing_if = "Option::is_none")]
pub notify: Option<Vec<String>>,
pub notify: Option<HashMap<String, String>>,
}
/// Update items as passed to the UIs.
@@ -323,14 +339,7 @@ impl Context {
return Ok(None);
};
let notify = if let Some(notify_list) = status_update_item.notify {
let self_addr = instance.get_webxdc_self_addr(self).await?;
notify_list.contains(&self_addr)
} else {
false
};
let mut notify_msg_id = instance.id;
let mut notify_text = "".to_string();
let mut param_changed = false;
let mut instance = instance.clone();
@@ -352,7 +361,6 @@ impl Context {
let summary = sanitize_bidi_characters(summary);
instance.param.set(Param::WebxdcSummary, summary.clone());
param_changed = true;
notify_text = summary;
}
}
@@ -362,18 +370,16 @@ impl Context {
.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;
}
if let (Some(info_msg_id), None) = (info_msg_id, &status_update_item.href) {
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,
@@ -387,9 +393,8 @@ impl Context {
)
.await?;
}
notify_text = info.to_string();
if let Some(href) = status_update_item.href {
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?;
@@ -409,12 +414,20 @@ impl Context {
});
}
if notify && !notify_text.is_empty() && from_id != ContactId::SELF {
self.emit_event(EventType::IncomingWebxdcNotify {
contact_id: from_id,
msg_id: notify_msg_id,
text: notify_text,
});
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).or_else(|| 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))
@@ -483,11 +496,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(())
}
@@ -498,7 +510,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
@@ -546,10 +557,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;
}
@@ -557,21 +568,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?;
@@ -609,7 +617,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(()),
};
@@ -626,7 +634,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()
};
@@ -910,9 +918,9 @@ impl Message {
}
}
let internet_access = manifest.request_internet_access.unwrap_or_default()
&& self.chat_id.is_self_talk(context).await.unwrap_or_default()
&& self.get_showpadlock();
let request_integration = manifest.request_integration.unwrap_or_default();
let is_integrated = self.is_set_as_webxdc_integration(context).await?;
let internet_access = is_integrated;
let self_addr = self.get_webxdc_self_addr(context).await?;
@@ -934,8 +942,11 @@ impl Message {
.get(Param::WebxdcDocument)
.unwrap_or_default()
.to_string(),
summary: if internet_access {
"Dev Mode: Do not enter sensitive data!".to_string()
summary: if is_integrated {
"🌍 Used as map. Delete to use default. Do not enter sensitive data".to_string()
} else if request_integration == "map" {
"🌏 To use as map, forward to \"Saved Messages\" again. Do not enter sensitive data"
.to_string()
} else {
self.param
.get(Param::WebxdcSummary)
@@ -947,8 +958,11 @@ impl Message {
} else {
"".to_string()
},
request_integration,
internet_access,
self_addr,
send_update_interval: context.ratelimit.read().await.update_interval(),
send_update_max_size: RECOMMENDED_FILE_SIZE as usize,
})
}
@@ -1133,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());
@@ -1184,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);
@@ -1267,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!(
@@ -1279,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!(
@@ -1310,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?;
@@ -1437,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))
@@ -1520,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());
@@ -1584,7 +1595,6 @@ mod tests {
t.send_webxdc_status_update(
instance.id,
r#"{"payload" : 1, "sender": "that is not used"}"#,
"",
)
.await?;
assert_eq!(
@@ -1719,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?;
@@ -1732,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,
@@ -1740,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))
@@ -1749,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
@@ -1822,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.
@@ -1897,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)
@@ -1913,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(
@@ -1964,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
@@ -1986,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,
@@ -1996,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");
@@ -2042,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!(
@@ -2093,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(())
@@ -2241,19 +2234,6 @@ sth_for_the = "future""#
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_parse_webxdc_manifest_request_internet_access() -> Result<()> {
let result = parse_webxdc_manifest(r#"request_internet_access = 3"#.as_bytes());
assert!(result.is_err());
let manifest = parse_webxdc_manifest(r#" source_code_url="https://foo.org""#.as_bytes())?;
assert_eq!(manifest.request_internet_access, None);
let manifest = parse_webxdc_manifest(r#" request_internet_access=false"#.as_bytes())?;
assert_eq!(manifest.request_internet_access, Some(false));
let manifest = parse_webxdc_manifest(r#"request_internet_access = true"#.as_bytes())?;
assert_eq!(manifest.request_internet_access, Some(true));
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_webxdc_min_api_too_large() -> Result<()> {
let t = TestContext::new_alice().await;
@@ -2282,6 +2262,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,
@@ -2387,11 +2369,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;
@@ -2402,11 +2380,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;
@@ -2457,7 +2431,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?;
@@ -2497,7 +2470,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?;
@@ -2575,13 +2547,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;
@@ -2608,12 +2580,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);
@@ -2642,7 +2614,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;
@@ -2662,7 +2634,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;
@@ -2672,16 +2644,16 @@ sth_for_the = "future""#
Ok(())
}
// check that `info.internet_access` is not set for normal, non-integrated webxdc -
// even if they use the deprecated option `request_internet_access` in manifest.toml
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_webxdc_internet_access() -> Result<()> {
async fn test_webxdc_no_internet_access() -> Result<()> {
let t = TestContext::new_alice().await;
let self_id = t.get_self_chat().await.id;
let single_id = t.create_chat_with_contact("bob", "bob@e.com").await.id;
let group_id = create_group_chat(&t, ProtectionStatus::Unprotected, "chat").await?;
let broadcast_id = create_broadcast_list(&t).await?;
let mut first_test = true; // only the first test has all conditions for internet access
for e2ee in ["1", "0"] {
t.set_config(Config::E2eeEnabled, Some(e2ee)).await?;
for chat_id in [self_id, single_id, group_id, broadcast_id] {
@@ -2700,16 +2672,11 @@ 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?;
let info = instance.get_webxdc_info(&t).await?;
assert_eq!(info.internet_access, first_test);
assert_eq!(info.summary.contains("Do not enter sensitive"), first_test);
assert_eq!(info.summary.contains("real summary"), !first_test);
first_test = false;
assert_eq!(info.internet_access, false);
}
}
}
@@ -2778,7 +2745,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?;
@@ -2898,7 +2864,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?;
@@ -2994,27 +2959,29 @@ sth_for_the = "future""#
.send_webxdc_status_update(
alice_instance.id,
&format!(
"{{\"payload\":7,\"info\": \"Alice moved\",\"notify\":[\"{}\"]}}",
"{{\"payload\":7,\"info\": \"Alice moved\",\"notify\":{{\"{}\": \"Your move!\"}} }}",
bob_instance.get_webxdc_self_addr(&bob).await?
),
"d",
)
.await?;
alice.flush_status_updates().await?;
let sent2 = alice.pop_sent_msg().await;
let info_msg = alice.get_last_msg().await;
assert!(info_msg.is_info());
assert!(!has_incoming_webxdc_event(&alice, info_msg, "Alice moved").await);
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!(has_incoming_webxdc_event(&bob, info_msg, "Alice moved").await);
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!(!has_incoming_webxdc_event(&fiona, info_msg, "Alice moved").await);
assert_eq!(info_msg.text, "Alice moved");
assert!(!has_incoming_webxdc_event(&fiona, info_msg, "").await);
Ok(())
}
@@ -3038,28 +3005,28 @@ sth_for_the = "future""#
.send_webxdc_status_update(
alice_instance.id,
&format!(
"{{\"payload\":7,\"info\": \"moved\", \"summary\": \"ignored for notify as info is set\", \"notify\":[\"{}\",\"{}\"]}}",
"{{\"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?
),
"d",
)
.await?;
alice.flush_status_updates().await?;
let sent2 = alice.pop_sent_msg().await;
let info_msg = alice.get_last_msg().await;
assert!(info_msg.is_info());
assert!(!has_incoming_webxdc_event(&alice, info_msg, "moved").await);
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, "moved").await);
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, "moved").await);
assert!(has_incoming_webxdc_event(&fiona, info_msg, "move, Fiona").await);
Ok(())
}
@@ -3085,55 +3052,129 @@ sth_for_the = "future""#
.send_webxdc_status_update(
alice_instance.id,
&format!(
"{{\"payload\":7,\"info\": \"moved\", \"notify\":[\"{}\"]}}",
"{{\"payload\":7,\"info\": \"moved\", \"notify\":{{\"{}\": \"bla\"}} }}",
alice2_instance.get_webxdc_self_addr(&alice2).await?
),
"d",
)
.await?;
alice.flush_status_updates().await?;
let sent2 = alice.pop_sent_msg().await;
let info_msg = alice.get_last_msg().await;
assert!(info_msg.is_info());
assert!(!has_incoming_webxdc_event(&alice, info_msg, "moved").await);
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, "moved").await);
assert!(!has_incoming_webxdc_event(&alice2, info_msg, "").await);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_webxdc_notify_summary() -> Result<()> {
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])
.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,\"summary\": \"4 moves done\",\"notify\":[\"{}\"]}}",
"{{\"payload\":7, \"notify\":{{\"{}\": \"notify bob\",\"*\": \"notify all\"}} }}",
bob_instance.get_webxdc_self_addr(&bob).await?
),
"d",
)
.await?;
alice.flush_status_updates().await?;
let sent2 = alice.pop_sent_msg().await;
assert!(!has_incoming_webxdc_event(&alice, alice_instance, "4 moves done").await);
bob.recv_msg_trash(&sent2).await;
assert!(has_incoming_webxdc_event(&bob, bob_instance, "4 moves done").await);
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(())
}
@@ -3154,7 +3195,6 @@ sth_for_the = "future""#
.send_webxdc_status_update(
instance.id,
r##"{"payload": "my deeplink data", "info": "my move!", "href": "#foobar"}"##,
"d",
)
.await?;
alice.flush_status_updates().await?;

View File

@@ -57,14 +57,34 @@ impl Context {
}
// Check if a Webxdc shall be used as an integration and remember that.
pub(crate) async fn update_webxdc_integration_database(&self, msg: &Message) -> Result<()> {
if msg.viewtype == Viewtype::Webxdc && msg.param.get_int(Param::WebxdcIntegration).is_some()
{
self.set_config_internal(
Config::WebxdcIntegration,
Some(&msg.id.to_u32().to_string()),
)
.await?;
pub(crate) async fn update_webxdc_integration_database(
&self,
msg: &mut Message,
context: &Context,
) -> Result<()> {
if msg.viewtype == Viewtype::Webxdc {
let is_integration = if msg.param.get_int(Param::WebxdcIntegration).is_some() {
true
} else if msg.chat_id.is_self_talk(context).await? {
let info = msg.get_webxdc_info(context).await?;
if info.request_integration == "map" {
msg.param.set_int(Param::WebxdcIntegration, 1);
msg.update_param(context).await?;
true
} else {
false
}
} else {
false
};
if is_integration {
self.set_config_internal(
Config::WebxdcIntegration,
Some(&msg.id.to_u32().to_string()),
)
.await?;
}
}
Ok(())
}
@@ -101,11 +121,26 @@ impl Message {
None
}
}
// Check if the message is an actually used as Webxdc integration.
pub(crate) async fn is_set_as_webxdc_integration(&self, context: &Context) -> Result<bool> {
if let Some(integration_id) = context
.get_config_parsed::<u32>(Config::WebxdcIntegration)
.await?
{
Ok(integration_id == self.id.to_u32())
} else {
Ok(false)
}
}
}
#[cfg(test)]
mod tests {
use crate::config::Config;
use crate::context::Context;
use crate::message;
use crate::message::{Message, Viewtype};
use crate::test_utils::TestContext;
use anyhow::Result;
use std::time::Duration;
@@ -126,4 +161,65 @@ mod tests {
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_overwrite_default_integration() -> Result<()> {
let t = TestContext::new_alice().await;
let self_chat = &t.get_self_chat().await;
assert!(t.init_webxdc_integration(None).await?.is_none());
async fn assert_integration(t: &Context, name: &str) -> Result<()> {
let integration_id = t.init_webxdc_integration(None).await?.unwrap();
let integration = Message::load_from_db(t, integration_id).await?;
let integration_info = integration.get_webxdc_info(t).await?;
assert_eq!(integration_info.name, name);
Ok(())
}
// set default integration
let bytes = include_bytes!("../../test-data/webxdc/with-manifest-and-png-icon.xdc");
let file = t.get_blobdir().join("maps.xdc");
tokio::fs::write(&file, bytes).await.unwrap();
t.set_webxdc_integration(file.to_str().unwrap()).await?;
assert_integration(&t, "with some icon").await?;
// send a maps.xdc with insufficient manifest
let mut msg = Message::new(Viewtype::Webxdc);
msg.set_file_from_bytes(
&t,
"mapstest.xdc",
include_bytes!("../../test-data/webxdc/mapstest-integration-unset.xdc"),
None,
)
.await?;
t.send_msg(self_chat.id, &mut msg).await;
assert_integration(&t, "with some icon").await?; // still the default integration
// send a maps.xdc with manifest including the line `request_integration = "map"`
let mut msg = Message::new(Viewtype::Webxdc);
msg.set_file_from_bytes(
&t,
"mapstest.xdc",
include_bytes!("../../test-data/webxdc/mapstest-integration-set.xdc"),
None,
)
.await?;
let sent = t.send_msg(self_chat.id, &mut msg).await;
let info = msg.get_webxdc_info(&t).await?;
assert!(info.summary.contains("Used as map"));
assert_integration(&t, "Maps Test 2").await?;
// when maps.xdc is received on another device, the integration is not accepted (needs to be forwarded again)
let t2 = TestContext::new_alice().await;
let msg2 = t2.recv_msg(&sent).await;
let info = msg2.get_webxdc_info(&t2).await?;
assert!(info.summary.contains("To use as map,"));
assert!(t2.init_webxdc_integration(None).await?.is_none());
// deleting maps.xdc removes the user's integration - the UI will go back to default calling set_webxdc_integration() then
message::delete_msgs(&t, &[msg.id]).await?;
assert!(t.init_webxdc_integration(None).await?.is_none());
Ok(())
}
}

View File

@@ -181,7 +181,7 @@ mod tests {
async fn test_maps_integration() -> Result<()> {
let t = TestContext::new_alice().await;
let bytes = include_bytes!("../../test-data/webxdc/mapstest.xdc");
let bytes = include_bytes!("../../test-data/webxdc/mapstest-integration-set.xdc");
let file = t.get_blobdir().join("maps.xdc");
tokio::fs::write(&file, bytes).await.unwrap();
t.set_webxdc_integration(file.to_str().unwrap()).await?;
@@ -199,13 +199,12 @@ mod tests {
let integration = Message::load_from_db(&t, integration_id).await?;
let info = integration.get_webxdc_info(&t).await?;
assert_eq!(info.name, "Maps Test");
assert_eq!(info.name, "Maps Test 2");
assert_eq!(info.internet_access, true);
t.send_webxdc_status_update(
integration_id,
r#"{"payload": {"action": "pos", "lat": 11.0, "lng": 12.0, "label": "poi #1"}}"#,
"descr",
)
.await?;
t.evtracker
@@ -239,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

Binary file not shown.