Compare commits

...

2598 Commits

Author SHA1 Message Date
Damir Jelić 9ffe5aa6ca chore: Add a description to the test utils crate 2025-09-04 16:42:00 +02:00
Damir Jelić c604e4acd2 chore: Update the cargo lock file for the matrix-sdk-test-utils crate 2025-09-04 16:38:45 +02:00
Damir Jelić f8b343bece chore: Include the test-utils crate in the release
Turns out, we do actually need to release it :(
2025-09-04 16:36:05 +02:00
Damir Jelić 94f8f8c44c Revert "chore: Disable releases for the matrix-sdk-test-utils crate"
This reverts commit f9bf492fdb.
2025-09-04 16:36:05 +02:00
Damir Jelić 4c1f80faf7 chore: Release matrix-sdk version 0.14.0 2025-09-04 16:05:48 +02:00
Damir Jelić f9bf492fdb chore: Disable releases for the matrix-sdk-test-utils crate
The crate is only used as a dev dependency, as such we don't need to
release it.
2025-09-04 16:05:48 +02:00
Damir Jelić 824fc0b62e chore: Add a changelog for the matrix-sdk-search crate 2025-09-04 16:05:48 +02:00
Kévin Commaille 359db7f28b Add changelog
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-09-04 11:48:48 +02:00
Kévin Commaille 30672e6feb Upgrade Ruma
Use the brand new release.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-09-04 11:48:48 +02:00
Benjamin Bouvier f9b419077d refactor(sdk): limit the quantity of data passed to the read receipt processor
This limits cloning.
2025-09-03 16:56:53 +02:00
Benjamin Bouvier d46f934d57 perf: process read receipts concurrently
By slightly changing the shape of the function used to process read
receipts, we can make it so that it's trivial to run concurrently, which
gives some nice speedups locally.

Distribution of 6 worst case processing time, initial response:

Before:
0.172524963s
0.216173016s
0.252289760s
0.257619156s
0.275838632s
0.280295891s

After:
0.083094692s
0.117074046s
0.130246646s
0.132577343s
0.138685246s
0.170287945s
2025-09-03 16:56:53 +02:00
Damir Jelić 0bed6afc29 fix(multiverse): Define the color of the placeholder text of the input line
This ensures that we're using the same color despite what your color
scheme of your terminal is. Some color schemes might produce unreadable
combinations of foreground color and background color.
2025-09-03 15:45:48 +02:00
Shrey Patel 412d4b80ee refactor(search): Move event processing out. 2025-09-03 15:05:23 +02:00
multi prise bcabf1bda4 Improve perfomance of build_room_key_bundle 2025-09-03 14:18:40 +02:00
Jorge Martín 7767ef6ca3 fix(ffi): Adapt FFI calls to Client::server_vendor_info to the new API
Specially important, the one from `ClientBuilder::build` as it avoids a situation where the builder would infinitely try to get a response for this request and never create the `Client` or fail.
2025-09-03 13:18:14 +02:00
Jorge Martín 6765ca0c39 refactor(sdk): Make Client::server_vendor_info accept an optional request config 2025-09-03 13:18:14 +02:00
Benjamin Bouvier 17abab0d53 chore(ci): bump wasm-pack and the runtime timeout 2025-09-03 12:46:09 +02:00
Benjamin Bouvier cc0bf91a06 feat(ffi): add a sync profiling log pack 2025-09-03 12:46:09 +02:00
Benjamin Bouvier 0b3345f592 feat(sdk): add more timers to sync processing 2025-09-03 12:46:09 +02:00
Benjamin Bouvier 472b934816 refactor(sdk): remove a few useless async 2025-09-02 19:56:23 +02:00
Ivan Enderlin 27a28e55d1 feat(ui): Add is_own and profile to LatestEventValue::Remote.
This patch adds 2 fields to `LatestEventValue::Remote`: `is_own` and
`profile`, which are necessary for the app consumers.
2025-09-02 18:18:26 +02:00
Ivan Enderlin d6a418f46a feat(ffi): Create Room::new_latest_event + LatestEventValue.
This patch creates the `LatestEventValue` in `matrix_sdk_ffi` and
exposes it via `Room::new_latest_event`.
2025-09-02 18:18:26 +02:00
Ivan Enderlin 268e14e4f5 feat(ui): Create timeline::LatestEventValue. 2025-09-02 18:18:26 +02:00
Ivan Enderlin f1190deef9 refactor(ui): TimelineAction::from_event zips two arguments.
This patch zips the `unable_to_decrypt_info` and the `meta` arguments
into a single one. They are strictly related, `meta` has no sense if
`unable_to_decrypt_info` has no sense neither.
2025-09-02 18:18:26 +02:00
Michael Goldenberg ee62cd749f refactor(indexeddb): add migrations and types for media retention metadata index
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-02 15:38:34 +02:00
Michael Goldenberg cea5c190d8 refactor(indexeddb): add migrations and types for media last access index
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-02 15:38:34 +02:00
Michael Goldenberg ad4cb4f6c9 refactor(indexeddb): add migrations and types for media source index
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-02 15:38:34 +02:00
Michael Goldenberg 949e7a6cac refactor(indexeddb): add migrations and types for media content size index
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-02 15:38:34 +02:00
Michael Goldenberg 8e66963a1e refactor(indexeddb): add types and migrations for storing media via event cache
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-02 15:38:34 +02:00
Michael Goldenberg aa02e31cf6 refactor(indexeddb): add types for representing media and associated metadata
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-02 15:38:34 +02:00
Michael Goldenberg 57c7972c63 refactor(indexeddb): add foreign (de)serialization for IgnoreMediaRetentionPolicy
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-02 15:38:34 +02:00
Benjamin Bouvier e89ac3d7df tests: add some sliding sync tests for thread subscriptions and catchup 2025-09-02 14:57:49 +02:00
Benjamin Bouvier a3704c3563 tests: rearrange some imports ni the sdk/tests/integration/client file 2025-09-02 14:57:49 +02:00
Benjamin Bouvier 5fb728e8f0 feat(sdk): use local thread subscription data if it's accurate \o/ 2025-09-02 14:57:49 +02:00
Benjamin Bouvier eab62ec0b5 feat(sdk): automatically catch up missing thread subscriptions 2025-09-02 14:57:49 +02:00
Benjamin Bouvier 2fae949a42 feat(sliding sync): add support for the thread subscriptions extension 2025-09-02 14:57:49 +02:00
Benjamin Bouvier 4adbb4aa88 feat(sdk): add support for persisting the thread subscription catchup tokens 2025-09-02 14:57:49 +02:00
Benjamin Bouvier 18affe3edd chore: bump Ruma 2025-09-02 14:57:49 +02:00
multisme ea59bc8955 Implement querying inboundgroupsessions by room_id (#5534)
History sharing: improve efficiency of building key bundle

Signed-off-by: multi
[multiestunhappydev@gmail.com](mailto:multiestunhappydev@gmail.com)

Partially Implement
https://github.com/matrix-org/matrix-rust-sdk/issues/5513

---------

Signed-off-by: multisme <korokoko.toi@gmail.com>
Co-authored-by: Richard van der Hoff <richard@matrix.org>
2025-09-02 12:07:07 +01:00
Shrey Patel 68f6d927f1 test(search): Add tests for edits in search index. 2025-09-02 12:25:53 +02:00
Shrey Patel c3766789cc feat(search): add edits to search index. 2025-09-02 12:25:53 +02:00
Shrey Patel 7c31525f68 test(search): Add tests for indexing redactions. 2025-09-02 12:25:53 +02:00
Shrey Patel b2dd5ce02d feat(search): add deletion from index 2025-09-02 12:25:53 +02:00
dependabot[bot] 1f2b4f87bc chore(deps): bump CodSpeedHQ/action from 3.8.0 to 3.8.1
Bumps [CodSpeedHQ/action](https://github.com/codspeedhq/action) from 3.8.0 to 3.8.1.
- [Release notes](https://github.com/codspeedhq/action/releases)
- [Changelog](https://github.com/CodSpeedHQ/action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codspeedhq/action/compare/0b6e7a3d96c9d2a6057e7bcea6b45aaf2f7ce60b...76578c2a7ddd928664caa737f0e962e3085d4e7c)

---
updated-dependencies:
- dependency-name: CodSpeedHQ/action
  dependency-version: 3.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-02 08:42:02 +02:00
dependabot[bot] 893c45af74 chore(deps): bump crate-ci/typos from 1.35.5 to 1.35.7
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.35.5 to 1.35.7.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.35.5...v1.35.7)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.35.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-02 08:41:24 +02:00
Richard van der Hoff a161dfa9a0 crypto: log message index for megolm sessions received over olm
When we receive a to-device message that contains a megolm decryption key, log
the ratchet index of the received key, for debugging.
2025-09-01 17:42:33 +01:00
Damir Jelić 20cd0bedfa chore: Fix a clippy warning about a useless conversion 2025-09-01 16:33:10 +02:00
Damir Jelić d4a1ce06e4 chore: Allow the CDLA license 2025-09-01 16:33:10 +02:00
Damir Jelić e53906a920 chore: Bump vergen
Vergen has split into multiple, more dedicated crates. This bump is
therefore a migration to vergen-gitcl.
2025-09-01 16:33:10 +02:00
Damir Jelić 1e30916754 chore: Bump most of our deps 2025-09-01 16:33:10 +02:00
Damir Jelić 15c46b503c chore: Update our deny config 2025-09-01 10:46:10 +02:00
Damir Jelić 5cd3818841 chore: Fix a clippy warning about unused lifetimes 2025-09-01 10:46:10 +02:00
Damir Jelić 79b7d6d235 chore: Use the upstream tracing repo
The necessary patch was merged[1] now, thanks to Jonas. We still need
the patch section since we don't have a release with the patch.

[1]: https://github.com/tokio-rs/tracing/pull/3000
2025-09-01 10:46:10 +02:00
Damir Jelić 05d0f9e077 chore: Don't patch the paranoid-android crate
Our paranoid-android for was configured to use our fork of the tracing
crates.

But paranoid-android will already use our tracing fork since it's
defined in the patch section. The patch section will override the
dependencies for all of our dependencies as well.

This can be seen in the dependency tree using:
    $ cargo tree -p matrix-sdk-ffi --target=aarch64-linux-android
2025-09-01 10:46:10 +02:00
Damir Jelić 4c1e2d6d51 chore: Fix a warning about a type visibility 2025-09-01 10:46:10 +02:00
Damir Jelić c9162373a1 chore: Bump the tracing version we're using
This gets rid of a vulnerability[1] in tracing-subscriber. The forked
version we're using for the bindings were bumped as well.


[1]: https://github.com/advisories/GHSA-xwfj-jgwm-7wp5
2025-09-01 10:46:10 +02:00
Benjamin Bouvier 9f22f550bf refactor(sdk): avoid duplicating the comparison of bumpstamps 2025-09-01 10:38:34 +02:00
Benjamin Bouvier 7a762035f1 feat(sdk): store the thread subscription bumpstamp and implement the correct upsert semantics 2025-09-01 10:38:34 +02:00
Benjamin Bouvier 8c0a918e6e refactor(sdk): introduce a lightweight ThreadSubscription for external consumers, and rename previous one to StoredThreadSubscription
External consumers are likely not interested about unsubscriptions and
the bump stamp values themselves, so let's not expose these to them.
2025-09-01 10:38:34 +02:00
Benjamin Bouvier 33c317e6d2 refactor(sdk): put the subscription status + bumpstamp back into the stored thread subscription 2025-09-01 10:38:34 +02:00
Eric Eastwood 371ed49670 Document --proxy option in the multiverse client
To be able to inspect network requests flying around as you interact

Spawning from https://github.com/matrix-org/matrix-rust-sdk/issues/5600#issuecomment-3238128112
2025-08-30 09:56:33 +02:00
Stefan Ceriu f0c1c65308 fix(spaces): mitigate eventual race conditions when updating the pagination state (part #2) 2025-08-29 18:35:27 +03:00
Stefan Ceriu ea5063ca84 fix(spaces): address potential race when setting up the room updates listener 2025-08-29 18:35:27 +03:00
Stefan Ceriu dcc07e1049 chore(spaces): acquire and retain an async lock on the pagination token for the duration of the request to futher prevent inconsistencies 2025-08-29 18:35:27 +03:00
Stefan Ceriu 76a0eb1599 fix(spaces): mitigate eventual race conditions when updating the pagination state 2025-08-29 18:35:27 +03:00
Stefan Ceriu a9c16c96e0 chore(spaces): cleanup clone names in room updates listener 2025-08-29 18:35:27 +03:00
Stefan Ceriu 93f8ebba27 chore(spaces): simplify the SpaceGraph interfaces
Fix graph reference crap
2025-08-29 18:35:27 +03:00
Stefan Ceriu 95bb153269 chore(spaces): improve how space graph edge additions work 2025-08-29 18:35:27 +03:00
Stefan Ceriu ff72a09870 chore(spaces): fix typo 2025-08-29 18:35:27 +03:00
Stefan Ceriu 06de58dee9 chore(spaces): switch some flat_maps to filter_map 2025-08-29 18:35:27 +03:00
Stefan Ceriu fbb49e9b65 chore(spaces): simplify the SpaceGraph interfaces 2025-08-29 18:35:27 +03:00
Stefan Ceriu 04728cc1a6 chore(spaces): various documentation fixes 2025-08-29 18:35:27 +03:00
Stefan Ceriu d43d141dc8 chore(spaces): use Client::joined_space_rooms within the space service to reduce the number of iterations required 2025-08-29 18:35:27 +03:00
Stefan Ceriu 3c069f0c5c fix(spaces): compute the initial joined_spaces value when first setting up a subscription
- fixes values being reported only after the first sync update
2025-08-29 18:35:27 +03:00
Stefan Ceriu a2200b6324 chore(spaces): switch from manually dropping JoinHandles to AbortOnDrop 2025-08-29 18:35:27 +03:00
Stefan Ceriu 9caa0817ae docs(spaces): add documentation, comments and examples 2025-08-29 18:35:27 +03:00
Stefan Ceriu b3229041fb chore(spaces): ignore room list change updates if empty 2025-08-29 18:35:27 +03:00
Stefan Ceriu 50446377de change(spaces): publish VectorDiffs instead of a full vectors for joined spaces and space room list subscriptions 2025-08-29 18:35:27 +03:00
Stefan Ceriu 4f02a6d3be chore(spaces): add changelog 2025-08-29 18:35:27 +03:00
Stefan Ceriu 87fdd3c3bf chore(spaces): converge on single naming scheme for all spaces related components on both the UI and the FFI crates 2025-08-29 18:35:27 +03:00
Stefan Ceriu 990fe86fdc change(spaces): make the SpaceService constructor non-async and instead automatically setup a client subscription when requesting the joined services subscription 2025-08-29 18:35:27 +03:00
Stefan Ceriu be2ba26974 fix(spaces): filter out the current parent space from the /hierarchy responses and SpaceServiceRoomList instances 2025-08-29 18:35:27 +03:00
Stefan Ceriu 1392a0e637 change(spaces): put a limit of 1 on the max depth of the /hierarchy calls so only the direct children are fetche 2025-08-29 18:35:27 +03:00
Stefan Ceriu 03ffa4c9a4 feat(spaces): expose the number of children each space room has 2025-08-29 18:35:27 +03:00
Stefan Ceriu f8b5992101 fix(spaces): return the TaskHandle from the FFI space service subscription methods so it can be retained on the client side 2025-08-29 18:35:27 +03:00
Stefan Ceriu 97a5fbebfb feat(ffi): expose SpaceService::subscribe_to_joined_spaces and SpaceServiceRoomList::paginate 2025-08-29 18:35:27 +03:00
Stefan Ceriu 74c2032974 chore(spaces): build a graph from joined spaces parent and child relations to correctly detect all the edges and be able to remove any cycles.
- cycle removing is done through DFS and keeping a list of visited nodes
2025-08-29 18:35:27 +03:00
Stefan Ceriu 274aaf5ba3 change(room_list): request both m.space.parent and m.space.child state events in the sliding sync required state as they're both required to build a full view of the space room hierarchy 2025-08-29 18:35:27 +03:00
Stefan Ceriu 3e72cce7a0 fix(spaces): use wasm compatible executor spawn and JoinHandle 2025-08-29 18:35:27 +03:00
Stefan Ceriu aa4b176ab3 fix(spaces): fix complement-crypto failures because of using an outdated uniffi version
- automatic Arc inference was introduced in 0.27 and complement is using 0.25
2025-08-29 18:35:27 +03:00
Stefan Ceriu ad2f4c731a feat(spaces): have the SpaceRoomList publish updates as known room states change i.e. they get joined or left. 2025-08-29 18:35:27 +03:00
Stefan Ceriu f78015fae1 change(spaces): return only top level joined rooms from SpaceService::joined_spaces and its reactive counterpart 2025-08-29 18:35:27 +03:00
Stefan Ceriu 211a1f5a40 feat(spaces): add a reactive version of the joined_spaces method 2025-08-29 18:35:27 +03:00
Stefan Ceriu b8be1fdb26 feat(spaces): introduce a SpaceRoomList that allows pagination and provides reactive interfaces to its rooms and pagination state 2025-08-29 18:35:27 +03:00
Stefan Ceriu a43e42c170 chore(spaces): introduce a SpaceServiceRoom 2025-08-29 18:35:27 +03:00
Stefan Ceriu f031eaf96b chore(spaces): setup a simple space service and some unit tests 2025-08-29 18:35:27 +03:00
Benjamin Bouvier 3ba31d1e97 tests: clarify that the other thread subscription endpoints are grouped by room 2025-08-29 11:28:29 +02:00
Benjamin Bouvier bbf8f9f900 feat(sdk): add support for msc4308 accompanying endpoint (fetching thread subscriptions) 2025-08-29 11:28:29 +02:00
Benjamin Bouvier 49dc2bb640 chore: bump Ruma
We get test fixes for free, thanks to the new push rules semantics
implemented in Ruma \o/
2025-08-29 11:28:29 +02:00
Skye Elliot 99af951d7a feat(crypto): Add EncryptionSettings::encrypt_state_events
This will be used inside the WASM SDK to introduce a similar field to
its EncryptionSettings struct.

Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2025-08-28 14:00:23 +02:00
Ivan Enderlin a17bf18ff2 chore(sdk): Improve a log message. 2025-08-28 12:58:19 +02:00
Ivan Enderlin 5d87570a33 doc(sdk,base): Fix outdated documentation and typos. 2025-08-28 12:58:19 +02:00
Ivan Enderlin 80806303b5 feat(sdk): The LatestEventValue is stored in RoomInfo and persisted.
This patch updates `LatestEvent::update` to call the new
`LatestEvent::store` method, which will store the new `LatestEventValue`
in the `RoomInfo` struct, and will persist it in the `StateStore`.

This patch also adds the test for this new feature.
2025-08-28 12:58:19 +02:00
Ivan Enderlin 6155772bb1 feat(base): Add RoomInfo::new_latest_event.
This patch adds the new `new_latest_event: LatestEventValue` field in
`RoomInfo`. The `latest_event` is kept for the moment, but it will be
removed once the new API has landed entirely.
2025-08-28 12:58:19 +02:00
Ivan Enderlin 33db267a89 test(base): test_cached_latest_event_can_be_redacted runs only if e2e-encryption is enabled.
This patch puts `test_cached_latest_event_can_be_redacted` as it runs
only if `e2e-encryption` is turned on.
2025-08-28 12:58:19 +02:00
Ivan Enderlin 6fc68dac83 refactor(sdk,base): Move LatestEventValue into matrix_sdk_base.
This patch moves `LatestEventValue` into the
`matrix_sdk_base::latest_event` module. If we want to store this value
in `RoomInfo`, it must be in this crate.

Because it's not allowed to `impl T` where `T` lives in a different
crate, this patch changes the `impl LatestEventValue` to `struct
LatestEventValueBuilder` + `impl LatestEventValueBuilder`. Luckily, all
methods on `LatestEventValue` are only constructors, so the change is
super straightforward.
2025-08-28 12:58:19 +02:00
Ivan Enderlin f3eeb82b0b refactor(sdk): LatestEvent::_room_id becomes _weak_room: WeakRoom.
This patch replaces `OwnedRoomId` by `WeakRoom` in `LatestEvent`. Apart
from simplifying a couple of method' signatures, it also opens the road
for storing the `LatestEventValue` in `RoomInfo`.
2025-08-28 12:58:19 +02:00
Richard van der Hoff 951d22ac24 common: js_tracing: drop TRACE logs
Now that (since https://github.com/matrix-org/matrix-js-sdk/pull/4918) Element
Web uses separate subscribers for each OlmMachine, rather than a single global
one with a separate LevelFilter, EW's logs are very verbose because they
receive all the TRACE logs.

Dropping the TRACE logs on the floor isn't my favourite solution, but
everything else seems to be a bit harder than I have time for right
now:

* Sending TRACE logs up to the JS side in case it wants to print them would (a)
  increase overhead and (b) be an annoying breaking change in JsLogger

* Ideally we'd let the application configure its logging more precisely via an
  `EnvFilter` or something, but I'm not really sure what the API would look like
  for that.

So for now, we take the easy path.
2025-08-28 11:55:04 +01:00
Damir Jelić 527d001010 fix: Only report duplicate one-time key errors once
Since the server will reject any duplicate one-time keys forever,
clients which encounter such an error will spam sentry with such
reports.

This patch ensures that we only send the sentry report once.
2025-08-28 12:48:30 +02:00
Stefan Ceriu 759eeeb27f feat(test): add event factory methods for creating space rooms and populating parent-children relationships. 2025-08-28 10:55:00 +03:00
Stefan Ceriu 97d6f57aee feat(base): add SyncResponse::RoomUpdates::is_empty method to check if there were any room updates 2025-08-28 10:45:02 +03:00
Stefan Ceriu 6622a3ac93 fix(room): fix event types in matrix_sdk::Room::parent_spaces method comments 2025-08-28 09:37:07 +02:00
Stefan Ceriu 39730173d4 chore(ffi): expose the SyncService.expire_sessions so client can expire Sliding Sync positions on demand
This is useful when changing the required sliding sync state in between position resets which Synapse doesn't handle well at the moment as per https://github.com/element-hq/synapse/issues/18844
2025-08-28 10:32:32 +03:00
Stefan Ceriu 763314645b feat(sdk): add a Client::joined_space_rooms method that allows retrieving the list of joined spaces. 2025-08-28 09:28:04 +02:00
Ivan Enderlin b2fee72d79 refactor(sdk): Revisit the assert_latest_event_content macro.
This patch revisits the `assert_latest_event_content` macro to not take
a `true` or `false` value. It feels a bit weird to read. Instead, `with
|factory| { … }, true` becomes `event |factory| { … } is a candidate`.
Same for the `false`case which becomes `is not a candidate`. No more
comma, it feels a bit more like a sentence.
2025-08-27 16:45:48 +02:00
Ivan Enderlin 9803d2bcca refactor(sdk): Use SerializableEventContent.
This patch replaces `LocalLatestEventValue::content` and `…::event_type`
fields by using the existing `SerializableEventContent`. It does exactly
the same thing.
2025-08-27 16:45:48 +02:00
Ivan Enderlin 296867d2ac feat(sdk): LatestEventValue implements Serialize and Deserialize.
This patch implements `Serialize` and `Deserialize` on
`LatestEventValue`.
2025-08-27 16:45:48 +02:00
Ivan Enderlin 710b57e035 refactor(sdk): Remove LatestEventContent.
The problem is: `LatestEventContent` cannot be serialized. It's annoying
because it means we can't store a `LatestEventValue` (that wraps a
`LatestEventContent`) in the database.

This patch revisits `LatestEventValue`. Before we got:

```rust
pub enum LatestEventValue {
     None,
     Remote(LatestEventContent),
     LocalIsSending(LatestEventContent),
     LocalCannotBeSent(LatestEventContent),
}

pub enum LatestEventContent {
    RoomMessage(RoomMessageEventContent),
    Sticker(StickerEventContent),
    Poll(UnstablePollStartEventContent),
    CallInvite(CallInviteEventContent),
    CallNotify(CallNotifyEventContent),
    KnockedStateEvent(RoomMemberEventContent),
    Redacted(AnySyncMessageLikeEvent),
}
```

`LatestEventContent::Redacted` contains an `AnySyncMessageLikeEvent`.
That's the part that is not serializable.

It appears that `LatestEventContent` isn't necessary! The only thing we
need is to _filter_ the events by their type, no need to _find and
map_. The `LatestEventValue` can contain the entry event directly (e.g.
a `TimelineEvent` for the event cache). Okay, let's do that.

```rust
pub enum LatestEventValue {
    None,
    Remote(RemoteLatestEventValue),
    LocalIsSending(???),
    LocalCannotBeSent(???),
}

type RemoteLatestEventValue = TimelineEvent;
```

What about the `Local*` variants? We can't use a `TimelineEvent`. We
need a new type for that:

```rust
pub enum LatestEventValue {
    None,
    Remote(RemoteLatestEventValue),
    LocalIsSending(LocalLatestEventValue),
    LocalCannotBeSent(LocalLatestEventValue),
}

pub struct LocalLatestEventValue {
    pub timestamp: MilliSecondsSinceUnixEpoch,
    pub content: Raw<AnyMessageLikeEventContent>,
    pub event_type: String,
}
```

We don't need the event ID nor the transaction ID in
`LocalLatestEventValue`.

That's the only change. All the other changes are about the tests.
2025-08-27 16:45:48 +02:00
Skye Elliot baa75368d6 ci: Add feature matrix for integration testing
This will resolve a number of transitive dependency issues when testing
crates that do not enable the `experimental-encrypted-state-events` feature
flag by default.

Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2025-08-27 14:55:45 +02:00
Damir Jelić feb264e899 test: Add a test to check that we don't create event cache reference cycles 2025-08-27 13:03:44 +02:00
Skye Elliot 8e5075569e feat: Add top-level support for decrypting state events (#5552)
Implements support for decryption of state events

- [ ] Introduce a case for `AnySyncStateEvent::RoomEncrypted` to the
`state_events` sync response processor.
- [ ] Introduce modified `Room::decrypt_event` and
`::try_decrypt_room_event`.
- [ ] Introduce testing macro
`assert_let_decrypted_state_event_content`.
- [ ] Add casts and explicit type hints where necessary.

---------

Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2025-08-27 10:53:55 +01:00
Michael Goldenberg 33c11d08f0 test(indexeddb): add tests for media retention policy related fns
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-27 09:56:50 +02:00
Michael Goldenberg 9714ac8e10 refactor(indexeddb): add IndexedDB-backed impl for media retention policy fns
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-27 09:56:50 +02:00
Michael Goldenberg e1d136aa6e refactor(indexeddb): add indexed type to represent media retention policy
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-27 09:56:50 +02:00
Michael Goldenberg 8018753332 refactor(indexeddb): add primary key to core object store in event cache database
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-27 09:56:50 +02:00
Michael Goldenberg 61824f866c refactor(indexeddb): delegate media-related queries via media service to EventCacheStoreMedia implementation
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-27 09:56:50 +02:00
Michael Goldenberg 6919444e98 feat(indexeddb): add MemoryStore-backed impl of EventCacheStoreMedia
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-27 09:56:50 +02:00
Michael Goldenberg c5097cf07e refactor(indexeddb): remove macros for implementing EventCacheStore
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-27 09:56:50 +02:00
Michael Goldenberg b77c6c65cc feat(indexeddb): derive Clone for IndexeddbEventCacheStore
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-27 09:56:50 +02:00
Benjamin Bouvier f4ce4356ab refactor(event cache): only process threaded linked chunks if thread support has been globally enabled 2025-08-26 16:25:56 +02:00
Benjamin Bouvier d66733052a feat(event cache): add indexes for finding related events 2025-08-26 16:25:56 +02:00
Benjamin Bouvier 001dadffe1 bench: add a benchmark for finding related events (#5578)
Does what it says on the tin. Split from the performance fix, so we can
get some initial numbers on the CI bench runs too.

Part of investigating
https://github.com/matrix-org/matrix-rust-sdk/issues/5572
2025-08-26 15:57:06 +02:00
Shrey Patel b2387bf3a9 test(search): Add tests for RoomIndex::contains and indexing idempotency 2025-08-26 13:25:49 +02:00
Shrey Patel d43858ecb2 fix(search): Make indexing idempotent 2025-08-26 13:25:49 +02:00
dependabot[bot] 8804966094 chore(deps): bump actions/setup-java from 4 to 5
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4 to 5.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-26 10:04:28 +02:00
dependabot[bot] a3ee011b61 chore(deps): bump crate-ci/typos from 1.35.4 to 1.35.5
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.35.4 to 1.35.5.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.35.4...v1.35.5)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.35.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-26 10:04:06 +02:00
dependabot[bot] f586172f3e chore(deps): bump actions/upload-pages-artifact from 3 to 4
Bumps [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/upload-pages-artifact/releases)
- [Commits](https://github.com/actions/upload-pages-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/upload-pages-artifact
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-26 10:03:51 +02:00
Jorge Martín 85d52586b6 fix(media): Use the right Option<Duration> value to get no timeout 2025-08-25 13:50:53 +02:00
Benjamin Bouvier 40d3dd57db tests(threads): add an exhaustive test to check for all notification mode combinations 2025-08-25 10:09:55 +02:00
Skye Elliot 4e2655aa1b feat(sdk): Use clearer fields for Span in SendRawStateEvent 2025-08-22 14:27:05 +01:00
Skye Elliot 41fcebbcb0 chore(sdk): Move ensure_room_encryption_ready to end of file 2025-08-22 14:27:05 +01:00
Skye Elliot 13b86a3f5d tests: Add test_room_encrypted_state_event_send 2025-08-22 14:27:05 +01:00
Skye Elliot dba23b66fa feat(sdk): Use SendStateEvent future in Room send_state methods
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2025-08-22 14:27:05 +01:00
Skye Elliot 132d0eb34a feat(sdk): Add SendStateEvent future
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2025-08-22 14:27:05 +01:00
Skye Elliot e2e70448ca feat(sdk): Modify Room::send_state_event_raw to return SendRawStateEvent
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2025-08-22 14:27:05 +01:00
Skye Elliot adaccbab2c feat(sdk): Add SendRawStateEvent future and deduplicate
Deduplicates common code that would be shared between
SendRawMessageLikeEvent and SendRawStateEvent to a helper method,
`ensure_room_encryption_ready`.

Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2025-08-22 14:27:05 +01:00
Richard van der Hoff 7a09ca0bbd Merge pull request #5545 from matrix-org/kaylendog/msc3414/json-castable
feat(sdk): Support room key downloading using `JsonCastable<EncryptedEvent>`
2025-08-21 18:59:23 +01:00
Richard van der Hoff fffdd34ebd Merge pull request #5559 from matrix-org/kaylendog/encrypt-poll-sync
This fixes a small issue encountered while developing the integration test for encrypted state events. If the first sync response received from the server after enabling encryption does not contain the m.room.encryption event, the client will report the encryption state as unknown, even if the next sync response does contain the event.

 - [x] Modify Room::enable_encryption_inner to spin on the room encryption state, rather than bailing after the first sync response is received.

Signed-off-by: Skye Elliot
2025-08-21 17:07:22 +01:00
Skye Elliot 24502d2706 fix(sdk): Check correct encryption state for encrypted state events 2025-08-21 15:43:35 +01:00
dragonfly1033 b6433dea27 refactor(sdk): Move index handling to EventCache and use linked_chunk_update_sender.
Remove previous code that got updates in `RoomEventCacheState::post_process_new_events`.
Add new task in `EventCache` that subscribes to `linked_chunk_update_sender` and forwards new events to `client::search::SearchIndex` same as before.

Signed-off-by: Shrey Patel shreyp@element.io
2025-08-21 14:17:45 +00:00
dragonfly1033 385f1a824f feat(multiverse): Add search feature to multiverse 2025-08-21 14:50:11 +01:00
dragonfly1033 dfc0ef8b35 refactor(multiverse): Create generic PopupInput widget 2025-08-21 14:50:11 +01:00
Benjamin Bouvier d2feeaac30 refactor(sdk): avoid an explicit dependency on ruma-common
The `ruma-common` crate is reexported by `ruma`, which is in our
dependency tree anyways. This change makes it so that qrcode, the only
crate that was making use of `ruma-common`, now depends on `ruma`. It
may move it further down the line in the compilation pipeline, but this
is unlikely to affect compile times in a crazy way. The benefit is that
this avoids the burden of having to specify the ruma commit hash twice
in the top-level Cargo.toml, as well as replacing it twice when
overriding it.
2025-08-21 15:42:25 +02:00
Benjamin Bouvier 25a81876a0 tests: allow conversion from EventBuilder into Raw<AnyGlobalAccountDataEvent>
This avoids a few explicit `.into_raw()` calls here and there.
2025-08-21 15:21:14 +02:00
Benjamin Bouvier e388fe6522 tests: streamline the SyncResponseBuilder global account methods
Only keep two: the one that uses the output of the event factory, and
one using custom JSON data.
2025-08-21 15:21:14 +02:00
Benjamin Bouvier ef20342ddf tests: use the new global account data methods a bit more 2025-08-21 15:21:14 +02:00
Benjamin Bouvier e6b1ffba99 tests: add support for the global account PushRules event in the event factory 2025-08-21 15:21:14 +02:00
Benjamin Bouvier 9a3ceb8be6 tests: add support for the global account IgnoredUserList event in the event factory 2025-08-21 15:21:14 +02:00
Benjamin Bouvier faee647c3a tests: get rid of GlobalAccountDataTestEvent::Direct 2025-08-21 15:21:14 +02:00
Benjamin Bouvier 1866143456 tests: add support for the global account Direct event in the event factory 2025-08-21 15:21:14 +02:00
Benjamin Bouvier be8e322ad6 doc(sdk): add comments around the NotificationSettings data structure 2025-08-21 15:21:14 +02:00
Benjamin Bouvier 838f607b32 refactor(benchmark): slightly change the benchmark to make it not use iter_custom
Instead of creating a fresh client every single time, this creates a
single client with a single event cache store, that's cleared between
runs of the benchmark.

So the tradeoff is:
- we don't have to create a new client anymore, which means no more
async setup code, which means we can avoid using `iter_custom`; a
benefit of this is that this can be benchmarked on CI now.
- but we're also measuring the time it takes to clear the database,
which isn't trivial in itself.
2025-08-21 15:19:26 +02:00
Benjamin Bouvier 6965004812 fix(benchmark): make the event cache benchmark compute what it's supposed to 2025-08-21 15:19:26 +02:00
Benjamin Bouvier 8ec23a95d5 fix(event cache): avoid cycle of EventCacheInner with the auto_shrink_linked_chunk_task
The task would stop when the receiver is closed; for the receiver to be
closed, the sender ought to be closed too.

Because the sender lives in `EventCacheInner`, and the task would hold
onto an `EventCacheInner` struct, the sender would never be dropped, so
the full `EventCacheInner` would leak, as a result.
2025-08-21 15:19:26 +02:00
Skye Elliot 7880ec5b01 fix(sdk): Return from poll when encryption state known
Fixes Room::enable_encryption_inner to break out of polling the
encryption state when it becomes known, rather than just encrypted.
2025-08-20 13:56:05 +01:00
Skye Elliot 36428564fc feat(sdk): Poll encryption state on sync event for up to 3 seconds. 2025-08-20 12:45:04 +01:00
Skye Elliot 7adaf7be73 feat(sdk): Rename state encryption methods for improved clarity
Changes `Room::enable_encryption_with_state` to
`Room::enable_encryption_with_encrypted_state_events`, and updates the
respective unit test and testing utilities.

Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2025-08-20 12:28:03 +02:00
Skye Elliot 7920723bb4 docs(sdk): Update CHANGELOG.md
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2025-08-20 12:28:03 +02:00
Skye Elliot b73163aa45 feat(sdk): Add Room::enable_encryption_with_state
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2025-08-20 12:28:03 +02:00
Ivan Enderlin feeeb53f19 refactor(sdk): Simplify Client::(joined|invited|left)_rooms.
This patch updates `Client::joined_rooms`, `invited_rooms` and
`left_rooms` to re-use `Client::rooms_filtered`.
2025-08-20 11:27:03 +02:00
Ivan Enderlin 1ac876db98 doc(sdk): Rephrase a couple of comments. 2025-08-20 08:37:27 +02:00
Ivan Enderlin afaf2cc036 test(sdk): Test that local latest event values has priority over remote's.
This patch moves the `test_update_ignores_none_value`
test in a new (correct) test module, and creates the new
`test_local_has_priority_over_remote` test.
2025-08-20 08:37:27 +02:00
Ivan Enderlin 5a3bb0a86d feat(sdk): Ensure the send queue has the priority over the event cache for computing a LatestEventValue. 2025-08-20 08:37:27 +02:00
Ivan Enderlin 2640aa1e23 fix(sdk): Compute the LatestEventValue on SentEvent before removing it from the buffer.
This patch changes the order in which the `LatestEventValue` is removed
from the buffer and re-created in case of a `SentEvent`. Previously,
the value from the buffer was removed before re-creating one, now it's
the opposite.

Why this order? Because once the local event is sent, it's not yet
received by the sync and consequently not stored in the event cache. So
once the local event is sent, it won't show up as a `LatestEventValue`
as it will immediately be replaced by an event from the event cache. By
computing the new value before removing it from the buffer, we ensure
the `LatestEventValue` represents the just sent local event.

See the comment in the code to learn more.
2025-08-20 08:37:27 +02:00
Ivan Enderlin d1a8392ce7 refactor(sdk): Rename LocalIsWedged to LocalCannotBeSent.
This patch renames `LocalIsWedged` to `LocalCannotBeSent` to avoid
confusion with the wedged/unwedged state of the `send_queue`. The
semantics is different in `latest_events`.
2025-08-19 16:35:41 +02:00
Ivan Enderlin 4d14dd3692 doc(sdk): Add a TODO marker for later. 2025-08-19 16:35:41 +02:00
Ivan Enderlin 63defca8af chore(sdk): Add mark_ prefix. 2025-08-19 16:35:41 +02:00
Ivan Enderlin fd83904b4d feat(sdk): Handle replacing a local event by a non-suitable latest event value.
This patch handles the case where a local event is replaced by another
local event which isn't suitable for being a latest event value. In this
case, the previous existing latest event value should be removed from
the buffer.
2025-08-19 16:35:41 +02:00
Ivan Enderlin 9254c38a8d chore(sdk): Log all deserialize errors in latest_events. 2025-08-19 16:35:41 +02:00
Ivan Enderlin 63d9dd5c6e refactor(sdk): Rename find_and_map_any_message… to extract_content_from_any_message_like. 2025-08-19 16:35:41 +02:00
Ivan Enderlin 23dacc329e refactor(sdk): Rename LatestEventKind to LatestEventContent. 2025-08-19 16:35:41 +02:00
Ivan Enderlin 5896a438f5 chore(sdk): Change error! to warn! when channels have been closed.
This patch reduces the level of logs when channels have been closed in
`LatestEvents`' tasks from `error` to `warn`. Indeed, when the `Client`
shutdowns, the channels will be closed, but it's not an error at all.
2025-08-19 16:35:41 +02:00
Ivan Enderlin 31a3d76436 feat(sdk): Introduce LatestEventKind::Redacted.
This patch introduces `LatestEventKind::Redacted` to handle the case
where an event is supposed to be a latest event but has been redacted.
2025-08-19 16:35:41 +02:00
Ivan Enderlin dfaaf323ad test(sdk): Test that a None latest event value is ignored. 2025-08-19 16:35:41 +02:00
Ivan Enderlin 94439d8913 test(sdk): Write tests for LatestEvent and SendQueue. 2025-08-19 16:35:41 +02:00
Ivan Enderlin 9cc29d7c65 feat(sdk): Implement LatestEvent::update_with_send_queue.
This patch implements `LatestEvent::update_with_send_queue`.
It introduces an intermediate type, for the sake of clarity,
`LatestEventValuesForLocalEvents`.

The difficulty here is to keep a buffer of `LatestEventValue`s requested
by the `SendQueue`. Why? Because we want the latest event value, but we
only receive `RoomSendQueueUpdate`s, we can't iterate over local events
in the `SendQueue` like we do for the `EventCache` to re-compute the
latest event if a local event has been cancelled or updated.

A particular care must also be applied when a local event is wedged:
this local event and all its followings must be marked as wedged too,
so that the `LatestEventValue` is `LocalIsWedged`. Same when the local
event is unwedged.
2025-08-19 16:35:41 +02:00
Ivan Enderlin f2fbdfbac2 chore(sdk): Rename and split a couple of types in latest_event.
This patch splits the `LatestEventValue` type into `LatestEventValue`
+ `LatestEventKind`. Basically, all variants in `LatestEventValue` are
moved inside the new `LatestEventKind` enum. `LatestEventValue` keeps
`None`, and see the new `Remote`, `LocalIsSending` and `LocalIsWedged`
variants.

This patch also extracts the message-like handling of `find_and_map`
(now renamed `find_and_map_timeline_event`) into its own function:
`find_and_map_any_message_like_event_content`. This is going to be
handful for the send queue part.
2025-08-19 16:35:41 +02:00
Ivan Enderlin fd34927f61 feat(sdk): compute_latest_events broadcasts the update to LatestEvent.
This patch updates `compute_latest_events` to broadcast a
`RoomSendQueueUpdate` onto `LatestEvent`. It introduces the new
`update_with_send_queue` method.
2025-08-19 16:35:41 +02:00
Ivan Enderlin 0190d3556d chore(sdk): Rename update to update_with_event_cache in latest_events.
This patch renames the `update` methods to `update_with_event_cache`
in the `latest_events` module. It frees the road to introduce
`update_with_send_queue`.
2025-08-19 16:35:41 +02:00
Ivan Enderlin 2d657fe908 feat(sdk): LatestEvents listens to the SendQueue.
This patch updates `LatestEvents` to listen to the updates from the
`SendQueue`. The `listen_to_event_cache_and_send_queue_updates` function
contains the important change. A new `LatestEventQueueUpdate` enum is
added to represent either an update from the event cache, or from the
send queue.

So far, `compute_latest_events` does nothing in particular, apart from
panicking with a `todo!()` when a send queue update is met.
2025-08-19 16:35:41 +02:00
Ivan Enderlin 5e43177d3a chore(sdk): Simplify code in listen_to_event_cache_and_send_queue_updates_task.
This patch removes the intermediate `rooms` variable in a new block. The
read-lock can be used immediately.
2025-08-19 16:35:41 +02:00
Skye Elliot e44b01cbe5 feat(sdk): Support room key downloading using JsonCastable<EncryptedEvent>
Allows `Backups::maybe_download_room_key` to accept any T:
JsonCastable<EncryptedEvent>, which will be required for state events to
trigger fetching the room key.

Implements JsonCastable<EncryptedEvent> for
OriginalSyncStateRoomEncryptedEventContent.

Implements JsonCastable<AnyStateEvent> for RoomEncryptedEventContent.
2025-08-19 14:47:44 +01:00
Michael Goldenberg 4882c98f99 refactor(indexeddb): allow IndexeddbSerializer::hash_key to be unused until event cache store is a default feature
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-19 14:46:05 +02:00
Michael Goldenberg 4bf0187310 style(indexeddb): format event cache store impl by temporarily removing enclosing macro
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-19 14:46:05 +02:00
Michael Goldenberg 10ca400d4d docs(indexeddb): correct key docs to express that keys are hashed, not encrypted
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-19 14:46:05 +02:00
Michael Goldenberg cc61e123b7 docs(indexeddb): remove references to room where relevant in transaction docs
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-19 14:46:05 +02:00
Michael Goldenberg 6f23981268 refactor(indexeddb): log linked chunk id rather than room id in handle_linked_chunk_updates
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-19 14:46:05 +02:00
Michael Goldenberg 859044285a test(indexeddb): use event cache store integration tests from matrix_sdk_base
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-19 14:46:05 +02:00
Michael Goldenberg 6c1134006e fix(indexeddb): integrate linked chunk id into relevant chunk- and gap-related types and fns
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-19 14:46:05 +02:00
Michael Goldenberg 6d1cdbc613 fix(indexeddb): integrate linked chunk id into relevant event-related types and fns
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-19 14:46:05 +02:00
Michael Goldenberg 6160c15103 refactor(indexeddb): re-organize type synonyms in event_cache_store::serializer
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-19 14:46:05 +02:00
Michael Goldenberg 100cbde526 refactor(indexeddb): use room-based queries in event-related fns that don't use linked chunk ids
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-19 14:46:05 +02:00
Michael Goldenberg 6ff8a26cca refactor(indexeddb): add room-based index to event object store in preparation for linked chunk id as primary key
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-19 14:46:05 +02:00
Michael Goldenberg a1c484fb6e refactor(indexeddb): expose hash_key fn in serializer for keys represented as bytes rather than strings
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-19 14:46:05 +02:00
Michael Goldenberg d2ecc77014 feat(linked chunk): derive ser/de traits for OwnedLinkedChunkId
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-19 14:46:05 +02:00
Michael Goldenberg 2e86fbc234 feat(linked chunk): add display impl for LinkedChunkId
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-19 14:46:05 +02:00
Michael Goldenberg 64698eaf1a feat(linked chunk): add trait-based conversions between owned and borrowed linked chunk id
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-19 14:46:05 +02:00
Michael Goldenberg f180a14c88 feat(linked chunk): expose OwnedLinkedChunkId::as_ref for use in other crates
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-19 14:46:05 +02:00
Benjamin Bouvier bb0d480f24 fix(ci): clean more space in the CI runner for the codecov space
An idea courtesy from the gentle folks at apache/arrow.
2025-08-19 14:25:13 +02:00
Richard van der Hoff 7af1d3ab0e Merge pull request #5539 from matrix-org/kaylendog/msc3414/crypto
feat(crypto): Add support for encrypted state events to `matrix-sdk-crypto`
2025-08-19 10:55:38 +01:00
Skye Elliot 13ee4c8098 tests(crypto): Document introduced tests and helper
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2025-08-19 10:17:14 +01:00
dependabot[bot] 7bbd02ca73 chore(deps): bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-19 10:33:03 +02:00
dependabot[bot] e22a7a2ed5 chore(deps): bump bnjbvr/cargo-machete from 0.8.0 to 0.9.1
Bumps [bnjbvr/cargo-machete](https://github.com/bnjbvr/cargo-machete) from 0.8.0 to 0.9.1.
- [Release notes](https://github.com/bnjbvr/cargo-machete/releases)
- [Changelog](https://github.com/bnjbvr/cargo-machete/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bnjbvr/cargo-machete/compare/v0.8.0...v0.9.1)

---
updated-dependencies:
- dependency-name: bnjbvr/cargo-machete
  dependency-version: 0.9.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-19 10:16:04 +02:00
dependabot[bot] 4aad2c6b07 chore(deps): bump crate-ci/typos from 1.35.0 to 1.35.4
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.35.0 to 1.35.4.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.35.0...v1.35.4)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.35.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-19 10:14:26 +02:00
Skye Elliot 960162453c feat(base): Add EncryptionState::StateEncrypted
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2025-08-18 17:46:46 +02:00
Benjamin Bouvier 1ea2162012 chore(ci): add a search feature to try out the experimental-search branch in CI 2025-08-18 17:33:34 +02:00
Benjamin Bouvier 1f0151705a fix(search): make the experimental-search feature compile
And simplify the code for parsing in the event cache.
2025-08-18 17:33:34 +02:00
Skye Elliot 84ebbd913c feat: Add naive state key verification to OlmMachine
Modifies `OlmMachine::decrypt_room_event_inner` to call a new method
`OlmMachine::verify_packed_state_key` which, if the event is a state
event, verifies that the original event's state key, when unpacked,
matches the state key and event type in the decrypted event content.

Introduces MegolmError::StateKeyVerificationFailed and
UnableToDecryptReason::StateKeyVerificationFailed which are thrown when
the verification fails.

Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2025-08-18 15:56:48 +01:00
Skye Elliot 756d50737e feat(crypto): Add state event encryption methods to OlmMachine
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2025-08-18 15:56:45 +01:00
Skye Elliot c32877284c feat(crypto): Add GroupSessionManager::encrypt_state
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2025-08-18 15:56:44 +01:00
Skye Elliot 6260811ea5 feat(crypto): Add OutboundGroupSession::encrypt_state
This commit also refactors out what would be common code between
::encrypt and ::encrypt_state to a helper ::encrypt_inner.

Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2025-08-18 15:56:38 +01:00
dragonfly1033 4f0415d4d2 add .envrc to .gitignore
Signed-off-by: dragonfly1033 <42915850+dragonfly1033@users.noreply.github.com>
2025-08-18 15:20:31 +02:00
Benjamin Bouvier b43aac129b chore: address review comments 2025-08-18 15:10:50 +02:00
Benjamin Bouvier c019009d00 refactor(event cache): avoid deserializing the full event content to be sent, for extracting its thread root 2025-08-18 15:10:50 +02:00
Benjamin Bouvier a88d6b37dc feat(event cache): also subscribe to a thread if we've sent a message into it 2025-08-18 15:10:50 +02:00
Benjamin Bouvier 705d6f870e refactor(event cache): move the listening to linked chunk updates to its own function, and introduce a select! at the top level 2025-08-18 15:10:50 +02:00
Benjamin Bouvier 4fc28c4701 feat(multiverse): enable threading support for multiverse with subscriptions 2025-08-18 15:10:50 +02:00
Benjamin Bouvier 64eecd0aee test(event cache): add tests for automatic thread subscriptions 2025-08-18 15:10:50 +02:00
Benjamin Bouvier c25be8b070 feat(event cache): automatically subscribe to threads according to msc4306 semantics 2025-08-18 15:10:50 +02:00
Benjamin Bouvier 1554c9d8fa feat(room): add a new function that will only subscribe to a thread if needed 2025-08-18 15:10:50 +02:00
Benjamin Bouvier d568c07489 feat(event cache): add a way to subscribe to any room's linked chunk updates 2025-08-18 15:10:50 +02:00
dragonfly1033 13c30f6691 feat(sdk): Add creation of indexes and indexing of messages (#5505)
Integrate matrix-sdk-search into matrix-sdk.
When a room is joined, a corresponding index is created.
When a message is received via sync or via a back-pagination, it is
added to the corresponding room's index.

Signed-off-by: Shrey Patel shreyp@element.io
2025-08-18 14:52:09 +02:00
Benjamin Bouvier 0e70a2fdfb test(sdk): add a regression test for the thread relation forwarding for poll start events
This got fixed in Ruma, and the Ruma's bump in the parent commit
includes this fix.
2025-08-18 13:04:35 +02:00
Benjamin Bouvier d70d758861 chore: bump Ruma 2025-08-18 13:04:35 +02:00
fkwp eefa9ff556 added comments including reference to MSCs 2025-08-15 15:58:28 +01:00
fkwp 28a8603f42 Allow new state key string-packing format for widget mode 2025-08-15 15:58:28 +01:00
Skye Elliot ae7f0fe022 feat: Experimental encrypted state feature flag with CI support (#5537)
This PR makes some non-domain-specific changes across multiple crates
that are required for proper testing of features implemented for #5397.

* Adds a `experimental-encrypted-state-events` feature flag across the
SDK.
* Introduces a feature set into xtask to ensure feature-gated tests are
run during CI.
* Minor fix to a test that would otherwise fail with the newly
introduced CI.
2025-08-15 12:54:41 +00:00
Skye Elliot d9f4e7c426 Merge pull request #5511 from kaylendog/kaylendog/room-settings
feat(crypto): Add RoomSettings::encrypt_state_events
2025-08-14 15:51:19 +01:00
Benjamin Bouvier 247ec1dcd2 refactor(event cache): shorten the name of room_event_cache_generic_update
We're in the inner workings of the event cache, so the prefix is
redundant, in the ambient context.
2025-08-14 13:05:57 +02:00
Benjamin Bouvier 558d7b56f9 refactor(event cache): get rid of RoomEventCacheGenericUpdate::Clear
The semantics of this variant are unclear: sometimes a timeline could be
cleared, which would result in a `UpdateTimeline` (and if we looked at
the vector diffs, it would include a `Clear`), but in this case the
`Clear` variant would not be emitted.

It was only emitted in a few adhoc spots, but it was missing the whole
picture. Also, the current observer was only interested in getting *a*
room update with the room id, and didn't react particularly to clears.
So, there's apparently little reason in having this variant, and as a
result we should get rid of it.
2025-08-14 13:05:57 +02:00
dragonfly1033 1201be484a fix!(sdk): Client::sync_once defaults to reuse previous token
Introduces a new `SyncToken` enum for the `SyncSettings::token` field.
The enum has 3 variants: ReusePrevious (default), NoToken, Specific(String).

Some tests were changed to use the old default (NoToken).
2025-08-14 12:14:06 +02:00
Benjamin Bouvier 1ffc014621 chore(tests): make some tests less flaky
When I run these locally, they may now take more than 100ms to run, when
being run in parallel. Increase the timeout duration to 1s.
2025-08-14 09:18:15 +02:00
Benjamin Bouvier 9491757cad chore: check in the correct version of Ruma for ruma-signatures 2025-08-14 09:18:15 +02:00
Kévin Commaille 33df0422e8 Upgrade Ruma: profile response
Handle the changes to the Response of the get_profile endpoint. The
content of the response is private and fields must be accessed with
methods.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-08-14 08:57:52 +02:00
Kévin Commaille a3a239f999 Upgrade Ruma: revert StrippedState
Handle the previous breaking change that was reverted: `StrippedState`
was removed and `AnyStrippedStateEvent` is used again.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-08-14 08:57:52 +02:00
Skye Elliot ca8b64e041 feat: Change type of DecryptedRoomEvent::event to Raw<AnyTimelineEvent> (#5512)
- [x] Change `DecryptedRoomEvent::event` to `Raw<AnyTimelineEvent>`
- [x] Update usages to pattern match on `AnyTimelineEvent::MessageLike`
where necessary

---------

Signed-off-by: kaylendog <actuallyori@gmail.com>
2025-08-14 08:53:56 +02:00
Stefan Ceriu 140e751af0 feat(timeline): consider unthreaded read receipts for ReceiptThread::Main timelines when computing timeline items states.
This patch updates the Timeline Controller's `handle_explicit_read_receipts` method to also consider unthreaded read receipts on **threaded** **main** timelines when calculating timeline items states and adds a test for it.

This picks up from #5442 and fixes https://github.com/matrix-org/matrix-rust-sdk/issues/5440
2025-08-14 05:24:20 +00:00
multisme a66b2c5123 feat(test): add a test utils crate to make log initialization possible everywhere
This PR allows `init_tracing_for_test` to be called by any other crate in the sdk

Signed-off-by: multi [multiestunhappydev@gmail.com](mailto:multiestunhappydev@gmail.com)
2025-08-14 05:24:03 +00:00
Copilot 69bef9a76a feat(sdk,ffi): Add server_vendor_info method to matrix-sdk with automatic logging in FFI
Add a new `server_vendor_info` method on the `matrix-sdk` `Client` that calls the `/_matrix/federation/v1/version` endpoint to retrieve the server's software name and version information.

Also add it to the bindings + log it when initializing the logs.
2025-08-14 05:14:25 +00:00
Michael Goldenberg b3c53dd08f test(indexeddb): run time-based integration tests on event cache
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-13 16:00:48 +02:00
Michael Goldenberg c8bffa26a4 test(event-cache-store): make time-based integration tests compatible with wasm targets
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-13 16:00:48 +02:00
Michael Goldenberg b4ef6cef55 refactor(indexeddb): add IndexedDB-backed impl of EventCacheStore::try_take_leased_lock
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-13 16:00:48 +02:00
Michael Goldenberg c6854a5c22 refactor(indexeddb): add support for indexing time-based locks in event cache
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-13 16:00:48 +02:00
Michael Goldenberg fb563953c9 refactor(indexeddb): add object store for tracking time-based lock on event cache
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-13 16:00:48 +02:00
Michael Goldenberg bc0018aecb refactor(indexeddb): add type to represent time-based lock on event cache
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-13 16:00:48 +02:00
Johannes Marbach 12292c5375 feat(ffi): allow specifying thumbnails using UploadSource
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-08-13 15:50:06 +02:00
Johannes Marbach cf9d058265 feat(ffi): allow specifying gallery items using UploadSource
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-08-13 15:30:34 +02:00
Benjamin Bouvier 4da13e1096 refactor!(ffi): use the send queue by default to upload medias
We do consider it stable now, after months of running it in production,
so let's use it by default to simplify the `UploadParameters`.
2025-08-13 15:16:16 +02:00
Benjamin Bouvier 333d4563ce refactor!(ffi): remove legacy progress upload tracking
This can now be achieved by using the send queue's global progress
support, i.e. `Client::enable_send_queue_upload_progress()`.
2025-08-13 15:16:16 +02:00
Andy Balaam 01059ef26c refactor(timeline): Make RoomDataProvider provide Decryptor to simplify redecryption 2025-08-13 12:40:07 +01:00
Kévin Commaille 7724271508 doc(test): Fix method auto-link
Rustdoc doesn't support passing parameters for auto-links.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-08-13 12:31:22 +01:00
Kévin Commaille 8dfe732cce doc(sdk): Fix method auto-link
Rustdoc doesn't do multi-level auto-link, so we have to provide the link
manually.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-08-13 12:31:22 +01:00
Kévin Commaille 1cf3477ada feat(crypto): Implement Default for SecretStorageKey
For the new_without_default clippy lint.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-08-13 12:31:22 +01:00
Kévin Commaille 0a2205f540 refactor(ffi): Remove dead code
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-08-13 12:31:22 +01:00
Kévin Commaille c586812159 refactor(crypto): Remove dead code
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-08-13 12:31:22 +01:00
Kévin Commaille c6210cad21 ci: Upgrade the version of Rust nightly
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-08-13 12:31:22 +01:00
Ivan Enderlin a9ce1c6e58 doc(sdk): Fix CHANGELOG.md entry.
The link for an entry was wrong. It's #5439, not #5442.
2025-08-13 10:08:50 +02:00
Kévin Commaille 1eb8f6ac16 Fix shared history test
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-08-12 17:25:22 +03:00
Kévin Commaille e0feebdb2b fix(sdk): Support unauthenticated media endpoint in Client::load_or_fetch_max_upload_size
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-08-12 17:25:22 +03:00
Kévin Commaille 0fee716c1e fix(sdk): Override timeout of media downloads for unauthenticated media endpoints too
There is no reason to treat them differently.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-08-12 17:25:22 +03:00
Kévin Commaille c41ed8a78a refactor(sdk): Use Ruma support for (un)stable feature flags for authenticated media
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-08-12 17:25:22 +03:00
Benjamin Bouvier 53f02c9f2d chore: bump Ruma
So as to get some changes for Element Call:
https://github.com/ruma/ruma/pull/2176
2025-08-12 16:06:26 +02:00
Johannes Marbach e2f0b4f3fd feat(ffi): expose media upload progress through EventSendState::NotSentYet
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-08-12 12:57:18 +02:00
Johannes Marbach 0a796cb468 feat(timeline): communicate media upload progress through EventSendState::NotSentYet
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-08-12 12:57:18 +02:00
copilot-swe-agent[bot] e3390c17ec docs: Add changelog entries for LowPriority and NonLowPriority filters
Co-authored-by: pixlwave <6060466+pixlwave@users.noreply.github.com>
2025-08-12 13:17:53 +03:00
Doug c6dc070c31 chore: Refactor non_space.rs filter to space.rs, using new_filter_not in the FFI. 2025-08-12 13:17:53 +03:00
copilot-swe-agent[bot] 486befc7fb feat: Add NonLowPriority to the FFI bindings
Co-authored-by: pixlwave <6060466+pixlwave@users.noreply.github.com>
2025-08-12 13:17:53 +03:00
copilot-swe-agent[bot] 9848d1472e feat: Add LowPriority filter implementation and FFI bindings
Co-authored-by: pixlwave <6060466+pixlwave@users.noreply.github.com>
2025-08-12 13:17:53 +03:00
Damir Jelić 6c944a9b39 Add a changelog entry for the sender data check when accepting historic room keys 2025-08-08 15:56:13 +02:00
Damir Jelić b4b010f9fe fix(crypto): Do a keys query before we accept historic room key bundles 2025-08-08 15:56:13 +02:00
Damir Jelić 536ba518bb feat(crypto): Check sender data before accepting room key bundles 2025-08-08 15:56:13 +02:00
Damir Jelić 917c46b570 chore: Remove a stale TODO comment 2025-08-08 15:56:13 +02:00
Damir Jelić b29886c0df test(crypto): Add a test that we refuse bundles if the sender isn't trusted enough 2025-08-08 15:56:13 +02:00
Damir Jelić 360c2d7f32 refactor(crypto): Turn should_recalculate function into an associated function for SenderData
This allows us to use the function in more places where SenderData is
used.
2025-08-08 15:56:13 +02:00
dragonfly1033 683f0f4027 feat(multiverse): Add room creation to multiverse 2025-08-08 12:33:48 +01:00
Damir Jelić c783ed8a6f Log a special error when try to upload duplicate one-time keys 2025-08-08 10:35:52 +02:00
Damir Jelić 139673810f Remember the public Curve25519 key of the sender of the historic room key bundle 2025-08-08 09:19:19 +02:00
Kévin Commaille 669ebf2408 refactor(base): Don't take room ID in Notification::push_notification_from_event_if
It is already in the `PushConditionRoomCtx`, so we don't need an extra
argument for it.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-08-07 16:02:56 +02:00
dragonfly1033 992774b8b5 Add the matrix-sdk-search crate
A new crate with a basic API for a creating, populating and searching a
room message index.

Signed-off-by: Shrey Patel shreyp@element.io
2025-08-07 16:01:41 +02:00
Kévin Commaille 9d90a92b4c doc(base): Use different Result type in doc example
`anyhow::Ok(())` doesn't work under WASM because it requires the error
types to be `Send + Sync`.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-08-07 15:11:30 +02:00
Michael Goldenberg d79975e0e3 refactor(indexeddb): simplify IndexeddbEventCacheStoreTransaction::get_events_by_related_event
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-07 12:53:32 +02:00
Michael Goldenberg 2e598c0532 refactor(indexeddb): remove unnecessary fn IndexedEventRelationKey::with_related_event_id
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-07 12:53:32 +02:00
Michael Goldenberg 5a3ef30fdc refactor(indexeddb): remove redundant room id arg on relevant serializer and transaction fns
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-07 12:53:32 +02:00
Michael Goldenberg 05178ccaf9 refactor(indexeddb): make room id a key component rather than fixed arg to IndexedKey::encode
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-07 12:53:32 +02:00
Michael Goldenberg 65b9bd20a8 refactor(indexeddb): express IndexedKeyRange::All as IndexedKeyRange::Bound to loosen constraints on various functions
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-07 12:53:32 +02:00
Michael Goldenberg 35505f9130 refactor(indexeddb): remove room id argument from Indexed::to_indexed
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-07 12:53:32 +02:00
Michael Goldenberg a6d630216d refactor(indexeddb): add room id to event_cache_store::types::Gap
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-07 12:53:32 +02:00
Michael Goldenberg 159c9b4547 refactor(indexeddb): add room id to event_cache_store::types::GenericEvent
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-07 12:53:32 +02:00
Michael Goldenberg aead1a4489 refactor(indexeddb): add room id to event_cache_store::types::Chunk
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-07 12:53:32 +02:00
Michael Goldenberg 7fee1c7fd7 refactor(indexeddb): add traits for constructing prefix key bounds
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-07 12:53:32 +02:00
Michael Goldenberg ab9bfb2d61 refactor(indexeddb): add reusable const and static values to construct key component bounds
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-07 12:53:32 +02:00
Michael Goldenberg de5f00fd33 refactor(indexeddb): use references in IndexedEventRelationKey::KeyComponents
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-07 12:53:32 +02:00
Michael Goldenberg 33c16b2979 refactor(indexeddb): use references in IndexedEventIdKey::KeyComponents
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-07 12:53:32 +02:00
Michael Goldenberg e9dcdb7176 refactor(indexeddb): add lifetime to IndexedKey::KeyComponents
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-08-07 12:53:32 +02:00
Benjamin Bouvier 0a3fe939c5 refactor(event cache): don't panic when loading a linked chunk's latest event fails
This can happen because of an OS-level error, so let's try to handle it
in a slightly cleaner way. At the moment, it will bubble up and only be
logged, but we might try to find a better way to handle this at the
top-level.
2025-08-07 12:17:15 +02:00
Benjamin Bouvier 37e07ea331 refactor(test): use the matrix mock server in test_notification_client_with_context 2025-08-07 11:59:36 +02:00
Benjamin Bouvier e4e3ff63f5 refactor(notification client): extract the common filtering out of events in a common helper 2025-08-07 11:59:36 +02:00
Benjamin Bouvier 8409e52654 doc(base): tweak some doc comment around notifications 2025-08-07 11:59:36 +02:00
Benjamin Bouvier e8096ee518 refactor(notification client): simplify get_notification_with_sliding_sync 2025-08-07 11:59:36 +02:00
Jonas Platte 6814e70aa4 refactor: Simplify some methods of FailuresCache
Follow-up to #5490.
2025-08-07 10:00:53 +02:00
multisme efa4539a91 refactor(client): have upload_encrypted_file own the Client instance (#5470)
Signed-off-by: multi multiestunhappydev@gmail.com
2025-08-07 09:51:23 +02:00
Jonas Platte 42d2b93489 refactor: Introduce TestResult and use it in a couple random places 2025-08-06 22:21:39 +00:00
Kévin Commaille 872713c4bc Add setting to ignore the timeout sync setting on first sync (#5481)
The `timeout` setting on the `/sync` endpoint is the maximum allowed
time for the server to send its response, because this is a poll-based
API. It means that if there is no new data to show, the server will wait
until the end of `timeout` before returning a response.

It can be an undesirable behavior when starting a client and informing
the user that we are "catching up" while waiting for the first response.

By not setting a `timeout` on the first request to `/sync`, the
homeserver should reply immediately, whether the response is empty or
not.

---------

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
Signed-off-by: Ivan Enderlin <ivan@mnt.io>
Co-authored-by: Ivan Enderlin <ivan@mnt.io>
2025-08-06 16:48:44 +02:00
Kévin Commaille feb22d4370 Move serde functions to module and use #[serde(with = "")]
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-08-06 16:44:51 +02:00
Kévin Commaille 6520c9b16e Add changelog
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-08-06 16:44:51 +02:00
Kévin Commaille cd6fe271ba refactor(crypto): Make deprecated sender_key and device_id optional in RoomEncryptedEventContent and RoomKeyRequestContent
They were deprecated in Matrix 1.3 and are now optional.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-08-06 16:44:51 +02:00
Jonas Platte 5f447bbb17 chore: Fix new clippy lints 2025-08-06 16:38:08 +02:00
Jonas Platte 94e7ddd1ab chore: Upgrade matrix-sdk to edition 2024 and format 2025-08-06 16:38:08 +02:00
Jonas Platte 6ac4a8431d chore: Prepare matrix-sdk-common for edition 2024
Cherry-picked some changes from cargo fix --edition.
2025-08-06 16:38:08 +02:00
Benjamin Bouvier b585963abb test(notification client): check msc4306 behavior in the notification client too 2025-08-06 15:28:43 +02:00
Benjamin Bouvier 5719fde701 feat(client): add a global toggle for enabling thread subscriptions support 2025-08-06 15:28:43 +02:00
Benjamin Bouvier 2914d7a727 feat(threads): provide has_thread_subscription_fn to push condition context 2025-08-06 15:28:43 +02:00
Benjamin Bouvier 0cdec9d912 refactor(threads): flatten the ThreadStatus enum 2025-08-06 15:28:43 +02:00
Benjamin Bouvier d180d49c07 refactor(threads): do not store the unsubscribed state in the DB 2025-08-06 15:28:43 +02:00
Benjamin Bouvier bcee5badae refactor(threads): adapt to Ruma API changes related to async evaluation of push rules 2025-08-06 15:28:43 +02:00
Benjamin Bouvier ebb7059d55 refactor(threads): adapt to Ruma API changes for thread subscriptions 2025-08-06 15:28:43 +02:00
Benjamin Bouvier 8d3b1d3c7e chore: update Ruma 2025-08-06 15:28:43 +02:00
Kévin Commaille 056e90db25 feat(sdk): Use state_after in sync v2 (#5488)
It is supposed to be an improvement over `state`, since it allows the
server to send updates to the state that might not be reflected in the
timeline.

This is also the same behavior as in Simplified Sliding Sync.

This is MSC4222 that was accepted and is about to get merged in the
spec.

---------

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-08-06 13:42:49 +01:00
Kévin Commaille 787861eb35 fix: Upgrade Ruma
This brings 2 important bug fixes:

- Make deprecated fields of `m.room.encrypted` optional: it seems that there are events without these fields in the wild.
- Fix deserialization of `RedactedRoomJoinRulesEventContent`. This was found by a bug report in Fractal that caused the same error as #3557 when restoring the client. So maybe we could consider that this bug is fixed? It is still possible that there is another deserialization error. 

There is also a breaking change in the format of the `state` field in response to `GET /v3/sync`.
2025-08-05 16:04:34 +02:00
dependabot[bot] f081416baa chore(deps): bump crate-ci/typos from 1.34.0 to 1.35.0
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.34.0 to 1.35.0.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.34.0...v1.35.0)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-05 10:02:48 +02:00
Jorge Martín 8a6cc7bc22 refactor(sdk): Only log the power levels computing error for joined rooms 2025-08-05 09:54:26 +02:00
Jorge Martín 61258e823f test: Fix and add tests for computing the push conditions requiring the room creation event 2025-08-05 09:54:26 +02:00
Jorge Martín e86aab68b4 fix(sdk): Make sure we log the error when we can't compute the power levels of a room 2025-08-05 09:54:26 +02:00
Jorge Martín 48f1bc0780 fix(notification_client): Add m.room.create to the required sliding sync state
With this missing, power levels couldn't be computed, and some push rules didn't take effect.
2025-08-05 09:54:26 +02:00
Stefan Ceriu 1fe71acbcb change(room_list): request space rooms through sliding sync and expose a room list filter for them (#5479)
This is a breaking change as spaces are now requested through sliding sync and they need to manually be excluded from the room list by using the newly introduced non-space filter.
2025-08-04 16:39:02 +02:00
Kévin Commaille 0e054deb19 fix(sdk): Allow legacy push rules to be missing when changing NotificationSettings
They are being removed from the spec with MSC4210, so we can't error if they are not found.
2025-08-04 13:40:01 +00:00
Doug d2b7dc6116 ffi: Export TimelineDiff as uniffi:Enum to match RoomDirectorySearchEntryUpdate & RoomListEntriesUpdate.
The difference in API shape has been weird for long enough.
2025-08-04 12:01:24 +02:00
Ivan Enderlin 1089a25588 fix(xtask): Use --package instead of -p on cargo ndk.
We use `cargo ndk -p {package_name}`, where `-p` is short for
`--package`. However, `cargo ndk` has introduced the `-p` option (see
https://github.com/bbqsrc/cargo-ndk/commit/c6b93a89a2723ff0fac99b5ac86ae6636a6cf54a),
short for `--platform`. It creates a confusion and the command line
doesn't execute properly. Let's use the long option `--package` to
clarify everything.
2025-08-04 10:41:17 +02:00
Kévin Commaille 3276bc87ad refactor(base): Don't drop whole m.room.create if predecessor is invalid
The `m.room.create` event contains at lot of important information for a
room, like its type (i.e. whether it is a space), its version and its
creator(s) (which are important in room version 12). So ignoring the
event completely might break a room.

Instead what we do here is simply ignore the `predecessor` field if it
is considered invalid, allowing us to access the other fields.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-08-04 10:09:52 +02:00
Hubert Chathi a4da6ba7c8 Exclude insecure devices on Olm encryption (#5457)
Fixes the encrypting part of
https://github.com/matrix-org/matrix-rust-sdk/issues/4147

Probably easiest to review commit-by-commit

<!-- description of the changes in this PR -->

- [x] Public API changes documented in changelogs (optional)

<!-- Sign-off, if not part of the commits -->
<!-- See CONTRIBUTING.md if you don't know what this is -->
Signed-off-by:
2025-08-04 08:50:32 +01:00
Kévin Commaille 033c6bd6a4 Add changelog
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-08-01 14:07:28 +02:00
Kévin Commaille b02e1da471 Upgrade Ruma
This brings in a new breaking change from Ruma, because not all events
are stripped in a room's stripped state. For simplicity, this still
considers the events as stripped during deserialization for now, since
this format is compatible with the other possible formats.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-08-01 14:07:28 +02:00
Damir Jelić 5ad477ac96 tests: Rename some more benchmarks 2025-08-01 11:33:08 +02:00
Damir Jelić 975432565d tests: Enable timers for the runtime of the store bench 2025-08-01 11:33:08 +02:00
Damir Jelić 46b0113765 tests: Rename some crypto benchmarks
This is mostly due to codspeed not showing the group name, so we're
repeating this info in the benchmark ID.
2025-08-01 11:33:08 +02:00
Damir Jelić 09eff8c6bd ci: Lower the amount of events we benchmark against on the CI 2025-08-01 11:33:08 +02:00
Damir Jelić 7ee546a3d9 ci: Enable more benchmarks 2025-08-01 11:33:08 +02:00
Benjamin Bouvier b164cd6a51 refactor(timeline): remove a few unused Self in some read-receipt related methods 2025-07-31 14:14:27 +02:00
Benjamin Bouvier 6d95abfb36 refactor(send queue): move the AbstractProgress to the new progress module 2025-07-31 13:04:28 +02:00
Benjamin Bouvier 33f09d6d26 refactor(send queue): move upload progress functionality to its own file 2025-07-31 13:04:28 +02:00
Benjamin Bouvier 8c01e99144 refactor(send queue): misc improvements to media upload progress reporting
Notable changes: don't send a global update whenever a media upload
progress happened, since this doesn't really matter for global
listeners.
2025-07-31 13:04:28 +02:00
Benjamin Bouvier 277cb7ac49 refactor(send queue): rename variables around the thumbnail file size cache 2025-07-31 11:16:48 +02:00
Johannes Marbach fc7124fd1a feat(send_queue): communicate media upload progress in RoomSendQueueUpdate::MediaUpload
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-07-31 11:16:48 +02:00
Johannes Marbach 30c0420f83 chore(send_queue): rename RoomSendQueueUpdate::UploadedMedia to MediaUpload to prepare it for communicating upload progress
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-07-31 11:16:48 +02:00
Johannes Marbach cb13c345ad feat(sdk): introduce AbstractProgress for tracking media progress in pseudo units
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-07-31 11:16:48 +02:00
Johannes Marbach cd26973082 feat(send_queue): enable progress monitoring in RoomSendQueue::handle_request
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-07-31 11:16:48 +02:00
Johannes Marbach 0edcdd33b2 chore(send_queue): Make parent_is_thumbnail_upload available outside of unstable-msc4274 to use it during media progress reporting later
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-07-31 11:16:48 +02:00
Johannes Marbach c191eb7cd1 feat(send_queue): cache thumbnail sizes to use them in progress reporting later
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-07-31 11:16:48 +02:00
Johannes Marbach fa6f270812 chore(send_queue): collect thumbnail-related metadata in a dedicated QueueThumbnailInfo struct for easier extension
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-07-31 11:16:48 +02:00
Johannes Marbach d4e96595d9 feat(send_queue): add global setting for sending media progress updates
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-07-31 11:16:48 +02:00
Jakob Lachermeier 540a11e7a8 fix(sqlite): made open_with_pool public again. 2025-07-31 09:15:47 +02:00
Damir Jelić 92192c549b fix(timeline): Remove UTD items that have been decrypted into unsupported events 2025-07-30 12:46:04 +02:00
Damir Jelić 88360040fb test(timeline): Add some integration tests for the redecryption logic
These tests now fully mock all the end-to-end encryption server
endpoints to test the redecryption and UTD item replacement logic of the
timeline without any manual room key insertions.

We test that the item replacement correctly handles supported event
types as well as unsupported ones.
2025-07-30 12:46:04 +02:00
Damir Jelić 4184e245a4 ci: Attempt to free up even more space for the coverage job 2025-07-30 12:10:12 +02:00
Benjamin Bouvier f37bf2f5d1 feat(store): also delete thread subscriptions when deleting a room in db 2025-07-30 12:07:07 +02:00
Benjamin Bouvier d57d3c4124 feat(sdk): save the unsubscribed status in the store, and use it to return something more precise than unknown when fetching a subscription 2025-07-30 12:07:07 +02:00
Benjamin Bouvier 1a5cb2beb8 feat(stores): allow saving thread subscriptions 2025-07-30 12:07:07 +02:00
Benjamin Bouvier b645c1101f refactor(test): avoid proliferation of builder submethods in the MockClientBuilder
Instead of having one static method duplicating an underlying
`ClientBuilder` method, we can pass the builder directly to a closure,
that will replace it. Call sites are a bit more verbose, but that would
avoid having to add duplicate `MockClientBuilder` methods for each
`ClientBuilder` method.
2025-07-30 11:56:31 +02:00
Robin 8091094bbc refactor(room): Remove methods for sending call notifications
As noted in the changelog entry, the event type they send is outdated, and Client is not actually supposed to be able to join MatrixRTC sessions at this time. A MatrixRTC client implementation which actually participates in sessions should be able to send these notifications without the SDK's help.
2025-07-29 15:04:25 +02:00
Robin feadfde1b5 feat(element-call): Support the sendNotificationType URL parameter
This URL parameter allows us to request Element Call widgets to send a notification when starting a call.
2025-07-29 15:04:25 +02:00
Damir Jelić e2ad07881c Merge pull request #5443 from matrix-org/poljar/ci/benchmarks
Enable benchmarks on the CI
2025-07-28 13:07:00 +02:00
Jorge Martín 1be8b42d03 refactor(ffi): expose privileged_creators_role in the FFI RoomInfo 2025-07-28 12:05:48 +02:00
Jorge Martín 7a5f83f6ec refactor(ffi): expose room_version in the FFI RoomInfo 2025-07-28 12:05:48 +02:00
Jorge Martín 88bb7a366f feat(ffi): enable unstable-hydra feature for the SDK bindings 2025-07-28 11:42:21 +02:00
Benjamin Bouvier 7d9bf56581 feat(ffi): add support for MSC4036 thread subscriptions 2025-07-28 10:39:38 +02:00
Benjamin Bouvier 770f65ede0 feat(multiverse): add support for subscribing/unsubscribing/showing the current sub status 2025-07-28 10:39:38 +02:00
Benjamin Bouvier 1f33e0f4d1 test(threads): add test for the thread subscription endpoints 2025-07-28 10:39:38 +02:00
Benjamin Bouvier 117f76102d test(threads): add mocks for the thread subscription endpoint 2025-07-28 10:39:38 +02:00
Benjamin Bouvier fd04ebfaba feat(sdk): add support for threads subscription CRUD (msc4306)
This doesn't store the subscriptions locally, nor does it implement the
automatic thread subscription.
2025-07-28 10:39:38 +02:00
Benjamin Bouvier afe9f7a979 chore: upgrade ruma for msc4306 2025-07-28 10:39:38 +02:00
Benjamin Bouvier 27a002c8e2 chore: add changelog entry for the breaking change in RelationalLinkedChunk::items 2025-07-28 10:34:56 +02:00
Benjamin Bouvier b8ab0972b3 fix(event cache store): return events from all linked chunks in a room, in find_event and find_event_with_relations of the memory store 2025-07-28 10:34:56 +02:00
Benjamin Bouvier d3419ea4ac test(event cache store): add tests for a thread's vs a room's linked chunk id 2025-07-28 10:34:56 +02:00
Benjamin Bouvier 019adb9a56 feat(common): implement the thread variants for LinkedChunkId and OwnedLinkedChunkId 2025-07-28 10:34:56 +02:00
Kévin Commaille ca89700dfe refactor(ffi): Fix clippy lint
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-25 17:55:31 +02:00
Kévin Commaille a0c87cfe4f Add changelog
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-25 17:36:19 +02:00
Kévin Commaille 9ddc892aa0 fix(sdk): Support event types with variable suffix for event handlers
Previously they would just not work as we were trying to match a full
event type with a prefix.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-25 17:36:19 +02:00
Kévin Commaille 0a7ac18d9f refactor: Add IsPrefix = False bound on StaticEventContent bounds
Since those APIs only support a full event type, not an event type prefix.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-25 17:36:19 +02:00
Robin d8b6966c0a Allow Element Call to send call notifications
For clients which integrate Element Call: Currently matrix-rust-sdk is responsible for sending the call notification event when joining MatrixRTC sessions, but this is planned to be changed soon. As of the upcoming Element Call 0.14.0 release, it will request the capability to send call notifications itself, and we should auto-approve this capability.
2025-07-25 09:20:39 +03:00
Damir Jelić d40f04e32c chore: Remove the criterion function in the benchmarks
It's now equivalent to Criterion::default().
2025-07-24 17:11:03 +02:00
Damir Jelić d8294a0788 Fix the compilation of the crypto bench 2025-07-24 17:09:59 +02:00
Damir Jelić 06a4476e7f ci: Disable a benchmark that panics on the CI 2025-07-24 16:48:28 +02:00
Andy Balaam def1fedea3 feat(crypto): Refuse to decrypt to-device messages from unverified devices (when in exclude insecure mode) 2025-07-24 15:08:13 +01:00
Andy Balaam d061e7a5b2 refactor(tests): Pass decryption_settings in to send_and_receive_encrypted_to_device_test_helper
To allow passing in different values in future tests.
2025-07-24 15:08:13 +01:00
Andy Balaam f4619c91d3 refactor(tests): Re-use send_and_receive_encrypted_to_device_test_helper in 2 more tests 2025-07-24 15:08:13 +01:00
Andy Balaam 227f6eab85 refactor(tests): Take a reference to content in send_and_receive_encrypted_to_device_test_helper
This will allow us to re-use it in more tests.
2025-07-24 15:08:13 +01:00
Andy Balaam 16d7c3c094 refactor(crypto): Extract a method to check for to-device events from dehydrated devices 2025-07-24 15:08:13 +01:00
Andy Balaam c238a0edb8 refactor(crypto): Pass DecryptionSettings in to OlmMachine::decrypt_to_device_event
This will be used in the next commit, but it was very noisy, so I
separated it out into this commit to make the next one easier to read.
2025-07-24 15:08:13 +01:00
Damir Jelić 06bf487512 chore: Attempt to get rid of a crash on CI where the runtime isn't used for a drop 2025-07-24 15:40:41 +02:00
Damir Jelić c636ec63f4 chore: Inherit the release profile for the bench profile 2025-07-24 15:07:48 +02:00
Damir Jelić ffe239d620 Actually respect the benchmarks matrix on CI 2025-07-24 14:40:14 +02:00
dragonfly1033 822b709107 feat(sdk): Leaving a room now leaves its predecessors as well
Signed-off-by: Shrey Patel shreyp@element.io
2025-07-24 13:56:05 +02:00
Damir Jelić d75d7973b2 ci: Enable benchmarks on the CI 2025-07-24 13:37:45 +02:00
Damir Jelić cfe3adce48 chore: Bump criterion 2025-07-24 13:37:45 +02:00
Damir Jelić b478ae65f7 tests: Remove pprof 2025-07-24 13:37:45 +02:00
Michael Goldenberg ada68e1114 feat(indexeddb): add IndexedDB-backed impl for EventCacheStore::find_event_relations
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-23 18:03:06 +02:00
Michael Goldenberg c9137f0cad fix(indexeddb): Updates::PushItems performs an update if any provided item already exists
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-23 18:03:06 +02:00
Michael Goldenberg 4e0dab959a feat(indexeddb): add IndexedDB-backed impl for EventCacheStore::save_event
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-23 18:03:06 +02:00
Michael Goldenberg e862ded147 feat(indexeddb): add IndexedDB-backed impl for EventCacheStore::find_event
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-23 18:03:06 +02:00
Michael Goldenberg 5cb033ad91 feat(indexeddb): add IndexedDB-backed impl for EventCacheStore::filter_duplicated_events
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-23 18:03:06 +02:00
Kévin Commaille 0e622cc5a1 Upgrade Ruma (phase 3)
This upgrade introduces support for room version 12[1].

[1]: https://matrix.org/blog/2025/07/security-predisclosure/)

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-23 14:32:05 +00:00
Jorge Martín 6d562eff2f fix: Restore the previous behaviour for calculating the timeout for syncs 2025-07-23 15:55:31 +02:00
Jorge Martín af2e15e02f refactor(ffi): Add the default read_timeout value to the FFI layer 2025-07-23 15:55:31 +02:00
Jorge Martín 79aa5aaf16 refactor: Remove the default timeout when downloading media, rely on read_timeout instead
Users with poor network connectivity complained their downloads were cancelled for no good reason after 30s before (the default `timeout` value).
2025-07-23 15:55:31 +02:00
Jorge Martín 0833ffdef2 refactor: Add RequestConfig::read_timeout to cancel network connections that have been stale for longer than the specified timeout 2025-07-23 15:55:31 +02:00
Jorge Martín 16f7239215 refactor: Make RequestConfig::timeout optional
This allows us to remove the default timeout on a per-request basis
2025-07-23 15:55:31 +02:00
Doug da946e51dd ffi: Update Client::get_url to return raw data and to throw on HTTP errors
Client::get_url is there for SDK consumers to be able to use the
existing HTTP stack (configuration and all) however in practice it was a
bit weird:
- Responses came back as strings limiting the types of resource that
could be fetched (as well as requiring the string to be converted back
to data before handed to a JSON decoder).
- HTTP errors weren't being raised and instead you would find the (e.g.
404) error response in the Ok case.

This patch fixes both of these issues.
2025-07-23 15:24:49 +02:00
Benjamin Bouvier cba711dbdf feat(timeline): add support for sending sticker/polls in thread automatically too 2025-07-23 15:05:23 +02:00
Benjamin Bouvier e87e9331c1 feat!(timeline): infer the reply type automatically when sending a media gallery 2025-07-23 15:05:23 +02:00
Benjamin Bouvier 6e9fc70d13 refactor(timeline): homogeneize naming of replied_to vs in_reply_to 2025-07-23 15:05:23 +02:00
Benjamin Bouvier e2148e46bc feat!(timeline): infer the reply type automatically when sending an attachment 2025-07-23 15:05:23 +02:00
Benjamin Bouvier d1163b75bf feat!(timeline): Timeline::send_reply() automatically includes the thread relationship too 2025-07-23 15:05:23 +02:00
Benjamin Bouvier 5ae7d0f60f feat(timeline): Timeline::send() automatically includes the thread relationship for thread foci 2025-07-23 15:05:23 +02:00
Benjamin Bouvier 6f067d5510 doc(event cache): add a code comment indicating why room updates processing isn't happening concurrently 2025-07-23 11:21:09 +02:00
Benjamin Bouvier d6fe654814 bench: add a benchmark for measuring the time it takes to handle a sync update in the event cache 2025-07-23 11:21:09 +02:00
Benjamin Bouvier 2710510786 refactor(base): use let chains where the comment said to do so \o/ 2025-07-23 09:57:50 +02:00
Benjamin Bouvier 35a8528712 chore(base): clippy fixes 2025-07-23 09:57:50 +02:00
Benjamin Bouvier f9735c75d3 chore(base): rustfmt 2024 edition 2025-07-23 09:57:50 +02:00
Benjamin Bouvier 15e6b81835 chore(base): upgrade matrix-rust-sdk-base to edition 2024 2025-07-23 09:57:50 +02:00
Damir Jelić bcb4ab4b10 contrib: Set our check command to check instead of clippy
Clippy is a bit too heavy to be used as the check on save command in
this repo.

Let's set a better default.
2025-07-22 15:20:37 +02:00
Kévin Commaille 4931c0749e Upgrade Ruma again
This patch updates our `Raw` API usage since the newly added `JsonCastable` that disallows Raw casts that are known to fail deserialization. 

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-22 12:59:26 +00:00
Kévin Commaille 37626b5ad9 Bump Ruma
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-22 14:00:53 +02:00
Benjamin Bouvier d19616da03 chore!: bump the MSRV to 1.88
let-chains ftw
2025-07-22 12:15:33 +02:00
Ivan Enderlin 7c8f870d16 Revert "fix(sdk): Disable OrderTracker for the moment."
This reverts commit c5f2460e02.
2025-07-22 10:24:27 +02:00
Benjamin Bouvier b482ccd318 feat(sqlite): make sqlite's implementation of load_all_chunks_metadata even faster
See the updated code comment.
2025-07-21 17:41:06 +02:00
Ivan Enderlin 165ec9db1b bench: Add a benchmark for EventCacheStore::load_all_chunks_metadata. 2025-07-21 10:31:44 +02:00
Ivan Enderlin 6f42210d6a feat(sqlite): Improve throughput of load_all_chunks_metadata by 1140%.
This patch changes the query used by
`SqliteEventCacheStore::load_all_chunks_metadata`. It was the cause of
severe slowness. The new query improves the throughput by +1140% and the
time by -91.916%. The benchmark will follow in the next patch.

Metrics for 10'000 events (with 1 gap every 80 events).

- Before:
  - throughput: 20.686 Kelem/s,
  - time: 483.43 ms.
- After:
  - throughput: 253.52 Kelem/s,
  - time: 39.478 ms.

This query will visit all chunks of a linked chunk with ID
`hashed_linked_chunk_id`. For each chunk, it collects its ID
(`ChunkIdentifier`), previous chunk, next chunk, and number of
events (`num_events`). If it's a gap, `num_events` is equal to 0,
otherwise it counts the number of events in `event_chunks` where
`event_chunks.chunk_id = linked_chunks.id`.

Why not using a `(LEFT) JOIN` + `COUNT`? Because for gaps, the entire
`event_chunks` will be traversed every time. It's extremely inefficient.
To speed that up, we could use an `INDEX` but it will consume more
storage space. Finally, traversing an `INDEX` boils down to traverse a
B-tree, which is O(log n), whilst this `CASE` approach is O(1). This
solution is nice trade-off and offers great performance.
2025-07-21 10:31:44 +02:00
Kévin Commaille 6125580275 feat: Add Account::fetch_account_data_static
It uses the statically-known type from the `StaticEventContent`
implementation to call `fetch_account_data()`.

This is the equivalent of `Account::account_data()` but for fetching
from the server. It avoids the need to cast to the proper type after.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-18 16:01:07 +02:00
Kévin Commaille 8b3e295429 refactor(test): Use EventBuilder::into_raw_timeline rather than into_event
We need the full event so its better to build it an cast it to a sync
event than the opposite.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-18 16:01:07 +02:00
Kévin Commaille 1e568efbb5 refactor: Remove unnecessary Raw casting
The types are already correct so there is no need for casting.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-18 16:01:07 +02:00
Andy Balaam f89ced3ded task(sliding_sync): Avoid logging the entire sliding sync response at the DEBUG level
Signed-off-by: Damir Jelić <poljar@termina.org.uk>
Co-authored-by: Damir Jelić <poljar@termina.org.uk>
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2025-07-18 13:18:04 +00:00
Damir Jelić a5fbcf1491 Merge pull request #5322 from matrix-org/poljar/shared-history/out-of-order
feat(sdk): Add a tasks that listens for historic room keys if they arrive out of order
2025-07-17 16:52:13 +02:00
Yorusaka Miyabi 8923e58ee3 feat(ffi): Expose legacy SSO support infomation
Currently Element X can't distinguish the cases where a homeserver only
supports legacy SSO without OIDC (and password login isn't avaliable),
and other server unreachable scenarios.

This patch exposes legacy SSO support infomation so that Element X side
can give a dedicated error message when it encounters a homeserver that
can only support legacy SSO.

Signed-off-by: Yorusaka Miyabi <23130178+ShadowRZ@users.noreply.github.com>
2025-07-17 16:42:27 +02:00
Damir Jelić f14994baa9 test(sdk): Test if we accept historic room key bundles arriving out of order 2025-07-17 16:39:31 +02:00
Damir Jelić d42d0f3e17 test(sdk): Add a bunch more useful mock helpers 2025-07-17 16:39:17 +02:00
Damir Jelić e4849d5cab test(sdk): Don't expect a default access token in a bunch of methods
The mocks can be configured to expect a default access token separately,
this seems to have been a copy/paste error.
2025-07-17 16:39:17 +02:00
Damir Jelić 65aec7ee7f test(sdk): Add a method for two test clients to exchange E2EE identities 2025-07-17 16:39:17 +02:00
Damir Jelić a6868386d0 test(sdk): Allow the test client to enable shared history 2025-07-17 16:39:17 +02:00
Damir Jelić b3c1ca1577 Use the invite details when deciding if we should accept a bundle 2025-07-17 16:39:17 +02:00
Damir Jelić ce66ee4a16 test(sdk): Test the conditions under which we accept a historic room key bundle 2025-07-17 13:41:30 +02:00
Damir Jelić 2bbf6fc711 feat(sdk): Add a tasks that listens for historic room keys if they arrive out of order
Historic room key bundles are uploaded as an encrypted file to the media
repo and the key to decrypt the file is sent as a to-device message to
the recipient device.

In the nominal case, the invite and this to-device message should arrive
at the same time and accepting the invite would download and import the
bundle.

If the to-device message arrives after the invite has already been
accepted we would never download and import the bundle.

To mitigate this problem, this patch introduces a task that listens for
bundles that arrive. If the bundle is for a room that we have joined we
will consider importing the bundle.
2025-07-17 13:41:30 +02:00
Kévin Commaille 1a9e5b904b Move changelog entry to the top
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-17 12:42:05 +02:00
Kévin Commaille 4c43b06445 Add changelogs
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-17 12:42:05 +02:00
Kévin Commaille 49a0765880 refactor(base): Remove the event_id field of PredecessorRoom
It is going away in MSC4291, which should be accepted next week.
Removing it now makes sure that no one uses it.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-17 12:42:05 +02:00
Florian 39cf8b325d Allow requesting additional scopes for OAuth2 authorization code flow
For custom integrations it might be necessary to allow the SDK to
request additional scopes for the OAuth2 authorization code flow.
Currently, only the MSC2967 client API and client device scopes are
requested statically.


Signed-off-by: fl0lli <github@fl0lli.de>
2025-07-16 10:23:02 +02:00
Jonas Platte bb67150d6b chore: Upgrade matrix-sdk-store-encryption to Rust Edition 2024 2025-07-16 09:36:10 +02:00
Michael Goldenberg 471e3c340c refactor(indexeddb): add timers to all EventCacheStore functions for easy performance tracking
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-15 16:02:10 +02:00
Michael Goldenberg 74972d8db7 feat(indexeddb): add IndexedDB-backed impl for EventCacheStore::load_all_chunks_metadata
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-15 16:02:10 +02:00
Michael Goldenberg 03a76fbaf5 feat(indexeddb): add IndexedDB-backed impl for EventCacheStore::clear_all_linked_chunks
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-15 16:02:10 +02:00
Michael Goldenberg 392a1ef47b feat(indexeddb): add IndexedDB-backed impl for EventCacheStore::load_previous_chunk
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-15 16:02:10 +02:00
Ivan Enderlin c5f2460e02 fix(sdk): Disable OrderTracker for the moment.
This patch removes the use of `OrderTracker` because the
implementation of `EventCacheStore::load_all_chunks_metadata` for
`SqliteEventCacheStore` is the cause of severe slownesses (up to 100s
for some account).

We are going to undo this patch once the problem has been solved.
2025-07-15 12:17:28 +02:00
fl0lli 8ad785a117 docs(ffi): move entry for device_id change of url_for_oidc to top
Signed-off-by: fl0lli <github@fl0lli.de>
2025-07-15 10:14:17 +02:00
fl0lli a5b936d0b6 docs(ffi): add breaking change entry for Client::url_for_oidc
Signed-off-by: fl0lli <github@fl0lli.de>
2025-07-15 10:14:17 +02:00
fl0lli 1a0544c8eb chore(ffi): formatting
Signed-off-by: fl0lli <github@fl0lli.de>
2025-07-15 10:14:17 +02:00
fl0lli 232c23e8df feat(ffi): allow setting existing device id for Client.url_for_oidc
Signed-off-by: fl0lli <github@fl0lli.de>
2025-07-15 10:14:17 +02:00
Jonas Platte 7d9d5bf3b4 refactor: Use if-let chain 2025-07-15 08:41:44 +02:00
Jonas Platte ea076b3d76 chore: Upgrade testing crates to Rust Edition 2024 2025-07-15 08:41:44 +02:00
Jonas Platte 8aa6f97f7c chore: Upgrade benchmarks to Rust Edition 2024 2025-07-15 08:39:27 +02:00
Jonas Platte 679aa07115 chore(ui): Upgrade to Rust edition 2024 2025-07-14 19:50:36 +02:00
Jonas Platte 0c66d8a53f refactor(ui): Cherry-pick some edition-fix changes
Automated with `cargo fix --edition -p matrix-sdk-ui`, reverting
unnecessary changes.
2025-07-14 19:50:36 +02:00
Michael Goldenberg 25ed7eef2b doc(indexeddb): add explanation of error types in EventCacheStore::load_last_chunk
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-14 18:46:20 +02:00
Michael Goldenberg a399840dff refactor(indexeddb): separate transaction and event cache error conversions
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-14 18:46:20 +02:00
Michael Goldenberg 8d4e7f0478 test(indexeddb): add IndexedDB-specific integration tests for loading last chunk
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-14 18:46:20 +02:00
Michael Goldenberg 3bd93130c5 feat(indexeddb): add IndexedDB-backed impl for EventCacheStore::load_last_chunk
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-14 18:46:20 +02:00
Michael Goldenberg 153618b77c refactor(indexeddb): add helper fns for EventCacheStore::load_last_chunk
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-14 18:46:20 +02:00
Kévin Commaille dd871ef9ac refactor(base): Store RoomPowerLevels in RoomMember
Avoids to carry around the event content only to convert it when we
want to use it. Avoids also to carry around the room creator when we
might not need it.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-14 18:04:33 +02:00
Kévin Commaille 8a29f17d1d refactor(base): Call Room::power_levels instead of loading event from the store
To reduce code duplication.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-14 18:04:33 +02:00
Kévin Commaille fab520ab33 refactor(base): Add methods on StateChanges to get a member or power level event
To reduce duplication.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-14 18:04:33 +02:00
Ivan Enderlin 435553c3d1 feat(sdk): Add more logs in generate_sync_request. 2025-07-14 17:48:07 +02:00
Ivan Enderlin 610ecd218c feat(sdk): Log the time spent waiting on the position lock. 2025-07-14 17:48:07 +02:00
Kévin Commaille fa300d1f33 refactor(tests): Prefer EventBuilder::into_raw to into_raw_(sync/timeline) then Raw::cast
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-14 17:46:02 +02:00
Ivan Enderlin af2a483158 feat(sdk): Add debug! log when updating the sliding sync pos. 2025-07-14 16:52:36 +02:00
Ivan Enderlin 753b0d8584 doc(sdk): Update doc of SlidingSyncPositionMarkers::pos.
With `SlidingSync::share_pos`, the `pos` can be persisted. This comment
is outdated.
2025-07-14 16:40:06 +02:00
Ivan Enderlin 36713adbdb feat(sdk): Add more logs around the sync_lock and response_processor in SlidingSync. 2025-07-14 14:58:09 +02:00
Ivan Enderlin d73a02c608 feat(sqlite): Add more timer! logs in each EventCacheStore methods.
This patch adds `timer!` logs in each method from `EventCacheStore` for
`SqliteEventCacheStore`. It will help to know the execution duration of
each of these methods.
2025-07-14 10:34:17 +02:00
Ivan Enderlin f73199b472 feat(sqlite): Instrument SqliteEventCacheStore::open_with_config. 2025-07-14 10:34:17 +02:00
Ivan Enderlin 420d373144 feat(sqlite): Add #[instrument] around all SqliteEventCacheStore methods. 2025-07-14 10:34:17 +02:00
Ivan Enderlin a79e9130e6 feat(sqlite): Add timer! tracings in read and write's SqliteEventCacheStore. 2025-07-14 10:34:17 +02:00
Ivan Enderlin 355b5327f8 refator(common): Rename TracingTimer::new_debug.
This patch renames `TracingTiming::new_debug` to `new`. The
documentation claims it sets the log level to `debug` while the `level`
is actually an argument of the constructor. It's then wrong, and the
constructor must be renamed.
2025-07-14 10:34:17 +02:00
Ivan Enderlin fa77852001 feat(common): TracingTimer uses the Debug impl of Duration.
This changes the `TracingTimer` message to use the `Debug` impl of
`Duration` instead of displaying it as milliseconds. It can help spotting
seconds without counting all the digits.
2025-07-14 10:34:17 +02:00
Ivan Enderlin 7b73311de5 feat(sqlite): Add logs around read and write. 2025-07-14 10:34:17 +02:00
Ivan Enderlin f03934bc4f feat(sqlite): SqliteEventCacheStore has 1 write connection.
Until now, `SqliteEventCacheStore` manages a pool of connections. A
connection is fetched from this pool and operations are executed on it,
regardless whether these are read operations or write operations.

We are seeing more and more _database is busy_ errors. We believe this
is because too many write operations are executed concurrently.

The solution to solve this is to use multiple connections for read
operations, and a single connection for write operations. That way,
concurrent writings are no longer a thing, and we hope it will reduce
the number of _database is busy_ errors to zero. That's our guess.

This patch does that. When the pool of connections is created, a
connection is elected as the `write_connection`. To get a connection for
read operations, one has to use the new `SqliteEventCacheStore::read`
method (it replaces the `acquire` method). To get a connection for
write operations, one has to use the new `SQliteEventCacheStore::write`
method. It returns a `OwnedMutexGuard` from an async `Mutex`. All
callers that want to do write operations on this store have to wait
their turn, this `Mutex` is fair, and the first to wait on the lock is
the first that will take the lock (FIFO). It guarantees the execution
ordering the code expects.

The rest of the patch updates all spots where `acquire` was used and
replaces them by `read()` or `write()`. A particular care was made to
see if other places are using `SqliteEventCacheStore::pool` directly. No
place remains except in `read()` and `write()`.
2025-07-14 10:34:17 +02:00
Ivan Enderlin 014ee98fb7 feat(sqlite): SqliteStoreConfig::pool_size sets a minimum to 2.
This patch updates `SqliteStoreConfig::pool_size` to be at least 2. We
need 2 connections: one for write operations, one for read operations.
This behaviour is coming in the next patches.
2025-07-14 10:34:17 +02:00
Ivan Enderlin fbcf9fce7c feat(sdk): Add more logs in EventCache.
This patch adds more logs inside `EventCache` around the
`multiple_room_updates_lock` and around the `listen_task`, just to be
sure if everything is listened and works as expected.
2025-07-14 09:24:38 +02:00
Damir Jelić 6de403276a feat(base): Remember the inviter if we accept an invite 2025-07-12 10:57:48 +02:00
Richard van der Hoff 6209bc942c indexeddb: Remove incorrect line from changelog 2025-07-11 16:35:50 +02:00
Doug edd371b570 ffi: Refactor ClientBuilder::build_with_qr_code into Client::login_with_qr_code
The FFI's API now matches the SDK and allows for checks to be made on the Client before logging in.
2025-07-11 15:56:46 +02:00
dragonfly1033 817f32e15b test(sdk): added configurable login response builders for mock login endpoint. 2025-07-11 14:14:24 +02:00
dragonfly1033 30eb12ed2d test(sdk): change test_login_username_refresh_token to use MatrixMockServer 2025-07-11 14:14:24 +02:00
Damir Jelić 900697bc3b chore: Add a missing changelog entry for PR #5250 2025-07-10 17:23:21 +02:00
Damir Jelić 18b169ca7e chore: Release matrix-sdk version 0.13.0 2025-07-10 15:15:04 +02:00
Damir Jelić b9ce4059fb refactor(sqlite): Move the transaction in find_event_relations into a function 2025-07-10 14:23:10 +02:00
Damir Jelić e8dcb5d250 chore: Add a changelog entry for the fix for GHSA-275g-g844-73jh
Co-authored-by: Denis Kasak <dkasak@termina.org.uk>
Signed-off-by: Damir Jelić <poljar@termina.org.uk>
2025-07-10 13:55:02 +02:00
Damir Jelić d0c01006e4 fix(sqlite): Fix a SQL injection issue in the find_event_relations function
The SQLite implementation for the
EventCache::find_event_with_relations() the relation type list isn't
inserted using SQL placeholders.

The relation types are inserted manually using a format!() call. The
usage of the format!() call can lead to SQL injection if a
RelationType::Custom variant is used which contains SQL expressions.

This patch modifies the, query logic which retrieves the related events,
to use two separate queries which use SQL placeholders to insert all
the dynamic variables.

Security-Impact: Moderate
CVE: CVE-2025-53549
GitHub-Advisory: GHSA-275g-g844-73jh
2025-07-10 13:55:02 +02:00
Damir Jelić dc98bf7633 test: Add a test for GHSA-275g-g844-73jh 2025-07-10 13:55:02 +02:00
Doug ae57156252 ffi: Add reload_tracing_file_writer() to reconfigure the RollingFileAppender. 2025-07-10 13:21:42 +02:00
Doug 3b09c60e20 ffi: Wrap the tracing text layers inside a reload layer.
The generics have been removed from text_layers() to make this possible.

---------

Co-authored-by: Damir Jelić <poljar@termina.org.uk>
2025-07-10 13:21:42 +02:00
Ivan Enderlin fcdb63dcbe feat(ui): Add RoomListService::new_with_share_pos.
This patch adds the new `RoomListService::new_with_share_pos`
constructor. It decides whether the `share_pos` feature of sliding sync
should be enabled or not.

`SyncServiceBuilder` gains a new `with_share_pos` method to configure
the way the `RoomListService` is built.

The FFI bindings are updated accordingly.
2025-07-10 11:52:31 +02:00
Nico Steinle a095872083 fix(examples): Remove a duplicate comment from the examples
Signed-off-by: Nico Steinle <Nico-Steinle@t-online.de>
2025-07-09 18:42:22 +02:00
Denis Kasak d68895f24a Clarify project structure and status of internal crates (#5377)
Signed-off-by: Denis Kasak <dkasak@termina.org.uk>
Co-authored-by: Damir Jelić <poljar@termina.org.uk>
2025-07-09 14:47:38 +00:00
Michael Goldenberg ab81388018 test(indexeddb): add event cache store integration tests
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-09 15:35:31 +02:00
Michael Goldenberg c5436ed73e test(indexeddb): add IndexedDB-specific integration tests
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-09 15:35:31 +02:00
Michael Goldenberg 2e7721b36c feat(indexeddb): add IndexedDB-backed impl for EventCacheStore::load_all_chunks
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-09 15:35:31 +02:00
Michael Goldenberg a514019d7c refactor(indexeddb): add helper fns for EventCacheStore::load_all_chunks
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-09 15:35:31 +02:00
Michael Goldenberg 2f8f39795f feat(indexeddb): add IndexedDB-backed impl for EventCacheStore::handle_linked_chunk_updates
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-09 15:35:31 +02:00
Michael Goldenberg b1ef15c346 refactor(indexeddb): add helper fns for EventCacheStore::handle_linked_chunk_updates
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-09 15:35:31 +02:00
Michael Goldenberg 829b6a7624 feat(indexeddb): add indexeddb event cache store impl (backed by memory store)
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-09 15:35:31 +02:00
Michael Goldenberg 7b2cd8e434 feat(indexeddb): add debug impl to serializers and event cache store
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-09 15:35:31 +02:00
Michael Goldenberg d567a45bee feat(indexeddb): add error type for communicating errors outside of module
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-09 15:35:31 +02:00
Michael Goldenberg 9c08cd8973 feat(indexeddb): add type for building IndexeddbEventCacheStore
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-09 15:35:31 +02:00
Michael Goldenberg e0ceef33f8 feat(indexeddb): add top-level type for implementing EventCacheStore
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-09 15:35:31 +02:00
Ivan Enderlin 72d133260c test(sdk): Test SendQueue::subscribe.
This patch updates the `assert_update` macro in the send queue test
suites to test that `SendQueue::subscribe` provides the same data that
`RoomSendQueue::subscribe`. It ensures `SendQueueUpdate` won't miss
an update.
2025-07-09 14:03:03 +02:00
Ivan Enderlin 91e0c76a2f feat(sdk): Introduce SendQueue::subscribe.
To get updates from the `SendQueue`, one needs to use
`RoomSendQueue::subscribe`, it emits `RoomSendQueueUpdate`s. However, if
one wants to receive updates for all rooms managed by the `SendQueue`,
instead of subscribing to all `RoomSendQueue` individually, this
patch introduces a new `SendQueue::subscribe` method, which emits
`SendQueueUpdate`s. It's basically a wrapper around
`RoomSendQueueUpdate` by adding an `OwnedRoomId`.
2025-07-09 14:03:03 +02:00
Ivan Enderlin 7fac1d246d doc(sdk): Update the CHANGELOG.md. 2025-07-09 12:43:09 +02:00
Ivan Enderlin 6762e70880 chore(sdk): Rename variants of RoomEventCacheGenericUpdate.
This patch renames variants of `RoomEventCacheGenericUpdate`:

- `TimelineUpdated` becomes `UpdateTimeline`,
- `Cleared` becomes `Clear`.

It matches the rest of the codebase where we use _verb_ + _subject_.
2025-07-09 12:43:09 +02:00
Ivan Enderlin 6b93f6698b feat(sdk): Add RoomEventCacheGenericUpdate::Cleared.
This patch adds the `RoomEventCacheGenericUpdate::Cleared` variant. It's
helpful to know when a room has been cleared.
2025-07-09 12:43:09 +02:00
Benjamin Bouvier c92a89d571 chore(sqlite): reorder methods and add doc comment for encode_key
This reorders methods so that they're grouped in "dual" pairs
(encode/decode, serialize/deserialize). Also adds a doc comment to
`encode_key`, as I've wondered in the past what it did.
2025-07-09 12:37:34 +02:00
Benjamin Bouvier 684f228e70 refactor(sqlite): share implementations of the encode/decode/serialize/deserialize sqlite store helpers 2025-07-09 12:37:34 +02:00
Ivan Enderlin 0f9faad48a doc(ffi,ui): Update the CHANGELOG.mds. 2025-07-09 12:31:22 +02:00
Ivan Enderlin d7550ec645 feat(ui): RoomListService::subscribe_to_rooms calls LatestEvents::listen_to_room.
This patch updates `RoomListService::subscribe_to_rooms` to call
`LatestEvents::listen_to_room` automatically. This method becomes async,
which propagates to a couple of callers.

The idea is that when one is interested by a specific room, a
subscription will be applied. This is an opportunity to also “activate”
the computation of the `LatestEvent` for this specific room, so that the
user doesn't have to do that manually (except if room subscription is
never used).
2025-07-09 12:31:22 +02:00
Ivan Enderlin ea5645869e feat(sdk): Add the request_duration log field in HttpClient::send_request.
This patch adds a new log field, named `request_duration` in
`HttpClient::send_request`. It helps to know how much time the request
took to be executed.
2025-07-09 12:19:34 +02:00
Ivan Enderlin 8eed17bbfd doc(sdk): Fix a typo in SlidingSyncInner::network_timeout. 2025-07-09 11:27:53 +02:00
Benjamin Bouvier 40c08335ee feat(multiverse): add support for read receipts 2025-07-09 11:05:09 +02:00
Benjamin Bouvier ae5ec0fa26 test(timeline): add a test for the initial filling of threaded read receipts 2025-07-09 11:05:09 +02:00
Benjamin Bouvier 880f754f32 test(timeline): add test for sending threaded read receipts 2025-07-09 11:05:09 +02:00
Benjamin Bouvier 4d23b6490d test(timeline): add test for read receipts updates received from sync 2025-07-09 11:05:09 +02:00
Benjamin Bouvier e0e531c737 test(timeline): add test for latest_user_read_receipt for a threaded-timeline 2025-07-09 11:05:09 +02:00
Benjamin Bouvier 82926d6f08 test(timeline): allow setting the timeline focus in TestTimeline 2025-07-09 11:05:09 +02:00
Benjamin Bouvier f45c9aa3a7 feat(timeline): support threaded read receipts 2025-07-09 11:05:09 +02:00
Benjamin Bouvier 7966dd0544 fix(timeline): when a threaded timeline has no read receipts, don't unset the unread flag 2025-07-09 11:05:09 +02:00
Kévin Commaille 54e6a7d8d1 Upgrade Ruma
This is a bugfix for the `compat-lax-room-create-deser` and
`compat-lax-room-topic-deser` features, which didn't work with events
in the wild.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-09 08:43:12 +02:00
Jonas Platte 995ec618df chore: Upgrade xtask and uniffi-bindgen to Rust edition 2024 2025-07-09 08:41:41 +02:00
Jonas Platte f9864b7ef4 refactor(ui): Use named return types over RPITs
… in private methods.
2025-07-09 08:38:38 +02:00
Jonas Platte d23eae262e refactor(ui): Inline a variable 2025-07-09 08:38:38 +02:00
Tobias Fella 52090bb199 docs(crypto): Remove wrong statement about encryption keys for OlmMachine::with_store
When using this function, whether encryption keys are dropped depends on the crypto store implementation used.
2025-07-09 08:32:31 +02:00
Valere 40e3cd3c22 changelog: add changelog for sending e2ee to-device with widgets 2025-07-08 15:40:05 +02:00
Ivan Enderlin a58a74eaa7 feat(sdk): LatestEventValue::None overwrites previous value.
This patch removes an optimisation that was considering
`LatestEventValue::None` as a value that should no overwrite the
previous value if any. This is a mistake: if the event cache is cleared,
it won't be possible to calculate a new value, and the previous value
must be overwritten.
2025-07-08 14:47:43 +02:00
Ivan Enderlin 1c549a3ca1 test(sdk): Test LatestEventValue is being updated. 2025-07-08 14:47:43 +02:00
Ivan Enderlin a7bef8870f feat(sdk): LatestEventValue implements Default. 2025-07-08 14:47:43 +02:00
Ivan Enderlin 5e946108fe feat(sdk): LatestEventValue represents all kind of suitable latest events.
This patch adds new variants to `LatestEventValue` that represent
all the possible suitable latest events. It's heavily inspired by
`matrix_sdk_base::latest_event::PossibleLatestEvent`. It uses the
new `find_and_map` method to find a suitable `TimelineEvent` and to
map it into a `LatestEventValue`. This method is heavily inspired by
`matrix_sdk_base::latest_value::is_suitable_for_latest_event`.

To be able to provide the `power_levels` to `find_and_map`, a
`WeakClient` is required by `LatestEvents::new`. It flows up to a
`WeakRoom` in `RoomLatestEvents`, used to create or to update the
`LatestEventValue`.

A particular care is applied to re-compute the `power_levels` only when
necessary and once **per room** (and not per latest event value).
Fetching the `power_levels` requires an access to the storage, it's
not anodyne.
2025-07-08 14:47:43 +02:00
Ivan Enderlin 34ccd26ee6 refactor(sdk): Rename RoomEventCache::rfind_event_in_memory_by to rfind_map_event_in_memory_by.
This patch changes `RoomEventCache::rfind_event_in_memory_by` to `rfind_map…`.
2025-07-08 14:47:43 +02:00
Ivan Enderlin 3eeb046e62 feat(skd): compute_latest_events calls the new LatestEvent::update method. 2025-07-08 14:47:43 +02:00
Ivan Enderlin 6f84a44a1c feat(sdk): Create LatestEventsError. 2025-07-08 14:47:43 +02:00
Ivan Enderlin cf16978b15 feat(sdk): RegisteredRooms gets a clone of EventCache. 2025-07-08 14:47:43 +02:00
Ivan Enderlin 6a30a802bb chore(sdk): Use std::fmt to simplify the code. 2025-07-08 14:47:43 +02:00
Benjamin Bouvier db477a84bf chore(tests): make new threads discovery deterministic 2025-07-08 10:20:30 +02:00
Benjamin Bouvier 96fbbd3cd8 fix(timeline): when reloading a fresh timeline, also reload the related events 2025-07-08 10:20:30 +02:00
Benjamin Bouvier bbbcec5963 chore(tests): make the tests independent to races with respect to initial values 2025-07-08 10:20:30 +02:00
Benjamin Bouvier df98b71836 fix(event cache): don't remove duplicated paginated events, if there's no new events 2025-07-08 10:20:30 +02:00
Benjamin Bouvier 0d901e4a86 fix(timeline): correctly update a thread root after a gappy sync 2025-07-08 10:20:30 +02:00
Benjamin Bouvier 9d7afaaa1c feat(timeline): clear a thread if a user has been ignored/unignored 2025-07-08 10:20:30 +02:00
Benjamin Bouvier 2fc616645f chore(tests): split the event cache integration test module into smaller files 2025-07-08 10:20:30 +02:00
Benjamin Bouvier b8f6ab066d test(timeline): add a test for local echo filtering in a threaded timeline 2025-07-08 10:20:30 +02:00
Benjamin Bouvier bb7e6cb562 fix(timeline): use the timeline handle to send a new reaction
This is more precise than using the event timeline item kind: indeed,
when the event for which we want to add a reaction is a local echo that
has been sent, it is still marked as a local echo, but the send queue
will not know about it, and thus will not be able to add the reaction
immediately, leading to a silent failure.

The `TimelineItemHandle` was made to help with this kind of situation:
in this case, it will return `TimelineItemHandle::Remote`, even though
the item is a local echo that has been sent; that way we can use the
event id, and correctly send a (remote) reaciton event immediately.

The following commit includes a regression test.
2025-07-08 10:20:30 +02:00
Benjamin Bouvier 3e81514d07 test(timeline): add a test that a related event comes to a threaded timeline 2025-07-08 10:20:30 +02:00
Benjamin Bouvier bfcf47743e chore(tests): add comments explaining what the timeline thread tests do 2025-07-08 10:20:30 +02:00
Kévin Commaille 69448cca61 Add changelog
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-08 09:25:26 +02:00
Kévin Commaille 5daf2922b7 refactor(sdk): Use SupportedVersions to cache the response of the /versions endpoint
Using this type will be mandatory in the next breaking release of Ruma,
that will gain support for recognizing unstable features. Besides, it
allows to cache the supported versions and features in a single
CachedValue, which makes more sense than separately because we always
get both at the same time in production.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-08 09:25:26 +02:00
Benjamin Bouvier c81a56c22b fix(doc): add a missing parameter to BaseClient::new() in a doctest 2025-07-07 16:18:45 +02:00
Benjamin Bouvier f891bd13cb chore(base): add changelog entry for the new parameter in BaseClient::new() 2025-07-07 16:18:45 +02:00
Benjamin Bouvier a50a570fc1 doc(ffi): update an oudated method doc comment 2025-07-07 16:18:45 +02:00
Damir Jelić ec112ca32d test(base): Test that we set the joined at timestamp 2025-07-07 12:36:23 +02:00
Damir Jelić 15fdf1e86e feat(base): Remember when a user explicitly accepted an invite 2025-07-07 12:36:23 +02:00
Damir Jelić 3d6d798ca3 docs(base): Document the room_joined() method a bit better 2025-07-07 12:36:23 +02:00
Benjamin Bouvier 935ffa5aea chore(multiverse): paginate fewer events, to better test the behavior of pagination 2025-07-07 11:40:18 +02:00
Benjamin Bouvier 1c19e7477c fix(event cache): when some thread events came from sync, don't consider we've reached the start if we haven't seen the root yet 2025-07-07 11:40:18 +02:00
Benjamin Bouvier 6a1576a085 refactor(timeline): make use of the threaded event cache in the timeline 2025-07-07 11:40:18 +02:00
Benjamin Bouvier a1f028c54a feat(event cache): add a linked chunk per thread and add some useful primitives 2025-07-07 11:40:18 +02:00
Kévin Commaille f2576e80ec Upgrade Ruma to latest release
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-07 11:08:44 +02:00
Ivan Enderlin e8f705d76f doc: Fix markup in README.md
This patch fixes the markup in the `README.md` file. Links were broken because of the mix of HTML and Markdown. Plus, the HTML markup was kind of incorrect. Plus, too much `<br />`, most of them were “collapsed” automatically.

Signed-off-by: Ivan Enderlin <ivan@mnt.io>
2025-07-07 10:17:14 +02:00
Kévin Commaille 049993d37e test(sdk): Port sliding-sync tests to MatrixMockServer and MockClientBuilder
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-07 09:42:47 +02:00
Kévin Commaille 14366e85b1 test(sdk): Port sliding-sync tests to MatrixMockServer and MockClientBuilder
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-07 09:42:47 +02:00
Kévin Commaille 849d705cd1 refactor(sdk): Port client tests to MatrixMockServer and MockClientBuilder
Because it's such a nicer API!

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-07 09:42:47 +02:00
Kévin Commaille d4adc81fe0 test(sdk): Allow to override the server versions and request config of MockClientBuilder
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-07 09:42:47 +02:00
Kévin Commaille 577a8feb12 refactor(sdk): Expose directly the URI of MatrixMockServer
Since there are several places that use it.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-07 09:42:47 +02:00
Kévin Commaille 7fb3d216f6 refactor(sdk): Take an Option<&str> in MockClientBuilder::new()
Usually tests that don't construct it via MockMatrixServer don't care
about the homeserver URL, so this allows to provide a default URL for
them.

Also we don't force to allocate a string when the inner API actually
uses a borrowed string.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-07 09:42:47 +02:00
Jonas Platte 07808b4301 refactor: Enable extra code style clippy lints 2025-07-05 21:20:34 +02:00
Kévin Commaille f9e7d16347 Upgrade Ruma
It seems that some `m.room.topic` events in the wild have the wrong
format for the `m.topic` field. Since this field was stabilized recently
in Ruma, deserializing these events now fails. We added a feature to
ignore this field if its deserialization fails.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-04 20:51:57 +02:00
Kévin Commaille c98b2a1b3f refactor(test): Change the bound on EventBuilder to StaticEventContent
The `EventContent` trait is gone in Ruma so this will ease the upgrade
to the next breaking release.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-04 12:13:22 +03:00
Stefan Ceriu b44a1e46c4 feat(base): ignore threaded messages when computing room read receipts and client wide threads feature flag
With the UI crate now sending threaded read receipts we need to start considering them when computing unread counts. As a first step before the participation model, threaded messages will be ignored when computing room unread counts.
2025-07-04 11:42:35 +03:00
Stefan Ceriu f17c3c5af4 feat(ui): send (un)threaded read receipts based on the current timeline instance's focus kind.
This patch moves away from always sending unthreaded read receipts to checking the timeline's focus and `hide_threaded_events` associated values to see whether `ReceiptThread::Main` or `ReceiptThread::Thread` should be used.
2025-07-04 11:42:35 +03:00
Kévin Commaille 5448192ea4 Upgrade Ruma
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-04 09:53:29 +02:00
Neil Johnson 74800e20b4 Update README to make Element sponsorship explicit 2025-07-03 18:06:18 +02:00
Benjamin Bouvier 8157193aef chore(ffi): adjust to uniffi-bindgen-go's expectations for types 2025-07-03 17:14:56 +02:00
Benjamin Bouvier 679c99aa76 refactor(notification): remove BatchNotificationFetchingResult abstraction
It's a hashmap!
2025-07-03 17:14:56 +02:00
Benjamin Bouvier 6fa76e4b12 refactor(notification): remove generic parameter in BatchNotificationFetchingResult 2025-07-03 17:14:56 +02:00
Benjamin Bouvier 8b33806496 refactor(notification): have NotificationClient::get_notifications also return statuses 2025-07-03 17:14:56 +02:00
Benjamin Bouvier 3d114aea50 refactor(notification): random tweaks here and there 2025-07-03 17:14:56 +02:00
Benjamin Bouvier 72dcf5ed46 refactor(ffi): return some error info when a single notification couldn't be obtained 2025-07-03 17:14:56 +02:00
Benjamin Bouvier 58748bec3a chore(ffi): tweak alias names in the FFI layer
It doesn't make sense to name a thing "Matrix" when it comes from the
core SDK crate.

Also adjust a now slightly-incorrect doc comment for `get_notification`.
2025-07-03 17:14:56 +02:00
Benjamin Bouvier f0b6225e40 feat(notifications): provide the NotificationStatus as the return type when getting notifications
This is more explicit for these API's users, which can decide to do
different things based on whether an event has been ignored, or filtered
out.
2025-07-03 17:14:56 +02:00
Benjamin Bouvier 17a58684f6 refactor(notification client): reduce indent again, and make use of Option::is_some_and 2025-07-03 17:14:56 +02:00
Benjamin Bouvier f8c468d6fa refactor(notification client): reduce indent in get_notifications_in_sliding_sync
No functional changes, only reducing indent.
2025-07-03 17:14:56 +02:00
Benjamin Bouvier 39d1ed9bc6 chore: exclude the room id / event id from the data to be sent to sentry
These are not included in Element's main privacy policy, and may
constitute PII (because the homeserver may include the name of some
user). We keep the information as separate log lines, so that
rageshakes/manual reports still include those.
2025-07-03 16:07:46 +02:00
Benjamin Bouvier 3d1d1c8f6d feat(state store): send deserialization errors to sentry \o/ 2025-07-03 16:07:46 +02:00
Benjamin Bouvier 5ad958722f feat(state store): include serde json error path when failing to deserialize in the state store 2025-07-03 16:07:46 +02:00
Benjamin Bouvier bedcbfd7ff fix(event cache): don't return an error when a linked chunk is empty
The metadata loading shouldn't cause an error to be displayed, when the
linked chunk is empty; this can happen for new rooms we've never
visited.

Spotted while investigating some failures in a rageshake.
2025-07-03 14:34:27 +02:00
Benjamin Bouvier 743dec9a65 chore(docs): fix a doc comment referring to the wrong type 2025-07-02 15:47:59 +02:00
Ivan Enderlin f1a2093cfc fix(ui): Avoid calling TimelineEvent::raw() twice.
This patch replaces two successive calls to `TimelineEvent::raw()` by a
single one.
2025-07-02 15:45:29 +02:00
Ivan Enderlin 8057991aee fix(sdk): Don't call event.raw() twice.
This patch replaces a second call to `event.raw()` by re-using the
`raw_event` variable.
2025-07-02 15:45:29 +02:00
Ivan Enderlin d45ef567d4 test(sdk): Add a new test for RoomEventCache::rfind_event_in_memory_by. 2025-07-02 14:44:21 +02:00
Ivan Enderlin 42ee967b46 feat(sdk): Add RoomEventCache::rfind_event_in_memory_by.
This patch introduces a new method named
`RoomEventCache::rfind_event_in_memory_by` to look for an in-memory
event, starting from the most recent event, by applying a predicate
closure on each event.
2025-07-02 14:44:21 +02:00
Ivan Enderlin 4c7575bc9e refactor(sdk): Rename RoomEventCache::event_with_relations to find_event_with_relations.
This patch renames the `RoomEventCache::event_with_relations` method to
`find_event_with_relations` for the sake of clarity, also to match the
other method names, and to match the Rust standard library namings.
2025-07-02 14:44:21 +02:00
Ivan Enderlin 5e927f8109 refactor(sdk): Rename RoomEventCache::event to RoomEventCache::find_event.
This patch renames the `RoomEventCache::event` method to `find_event` to
clarify what it does, and to match the Rust standard library namings.
2025-07-02 14:44:21 +02:00
Ivan Enderlin f91ee36245 doc(sdk): Fix a typo in the documentation. 2025-07-02 14:44:21 +02:00
Benjamin Bouvier 0f8fc53019 chore(ci): use the dev profile when building the swift bindings (#5328)
The swift bindings aren't getting tested (they don't run) in CI anymore,
so building with the reldbg profile (that's a workaround to make it run
and not crash in production) doesn't provide more value than building in
debug mode, while taking much longer to build.

Let's use the default dev profile for this; we have to specify it
manually, because the default for the xtask command is to use the
`reldbg` profile otherwise.

This requires a fix for the dev profile, that consists in being able to
set the iOS deployment target, and set it to a high value in CI
settings. Production builds *don't* have to set it, though.
2025-07-02 14:20:53 +02:00
Benjamin Bouvier be3af5e0d4 refactor(event cache): push down absence of gap handling in back-paginations
This will be useful to do for threaded paginations too.
2025-07-02 12:43:42 +02:00
Benjamin Bouvier 38bbdf0547 doc(event cache): tweak outdated comment 2025-07-02 12:43:42 +02:00
Benjamin Bouvier b188a157af refactor(event cache): inline most uses of EventLinkedChunk::push_events
The only remaining uses outside the `EventLinkedChunk` are in tests of
deduplication, so keep the method as a test-only method for now.
2025-07-02 12:43:42 +02:00
Benjamin Bouvier 9ccbac0c0e refactor(event cache): inline and remove from public API remove_empty_chunk_at 2025-07-02 12:43:42 +02:00
Benjamin Bouvier 137fc9cfbb refactor(event cache): remove EventLinkedChunk::push_gap from the public API
it's only used internally (and in tests), and it's not doing much, so we
can inline it as call sites instead.
2025-07-02 12:43:42 +02:00
Benjamin Bouvier 40a4c9a7e1 refactor(event cache): move pushing the live events into its own method in EventLinkedChunk 2025-07-02 12:43:42 +02:00
Benjamin Bouvier ad358955fd refactor(linked chunk): simplify remove_item_at 2025-07-02 12:43:42 +02:00
Benjamin Bouvier 0ee89c86ab refactor(event cache): remove methods that are plain private wrappers
These two methods could be only made private now, since they're only
used in `finish_back_pagination` (and in tests). But they only call
inside other methods of the underlying linked chunk without any extra
value, so they can be inlined instead. This reduces the public API and
removes tests for other APIs that were tested some place else.
2025-07-02 12:43:42 +02:00
Benjamin Bouvier 216f0df945 refactor(linked chunk): invert the position of insert_items_at parameters
Same logic as `replace_gap_at`.
2025-07-02 12:43:42 +02:00
Benjamin Bouvier 129e9e173e refactor(event cache): move finishing a network paginations in a EventLinkedChunk method 2025-07-02 12:43:42 +02:00
Benjamin Bouvier f7df0ebf97 refactor(event cache): change order of parameters in replace_gap
"Replace *this* gap with *these* events" read more natural to me than
"replace with *these* events *this* gap".
2025-07-02 12:43:42 +02:00
Benjamin Bouvier 2f78701374 refactor(event cache): move order of parameters in filter_duplicate_events 2025-07-02 12:43:42 +02:00
Benjamin Bouvier 872bded711 refactor(event cache): rename room_event names to linked_chunk or room_linked_chunk where it makes sense 2025-07-02 12:43:42 +02:00
Benjamin Bouvier 5196298e5f refactor(event cache): rename RoomEvents to EventLinkedChunk 2025-07-02 12:43:42 +02:00
Johannes Marbach 308526a6bc feat(ffi): expose timestamp and identifier on EmbeddedEventDetails
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-07-02 12:38:53 +02:00
Johannes Marbach 12f94a3fd2 feat(ui): expose timestamp and identifier on EmbeddedEvent
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-07-02 12:38:53 +02:00
Benjamin Bouvier 3c873262c7 fix(event cache): disable order assertions entirely
Until we figure out some panics in production this has caused.
2025-07-02 12:18:29 +02:00
Benjamin Bouvier 9689c4a40a feat(ffi): add a new log pack for the notification client 2025-07-02 10:52:55 +02:00
Benjamin Bouvier 57e7ae488e chore(ui): tweak the notification client logs 2025-07-02 10:52:55 +02:00
Jade Ellis a9ffe5fd72 chore: Update rusqlite 2025-07-01 21:25:15 +02:00
Damir Jelić 59c29801e5 refactor: Remove some useless cfg guards
The whole module is behind a e2e-encryption feature guard, so those
individual ones in the module are not useful.
2025-07-01 14:32:53 +02:00
Ivan Enderlin 0a822c1a06 fix(sdk): EventCache::for_room returns an error when room isn't found.
This patch fixes `EventCache::for_room` to return an error when the room
isn't found. Additionally, it also returns an error when the `Client`
(actually the `WeakClient`) is dropped.

These errors were “hidden” behind the room version logic because of
the fallback. The code has been rewritten in a way it makes it harder to
do it again.
2025-07-01 14:31:01 +02:00
Valere Fedronic 4b5e1c6676 feat(widget): Add support for the widget to send encrypted to-device messages
This patch adds support for widgets to send encrypted to-device messages as described in MSC3819.

MSC3819: https://github.com/matrix-org/matrix-spec-proposals/pull/3819
2025-07-01 14:13:31 +02:00
Benjamin Bouvier ecb9d4d2e8 refactor(timeline): remove fields from TimelineFocusKind that are used only to init the focus 2025-07-01 14:06:57 +02:00
Benjamin Bouvier 9ade32fcd0 refactor(timeline): remove generic from a few function signatures by using plain bools 2025-07-01 14:06:57 +02:00
Benjamin Bouvier 8b4a01ea54 refactor(timeline): pass TimelineSettings immediately when creating a Timeline 2025-07-01 14:06:57 +02:00
Benjamin Bouvier d5d5b9ee01 refactor(timeline): move the crypto tasks spawning to decryption_retry_task too
This will make it easier to move all the code around, when needs be.
2025-07-01 14:06:57 +02:00
Benjamin Bouvier a3dd594c9e refactor(timeline): move the crypto drop handles to their own data structure
And put it in the `decryption_retry_task`, so it can be moved
altogether, later, to the event cache or some place else.
2025-07-01 14:06:57 +02:00
Benjamin Bouvier 98c331466e doc(timeline): beef up comments for the long-lived tasks 2025-07-01 14:06:57 +02:00
Benjamin Bouvier e8877fd987 refactor(timeline): move long-lived tasks to a new tasks mod
Only code motion.
2025-07-01 14:06:57 +02:00
Benjamin Bouvier babf16f15a refactor(timeline): use the num_events parameter for a thread back-pagination
And not the initial `num_events` parameter used for the initial
pagination.
2025-07-01 14:06:57 +02:00
Benjamin Bouvier 000419cdf3 refactor(timeline): rename TimelineFocusData to TimelineFocusKind 2025-07-01 14:06:57 +02:00
Benjamin Bouvier 89d661ca8c refactor(timeline): get rid of TimelineFocusKind \o/ 2025-07-01 14:06:57 +02:00
Benjamin Bouvier 1624d798ee refactor(timeline): replace remaining uses of TimelineFocusKind with TimelineFocusData 2025-07-01 14:06:57 +02:00
Benjamin Bouvier 726218000a refactor(timeline): add TimelineFocusKind's fields to TimelineFocusData
This will make it possible to get rid of `TimelineFocusKind`!
2025-07-01 14:06:57 +02:00
Benjamin Bouvier 08dcb267b3 refactor(timeline): use the TimelineFocusData when processing relations 2025-07-01 14:06:57 +02:00
Benjamin Bouvier da0a32b088 refactor(timeline): use the TimelineFocusData to update the skip count 2025-07-01 14:06:57 +02:00
Benjamin Bouvier e22a833057 refactor(timeline): add a TimelineFocusData to TimelineState and TimelineStateTransaction 2025-07-01 14:06:57 +02:00
Benjamin Bouvier 8aba664578 refactor(timeline): get rid of internal locking for TimelineFocusData
Turns out it's not needed, because all the internal data structures
already use inner mutability patterns.
2025-07-01 14:06:57 +02:00
Benjamin Bouvier e117a3d22f fix(sdk): use a cfg guard instead of if cfg! to avoid build failures on !test && !debug_assertions builds 2025-07-01 12:11:09 +02:00
Andy Balaam c2f50fd8a5 doc(crypto): Attempt to explain what handle_to_device_event does and make all types explicit 2025-07-01 11:25:03 +02:00
Ivan Enderlin dc90c77c7d feat(sdk): Introduce the LatestEvents API.
This patch is the first part of the new `LatestEvents` API. It contains
the “framework”, i.e. the structure, tasks, logic to make it work, but
no latest events are computed for the moment.

The Latest Events API provides a lazy, reactive and efficient way to
compute the latest event for a room or a thread.

The latest event represents the last displayable and relevant event
a room or a thread has been received. It is usually displayed in a
_summary_, e.g. below the room title in a room list.

The entry point is `LatestEvents`. It is preferable to get a reference
to it from `Client::latest_events`, which already plugs everything to
build it. `LatestEvents` is using the `EventCache` and the `SendQueue`
to respectively get known remote events (i.e. synced from the server),
or local events (i.e. ones being sent).

\## Laziness

`LatestEvents` is lazy, it means that, despites `LatestEvents`
is listening to all `EventCache` or `SendQueue` updates, it will
only do something if one is expected to get the latest event for a
particular room or a particular thread. Concretely, it means that until
`LatestEvents::listen_to_room` is called for a particular room, no
latest event will ever be computed for that room (and similarly with
`LatestEvents::listen_to_thread`).

If one is no longer interested to get the latest event for a
particular room or thread, the `LatestEvents::forget_room` and
`LatestEvents::forget_thread` methods must be used.

\## Reactive

`LatestEvents` is designed to be reactive. Use
`LatestEvents::listen_and_subscribe_to_room` (same for thread) to get
a `Subscriber`.
2025-07-01 10:52:32 +02:00
Ivan Enderlin 6c9038eb4f refactor(sdk,common): Move JoinHandleExt inside matrix-sdk-common.
This patch moves the `JoinHandleExt` trait and the
`AbortOnDrop` type from `matrix_sdk::sliding_sync::utils` into
`matrix_sdk_common::executor`. This is going to be useful for other
modules.
2025-07-01 10:52:32 +02:00
dependabot[bot] 2b9b4cc589 chore(deps): bump crate-ci/typos from 1.33.1 to 1.34.0
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.33.1 to 1.34.0.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.33.1...v1.34.0)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.34.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-01 08:32:45 +03:00
Ivan Enderlin dd0336ee72 test(ui): Update test_sync_indicator and make it non-flaky.
This patch updates `test_sync_indicator` since the `SyncIndicator`
is shown for the `SettingUp` state. Also, this patch makes this
test non-flaky entirely by changing the `sync::mpsc::channel` to a
`sync::Barrier`.
2025-06-30 16:55:17 +02:00
Ivan Enderlin 0095912091 doc(ui): Update the documentation to be in-sync with the code. 2025-06-30 16:55:17 +02:00
Ivan Enderlin e6774a34da feat(ui): Show the sync indicator when RoomListService is in SettingUp state.
Since `RoomListService` uses a persistent `pos` for sliding sync, the
`SyncIndicator` no longer shows its face except if the sliding sync
session doesn't exist or has expired.

This patch changes that by extending the `Show` from `Init` to
`SettingUp`.
2025-06-30 16:55:17 +02:00
Kévin Commaille 8ad52e34ea refactor: Don't use AnyMessageLikeEventContent with the event factory
When we upgrade Ruma, the `EventContent` bound on `EventBuilder` will be
changed to `StaticEventContent`, which is not implemented by the
`Any*EventContent` enums.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-06-30 16:44:14 +02:00
Ivan Enderlin 60a7bf0c3f doc(sdk): Fix a typo in the documentation of EventCacheDropHandles.
This patch fix a typo in the documentation of `EventCacheDropHandles`:
`EventCache` starts the tasks, not `RoomEventCache`.
2025-06-30 16:41:40 +02:00
Benjamin Bouvier 8b31d8f6a3 test(event cache): add an integration test for the ordering of relations 2025-06-30 16:10:49 +02:00
Benjamin Bouvier e21dd763e8 feat(event cache): return related events sorted by their position in the linked chunk 2025-06-30 16:10:49 +02:00
Benjamin Bouvier 31df84f5a1 feat(event cache): return the event's positions in find_event_relations 2025-06-30 16:10:49 +02:00
Benjamin Bouvier e68bdf8460 refactor(linked chunk): shortcut if there's no events at the top of MemoryStore::filter_duplicate_events
And remove useless comments.
2025-06-30 16:10:49 +02:00
Benjamin Bouvier 6ca1f16f48 refactor(linked chunk): make events iteration order deterministic in the relational linked chunk 2025-06-30 16:10:49 +02:00
Benjamin Bouvier 8c5d878172 refactor(linked chunk): simplify API of RelationalLinkedChunk::items()
One must now specify the target linked chunk; both callers would filter
out items based on this after the fact, which is a bit overkill since
most items would thus be filtered out on the sinking end.
2025-06-30 16:10:49 +02:00
Benjamin Bouvier d6239d614a refactor(test): reflect that unordered events can come, well, unordered 2025-06-30 16:10:49 +02:00
Benjamin Bouvier 5af084c8c9 refactor(test): don't make test_filter_duplicated_event rely on the position in the duplicate set 2025-06-30 16:10:49 +02:00
Benjamin Bouvier dc450ac25a refactor(linked chunk): rejigger the relational linked chunk to include the position of items 2025-06-30 16:10:49 +02:00
Benjamin Bouvier cf375dd753 refactor(linked chunk): don't try to find events or related events in any linked chunk, only the room one 2025-06-30 16:10:49 +02:00
Benjamin Bouvier ff935df136 refactor(linked chunk): don't nest internal methods 2025-06-30 16:10:49 +02:00
Jonas Richard Richter 0d080935cf chore(changelog): add pull request reference for NotificationItem room topic addition 2025-06-30 11:26:17 +01:00
Jonas Richard Richter 4d140d8155 feat(notification): add room topic to NotificationItem and NotificationRoomInfo structs 2025-06-30 11:26:17 +01:00
Benjamin Bouvier cef1f8c5cb chore(event cache): address review comments 2025-06-30 11:09:11 +02:00
Benjamin Bouvier 6a054d6c74 refactor(event cache): use the linked chunk metadata to construct the OrderTracker 2025-06-30 11:09:11 +02:00
Benjamin Bouvier 1f89efb88d feat(event cache store): add a method to return the chunks' metadata 2025-06-30 11:09:11 +02:00
Benjamin Bouvier e83c09e425 refactor(event cache): regroup lazy-loading methods in the same impl block 2025-06-30 11:09:11 +02:00
Benjamin Bouvier a85dac1f52 feat(event cache): introduce an OrderTracker for each room tracked by the event cache
The one hardship is that lazy-loading updates must NOT affect the order
tracker, otherwise its internal state will be incorrect (disynchronized
from the store) and thus return incorrect values upon shrink/lazy-load.

In this specific case, some updates must be ignored, the same way we do
it for the store using `let _ = store_updates().take()` in a few places.

The author considered that a right place where to flush the pending
updates was at the same time we flushed the updates-as-vector-diffs,
since they would be observable at the same time.
2025-06-30 11:09:11 +02:00
Benjamin Bouvier a0bc9aafcf feat(linked chunk): introduce an OrderTracker to keep track of the ordering of the current items
This is a new data structure that will help figuring out a local,
absolute ordering for events in the current linked chunk. It's designed
to work even if the linked chunk is being lazily loaded, and it provides
a few high-level primitives that make it possible to work nicely with
the event cache.
2025-06-30 11:09:11 +02:00
Benjamin Bouvier 8217f967d4 doc(linked chunk): explain briefly what the reattaching/detaching flags are doing 2025-06-30 11:09:11 +02:00
Benjamin Bouvier 47c9585606 refactor(linked chunk): generalize UpdateToVectorDiff to handle different accumulators
In the next patch, we're going to introduce another user of
`UpdatesToVectorDiff` which doesn't require accumulating the
`VectorDiff` updates; so as to make it optional, let's generalize the
algorithm with a trait, that carries the same semantics.

No changes in functionality.
2025-06-30 11:09:11 +02:00
Benjamin Bouvier 20e09531fb refactor(sdk): move the common pagination types to the paginators root mod 2025-06-30 10:48:27 +02:00
Benjamin Bouvier d92b33f959 refactor(sdk): move the ThreadedEventsLoader into the sdk's paginators module
A bit of code motion, sprinkling a new interface here, adjusting
expectations in the timeline, and we're all set 👌
2025-06-30 10:48:27 +02:00
Benjamin Bouvier a74bcfab8f refactor(sdk): move the room Paginator outside of the event cache
It doesn't belong there anymore, because it's not used within the event
cache itself. It's used in the timeline, and I think it's nice to keep
it available for external users as well, so it now lives in its own
`matrix-sdk/src/paginators` module. Next step is to move the thread
loader in there as well.
2025-06-30 10:48:27 +02:00
Benjamin Bouvier 6dcc744b48 refactor(event cache): remove unused impl of PaginableRoom for WeakRoom 2025-06-30 10:48:27 +02:00
Benjamin Bouvier 9d1c296657 doc(ffi): fix incorrect reference to a function that's been renamed 2025-06-30 10:48:27 +02:00
Damir Jelić 3c7683ea53 chore(sdk): Add a missing dot 2025-06-30 09:53:47 +02:00
Jonas Platte cd03a58083 refactor(examples): Use if-let chains in oauth_cli 2025-06-29 20:58:05 +02:00
Jonas Platte 4a1249fa96 chore(examples): Upgrade to Rust edition 2024 2025-06-29 20:58:05 +02:00
Kévin Commaille 06732ca71a refactor(common): Use a constant for the room version to use as a fallback
It avoids using different versions in several places for consistency. It
also allows to be able to change it in a single place when needed.

This also bumps the fallback to v11 everywhere, since it is the default
version for new rooms since Matrix 1.14 and it has the sanest redaction
rules.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-06-29 16:59:37 +02:00
Jonas Platte 115c7578d4 refactor(multiverse): Rewrite condition for readability 2025-06-29 09:42:27 +02:00
Jonas Platte 9f3e7debb1 refactor(multiverse): Use let chains 2025-06-29 09:42:27 +02:00
Jonas Platte 58d2ae4c39 chore(multiverse): Upgrade to Rust edition 2024 2025-06-29 09:42:27 +02:00
Kévin Commaille 8a847a99d4 ci: Bump the version of Rust nightly
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-06-28 09:47:28 +02:00
Jonas Platte 3d642356c6 refactor: Clean up formatting in many places
Process:
- set style_edition to 2024 in .rustfmt.toml
- run `cargo fmt`
- undo .rustfmt.toml change
- run `cargo fmt` again
- manually rewrap some strings
2025-06-27 19:54:13 +02:00
Kévin Commaille b4b0f3a203 refactor(sdk): Remove fallback support for the /auth_issuer endpoint
The `/auth_metadata` endpoint has been supported by Synapse for 6 months
now so there shouldn't be any homeserver that still depend exclusively
on it. This endpoint is also part of Matrix 1.15.

Support for this endpoint has been removed from Ruma so this is
necessary before an upgrade of the dependency.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-06-27 17:24:30 +00:00
Kévin Commaille ca99977207 ui: Inline format! args
Detected by lint clippy::uninlined_format_args.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-06-27 19:04:00 +02:00
Kévin Commaille dd02274883 base: Avoid clone in test
Detected by lint clippy::cloned_ref_to_slice_refs.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-06-27 19:04:00 +02:00
Kévin Commaille ad2e3a3b8f common: Put UpdatesSubscriber behind test cfg
Since it is only used in tests, it is now detected as dead code.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-06-27 19:04:00 +02:00
Kévin Commaille 96119f9a30 crypto: Use Ord implementation of SequenceNumber for its PartialOrd implementation
Detected by lint clippy::non_canonical_partial_ord_impl.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-06-27 19:04:00 +02:00
Damir Jelić 737e06b581 Add a missing changelog entry for PR 5177 2025-06-27 14:54:21 +02:00
Richard van der Hoff 4ecd599c15 fix(sdk): correctly import e2ee history in join_room_by_id (#5284)
It turns out that downstream clients can and do call
`Client::join_room_by_id()` rather than `Room::join`, so we need to do
the room key history import in the lower-level method.

---------

Signed-off-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
Co-authored-by: Benjamin Bouvier <benjamin@bouvier.cc>
2025-06-27 12:29:01 +01:00
Doug c6521a8aaf ffi: Remove the ElementWellKnown struct and related function. 2025-06-27 12:00:33 +02:00
Benjamin Bouvier 4046a59786 refactor(event cache): make filter_duplicate_events operate on a linked chunk id 2025-06-26 16:23:39 +02:00
Benjamin Bouvier d931cd0ea7 refactor(event cache): include non_empty_all_duplicates in the DeduplicationOutcome 2025-06-26 16:23:39 +02:00
Benjamin Bouvier a14488617e refactor(common): don't parse the full bundled latest thread event, but only its type
The previous code would parse the entire bundled event, only to look at
its type indirectly. We can do better than this, and only look at the
type instead. This brings a few benefits:

- it is faster, as we don't have to deserialize the entire event
- while the spec seems to indicate that the latest thread event has a
  `room_id`, it seems that, under some circumstances, it does not, as
  indicated by some rageshakes. As such, not parsing as `AnyMessageLike`
  (which mandates a room id) makes it more robust to the absence of a
  room id in there, marking more events as latest events.
2025-06-26 15:33:26 +02:00
Jorge Martín 1de51614f1 fix: Add m.room.avatar to the required state
This works around the issue with removed avatars not being explicitly flagged by sliding sync until https://github.com/element-hq/synapse/issues/18598 is fixed
2025-06-26 14:21:00 +02:00
Robin 6cc98ee9f7 feat(widget): Allow Element Call to learn the room name
The latest mobile designs for Element Call have it displaying the room name in an "app bar". So the Element Call widget will soon start requesting the capability to learn the room name, and the SDK should auto-approve this capability.
2025-06-26 13:28:34 +02:00
Damir Jelić 3a98d46bfa feat: Add a stream to listen for historic room key bundles 2025-06-26 13:22:24 +02:00
Damir Jelić 1558858bde chore: Add a TODO item reminding us that we should zeroize room key bundle contents 2025-06-26 13:22:24 +02:00
Damir Jelić e4d2f62d48 docs: Document the store/types module properly 2025-06-26 12:11:16 +02:00
Benjamin Bouvier 70f48be582 refactor(sliding sync): avoid an unwrap by inlining a function into its one caller 2025-06-26 12:08:43 +02:00
Ivan Enderlin 836c643769 doc(indexeddb): Add documentation about IndexedKeyBounds and IndexedKeyComponentBounds. 2025-06-25 16:17:34 +02:00
Michael Goldenberg a48099d5ac refactor(indexeddb): add function to transaction type for clearing all data from an object store in IndexedDB
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-25 16:17:34 +02:00
Michael Goldenberg 09d8be7b4c doc(indexeddb): add docs to transaction type where they are missing
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-25 16:17:34 +02:00
Michael Goldenberg 03b8cabc22 refactor(indexeddb): add functions to transaction type for deleting data from IndexedDB
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-25 16:17:34 +02:00
Michael Goldenberg 07372c475c refactor(indexeddb): add functions to transaction type for adding data to IndexedDB
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-25 16:17:34 +02:00
Michael Goldenberg a00e4089e8 refactor(indexeddb): add functions to transaction type for getting data out of IndexedDB
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-25 16:17:34 +02:00
Michael Goldenberg 1a24b21d42 refactor(indexeddb): add type to represent IndexedDB transactions specific to event cache store
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-25 16:17:34 +02:00
Michael Goldenberg f51496fa0f refactor(indexeddb): track corresponding index for type in IndexedKey trait
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-25 16:17:34 +02:00
Michael Goldenberg c6ce9c560b refactor(indexeddb): track corresponding object store for type in Indexed trait
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-25 16:17:34 +02:00
Michael Goldenberg 60af16ada8 refactor(indexeddb): add function for encoding key range from key component range
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-25 16:17:34 +02:00
Michael Goldenberg 159fb73b0a refactor(indexeddb): join key range encoding functions using indexed key range enum
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-25 16:17:34 +02:00
Michael Goldenberg 0fa0f2329d refactor(indexeddb): add enum for representing ranges over indexed keys
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-25 16:17:34 +02:00
Michael Goldenberg 3b64d18c99 refactor(indexeddb): move key component bounds into a separate trait
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-25 16:17:34 +02:00
Michael Goldenberg f67fd87e57 refactor(indexeddb): rename IndexedKeyBounds fn's so they are consistent
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-25 16:17:34 +02:00
Michael Goldenberg 5e64da660c refactor(indexeddb): add default impls for IndexedKeyBounds::{encode_lower, encode_upper}
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-25 16:17:34 +02:00
Michael Goldenberg d152ce13a0 refactor(indexddb): add fn's to IndexedKeyBounds to get bounds of key components
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-25 16:17:34 +02:00
Michael Goldenberg d021020ee6 refactor(indexeddb): use generic type rather than dynamic type in serializer error
This prevents us from having to add type constraints on the dynamic type
and instead only having to specify the type constraint at the call site.

Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-25 16:17:34 +02:00
Timo 7e5f22ba9e feat(widgets) Add backwards compatibility hide_header for element-call url query parameter 2025-06-25 16:12:28 +03:00
Timo 6bc6ea4e72 feat(widgets): Update the widget url parameters to use the new header property (was hideHeader which is now deprecated) 2025-06-25 16:12:28 +03:00
Timo 585ae29868 refactor(uniffi): make the sdk widget config struct derive uniffi.
This allows to not duplicate the struct in the uniffi crate.
2025-06-25 16:12:28 +03:00
Benjamin Bouvier 3919c2a89a feat(ffi): also disable the send queues when clearing caches
And beef up the doc comment.
2025-06-25 14:32:25 +02:00
Benjamin Bouvier 7c85e7aa4f feat(room list service): allow to manually expire sessions
This can be used to invalidate the persisted state on disk related to
the sliding sync positions. It's useful to do so when clearing up all
the caches, since the sliding sync `pos`itions are stored in the crypto
store (to benefit from the cross-process lock).
2025-06-25 14:32:25 +02:00
Benjamin Bouvier 4b845e17c8 feat(sliding sync): also empty the list caches when expiring a session 2025-06-25 14:32:25 +02:00
Benjamin Bouvier 394124cda5 refactor(sliding sync): rename invalidate_cached_list to remove_cached_list 2025-06-25 14:32:25 +02:00
Benjamin Bouvier bbf9bf2c0b feat(room list service): skip the initial state when an initial pos is set 2025-06-25 14:32:25 +02:00
Benjamin Bouvier 67327a0365 doc(sliding sync): remove dubious doc comment
It speaks of a time where the `storage_key` was optional, and it's also
a private field, so it's doubly confusing.
2025-06-25 14:32:25 +02:00
Benjamin Bouvier 5e40426b99 refactor(sliding sync): don't eagerly reload sliding sync state if it's unused 2025-06-25 14:32:25 +02:00
Ivan Enderlin 0f264cac6e feat(ui): RoomListService re-enables share_pos. 2025-06-25 14:32:25 +02:00
Andy Balaam 3c1d0b37e5 refactor(crypto): Provide a specific error type for to-device events from dehydrated devices
This will get more usage soon, when we add a variant for events with
unverified senders.
2025-06-25 12:05:14 +01:00
Stefan Ceriu 62231878cc fix(ffi): make RoomInfo power levels optional as they can be missing depending on the required state
The `m.room.power_levels` state event is not part of the room list required state and computing the RoomInfo would fail in that case.
2025-06-25 12:41:33 +03:00
Benjamin Bouvier 22c99f30f3 chore(sdk): fill in the pull request numbers in changelogs
Signed-off-by: Benjamin Bouvier <benjamin@bouvier.cc>
2025-06-25 10:32:01 +02:00
Benjamin Bouvier a7efff9849 chore: add changelog entries 😅 2025-06-25 10:32:01 +02:00
Benjamin Bouvier bc9192f818 refactor!(sdk): make the join_rule and related getters optional
The join rule state event can be missing from a room state. In this
case, it's an API footgun to return a default value; instead, we should
return none and let the caller decide what to do with missing
information.
2025-06-25 10:32:01 +02:00
Richard van der Hoff 0722ed9d8f Indexeddb: support for received room key bundles
Add a new store to keep track of the information we have received about room
key bundles.
2025-06-25 10:26:11 +02:00
Valere Fedronic 1aa933cfd6 widgets: Filter out crypto related fields of raw decrypted to-device
Ensure that only sender/type/content are exposed to the widgets, and not the crypto-related fields.
2025-06-24 16:04:52 +02:00
Ivan Enderlin e0ab16f979 chore(sdk): Address feedback. 2025-06-24 15:14:57 +02:00
Ivan Enderlin a9c999af72 doc(sdk): Precise when the side-effect of RoomEventCacheSubscriber happens. 2025-06-24 15:14:57 +02:00
Ivan Enderlin 8156413132 feat(sdk): Introduce RoomEventCacheGenericUpdate, one channel to get update of all rooms.
`RoomEventCache::subscribe` is nice to subscribe to every update
happening inside a room in the event cache. However, the returned
`RoomEventCacheSubscriber` has side-effects when dropped (see
auto-shrink to save memory space). In some situation, this is pretty
annoying. for example, if one wants to listen to multiple room updates,
all the room event cache subscribers must be kept in memory, thus
breaking the side-effects. This isn't always the desired output. In
addition, listening to multiple channels/subscribers at the same is
quite complex, as it implies non-trivial async runtime efforts or
complex future types.

To solve this problem, this patch introduces a new
`EventCache::subscribe_to_room_generic_updates` method, which returns a
single `Receiver<RoomEventCacheGenericUpdate>`.

First off, it hides the details of `RoomEventCacheUpdate` (returned by
`RoomEventCacheSubscriber`), which might be desired, but particularly
lighter because events aren't part of the payload.

Second, one no longer needs to subscribe to all rooms. Only one channel
can be listened to get updates for all rooms. It reduces the complexity
on the caller side, plus `Receiver<RoomEventCacheGenericUpdate>` doesn't
have any side-effect.

This patch tests this feature in 4 situations:

1. when a room is created/loaded empty,
2. when a room is loaded and is not empty because data exists in the storage,
3. when a room receives data from the sync,
4. when a room receives data from the pagination.
2025-06-24 15:14:57 +02:00
Ivan Enderlin e551efec8d doc(sdk): Fix comment of RoomEventCacheState::handle_sync.
This patch fixes a comment about `RoomEventCacheState::handle_sync`
returned values.
2025-06-24 15:14:57 +02:00
Benjamin Bouvier 877a7d678f fix(notification client): request the join rules so as to be able to compute them 2025-06-24 14:34:54 +02:00
Daniel Salinas 457af2a2f8 feat(wasm): Remove network config features from ffi ClientBuilder for Wasm (#5248)
<!-- description of the changes in this PR -->
Features to configure UserAgent, Proxy, Disabling SSL, and additional
certificates are not available on Wasm platforms. We remove these
configuration options from the FFI layer, while preserving them on
non-Wasm platforms.

- [ ] Public API changes documented in changelogs (optional)

<!-- Sign-off, if not part of the commits -->
<!-- See CONTRIBUTING.md if you don't know what this is -->
Signed-off-by: Daniel Salinas

---------

Co-authored-by: Daniel Salinas <danielsalinas@Daniels-MacBook-Pro-2.local>
2025-06-24 14:22:43 +02:00
Andy Balaam 7b38c442c7 refactor(crypto): Hold a DecryptionSettings in base Client
Previously, we held a TrustRequirement inside Client, and had to wrap it
in a DecryptionSettings manually a couple of times.

It feels more right to hold on to a DecryptionSettings directly. If
there were ever some additional settings, most likely we would need them
all here.

If I haven't screwed it up, this should not affect behaviour in any way.
2025-06-24 12:36:31 +01:00
Benjamin Bouvier 201b818cc8 fix(timeline): take into account the skip count when computing the pagination status
The pagination status was only mapped onto the "global" state, that is, the state of the event cache pagination.

Now, consider the following case, where two timeline instances are live:

- the first one could have backpaginated all the items back to the start of the room
- the second one is created later. Because of the initial value for the skip count, it will only return a subset of event items (~20).

However, listening to the pagination status for the second timeline would incorrectly state that the timeline was entirely paginated (because it returned the "global" pagination status). As such, an observer might think that there are no more items in the timeline, while a subsequent pagination would adjust the skip count and return more items.

This fixes it by combining the global pagination state with the local timeline state (aka the skip count value). If the skip count is positive (meaning, we could set it to 0 later and thus returning more events), we pretend we haven't reached the start of the timeline. This way, an observer can call pagination later, which may adjust the skip count and "return" more items.
2025-06-24 09:41:04 +00:00
Daniel Salinas 1f98e0cd19 Run cargo fmt 2025-06-23 18:51:51 +03:00
Daniel Salinas 21c59c95c4 Avoid using of tokio::time:sleep which does not work on Wasm
We already have a wrapped sleep in matrix-sdk-base, so use that.
2025-06-23 18:51:51 +03:00
Andy Balaam 4025c11e73 refactor(crypto): Simplify the code that ignores to-device messages from dehydrated devices 2025-06-23 16:24:41 +01:00
Stefan Ceriu dc6130562a feat(ffi): add simpler methods for checking own user permissions
This is so that the final client no longer needs to go through the string to user_id conversion and error handling step before retrieving permissions.
2025-06-23 18:04:59 +03:00
Stefan Ceriu d1a14f895e chore(ffi): move user_power_levels outside the RoomInfo and into the newly introduced RoomPowerLevels.
`RoomPowerLevels` already holds an inner version of the power levels and has access to everything needed to compute the user to level map.
This way the async fetching only happens once and the mapping only on request.
2025-06-23 18:04:59 +03:00
Stefan Ceriu b680705d15 feat(ffi): expose the RoomPowerLevels on the RoomInfo directly.
Currently, clients have to make async requests to `Room::get_power_levels` from multiple places throughout the app in order to correctly configure the various UI components and again, on randomly decided events, to keep them up to date.
This patch starts publishing the power levels directly on the `RoomInfo` and allows them to be handled (and updated!) through the normal `subscribe_to_room_info_updates` mechanism.
2025-06-23 18:04:59 +03:00
Stefan Ceriu 2b1ee853fc chore(ffi): move RoomInfo to the room module 2025-06-23 18:04:59 +03:00
Ivan Enderlin ef137730cb doc(sdk): Add #5269 to the CHANGELOG.md. 2025-06-23 15:45:28 +02:00
Ivan Enderlin 45caaffb26 refactor(sdk): Rename RoomEventCacheListener to RoomEventCacheSubscriber.
This patch removes a name ambiguity around _listener_ vs. _subscriber_.
Both terms are used to talk about `RoomEventCacheListener`. We usually
use the term _subscriber_ for the type being returned by a `subscribe`
method. The code refers to this sometimes as listener, sometimes
as subscriber, sometimes both in the same sentence, which can be very
confusing! This patch solves this by using the _subscriber_ term only.
2025-06-23 15:45:28 +02:00
Ivan Enderlin 50c3217353 doc(sdk): Document the side-effect of RoomEventCacheListener. 2025-06-23 15:45:28 +02:00
Ivan Enderlin b5dafd9798 chore(sdk): Rename variables.
This patch renames `tx` and `rx` to `auto_shrink_sender` and
`auto_shrink_receiver` to clarify the code.
2025-06-23 15:45:28 +02:00
Ivan Enderlin 5a39fd051b chore(sdk): Rename a variable.
This patch renames a variable to match the `EventCacheDropHandles`
field's name.
2025-06-23 15:45:28 +02:00
Andy Balaam ff52cf36dd refactor(crypto): Rename raw_event to processed_event to reflect its changed state 2025-06-23 11:34:59 +02:00
Daniel Salinas 25d217cc6f Address timeline panic to avoid resume_unwind on Wasm 2025-06-23 10:06:04 +02:00
Jonas Platte 2116ad82df refactor: Move comments with visual indentation
Visual indentation was removed from rustfmt's style defaults in the 2024
edition of the style rules.
2025-06-23 09:37:45 +02:00
Jonas Platte fe4109cb9a refactor: Remove {self} imports
They are only semantically different if a macro of the same name exists,
in which case the foo::{self} import only imports foo-the-module, not
foo-the-macro.
2025-06-23 09:37:45 +02:00
Jonas Platte 41f107e5ba refactor: Remove useless comment
This comment was added in the very first commit for matrix-sdk-ffi and I
have no recollection of what was meant there.
2025-06-23 09:37:45 +02:00
Daniel Salinas ab699a90f1 Adjust platform init on Wasm to avoid tokio environment
The multi-threaded tokio environment does not work in Wasm.
console_error_panic_hook turns rust panics into JS console statements
2025-06-23 09:47:08 +03:00
Daniel Salinas ddee7f8ccd feat(wasm): Small fixes for imports in ffi timeline (#5263)
Adjust some imports to use our shims to support Wasm.

Signed-off-by: Daniel Salinas
2025-06-20 22:22:15 +02:00
Daniel Salinas 2ab5ab527b refactor(wasm): Remove special Send/Sync behavior for Wasm on a crypto-store error (#5265)
Not necessary it turns out

Signed-off-by: Daniel Salinas
2025-06-20 22:18:32 +02:00
Daniel Salinas 53e3b90436 feat(wasm): Mark CapabilitiesProvider::acquire_capabilities as SendOutsideWasm (#5262)
Correct an accidental recent addition of a `Send` trait instead of
`SendOutsideWasm`

Signed-off-by: Daniel Salinas
2025-06-20 21:58:11 +02:00
Daniel Salinas 08e1d3876b Reorganize QRCode related functionality into its own file
We have a working implementation of the additional forms of QR Code login
of MSC4108. This commit moves the existing code into its own file
to make future updates easier to follow.
2025-06-20 14:24:28 +01:00
Richard van der Hoff c3179ea5ed Merge pull request #5260 from matrix-org/rav/join_room_logging
sdk: add logging to `Room::join()`
2025-06-20 12:12:05 +01:00
Daniel Salinas 9676daee5a feat(wasm): Remove MediaFileHandle from ffi on Wasm platforms (#5249)
Remove the MediaFileHandle concept from the matrix-sdk-ffi crate on Wasm
platforms. File handles are not supported in the browser.

Signed-off-by: Daniel Salinas
2025-06-19 18:55:23 +02:00
Daniel Salinas 798cece4a2 feat(wasm): Add lib to matrix-sdk-ffi target (#5242)
The uniffi tool for generating JS/Wasm bindings utilizes rust as its
intermediate language.

As a result, the 'target' uniffi annotated library needs to be marked as
a 'lib' so that the generated rust code can utilize it to generate the
Wasm create + typescript bindings.

Signed-off-by: Daniel Salinas
2025-06-19 18:16:16 +02:00
Doug 06b387101b chore: Fix changelogs after rebase 2025-06-19 17:40:04 +02:00
Doug 675963ec4b chore: Make the RTC foci crash fix type-safe. 2025-06-19 17:40:04 +02:00
Doug d30dae3322 fix: Handle a crash accessing the RTC foci when the well-known was None. 2025-06-19 17:40:04 +02:00
Doug bdb640a126 ffi: Expose a check for LiveKit RTC support. 2025-06-19 17:40:04 +02:00
Doug ea28234d95 sdk: Cache the client well-known file and add Client::rtc_foci which uses it. 2025-06-19 17:40:04 +02:00
Doug c74295c604 chore: Refactor ServerCapabilities into ServerInfo.
It has nothing to do with /capabilities so is confusing. We can use this new struct to combine the well-known response into a single cache too.
2025-06-19 17:40:04 +02:00
Michael Goldenberg ec30e7b85c docs(indexeddb): add license to event_cache_store::types file
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-19 17:26:30 +02:00
Michael Goldenberg fd17c28ebb refactor(indexeddb): add convenience functions for (de)serializing Indexed types
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-19 17:26:30 +02:00
Michael Goldenberg 841131f127 refactor(indexeddb): add indexing trait impls for gap
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-19 17:26:30 +02:00
Michael Goldenberg a22d592bf1 refactor(indexeddb): add chunk identifier into gap type
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-19 17:26:30 +02:00
Michael Goldenberg 1a32aa59a6 refactor(indexeddb): add indexing trait impls for event
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-19 17:26:30 +02:00
Michael Goldenberg a99df7e1d8 refactor(indexeddb): add indexing trait impls for chunk
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-19 17:26:30 +02:00
Michael Goldenberg 3e37f9d0ad refactor(indexeddb): expose current version and keys used in event cache store database 2025-06-19 17:26:30 +02:00
Michael Goldenberg 2689e2d25a refactor(indexeddb): add trait for constructing key bounds for indexed types in event cache store
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-19 17:26:30 +02:00
Michael Goldenberg 2cfba4cd9b refactor(indexeddb): add trait for encoding keys for indexed types in event cache store
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-19 17:26:30 +02:00
Michael Goldenberg 1bce2af93c refactor(indexeddb): add trait for converting between high-level types and indexed types in event cache store
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-19 17:26:30 +02:00
Stefan Ceriu 47c8df0ef8 chore(ffi): move the room power levels to their own file and restructure the code 2025-06-19 17:55:48 +03:00
Stefan Ceriu 9f32dfe9a0 change(ffi): expose the full RoomPowerLevels object and move corresponding methods to it.
This patch expands on the already existent `RoomPowerLevels` record (which it renames to `RoomPowerLevelsValues` to work around uniffi not exposing public fields) and nests them inside a new exported object that also provides methods for retrieving the actions that an user can take, methods moved from the room object.
This reduces the amount of async calls the clients need to make, simplifies the API and groups the code better together.
2025-06-19 17:55:48 +03:00
Daniel Salinas 040fd6c736 Run cargo fmt 2025-06-19 16:16:59 +02:00
Daniel Salinas 4dac175db0 Back to other phrasing to make CI run 2025-06-19 16:16:59 +02:00
Daniel Salinas 5faf97cf99 Rework phrasing to make CI run 2025-06-19 16:16:59 +02:00
Daniel Salinas 7236b80b3b Adjust language to make CI do something 2025-06-19 16:16:59 +02:00
Daniel Salinas 79b0941687 Remove lib 2025-06-19 16:16:59 +02:00
Daniel Salinas ad001e475f Update bindings/matrix-sdk-ffi/Cargo.toml
Co-authored-by: Jonas Platte <jplatte+git@posteo.de>
Signed-off-by: Daniel Salinas <zzorba@users.noreply.github.com>
2025-06-19 16:16:59 +02:00
Daniel Salinas 5106d55be9 Update bindings/matrix-sdk-ffi/Cargo.toml
Co-authored-by: Jonas Platte <jplatte+git@posteo.de>
Signed-off-by: Daniel Salinas <zzorba@users.noreply.github.com>
2025-06-19 16:16:59 +02:00
Daniel Salinas 9771b99395 Update bindings/matrix-sdk-ffi/Cargo.toml
Co-authored-by: Jonas Platte <jplatte+git@posteo.de>
Signed-off-by: Daniel Salinas <zzorba@users.noreply.github.com>
2025-06-19 16:16:59 +02:00
Daniel Salinas 2c287e706f Update bindings/matrix-sdk-ffi/Cargo.toml
Co-authored-by: Jonas Platte <jplatte+git@posteo.de>
Signed-off-by: Daniel Salinas <zzorba@users.noreply.github.com>
2025-06-19 16:16:59 +02:00
Daniel Salinas fffff783d4 Spell check 2025-06-19 16:16:59 +02:00
Daniel Salinas b047bd0dc6 Add refactor flag 2025-06-19 16:16:59 +02:00
Daniel Salinas 28b3b6aedf Update bindings/matrix-sdk-ffi/CHANGELOG.md
Co-authored-by: Damir Jelić <poljar@termina.org.uk>
Signed-off-by: Daniel Salinas <zzorba@users.noreply.github.com>
2025-06-19 16:16:59 +02:00
Daniel Salinas 171974a44b Add note about bundled-sqlite 2025-06-19 16:16:59 +02:00
Daniel Salinas f53302a7a0 Code review feedback, improved documentation 2025-06-19 16:16:59 +02:00
Daniel Salinas dd709682d7 Update bindings/matrix-sdk-ffi/Cargo.toml
Co-authored-by: Damir Jelić <poljar@termina.org.uk>
Signed-off-by: Daniel Salinas <zzorba@users.noreply.github.com>
2025-06-19 16:16:59 +02:00
Daniel Salinas 991e0cd395 Update bindings/matrix-sdk-ffi/Cargo.toml
Co-authored-by: Damir Jelić <poljar@termina.org.uk>
Signed-off-by: Daniel Salinas <zzorba@users.noreply.github.com>
2025-06-19 16:16:59 +02:00
Daniel Salinas abcc05f889 Change matrix-sdk-ffi to rely on features over platform targets
The system of platform targets was already quite messy, and becoming
even worse as we start preparing for Wasm support. Switch to features
instead to make this easier to work with.
2025-06-19 16:16:59 +02:00
Daniel Salinas f3e636ea42 fix(wasm): Fix unwrap error on Wasm platforms caused by UInt::MAX conversions (#5240)
UInt::MAX.try_into().unwrap() was causing errors on Wasm platforms, due
to the result being unrepresentable.

This `unwrap_or` was also always being calculated regardless, so I think
using `usize::MAX` is preferable on all platforms.

Signed-off-by: Daniel Salinas <zzorba@users.noreply.github.com>
2025-06-19 13:56:50 +00:00
Daniel Salinas 1d47507faa refactor(wasm) Remove some unnecessary wasm32 differentiations (#5256)
Some of these Box<dyn Error + Send + Sync> are okay, but a few are
problematic.

Confirmed this still compiles fine on the fully working wasm tree.

Signed-off-by: Daniel Salinas
2025-06-19 13:28:04 +00:00
Damir Jelić cc7f6243c6 ci: Make the text output for our coverage xtask the default 2025-06-19 13:57:58 +02:00
Jonas Platte e7e9d5b746 refactor: Use native async fn in traits for widget::CapabilitiesProvider (#5135)
The main thing left now are the store traits, unsure how to deal with
those. `dynosaur` + `trait_variant` are kind of the modern replacement
for `async_trait`, but (a) `trait_variant` seemed to generate invalid
code here when I tried it and (b) even with that fixed I think the error
type erasure is going to present some extra problems. Maybe it's fine to
just keep the current solution for the store traits for now.

Signed-off-by: Jonas Platte <jplatte+matrix@posteo.de>
2025-06-19 11:47:36 +00:00
Andy Balaam b4146caac8 refactor(crypto): Extract a method for handling encrypted to-device events 2025-06-19 11:22:11 +01:00
Damir Jelić 1cb51f49be ci: Store successful test results in the JUnit file as well
This is useful to detect which tests might be the slowest.
2025-06-19 11:14:51 +02:00
Damir Jelić fea0e0d373 ci: Use the correct token to upload JUnit reports 2025-06-19 10:26:43 +02:00
Damir Jelić 72911c66ad ci: Upload the JUnit reports to codecov as well 2025-06-19 09:50:16 +02:00
Damir Jelić dc047854d4 ci: Use a separate cache prefix for the coverage workflow 2025-06-19 09:50:16 +02:00
Damir Jelić f51a008921 ci: Attempt to free up some space on the container for the coverage workflow 2025-06-19 09:50:16 +02:00
Damir Jelić 3c5bcce217 ci: Use llvm-cov for coverage reports
This patch switches from tarpaulin to llvm-cov for our coverage reports.
llvm-cov can use cargo-nextest to run the tests which means that we can
tolerate flaky tests for coverage just like do for the rest of our CI
run.

We can also start using JUnit reports to track flaky tests.
2025-06-19 09:50:16 +02:00
Damir Jelić 422fd19d10 ci: Add an xtask subcommand for coverage reports
This command uses llvm-cov which we are planning to switch to.
2025-06-19 09:50:16 +02:00
Damir Jelić 0ea07e11e9 ci: Add a CI specific profile for nextest 2025-06-19 09:50:16 +02:00
Damir Jelić 059a6fa573 test: Make some tests less flaky by increasing timeouts 2025-06-19 09:50:16 +02:00
Daniel Salinas 07656c2e26 Correct use to propagate error with additional text 2025-06-19 09:42:43 +02:00
Daniel Salinas 940325574b Address use of errors and panic::resume_unwind for wasm targets 2025-06-19 09:42:43 +02:00
Damir Jelić 9f8824b9a5 ci: Use a tag for the changed-files github action 2025-06-17 16:13:39 +02:00
Valere Fedronic cd141c5b84 feat(widget): Receive custom to-device messages in widgets in e2ee rooms
Proper support for receiving to-device messages for widgets.

If the widget is in an e2ee room, clear to-device traffic will be excluded. Also filter out internal to-device messages that widgets should not be aware off.
2025-06-17 16:00:44 +02:00
dependabot[bot] 9596aa0830 chore(deps): bump qmaru/wasm-pack-action from 0.5.0 to 0.5.1
Bumps [qmaru/wasm-pack-action](https://github.com/qmaru/wasm-pack-action) from 0.5.0 to 0.5.1.
- [Release notes](https://github.com/qmaru/wasm-pack-action/releases)
- [Commits](https://github.com/qmaru/wasm-pack-action/compare/v0.5.0...v0.5.1)

---
updated-dependencies:
- dependency-name: qmaru/wasm-pack-action
  dependency-version: 0.5.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-17 13:56:43 +02:00
Benjamin Bouvier 11424ce443 chore(ffi): add the SDK commit hash to sentry as the release version 2025-06-16 16:44:48 +02:00
Benjamin Bouvier cd4ec90b38 chore(timeline): report day divider invariant violations to sentry
On this date (heh), the date divider reports include the following
information:

- initial timeline items in a shortened format,
- operations to apply,
- final timeline items in the same shortened format,
- errors

The shortened format includes:

- either, for events: "[event id]: server timestamp"
- or for date divider: "--- date divider timestamp"

As such, they don't include any personal information.

The initial timeline items state and set of operations to apply
constitutes a fully enclosed test case, so it's nice to report it to
Sentry, so we can reuse it almost as is (we'd only need to randomize the
event IDs) and fix it in a subsequent commit.
2025-06-16 16:44:48 +02:00
Benjamin Bouvier 4680354abd fix(timeline): remove development tracing log in the pinned events loader 2025-06-16 14:25:30 +02:00
Damir Jelić 145d6c5782 refactor(multiverse): Use a paragraph to render an individual read receipt 2025-06-16 12:54:40 +02:00
Damir Jelić a955af61e1 refactor(multiverse): Simplify the selected read receipt rendering
This patch simplifies the selected read receipt rendering by the fact
that we can simply fetch the selected timeline item instead of the event
ID and then do a search for the selected item.

Co-authored-by: Benjamin Bouvier <benjamin@bouvier.cc>
Signed-off-by: Damir Jelić <poljar@termina.org.uk>
2025-06-16 12:54:40 +02:00
Damir Jelić 2a78b5b67a chore: Fix a clippy lint 2025-06-16 12:54:40 +02:00
Damir Jelić 9d29c36531 feat(multiverse): Only render read receipts for a single event if one is selected 2025-06-16 12:54:40 +02:00
Damir Jelić ed9c7d90b4 feat(multiverse): Allow event selection even if the details view is open 2025-06-16 12:54:40 +02:00
Damir Jelić 2af23d052c feat(multiverse): Show the selected event in the read receipts view 2025-06-16 12:54:40 +02:00
Damir Jelić fb80e06839 feat(multiverse): Show read receipts for individual messages 2025-06-16 12:54:40 +02:00
Daniel Salinas b8f9cba5e7 Wasm corrections for ffi's error file
Add a From to handle RequestVerificationError to avoid anyhow
Stop using SystemTime directly
2025-06-16 09:14:54 +02:00
Daniel Salinas d119b01322 Add AbortHandle as well 2025-06-16 09:09:28 +02:00
Daniel Salinas 85833c74ba Update JoinHandle as well 2025-06-16 09:09:28 +02:00
Daniel Salinas 5b20136a50 Stop using tokio::runtime::Handle directly
Use our platform aware export from matrix-sdk-common instead
2025-06-16 09:09:28 +02:00
Ivan Enderlin 362ca2bd59 doc(base): Remove ambiguities around inline comments. 2025-06-13 14:52:15 +02:00
Ivan Enderlin 0a9a849826 fix(base): Ignore invalid state events instead of throwing an error.
This patch changes the strategy of `check_room_upgrades`.
Instead of checking all state events in `StateChanges` and
throwing an error in case of an invalid state, this patch updates
`state_events::sync::dispatch` to filter out invalid state events
(specifically `m.room.create` and `m.room.tombstone`), and log the
error. That way, the sync doesn't stop and the app can continue working
smoothly.

So `check_room_upgrades` is splited into two functions:
`is_create_event_valid` and `is_tombstone_event_valid`. It's no longer
necessary to detect mergers or splitters because those are _soft
errors_, however loops are still critical errors (hence the fact
the state events aren't stored nor saved nor applied, and that a log
is emitted).

Note: `check_room_upgrades` has been reverted in a previous commit,
that's why it doesn't appear in this diff.
2025-06-13 14:52:15 +02:00
Valere Fedronic 7126fc8a29 feat(crypto): Emmit EncryptionInfo with event handlers for to-device messages as well 2025-06-13 14:31:22 +02:00
Stefan Ceriu f4e612ca9e feat: add thread support to the room message draft facilities
This patch adds optional thread root event id parameters to the drafting functions exposed on the room level
allowing unfinished messages to be managed for the main room as well as any inner thread.

Internally it uses the room id or a tuple of the room id and the thread as keys for the various backing stores.
2025-06-13 14:41:10 +03:00
Richard van der Hoff 6ab11a0323 Merge pull request #5219 from matrix-org/rav/megolm_sender_verification_main
crypto: new `VerificationLevel::MismatchedSender`
2025-06-12 12:44:47 +01:00
Stefan Ceriu 76626db613 chore(ffi): expose ThreadSummary num_replies on the ffi layer. 2025-06-12 14:26:40 +03:00
Benjamin Bouvier bcea1d32e6 refactor(multiverse): use a log line instead of a status message for showing intent to open a thread view 2025-06-12 13:25:08 +02:00
Benjamin Bouvier 346f11319c refactor(multiverse): move the opening of a threaded timeline to its own function 2025-06-12 13:25:08 +02:00
Benjamin Bouvier 937b223627 chore(multiverse): add missing help lines for the new functionalities 2025-06-12 13:25:08 +02:00
Benjamin Bouvier 000d8514f6 refactor(multiverse): store the selected room in the TimelineKind::Room field 2025-06-12 13:25:08 +02:00
Benjamin Bouvier 72692b7b33 feat(multiverse): add basic support for threads 2025-06-12 13:25:08 +02:00
Benjamin Bouvier 0f84d482b9 refactor(multiverse): inline send_message_impl into its own caller
Also don't clear the input if the timeline wasn't found yet.
2025-06-12 13:25:08 +02:00
Benjamin Bouvier c609150a3e refactor(multiverse): introduce RoomView::get_selected_timeline() 2025-06-12 13:25:08 +02:00
Benjamin Bouvier 2f46a6c8a0 refactor(multiverse): use Client to get a Room object by room id 2025-06-12 13:25:08 +02:00
Benjamin Bouvier 7bdddc9d35 refactor(multiverse): misc tiny changes
Notably, avoid holding a lock if it's not going to be used later.
2025-06-12 13:25:08 +02:00
Stefan Ceriu 5113f114a7 fix(ui): forward live events to threaded timelines, the same as live ones
- drop `is_live` and `is_pinned_events` and use the timeline focus directly at the decision point.
2025-06-12 12:57:10 +03:00
Stefan Ceriu 9d96d6ead2 feat(ffi): add support for sending locations as replies or within threads 2025-06-12 12:57:10 +03:00
Daniel Salinas c340a7187a feat(wasm): Fix cargo runtime on Wasm platforms (#5220)
When file was
[moved](https://github.com/matrix-org/matrix-rust-sdk/commit/2a140770a02cbeeaedc8c4dec90de9135aebc679)
it looks like an update was missed, since wasm is not in the CI yet.

Signed-off-by: Daniel Salinas
2025-06-11 22:07:54 +02:00
Richard van der Hoff 0aece695dc crypto: update changelog 2025-06-11 17:15:44 +01:00
Richard van der Hoff b2210292bf crypto: Add a test for spoofed sender, with TrustRequirement::CrossSigned 2025-06-11 17:06:44 +01:00
Richard van der Hoff f0ab6cb1a4 crypto: use a dedicated VerificationLevel if we know the sender of an event is spoofed 2025-06-11 17:06:44 +01:00
Richard van der Hoff c2eeca3f33 crypto: add some instrumentation to get_room_event_encryption_info
It helps to know which event we're getting the encryption info for.
2025-06-11 16:51:51 +01:00
Benjamin Bouvier cc974dd3c9 refactor(event cache): use Event instead of TimelineEvent more evenly 2025-06-11 17:04:15 +02:00
Benjamin Bouvier 8b2a8e7265 refactor(event cache): move the timeline-event-diffs sending back into the callers 2025-06-11 17:04:15 +02:00
Benjamin Bouvier 7cad237dc6 refactor(event cache): reduce indent in maybe_apply_new_redaction 2025-06-11 17:04:15 +02:00
Benjamin Bouvier 72a3972303 refactor(event cache): simplify mutating the RoomEvents in RoomEventCacheStore
No more generic function parameter! Having a separate function for
post-processing is simple enough.
2025-06-11 17:04:15 +02:00
Benjamin Bouvier 2e590e2f67 refactor(event cache): only mark that we've waited for an initial previous-batch token after a sync
It doesn't make sense to do it after a back-pagination, since a
back-pagination does require a previous-batch token in the first place,
meaning that if we did paginate, then we did wait for a previous-batch
token beforehand.
2025-06-11 17:04:15 +02:00
Benjamin Bouvier 224e437a78 refactor(event cache): simplify handling of previous-batch token in handle_backpagination too 2025-06-11 17:04:15 +02:00
Benjamin Bouvier 8a9cae4af3 refactor(event cache): have even fewer methods return timelinediff updates 2025-06-11 17:04:15 +02:00
Benjamin Bouvier 22a15f1342 refactor(event cache): remove code comment that doesn't make sense anymore 2025-06-11 17:04:15 +02:00
Benjamin Bouvier 3ab4584dfe refactor(event cache): have fewer methods return timelinediff updates 2025-06-11 17:04:15 +02:00
Benjamin Bouvier a3238cdadf refactor(event cache): remove indent in RoomEventCacheState::handle_sync 2025-06-11 17:04:15 +02:00
Benjamin Bouvier a884b2c696 refactor(event cache): move handling of a backpagination in RoomEventCacheState 2025-06-11 17:04:15 +02:00
Benjamin Bouvier ec0d7b4311 refactor(event cache): move handling of a sync in RoomEventCacheState 2025-06-11 17:04:15 +02:00
Benjamin Bouvier e8c2d27c9e refactor(event cache): slightly tweak logic around prev-batch token suppression 2025-06-11 17:04:15 +02:00
Benjamin Bouvier bff600a937 refactor(event cache): make deduplication entirely stateless
Having a small data structure to hold the room id and store isn't that
useful, after all.
2025-06-11 17:04:15 +02:00
Michael Goldenberg 404a982503 refactor(indexeddb): support querying by next chunk index, even when next chunk does not exist
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-11 16:09:33 +02:00
Michael Goldenberg e904a98735 refactor(indexeddb): re-type IndexedEventPositionIndex as usize as IndexedDB supports numeric keys
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-11 16:09:33 +02:00
Michael Goldenberg b55e79fdac refactor(indexeddb): re-type IndexedChunkId as u64 as IndexedDB supports numeric keys
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-11 16:09:33 +02:00
Michael Goldenberg 717116cc05 refactor(indexeddb): re-type IndexedRoomId and IndexedEventId as String for compatibility with SafeEncode
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-11 16:09:33 +02:00
Michael Goldenberg 0ad4df2031 refactor(indexeddb): remove extraneous room id field from event in event cache store
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-11 16:09:33 +02:00
Michael Goldenberg 891e9813b1 refactor(indexeddb): re-type next/previous chunk fields as chunk identifiers rather than entire chunks
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-11 16:09:33 +02:00
Michael Goldenberg 19b21fdd49 fix(indexeddb): enforce type rather than variant distinction between in-band/out-of-band events
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-11 16:09:33 +02:00
Michael Goldenberg 307fa355ad refactor(indexeddb): add internal types that support encryption and indexing in event cache store
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-11 16:09:33 +02:00
Michael Goldenberg 351053fef5 refactor(indexeddb): add internal types that support encryption and indexing in event cache store
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-11 16:09:33 +02:00
Michael Goldenberg 8c735c602a refactor(indexeddb): add internal types for event cache store
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-11 16:09:33 +02:00
Michael Goldenberg 7ffc390cea feat(indexeddb): put event cache store module behind feature flag
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-11 16:09:33 +02:00
Michael Goldenberg 05b67df6e2 feat(indexeddb): add initial database migrations for event cache store
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-06-11 16:09:33 +02:00
Denis Kasak f3f3d968b5 Merge pull request #5214 from matrix-org/update-changelog-main
Update changelog main
2025-06-11 12:18:22 +02:00
Denis Kasak bde1d4a353 Merge branch 'release-0.11' 2025-06-11 12:01:48 +02:00
Denis Kasak 4f6ddcd072 Merge pull request #5213 from matrix-org/update-changelog-sec-ref
chore: Add CVE-2025-48937 reference to the CHANGELOG
2025-06-11 11:36:16 +02:00
Denis Kasak b99188dd59 chore: Add CVE-2025-48937 reference to the CHANGELOG
Signed-off-by: Denis Kasak <dkasak@termina.org.uk>
2025-06-11 11:27:36 +02:00
homersimpsons e2fee14ced 📝 Fix changelog links
Signed-off-by: homersimpsons <guillaume.alabre@gmail.com>
2025-06-11 10:32:33 +02:00
Benjamin Bouvier 9fca8f0007 refactor(timeline): include the bundled item owner in edit aggregation metadata 2025-06-10 15:52:00 +02:00
Benjamin Bouvier ca0fc3cf6d fix(timeline): correctly use the bundled event id when handling replacements 2025-06-10 15:52:00 +02:00
Benjamin Bouvier 378f50d8b5 fix(ci): bump the timeout values for test_subscribe_to_knock_requests
They were exceedingly low, especially in the context of code coverage
which can be quite slow.
2025-06-10 14:42:27 +02:00
Damir Jelić 485bb0790e refactor: Move the store caches into the caches module 2025-06-10 13:53:09 +02:00
Damir Jelić 0e9ce0271e refactor: Create a store/types submodule 2025-06-10 13:53:09 +02:00
Damir Jelić c0294d5e33 refactor: Use the EVENT_TYPE constant to deserialize AnyDecryptedToDeviceEvent 2025-06-10 13:53:09 +02:00
Damir Jelić b41efb063e chore: Release matrix-sdk version 0.12.0 2025-06-10 13:33:01 +02:00
Damir Jelić 23db199262 Merge branch 'release-0.11' 2025-06-10 12:51:59 +02:00
Damir Jelić 76d1f8bd18 chore: Fix a PR link in the changelog file 2025-06-10 12:37:32 +02:00
Damir Jelić 550f4c5fde Update crates/matrix-sdk-crypto/CHANGELOG.md
Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
Signed-off-by: Damir Jelić <poljar@termina.org.uk>
2025-06-10 12:37:32 +02:00
Damir Jelić b3f07f4587 chore: Release matrix-sdk version 0.11.1 2025-06-10 12:37:32 +02:00
Damir Jelić 56980745b4 chore: Add a changelog entry for GHSA-x958-rvg6-956w 2025-06-10 12:37:32 +02:00
Richard van der Hoff 13c1d20482 fix(crypto): Check the sender of an event matches owner of session
Having decrypted an event with a given megolm session, we need to check that
the owner of that session actually matches the sender of an event, otherwise
there is a danger of the sender being spoofed to make it look like it was sent
by another user.

Security-Impact: High
CVE: CVE-2025-48937
GitHub-Advisory: GHSA-x958-rvg6-956w
2025-06-10 12:07:10 +02:00
Richard van der Hoff 7f3e144cb3 refactor (crypto): clarify some comments 2025-06-10 12:07:10 +02:00
Richard van der Hoff fe8bd2fdf3 refactor(crypto): Break get_or_update_verification_state in two
Split this into `get_room_event_verification_state` and
`get_or_update_sender_data`, which I think is a bit clearer.
2025-06-10 12:07:10 +02:00
Benjamin Bouvier 7cdfb0d1c0 chore(sqlite): revert the busy_timeout pragmas
Internal Sentry reports tell us that enabling the busy_timeout seems to
have *increased* the number of "database is busy" errors, instead of
lowering those. As a result, we're going to disable the pragmas in all
the places where we enabled it before, and observe how the number of
"database is busy" errors evolves.
2025-06-10 11:13:28 +02:00
Benjamin Bouvier d8652d27f8 feat(sdk): forbid ignoring the current user 2025-06-10 10:50:34 +02:00
Damir Jelić aa67148247 refactor(xtask): Use a helper to append options for the release tasks 2025-06-10 09:47:42 +02:00
Damir Jelić 769fcdb1fb feat: Support releasing a specific package 2025-06-10 09:47:42 +02:00
Damir Jelić 0f4afb32f7 refactor(xtask): Use a helper to append options for the release tasks 2025-06-10 09:34:56 +02:00
Damir Jelić 398787253d feat: Support releasing a specific package 2025-06-10 09:34:56 +02:00
Benjamin Bouvier 7a6e29c347 feat(ui): don't mark each thread reply as an actual reply to the previous message, in threaded timelines
This correctly handles the reply fallback behavior:

- the behavior isn't changed for a live timeline,
- when a timeline is thread-focused, we will extract the `replied_to`
field if and only if the thread relation is *not* marked as behaving in
a fallback manner.

This makes it possible to distinguish actual in-thread replies.
2025-06-10 09:03:30 +02:00
Damir Jelić 6e628781c0 release: Add a changelog entry for the tracing-attributes issue 2025-06-09 20:29:09 +02:00
VerdeQuar a75a2b4113 fix(crypto): Remove wildcard enum variant import
Signed-off-by: VerdeQuar <verdequar@gmail.com>
2025-06-09 20:29:09 +02:00
Damir Jelić 216e878231 Revert "feat(base): Detecting invalid states in room upgrades."
This reverts commit c7f6190cff.
2025-06-09 18:08:03 +02:00
Damir Jelić e54b20fa68 Revert "doc(base): Improve documentation. "
This reverts commit 8e3ad22d92.
2025-06-09 18:08:03 +02:00
Damir Jelić a120057ec3 Revert "fix(base): Revisit check_tombstone entirely."
This reverts commit 0478037b57.
2025-06-09 18:08:03 +02:00
dependabot[bot] 737bda44a2 chore(deps): bump crate-ci/typos from 1.32.0 to 1.33.1
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.32.0 to 1.33.1.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.32.0...v1.33.1)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.33.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-09 17:45:37 +02:00
Benjamin Bouvier df65c94974 chore(ui): apply review comments 2025-06-09 16:59:32 +02:00
Stefan Ceriu 39ee5112b4 chore(ui): add timeline threaded filtering tests 2025-06-09 16:59:32 +02:00
Stefan Ceriu 96afdeffad feat(ui): add TimelineFocus associated values for defining whether threaded events should be hidden on those particular timeline instances. 2025-06-09 16:59:32 +02:00
Stefan Ceriu 669d91e7e9 feat(ui): add thread based filtering for local event items 2025-06-09 16:59:32 +02:00
Stefan Ceriu dcec53ba00 feat(ui): filter threaded remote events based on the timeline focus and extracted relations 2025-06-09 16:59:32 +02:00
Stefan Ceriu 9d9ce4a68b chore(ui): remove relation processing when dealing with EmbeddedEvents 2025-06-09 16:59:32 +02:00
Stefan Ceriu dc24365ddf chore(ui): move event based relation handling to TimelineMetadata and out of the TimelineAction 2025-06-09 16:59:32 +02:00
Stefan Ceriu ba53390547 chore(ui): move timeline item relation handling from the TimelineAction to the TimelineMetadata 2025-06-09 16:59:32 +02:00
Stefan Ceriu c860e7fca7 fix(ui): load up reactions to threaded messages 2025-06-09 16:59:32 +02:00
Benjamin Bouvier ebcb74a86d refactor!(event cache): introduce LinkedChunkId in the backends (#5182)
In a "soon" future, threads have their own linked chunk. All our code
has been written with the fact that a linked chunk belong to *a room* in
mind, so it needs some biggish update. Fortunately, most of the changes
are mechanical, so they should be rather easy to review.

Part of #4869, namely #5122.
2025-06-09 13:26:46 +00:00
Jonas Platte 65bb20c965 refactor: Clean up tracing and formatting macro uses (#5192)
Signed-off-by: Jonas Platte <jplatte+matrix@posteo.de>
2025-06-09 12:15:59 +02:00
VerdeQuar bdda4abf56 fix(crypto): Remove wildcard enum variant import
Signed-off-by: VerdeQuar <verdequar@gmail.com>
2025-06-07 14:55:27 +02:00
Jorge Martín 47e81818bc test: Add extra test for the active call state
Also, adapt the other tests to the new return type
2025-06-06 15:02:17 +02:00
Jorge Martín fe015b7eda refactor: Expose the new return type of Client::send_call_notifications_if_needed in the FFI layer 2025-06-06 15:02:17 +02:00
Jorge Martín baf6824bc4 refactor: Add extra logs to Client::send_call_notifications_if_needed
Also make it return `Result<bool>` instead of `Result<()>` so we can check if the event was sent.
2025-06-06 15:02:17 +02:00
Jorge Martín c1ce92bf48 refactor(ffi): Use Client::room_info_notable_update_receiver instead of RoomUpdates
This allows us to also react to local changes that would take several extra seconds to be received in a new sync response.
2025-06-06 13:18:49 +02:00
Benjamin Bouvier 4eb7e0c845 feat(timeline): insert the timeline start item just after creating a live timeline
This is even better, as we don't need to look at the live pagination
status to know whether we've reached the start of the timeline or not.
2025-06-06 12:57:39 +02:00
Benjamin Bouvier 766ff3f8e9 test(timeline): add a test for the mysterious missing timeline start case 2025-06-06 12:57:39 +02:00
Benjamin Bouvier a855f1df2c fix(timeline): add the timeline start of the current pagination state too 2025-06-06 12:57:39 +02:00
Ivan Enderlin 0478037b57 fix(base): Revisit check_tombstone entirely.
This patch renames `check_tombstone` to `check_room_upgrades`.
Then it rewrites it **entirely** to remove all false-positives and
false-negatives. More importantly, the room versions are no longer
involved: they can't be compared or ordered, they must be treated as
opaque values.

This new version of `check_room_upgrades` does a first path to check
predecessor-successor consistency. Then it does a second version to
detect loops. This new algorithm is robust to absent `m.room.create`
events. Making them mandatory is left to another patch.

More tests are added, especially to ensure that `m.room.create` cannot
be overwritten, and to ensure loops or inconsistent predecessors and
successors are correctly detected.
2025-06-06 12:54:45 +02:00
Benjamin Bouvier caa07a8007 refactor(sdk): regroup bundled thread extraction in TimelineEvent ctors 2025-06-06 13:34:12 +03:00
Benjamin Bouvier 93f2c61447 feat(event cache): store a bundled thread's latest TimelineEvent if provided 2025-06-06 13:34:12 +03:00
Benjamin Bouvier c8a5c43232 feat(common): temporarily store a bundled thread's latest TimelineEvent 2025-06-06 13:34:12 +03:00
Benjamin Bouvier 2aeb1a0353 refactor(sdk): rename TimelineEvent::new to from_plaintext 2025-06-06 13:34:12 +03:00
Benjamin Bouvier a1ad772642 refactor(sdk): rename TimelineEvent::new_utd_event to from_utd 2025-06-06 13:34:12 +03:00
Benjamin Bouvier b8f850b6f2 refactor(sdk): make it clearer that Context isn't mutated in a few processor helpers
It isn't mutated by the function, so there's no need to pass a mutable
reference here.
2025-06-06 13:34:12 +03:00
Benjamin Bouvier c6ed2d1963 refactor(sdk): compute push actions before creating a decrypted TimelineEvent
This reduces the number of callers to `set_push_actions()`, which should
be minimally used.
2025-06-06 13:34:12 +03:00
Benjamin Bouvier c48a2d68d1 refactor(base): streamline the verification processor 2025-06-06 13:34:12 +03:00
Benjamin Bouvier 5a1909aab9 refactor(sdk): get rid of the implicit conversion from DecryptedEvent to TimelineEvent 2025-06-06 13:34:12 +03:00
Benjamin Bouvier fc81178504 refactor(sdk): make TimelineEvent::push_actions private
And add getters and setters. It makes it clear who are the external
readers/writers of the push actions, and it makes it impossible to
create a `TimelineEvent` out of thin air (since it now has a private
field).
2025-06-06 13:34:12 +03:00
Daniel Salinas d3f63e91d5 Enable subscribe_to_send_progress for Wasm 2025-06-06 10:08:04 +02:00
Benjamin Bouvier 70705f4e9d chore(ci): exclude some crate from codecov testing
There's been many segfaults happening in tests, while running the test
coverage for this specific crate. An issue has been opened on
cargo-tarpaulin's repository:

https://github.com/xd009642/tarpaulin/issues/1749

Until this is fixed or worked around, we'll disable coverage testing for
this specific crate.
2025-06-06 09:28:55 +02:00
Benjamin Bouvier 8c66e0ba2f chore(ci): remove unused SLIDING_SYNC_PROXY_URL env variable 2025-06-06 09:28:55 +02:00
Benjamin Bouvier f5e0c6f004 chore(ci): exclude multiverse from the codecov reports 2025-06-06 09:28:55 +02:00
Jonas Platte 2a140770a0 fix: Move runtime module from matrix-sdk-common to matrix-sdk-ffi (#5184)
This module only builds on non-wasm with the patched async-compat from
the workspace Cargo.toml's patch section, and it is only used by the ffi
crate. It is currently breaking the use of the SDK as a git dependency,
and would prevent the publishing of matrix-sdk-common (unless using
--no-verify, but then that would just break all users of the newly
published crates.io version).

This bug was introduced in
https://github.com/matrix-org/matrix-rust-sdk/pull/5089.

Signed-off-by: Jonas Platte <jplatte+matrix@posteo.de>
2025-06-05 19:30:15 +00:00
Jorge Martín becbb63ad7 feat(ffi): Subscribe to a room's RoomInfo through Client
This helps in the case we want to observe the membership state changes - or some other info - for a room that's still not known so we can't just use `Client::get_room` to fetch it.
2025-06-05 16:49:39 +02:00
Damir Jelić 34d3cd496b feat(multiverse): Show thread roots even if we can't find the latest message 2025-06-05 16:29:54 +02:00
Damir Jelić 1f9c3394c5 refactor(multiverse): Split out the timeline item formatting logic 2025-06-05 16:29:54 +02:00
Damir Jelić 005f002747 feat(multiverse): Start to render threads 2025-06-05 16:29:54 +02:00
Damir Jelić 80b8a6d8cc feat(multiverse): Allow timeline items to be selected 2025-06-05 16:29:54 +02:00
Valere f7265c39e0 cleanup: Reuse existing server.mock_sync instead of custom function 2025-06-05 14:29:50 +02:00
Valere 4468c36b14 review: extend existing MatrixMockServer instead of creating another 2025-06-05 14:29:50 +02:00
Valere 25841c787e refactor(test): Extract common crypto mock server helper 2025-06-05 14:29:50 +02:00
Damir Jelić 9461ef3a5a chore: Fix a typo in the cargo-deny file 2025-06-05 11:11:40 +02:00
Johannes Marbach b8ae210e4a feat(ffi): Add reply_params to GalleryUploadParameters (#5173)
Looks like I forgot adding this in
https://github.com/matrix-org/matrix-rust-sdk/pull/5163, sorry.
Everything below the FFI layer was already prepared for replies.

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-06-05 10:07:32 +01:00
Daniel Salinas a3225e5cd7 feat(wasm): Wasm equivalent of get_runtime_handle and corresponding tokio types (#5089)
Adds a Wasm equivalent of the get_runtime_handle method provided by
tokio, as well as Handle/Runtime types that can be used on either Wasm
or non-Wasm platforms interchangeably.

Dependent on https://github.com/matrix-org/matrix-rust-sdk/pull/5088
<!-- description of the changes in this PR -->

- [ ] Public API changes documented in changelogs (optional)

<!-- Sign-off, if not part of the commits -->
<!-- See CONTRIBUTING.md if you don't know what this is -->
Signed-off-by: Daniel Salinas

---------

Signed-off-by: Daniel Salinas <zzorba@users.noreply.github.com>
Co-authored-by: Daniel Salinas <danielsalinas@Daniels-MacBook-Pro-2.local>
Co-authored-by: Daniel Salinas <danielsalinas@daniels-mbp-2.myfiosgateway.com>
Co-authored-by: Jonas Platte <jplatte+git@posteo.de>
Co-authored-by: Ivan Enderlin <ivan@mnt.io>
2025-06-05 08:07:01 +02:00
Daniel Salinas 0777e6e08a feat(wasm): Enable subscribe_to_send_progress on wasm platforms (#5170) 2025-06-04 18:13:47 +02:00
Daniel Salinas 8b2088fd61 feat(wasm): Eliminate some unecessary wasm removals from matrix-sdk crate (#5169) 2025-06-04 18:13:27 +02:00
Johannes Marbach d38f409351 feat(ffi): Expose method for sending galleries (#5163)
Addendum to https://github.com/matrix-org/matrix-rust-sdk/pull/5125 to
allow sending galleries from the FFI crate. This is the final PR for
galleries (apart from possible enhancements to report the upload status
in https://github.com/matrix-org/matrix-rust-sdk/pull/5008).

This is somewhat lengthy again, apologies. Most of the changes are just
wrappers and type mapping, however. So I was hoping that it's relatively
straightforward to review in bulk. Happy to try and elaborate on the
changes or break them up into smaller commits if that helps, however.

---------

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-06-04 16:41:03 +02:00
Daniel Salinas 58b8a2560c feat(wasm): Comprehensive Send+Sync bound improvements for Wasm compatibility (#5082)
This commit systematically replaces Send+Sync trait bounds throughout
the matrix-rust-sdk codebase to enable Wasm compatibility while
maintaining thread safety on native targets.

Key changes:
- Use SendOutsideWasm/SyncOutsideWasm traits instead of Send/Sync for
trait bounds
- Apply conditional compilation for error types and trait objects
- Update FFI trait definitions to use Wasm-compatible bounds
- Fix event handler and type alias definitions for cross-platform
compatibility
- Maintain existing functionality while enabling WebAssembly target
support

The SendOutsideWasm/SyncOutsideWasm traits are empty on Wasm (allowing
all types) and alias to Send/Sync on native targets, ensuring zero-cost
abstraction.

Files updated:
- All FFI bindings (30+ trait definitions)
- Core SDK error types and type aliases
- Event handler infrastructure
- Store and crypto abstractions
- UI service filters and sorters
- Timeline and authentication modules

<!-- description of the changes in this PR -->

- [ ] Public API changes documented in changelogs (optional)

<!-- Sign-off, if not part of the commits -->
<!-- See CONTRIBUTING.md if you don't know what this is -->
Signed-off-by: Daniel Salinas

---------

Signed-off-by: Daniel Salinas <zzorba@users.noreply.github.com>
Co-authored-by: Daniel Salinas <danielsalinas@Daniels-MacBook-Pro-2.local>
Co-authored-by: Daniel Salinas <danielsalinas@daniels-mbp-2.myfiosgateway.com>
2025-06-04 15:59:37 +02:00
Daniel Salinas edb7a0f433 Add Wasm platform aware test to export proc-macro 2025-06-04 12:32:33 +02:00
Valere Fedronic 0f73ffde68 feat(crypto): Add the EncryptionInfo to the Decrypted ProcessedToDeviceEvent variant
The `ProcessedToDeviceEvent::Decrypted` variant now also have an
`EncryptionInfo` field.

The enum variant  changed from `Decrypted(Raw<AnyToDeviceEvent>)` to `Decrypted {
raw: Raw<AnyToDeviceEvent>, encryption_info: EncryptionInfo) }`
2025-06-04 11:54:38 +02:00
Daniel Salinas d3be744244 feat(wasm): Remove direct use of tokio::spawn in favor of matrix-sdk-common (#5159)
Mechanical move from tokio::spawn to matrix_sdk_common::executor::spawn
that has support for Wasm platforms. On non-Wasm, this shim defaults to
tokio::spawn.
2025-06-03 12:22:53 -04:00
Andy Balaam ca63d60068 doc(crypto): Add missing word 'verify' in 'verify_device' docs 2025-06-03 17:04:48 +02:00
Damir Jelić 8cc3b0fa33 refactor(multiverse): Add a common method to execute commands on rooms 2025-06-03 16:46:17 +02:00
Damir Jelić bf201e317e feat(multiverse): Add a /leave command 2025-06-03 16:46:17 +02:00
Benjamin Bouvier fe11fda832 refactor(timeline): extract the method to fetch a latest thread reply as an item
Do the ~~~loco~~ code motion 🎶
2025-06-03 16:28:25 +02:00
Benjamin Bouvier 281faa7a0b refactor(ffi): simplify From<TimelineDetails<Profile>> for ProfileDetails 2025-06-03 16:28:25 +02:00
Benjamin Bouvier 7962253ebd refactor(timeline): only use the publicly exposed content/sender/sender_profile on EmbeddedEvent 2025-06-03 16:28:25 +02:00
Benjamin Bouvier dd14df086e refactor(ffi): simplify FFI layer and use only EmbeddedEventDetails there for both thread latest event + replied-to event item 2025-06-03 16:28:25 +02:00
Benjamin Bouvier c426138971 refactor(timeline): rename RepliedToEvent to EmbeddedEvent and reuse it for the thread summary 2025-06-03 16:28:25 +02:00
Benjamin Bouvier ff4b7a8acc feat(event cache): add basic support for the latest event in the thread summary 2025-06-03 16:28:25 +02:00
Benjamin Bouvier a152f9c074 refactor(event cache): remove one caller of with_events_mut
The actual code useful in `with_events_mut` and used in that function
was to propagate the changes to the store and propagating diffs to
observers. It often striked me as hacky to use this method to do that,
so instead I'm proposing here to inline the useful bits. That way,
`with_events_mut` is now clearly called only in two cases: after a sync,
or after a successful network back-pagination.
2025-06-03 16:28:25 +02:00
Benjamin Bouvier 50be8a158c refactor(event cache): only send a thread summary update when a thread has changed 2025-06-03 16:28:25 +02:00
Benjamin Bouvier aa291079d0 feat(event cache): include the reply count in the ThreadSummary 2025-06-03 16:28:25 +02:00
Benjamin Bouvier 672bb9f460 feat: add the busy timeout pragma to the event cache store acquire() method too
It will tell us if this is sufficient to avoid locking the event cache
store database, now that we have some proof that this is happening in
the wild.
2025-06-03 16:17:36 +02:00
Kévin Commaille 9a75007535 Upgrade Ruma
Use the newly released version.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-06-03 15:52:15 +02:00
Johannes Marbach ee06965d2e feat(timeline): Expose method to send galleries in matrix-sdk-ui (#5125)
This was broken out of
https://github.com/matrix-org/matrix-rust-sdk/pull/4838 and is a step
towards implementing
https://github.com/matrix-org/matrix-spec-proposals/pull/4274. Building
upon https://github.com/matrix-org/matrix-rust-sdk/pull/4977, a new
method `Timeline::send_gallery` in matrix-sdk-ui for sending galleries.

- [x] Public API changes documented in changelogs (optional)

---------

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-06-03 15:24:02 +02:00
Ivan Enderlin 8e3ad22d92 doc(base): Improve documentation.
This patch improves an inline documentation.

Co-authored-by: Stefan Ceriu <stefan.ceriu@gmail.com>
Signed-off-by: Ivan Enderlin <ivan@mnt.io>
2025-06-03 15:04:26 +02:00
Ivan Enderlin c7f6190cff feat(base): Detecting invalid states in room upgrades.
This patch adds the `check_tombstone` response processor to
detect invalid state with room upgrades. One can check the new
`InconsistentTombstonedRooms` error to learn more about the detected
patterns.
2025-06-03 15:04:26 +02:00
Ivan Enderlin 5ccb1c6fa8 fix(base): Fix the Debug implementation of RoomUpdates.
The field of `RoomUpdates` has been renamed, but the `Debug`
implementation does not reflect that change. This patch fixes that.
2025-06-03 15:04:26 +02:00
Ivan Enderlin b77a02662d feat(base): Add RoomUpdates::iter_all_room_ids.
This patch adds the `RoomUpdates::iter_all_room_ids` method, to iterate
over all IDs of rooms that have received an update.
2025-06-03 15:04:26 +02:00
Damir Jelić 13306be4ed fix(multiverse): Wait for the join task to finish before switching the view 2025-06-03 14:07:37 +02:00
Yousef Moazzam 491f81c376 test: remove unnecessary image info field values 2025-06-03 13:19:34 +02:00
Yousef Moazzam 703c01004c test: remove unnecessary server timestamp field on sticker event 2025-06-03 13:19:34 +02:00
Yousef Moazzam b1d34763d4 test: create timeline event with EventBuilder method 2025-06-03 13:19:34 +02:00
Yousef Moazzam c4aa200f19 test: create sticker event with EventFactory 2025-06-03 13:19:34 +02:00
Yousef Moazzam a099879563 test: add reply thread relation method to sticker event builder 2025-06-03 13:19:34 +02:00
Yousef Moazzam b6569762db test: add sticker event method to EventFactory 2025-06-03 13:19:34 +02:00
Jorge Martín edabb2362b refactor: revert some method removals in SendRequest 2025-06-03 12:59:00 +02:00
Jorge Martín 8a5d4f0d82 test: Increase delay to fix code coverage failure 2025-06-03 12:59:00 +02:00
Jorge Martín 12aed8dc67 test: fix tests after adding the media config check 2025-06-03 12:59:00 +02:00
Jorge Martín 7d03d4ce0d feat(ffi): Add Client::get_max_media_upload_size function
This allows the clients to know the max upload size for a media file and try to compress if it's too large for the homeserver.
2025-06-03 12:59:00 +02:00
Jorge Martín 2680dc65fd fix(sdk): check max_upload_size before attempting to upload any media 2025-06-03 12:59:00 +02:00
Richard van der Hoff a1e2eed467 sdk: Add ClientBuilder::with_enable_share_history_on_invite (#5141)
Replace `experimental-share-history-on-invite` feature flag with a
runtime flag on the `Client`.
2025-06-03 11:36:48 +01:00
Benjamin Bouvier 5600ce7a77 fix(event cache): after adding a thread summary in memory, also save the result in the store 2025-06-03 12:22:21 +02:00
Benjamin Bouvier f28a2b1cc3 fix(event cache): after redacting an in-memory event, also save the result in the store 2025-06-03 12:22:21 +02:00
Benjamin Bouvier 1a07ec22b8 test(event cache): move tests which require the cross process lock under a different test mod
This avoids a few `use` statements within the test functions themselves,
and helps finding out which tests are integration tests.
2025-06-03 12:22:21 +02:00
dependabot[bot] 7b8671d82c chore(deps): bump tj-actions/changed-files
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from c6634ca281a9fc05b03bee224ba00910cb78ab6e to 115870536a85eaf050e369291c7895748ff12aea.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](https://github.com/tj-actions/changed-files/compare/c6634ca281a9fc05b03bee224ba00910cb78ab6e...115870536a85eaf050e369291c7895748ff12aea)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-version: 115870536a85eaf050e369291c7895748ff12aea
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-03 11:17:12 +02:00
Daniel Salinas 0f877a3e80 feat(wasm): Replace async_trait with wasm aware cfg conditional 2025-06-02 23:07:05 +02:00
Daniel Salinas c6e55c1a36 Mechanical move from target_arch="wasm32" to target_family="wasm" 2025-06-02 17:27:34 +02:00
Benjamin Bouvier 40648f6998 chore: formatting, clippy and review comments 2025-06-02 16:24:25 +02:00
Benjamin Bouvier 58b46813d6 feat(event cache): when we see a thread reply, add a thread summary to the thread root 2025-06-02 16:24:25 +02:00
Benjamin Bouvier f2a90cb921 test: use helpers to create a bundled edit and thread summary 2025-06-02 16:24:25 +02:00
Benjamin Bouvier efda26ef6f feat(timeline): forward the TimelineEvent::thread_summary to timeline items
Right now, we're not passing the latest event yet, but we could improve
that later!
2025-06-02 16:24:25 +02:00
Benjamin Bouvier 433209ca9b feat(common): always extract a bundled thread summary when creating or deserializing a timeline event 2025-06-02 16:24:25 +02:00
Benjamin Bouvier 1680891982 feat(common): add helper to extract a bundled thread summary 2025-06-02 16:24:25 +02:00
Benjamin Bouvier 3c965ed56e feat(common): add a ThreadSummary field to TimelineEvent
It can be either known to be there or not there, or in an unknown state,
hence a tiny enum making this very explicit.
2025-06-02 16:24:25 +02:00
Benjamin Bouvier 0bb2c44254 feat(common): don't serialize push actions if there's none
If the vec of actions is defined (Some) but empty, then it means we
could determine there are no push actions. It is a slight optimization
to not serialize them, in this case; otherwise, this can result in many
serialized empty action vector, in the event cache's persistent storage.
2025-06-02 16:24:25 +02:00
Benjamin Bouvier 23cdcaf5c8 feat(common): add helpers to extract only the thread root from a raw event 2025-06-02 16:24:25 +02:00
Benjamin Bouvier ebc7396334 refactor(sdk): remove unused TimelineEvent ctor
It was intended for test purposes, but it seems it's unused, so let's
get rid of it.
2025-06-02 16:24:25 +02:00
Benjamin Bouvier c7234b6f13 doc: make the self type clearer in doc comment of TimelineEvent 2025-06-02 16:24:25 +02:00
Richard van der Hoff 56be9dec59 multiverse: expand user names when inviting (#5146)
When the user does an `/invite`, if the target user doesn't start with
an `@`, try expanding it as a user on the local server.

This makes it much easier when repeatedly testing inviting!
2025-06-02 14:41:20 +01:00
Richard van der Hoff 2fa6a98052 multiverse: write logs to session dir (#5145)
Rather than always writing the logs to `/tmp`, write them to the session
directory. The session directory defaults to `/tmp` so by default this
will do the same as before, but if you override the session path on the
commandline, the logs will get stored alongside the stores and caches.

This is particularly useful when running two instances of multiverse,
and you want them to put their logs in different places.
2025-06-02 14:41:11 +01:00
Yousef Moazzam f03407fdb9 test: remove unnecessary server timestamp fields on events 2025-06-02 14:59:23 +02:00
Yousef Moazzam fd4fd3c4f9 test: add timeline events in bulk to room builder 2025-06-02 14:59:23 +02:00
Yousef Moazzam a9513e6f73 test: remove import of unused sync_timeline_event! macro in timeline subscribe test module 2025-06-02 14:59:23 +02:00
Yousef Moazzam 5f0bcef5ce test: create events with EventFactory in profile updates test 2025-06-02 14:59:23 +02:00
Yousef Moazzam 96049f41a5 test: create events with EventFactory in event filter test 2025-06-02 14:59:23 +02:00
Daniel Salinas ba8998623e Fix annotations on comments for new StreamExt
Hook up use of new extension in matrix-sdk-ffi crate
2025-06-02 12:26:52 +02:00
Daniel Salinas d6bf3019f9 StreamExt that supports wasm platforms
The .boxed() method requires a Send trait that makes it
unusable on Wasm platforms. This re-exports the existing StreamExt,
while providing a new extension for the wasm family of targets.
2025-06-02 12:26:52 +02:00
Damir Jelić ef037631a1 refactor(crypto): Simplify the checking of device keys when decrypting Olm events 2025-05-30 15:45:27 +02:00
Damir Jelić 42ade32bea feat(sdk): Fallback to the device keys in the Olm event for room key bundles
This makes it possible to correctly accept a historic room key bundle if
we previously didn't know about the device of the sender of the bundle.
2025-05-30 15:45:27 +02:00
Damir Jelić 37d7e26929 feat(sdk): Ensure that we have all the devices of a user we invite
This makes it possible to correctly share a historic room key bundle with the
invited user without sharing another room with the user.
2025-05-30 15:45:27 +02:00
Robin e51dceb399 refactor(widget): Improve wording of ApiVersion docs 2025-05-30 15:32:35 +02:00
Robin cee0129225 feat(widget): Distinguish room state and timeline events
This is an implementation of an update to MSC2762 (https://github.com/matrix-org/matrix-spec-proposals/pull/4237). It changes the widget driver to split state updates out into an `update_state` action and use the `send_event` action exclusively for forwarding timeline events. Accordingly, `read_events` now always reads from /messages, never the state store.
2025-05-30 15:32:35 +02:00
Robin ef36665d7d doc(widget): Note that capability renegotiation is unimplemented 2025-05-30 15:32:35 +02:00
Robin e45e357841 refactor(widget): Extract method for processing requested capabilities 2025-05-30 15:32:35 +02:00
Robin 6657501ef4 refactor(widget): Extract method for acquire capabilities response 2025-05-30 15:32:35 +02:00
Robin 69e8f1e86c refactor(widget): Extract method for message-like event reading response 2025-05-30 15:32:35 +02:00
Robin e613fc269f refactor(widget): Remove 'Matrix' from some identifiers
Just making these a bit less verbose and more consistent with surrounding identifiers.
2025-05-30 15:32:35 +02:00
Robin 5c7566c6c9 refactor(widget): Allow state events to be converted to filter inputs
So that when I need to do this (in later commits) I don't have to cast.
2025-05-30 15:32:35 +02:00
Robin aba0adf18d refactor(tests): Move some JSON into static items
I want to use this JSON in multiple tests.
2025-05-30 15:32:35 +02:00
Robin 2c8e71e560 refactor(tests): Allow converting EventBuilder to state 2025-05-30 15:32:35 +02:00
Robin 6f683d3cde refactor(tests): Accept more types for sync builder state events
Refactoring the test event implementation to use the From trait rather than ad-hoc methods along the way.
2025-05-30 15:32:35 +02:00
Jonas Platte 9242d1869a refactor: Use native async fn in traits for BackingStore 2025-05-30 10:48:06 +02:00
Daniel Salinas 59d632fd45 feat(wasm): Improve wasm join handle to implement more tokio methods (#5088)
This change adds support for the abort/abort_handle/is_finished methods
onto the JoinHandle shim for Wasm targets.

Signed-off-by: Daniel Salinas
2025-05-29 14:22:38 +00:00
Richard van der Hoff c55652d327 crypto: changelog fixes (#5136)
Put one change in the right place in the changelog, and add missing PR
links.
2025-05-29 14:22:00 +01:00
Jonas Platte 0220689964 refactor: Wrap EncryptionInfo in Arc
It's >100 bytes large and often optional, so it makes sense to put it
on the heap to reduce the size of structs with such optional fields,
and the stack size of functions with such optional parameters.
It's also cloned in a couple places in the UI crate, so it probably
makes sense to just always refcount it.

This started as a clippy suggestion to box PendingEdit inside
AggregationKind::Edit.
2025-05-29 14:08:10 +02:00
Jonas Platte d8969db30a refactor: Increase readability of WidgetMachine::process
Based on a clippy suggestion.
2025-05-29 13:19:59 +02:00
Jonas Platte 8eec683793 refactor: Use inline format arguments more
Automated with cargo clippy --fix --workspace --all-targets.
2025-05-29 13:19:59 +02:00
Jonas Platte 4705389ab7 refactor: Use native async fn in traits for testing traits 2025-05-29 11:45:16 +02:00
Damir Jelić db931c5d5c chore: Bump our decancer version
This removes the paste dependency decancer had. We still need to have a
denyc exception for paste because of rmp-serde and ratatui.
2025-05-29 11:40:37 +02:00
Jonas Platte 9a0b56ad1a refactor(ci): Don't rerun most CI jobs when un-drafting a PR
This only makes sense to do for workflows that branch off of
github.event.pull_request.draft, which only bindings_ci.yml does at this
point in time.
2025-05-29 11:05:03 +02:00
Jonas Platte 3c20ee41d6 chore: Fix clippy lints 2025-05-29 10:59:56 +02:00
Jorge Martín ed245a0cf0 refactor(ffi): When mapping ffi::StateEventContent, log any unsupported event types
The same was done for unsupported message-like event contents.
2025-05-29 09:54:39 +02:00
Jorge Martín 4626c4caaf refactor(ffi): When mapping ffi::MessageLikeEventContent, log any unsupported event types
At the moment, the logs just say 'Unsupported Event Type', which is not that helpful.
2025-05-29 09:54:39 +02:00
Ivan Enderlin 7cda6d2ea6 chore(sdk): Add more logs for Room::leave.
This patch adds a bit more logs in `Room::leave` to understand what's
happening for some users.
2025-05-28 14:03:05 +02:00
Jorge Martín 2ec15984a8 test: Improve NotificationClient tests using the modern test utils 2025-05-28 12:32:58 +02:00
Yousef Moazzam 01f035d574 Test: Replace sync_timeline_event! with EventFactory for events in event item tests (#5093)
Part of #3716

I did notice that the `sender` and `member` methods have some overlap in
what values get set for the "sender" and "state key", and I tried to
make sure that in my changes to use `EventFactory` the original event
configuration is being replicated, so please do double-check me on that
note in particular.

Signed-off-by: Yousef Moazzam <yousefmoazzam@hotmail.co.uk>
2025-05-28 10:48:25 +02:00
Damir Jelić 53b01fb8a5 test: Enable the historic room key bundle storage test, except on WASM
The test was ignored since the functionality was only implemented for
the memory and SQLite store. This caused a bug in the SQLite
implementation to go unnoticed.

Let's just disable it for WASM since this is the only place where we
didn't yet implement the necessary methods.
2025-05-27 18:15:36 +02:00
Jorge Martín 79c47b4470 fix(sdk): Handle 520 HTTP status code as a permanent error
The status code is usually returned by Cloudflare to indicate an unknown server error, so we should cancel the upload and let the user retry if they want to.
2025-05-27 17:56:44 +02:00
Damir Jelić 089abec866 fix(sdk): Don't require the invite details to be present when accepting an room invite 2025-05-27 17:46:10 +02:00
Damir Jelić 064fd6cb0b fix(sqlite): Use the correct column name for the sender of bundled room keys 2025-05-27 17:46:10 +02:00
Damir Jelić 60d3b3d56b test: Finish up the shared history integration test 2025-05-27 17:46:10 +02:00
Damir Jelić 995838d9d3 refactor(tests): Move the shared history test into its own module 2025-05-27 17:46:10 +02:00
Damir Jelić 1d4d4bc741 refactor(tests): Create a submodule for the end-to-end encryption integration tests 2025-05-27 17:46:10 +02:00
Damir Jelić 41a5fd90f4 feat(tests): Add a macro to assert that a TimelineEventKind was encrypted 2025-05-27 17:46:10 +02:00
Damir Jelić 3d9d619f8b feat(sdk): Import the room keys we download from the shared history bundle 2025-05-27 17:46:10 +02:00
Damir Jelić af38f0d1ee refactor(crypto): Move the room key bundle import method under the store 2025-05-27 17:46:10 +02:00
Damir Jelić 091a5fb354 refactor(crypto): Clarify some things in the room key bundle import logic 2025-05-27 17:46:10 +02:00
Richard van der Hoff 8a1d6ce0eb feat(sdk): Attempt to download room key bundles when we accept an invite 2025-05-27 17:46:10 +02:00
Ivan Enderlin 0f5f24527b chore(ffi): Remove Room::is_tombstoned.
This patch removes the `Room::is_tombstoned` method as using
`Room::successor_room` plays the same role and its returned value is
always useful.
2025-05-27 17:35:31 +02:00
Ivan Enderlin da60c1488e feat(ffi): Add the DeduplicateVersions room list filter.
This patch adds `RoomListEntriesDynamicFilterKind::DeduplicateVersions`
to use `new_filter_deduplicate_versions`.
2025-05-27 17:35:31 +02:00
Ivan Enderlin f65893d65e doc(base): Remove a useless link reference. 2025-05-27 17:35:31 +02:00
Ivan Enderlin 3e89b6b8f9 feat(ffi): Add SuccessorRoom and PredecessorRoom.
This patch removes `RoomTombstoneInfo` and replaces it by
`SuccessorRoom`. This patch renames `RoomInfo::tombstone` to
`RoomInfo::success_room`.

This patch also implements `Room::successor_room()` and
`Room::predecessor_room()`, and adds documentation.
2025-05-27 17:35:31 +02:00
Mauro 7531167824 feat: Let the media preview config return an optional
This allows applications to decide what they'd like to do if there isn't a value present. It allows the application to decide what the default should be.
2025-05-27 16:24:41 +02:00
Ivan Enderlin d24e269ecc doc(ui): Fix typos. 2025-05-27 14:02:21 +02:00
Ivan Enderlin 88c18c5499 feat(ui): New Room List filter: deduplicate_versions.
This patch adds the new `deduplicate_versions`. This new filter will
filter out room versions that are outdated. Only the “active” versions
are kept.

A room version is considered active if and only if:

* the room is joined and has no successor,
* the room is joined and has a successor room that is invited or knocked,
* the room is left, invited, banned or knocked.

All other rooms are filtered out.
2025-05-27 14:02:21 +02:00
Ivan Enderlin d0f1e6ce6d chore(base): Expose the new SuccessorRoom and PredecessorRoom types.
This patch makes the `SuccessorRoom` and `PredecessorRoom` types public.
2025-05-27 14:02:21 +02:00
Johannes Marbach 10668f20b0 feat(send_queue): Implement sending of MSC4274 galleries (#4977)
This was broken out of
https://github.com/matrix-org/matrix-rust-sdk/pull/4838 and is a step
towards implementing
[MSC4274](https://github.com/matrix-org/matrix-spec-proposals/pull/4274).

* The entry point for sending galleries via the send queue is a new
method
[`RoomSendQueue::send_gallery`](https://github.com/matrix-org/matrix-rust-sdk/pull/4977/files#diff-8752e86459c22cff470d7ca617240dcbdf222c3c6c98be2af2e43ddec071154cR362)
which is a generalization of `RoomSendQueue::send_attachment`.
* `send_gallery` takes as input parameters a
[`GalleryConfig`](https://github.com/matrix-org/matrix-rust-sdk/pull/4977/files#diff-d38d74cec6159cf769c32bed496199146175f05428fc23ab13bc2c629900da3eR283)
(containing info about the gallery itself, such as its caption) and a
vector of
[`GalleryItemInfo`](https://github.com/matrix-org/matrix-rust-sdk/pull/4977/files#diff-d38d74cec6159cf769c32bed496199146175f05428fc23ab13bc2c629900da3eR355)s
(containing info about each image / file / etc. in the gallery).
* `send_gallery` creates the gallery event content via
[`Room:make_message_event`](https://github.com/matrix-org/matrix-rust-sdk/pull/4977/files#diff-474f20e47fdcf60feac3f839a81f82fbb2c1fd0bb406388b0262adb60216ce3bR2281)
which was renamed from `make_attachment_event` to reflect the fact that
it creates general `msgtype` events now.
* `send_gallery` maps the passed item infos into
[`GalleryItemQueueInfo`](https://github.com/matrix-org/matrix-rust-sdk/pull/4977/files#diff-d569f54c7901b5cd660aae77045c80dabbd3c251f0fa5891530469f35941327aR2008)s.
This additional struct allows grouping all the metadata for a single
gallery item together.
* Finally `send_gallery` invokes
[`QueueStorage::push_gallery`](https://github.com/matrix-org/matrix-rust-sdk/pull/4977/files#diff-d569f54c7901b5cd660aae77045c80dabbd3c251f0fa5891530469f35941327aR1294)
which is a generalization of `QueueStorage::push_media`.
* `send_gallery` pushes upload requests for the media and thumbnails to
the queue in a "daisy chain" manner. The first thumbnail (or media if no
thumbnail exists) is pushed as a `QueuedRequestKind::MediaUpload`. The
remaining thumbnails and media are pushed as
`DependentQueuedRequestKind::UploadFileOrThumbnail`s while chaining each
request to the previous one.
* Finally a
[`DependentQueuedRequestKind::FinishGallery`](https://github.com/matrix-org/matrix-rust-sdk/pull/4977/files#diff-42a1a7ebe2446d916c9e013293bafc5eb16fd89bfe89248e3877ef031cc83ef2R265)
is pushed to finalize the gallery upload (analogous to the existing
`FinishUpload` for single media uploads).
* The `FinishGallery` request is handled in
[`QueueStorage::handle_dependent_finish_gallery_upload`](https://github.com/matrix-org/matrix-rust-sdk/pull/4977/files#diff-8752e86459c22cff470d7ca617240dcbdf222c3c6c98be2af2e43ddec071154cR628)
which was modeled after `handle_dependent_finish_upload`.
* To be able to map the temporary event source for each gallery item to
the final `AccumulatedSentMediaInfo`, a hash map is used.
* Using the hash map, the gallery event is then updated to use the
actual media sources via
[`update_gallery_event_after_upload`](https://github.com/matrix-org/matrix-rust-sdk/pull/4977/files#diff-8752e86459c22cff470d7ca617240dcbdf222c3c6c98be2af2e43ddec071154cR104)
and a new method
[`Room::make_gallery_item_type`](https://github.com/matrix-org/matrix-rust-sdk/pull/4977/files#diff-474f20e47fdcf60feac3f839a81f82fbb2c1fd0bb406388b0262adb60216ce3bR2303)
* An [integration
test](https://github.com/matrix-org/matrix-rust-sdk/pull/4977/files#diff-21532ad5467a69489d7913b8da7afd4d618b7e357ce94f769e6e60e395b58055R2048)
has been added to demonstrate the functionality.

This is relatively large, unfortunately, but including everything needed
to actually send the event made it possible to also add a test for it.
It would be nice if the amount of new code could be reduced but I'm
struggling a bit to find ways to integrate galleries with the existing
media uploads further.

- [x] Public API changes documented in changelogs (optional)

---------

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-05-27 11:53:05 +02:00
Ivan Enderlin 2537f0a508 refactor(base): Create dispatch_room_member.
This patch extracts a bit of code from `dispatch` in a new response
processor: `dispatch_room_member`. The idea is to clarify the content of
`dispatch` itself.
2025-05-27 10:00:16 +02:00
Ivan Enderlin 1387772589 refactor(base): Remove a BTreeSet clone.
This patch moves the insertion of `new_user_ids` in
`updated_members_in_room` after the use of `new_user_ids` by
`update_or_set_if_room_is_newly_encrypted`. By moving it after, it saves
the need to clone it entirely, thus saving memory allocations.
2025-05-27 10:00:16 +02:00
Ivan Enderlin c4f9ef2e2e refactor(base): Improve dispatch_and_get_new_users.
This patch renames `dispatch_and_get_new_users` to `dispatch`. This
response processor no longer returns the new user IDs, but they are
written in a mutable reference argument. This argument is
typed by a new private trait: `NewUsers`. This trait is implemented on
`BTreeSet<OwnedUserId>` and on `()`. It helps to avoid allocations when
the new users are not needed.
2025-05-27 10:00:16 +02:00
Benjamin Bouvier acce75a6dc test: make the test for the previous commit more realistic
This reproduces the issue originally described, where it wasn't possible
to run a /members query in an event handler, because the sync lock was
taken at this point.
2025-05-27 09:09:41 +02:00
aeoncl 5e0704a7c7 fix(sliding sync): don't take the sync lock while handling events
Fixes #5091.
2025-05-27 09:09:41 +02:00
Benjamin Bouvier 206857bc9e feat(ffi): remove the state store when clearing caches
This is a dangerous operation, and requires that the sync service must
be stopped while we're touching this. Since this is an internal method,
that shouldn't be used in most clients anyways (as it usually papers
over actual issues happening elsewhere, on the server for instance), I
kept it simple with a scary doc comment explaining the preconditions,
but we could assert them at runtime, with a little bit more effort.
2025-05-27 08:59:59 +02:00
Benjamin Bouvier 9e1ea5d7d3 feat(sdk): expose the state store database name 2025-05-27 08:59:59 +02:00
Hugh Nimmo-Smith e90b105b36 feat(sdk): Add support for MSC4286
This patch updates Ruma to include support for the mx-external-payment-details span attribute from MSC4286.
2025-05-27 08:47:56 +02:00
dependabot[bot] 40ffd404e8 chore(deps): bump tj-actions/changed-files
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 480f49412651059a414a6a5c96887abb1877de8a to c6634ca281a9fc05b03bee224ba00910cb78ab6e.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](https://github.com/tj-actions/changed-files/compare/480f49412651059a414a6a5c96887abb1877de8a...c6634ca281a9fc05b03bee224ba00910cb78ab6e)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-version: c6634ca281a9fc05b03bee224ba00910cb78ab6e
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-26 17:05:29 +02:00
Benjamin Bouvier b6b9dc8c1a fix(test): make the test_monthly_divider_mode test pass all on timezones 2025-05-26 15:39:04 +02:00
Benjamin Bouvier 4a397f5ab4 refactor(ffi): pass the UTD hook when constructing a Room from the room list (again!) 2025-05-26 14:48:16 +02:00
Benjamin Bouvier be927730fa refactor(ffi): get rid of RoomListItem
It is simply a Room now.
2025-05-26 14:48:16 +02:00
Benjamin Bouvier 81036da44c refactor(ffi): rename the SDK Room import to SdkRoom 2025-05-26 14:48:16 +02:00
Benjamin Bouvier b016f60e46 refactor(ffi): move methods from RoomListItem to Room
Only code motion. Most methods already existed on the `Room` impl block.
2025-05-26 14:48:16 +02:00
Benjamin Bouvier cd76cec089 refactor(ffi): also pass the UTD hook to the notification client 2025-05-26 14:48:16 +02:00
Benjamin Bouvier 820c73dd2f refactor(ffi): get rid of the timeline stored in a Room 2025-05-26 14:48:16 +02:00
Benjamin Bouvier 42a4a6c1a8 refactor(ui): get rid of the RoomListService::Room wrapper
It's just a room, now! Since there were some users of the
`latest_event()` that returned an `EventTimelineItem`, I kept this
method as a method in the Room trait extension that's in the UI crate,
so it's still convenient to use.
2025-05-26 14:48:16 +02:00
Ivan Enderlin eec9a067be fix(sdk): Continue leaving a room if server replies with 403.
This patch updates `Room::leave` to not early return when the server
returns a 403 error code on `/leave`. Indeed, if the user doesn't have
the permissions to leave a room, it's impossible for they to leave it.
Let's consider it's fine to ignore this particular error and continue
the process of leaving the room.
2025-05-26 14:42:17 +02:00
Jorge Martín e53eaf4213 refactor: Fetch the ignored user list account data from the store for Client::is_user_ignored
Also move it to `BaseClient` instead.
2025-05-26 13:02:58 +02:00
Jorge Martín a3725b6b24 fix: Do not retrieve notification events sent by ignored users 2025-05-26 13:02:58 +02:00
Stefan Ceriu f0c7370637 feat(ffi): expose the inviter directly on the room to avoid having to fetch the room preview
The room preview doesn't actually contain the inviter but instead retrieves it from the room invite details.
2025-05-24 10:49:44 +03:00
Yousef Moazzam f108840e28 test: put repeated event field values into variables 2025-05-23 14:12:14 +01:00
Yousef Moazzam f4f63a7e41 test: create plain text room event with EventFactory 2025-05-23 14:12:14 +01:00
Ivan Enderlin 26afd890ce test(base): Use EventFactory instead of JSON hardcoded value.
This patch updates two tests to use the `EventFactory` to replace JSON
hardcoded values to represent the events.
2025-05-23 11:35:53 +02:00
Ivan Enderlin bfac815a5e test: EventBuilder<RoomCreateEventContent> can set predecessor.
This patch adds the `predecessor` and `no_predecessor` methods on
`EventBuilder<RoomCreateEventContent>`. This is helpful to configure the
`predecessor` field.
2025-05-23 11:35:53 +02:00
Ivan Enderlin 95e8d0589b test: Fix EventFactory::create.
This patch fixes `EventFactory::create` where the `m.room.create` wasn't
created as a state-event (the `state_key` field was missing).

Also, it uses the `creator_user_id` in the `sender` field if no sender
was given.
2025-05-23 11:35:53 +02:00
Ivan Enderlin 16a923edda test: Simplify EventBuilder::into_raw_timeline.
This patch simplifies `EventBuilder::into_raw_timeline`. It is exactly
like `EventBuilder::into_raw`, so let's use it.
2025-05-23 11:35:53 +02:00
Ivan Enderlin 9c227c2321 feat(base): Add Room::successor_room and Room::predecessor_room.
First off, this patch renames `Room::tombstone` to
`Room::tombstone_content` (to be consistent with other methods, such as
`Room::create_content`).

Second, this patch adds the `Room::successor_room` and
`Room::predecessor_room` methods, along with the `SuccessorRoom` and
`PredecessorRoom` types. This naming more or less comes from the Matrix
specification:

- the term _predecessor_ is part of the specification,
- the term _successor_ isn't present _per se_, the words _replacement
  room_ are used instead, but I prefer _successor_ as it brings a nice
  symmetry with _predecessor_.
2025-05-23 11:35:53 +02:00
Ivan Enderlin 5d617da74c chore(base): Move the Room::*tombstone* methods in new tombstone module.
This patch moves the `Room::is_tombstoned` and `Room::tombstone` methods
in the new `tombstone` module.
2025-05-23 11:35:53 +02:00
Jonas Platte 3aa356dcd6 chore: Use shorter syntax for workspace inheritance where possible 2025-05-23 10:23:36 +02:00
Jonas Platte 491f7cd529 chore: Clean up Cargo.toml formatting 2025-05-23 10:23:36 +02:00
Timo 6d5ad4eddc feat(widget-driver): Add to-device support
The widget Driver should be able to send and receive to-device events.
This is useful for element call encryption keys.

This PR focusses on the widget driver and machine logic. To
send/communicate the events from the widget to the driver.

It skips any encryption logic. Some of the encryption logic will be part
of crypto crate and the code in the widget driver crate should be kept
minimal once the crypto crate is ready.

---------

Co-authored-by: Valere <bill.carson@valrsoft.com>
2025-05-22 13:38:28 +02:00
Yousef Moazzam ec638e017b test: remove import of unused sync_timeline_event! macro in shield test 2025-05-22 12:46:56 +02:00
Yousef Moazzam 8f693f4615 test: create plain text room event with EventFactory 2025-05-22 12:46:56 +02:00
Jorge Martín fe8f77ed93 refactor: when leaving an Invited room also forget it
This behaviour was added only at the `RoomPreview::leave` method, but since we're slowly moving away from it we should move the forget action to the `Room::leave` method instead
2025-05-22 12:07:19 +02:00
Benjamin Bouvier 5c6238f132 chore(deps): bump wasm-bindgen-test 2025-05-22 11:50:13 +02:00
Benjamin Bouvier a5b932d086 chore(base): rename config file to config.toml
There was a warning in the console about this, when running the
following: cargo xtask ci wasm-pack matrix-sdk-base
2025-05-22 11:50:13 +02:00
Benjamin Bouvier b0e8b8a532 chore(deps): bump web-sys 2025-05-22 11:50:13 +02:00
Benjamin Bouvier 2984030f90 chore(ffi): configure the sentry dependency differently on android
On Android, we should use rustls instead of native-tls; this requires
unsetting the default features of the `sentry` crate, and specifying
them by hand instead.

For consistency, I've done the same for the non-android sentry
dependency.
2025-05-22 11:50:13 +02:00
Benjamin Bouvier a3c82e9087 chore(deps): bump reqwest to 0.12.15 2025-05-22 11:50:13 +02:00
Benjamin Bouvier 56082f93d0 chore(sdk): forward "database is busy" errors to sentry 2025-05-22 11:50:13 +02:00
Benjamin Bouvier c8474511a7 feat(ffi): add support for sentry logging 2025-05-22 11:50:13 +02:00
Ivan Enderlin 1348525447 test(base): Fix a test. 2025-05-21 17:26:52 +02:00
Ivan Enderlin 56f9b2d9f6 chore(base): Rename rooms to room.
This patch renames the `rooms` module into `room`. It contains a single
kind of `Room`.
2025-05-21 17:26:52 +02:00
Ivan Enderlin b18680b853 test(base): Use SystemTime from web_time for Wasm. 2025-05-21 17:26:52 +02:00
Ivan Enderlin 72b2763dad chore(base): Run rustfmt from nightly. 2025-05-21 17:26:52 +02:00
Ivan Enderlin 7278a36704 chore(base): Move Room::get_member_hints in the display_name module.
This patch moves the `Room::get_member_hints` method inside the newly
created `display_name` module. That way it is isolated from the rest of
the codebase.
2025-05-21 17:26:52 +02:00
Ivan Enderlin bd9a895089 chore(base): Move Room::*room_call* methods in the new call module.
This patch moves the `Room::has_active_room_call` and
`Room::active_room_call_participants` methods into the new `call`
module. This patch also moves the associated tests in this new module.
2025-05-21 17:26:52 +02:00
Ivan Enderlin a71e7923e0 chore(base): Move the Room::MAX_ENCRYPTED_EVENTS constant in latest_event.
This patch moves the declaration of the `Room::MAX_ENCRYPTED_TESTS`
constants in the `latest_event` module.
2025-05-21 17:26:52 +02:00
Ivan Enderlin 2433e91a6c chore(base): Move the m.room.create wrapper types in the new create module.
This patch moves the `m.room.create` wrapper types, aka
`RoomCreateWithCreatorEventContent` type and siblings, in the new
`create` module.
2025-05-21 17:26:52 +02:00
Ivan Enderlin 48d2a1543c chore(base): Move code inside the same module.
This patch puts tests at the end of the file.
2025-05-21 17:26:52 +02:00
Ivan Enderlin bcd75362f7 chore(base): Move Room::*knock* methods into the new knock module.
This patch moves the `Room::*knock*` methods into the new `knock`
module.

The idea is to group API by “theme” to get smaller modules and more
organised code.
2025-05-21 17:26:52 +02:00
Ivan Enderlin f4488e42a2 chore(base): Move Room::state and sibling types into new state module.
This patch moves the `Room::state` method, along with the ``RoomState`
and `RoomStateFilter` types into the new `state` module. This patch also
moves the tests in this new module.

The idea is to group API by “theme” to get smaller modules and more
organised code.
2025-05-21 17:26:52 +02:00
Ivan Enderlin 3d13b60b68 chore(base): Merge rooms::normal into rooms.
This patch merges the `rooms/normal.rs` file into `rooms/mod.rs`.

The name `normal` is present for historical reasons that no longer stand
today. This is no needed anymore. Let's simplify the modules.
2025-05-21 17:26:52 +02:00
Ivan Enderlin f1c54d1e27 chore(base): Move tests in the correct module.
This patch move tests about the `RoomDisplayName` in the newly created
`display_name` module.
2025-05-21 17:26:52 +02:00
Ivan Enderlin 27edb163e9 chore(base): Move tests in the correct module.
This patch moves tests about `RoomNotableTags` in the newly created
`tags` module.
2025-05-21 17:26:52 +02:00
Ivan Enderlin 6d9d202701 chore(base): Move Room::*latest_event*() inside the new latest_event module.
This patch moves everything related to the `Room` latest event API
inside the new `latest_event` module. This patch also moves the tests.
The idea is to get a smaller `rooms::normal` module, and to clarify the
code by grouping `Room` APIs by “theme”.
2025-05-21 17:26:52 +02:00
Ivan Enderlin 61d99ef709 chore(base): Move everything related to Room members in members.
This patch moves all types and methods used by or implemented on `Room`
inside the existing `members` module.
2025-05-21 17:26:52 +02:00
Ivan Enderlin 6f031261d5 chore(base): Move Room::is_favourite and ::is_low_priority into new tags module.
This patch moves the  Room::is_favourite` and `::is_low_priority`, along
with the `RoomNotableTags` into the new `tags` module. This patch also
moves the tests in this new module.

The idea is to group API by “theme” to get smaller modules and more
organised code.
2025-05-21 17:26:52 +02:00
Ivan Enderlin 4a57044680 chore(base): Move Room::encryption_* mtehods into new encryption module.
This patch moves the `Room::encryption_state()` and
`Room::encryption_settings()` methods, and the sibling types, into a new
`encryption` module. This patch also moves the tests in this new module.
The idea is to group `Room` API by “theme” in modules, to clarify the
code.
2025-05-21 17:26:52 +02:00
Ivan Enderlin d5c0e209fc chore(base): Move Room::display_name() and siblings into new display_name module.
This patch moves the `Room::display_name()` method, and all the
siblings, into a new `display_name` module. It includes types like
`RoomDisplayName`, `UpdatedRoomDisplayName`, `RoomSummary` etc. This
patch also moves the tests in this new module. The idea is to group
`Room` API by “theme” in modules, to clarify the code and to make the
big `rooms::normal` module smaller.
2025-05-21 17:26:52 +02:00
Ivan Enderlin 737654549b chore(base): Move the Room's room info related methods and tests in room_info.
This patch moves the `Room::subscribe_info`, `::clone_info` and
`::set_room_info` methods from the `rooms::normal` module to the
`rooms::room_info` module. This patch also moves the tests related to
room info inside `room_info`.
2025-05-21 17:26:52 +02:00
Ivan Enderlin cc34603864 chore(base): Move RoomInfoNotableUpdate* inside room_info.
This patch moves the `RoomInfoNotableUpdate` and
`RoomInfoNotableUpdateReasons` types inside the `room_info` module.
2025-05-21 17:26:52 +02:00
Ivan Enderlin bad82ccd42 doc(ui): Explain why m.room.create is necessary.
This patch explains why having `m.room.create` is necessary. It's not
only about the room previews, but also about the room versions, and thus
the tombstoned rooms.
2025-05-21 17:26:52 +02:00
Ivan Enderlin d71cd68a90 refactor(base): Rename RoomInfo::version to data_format_version.
This patch renames the `RoomInfo::version` field to
`data_format_version` to avoid all possible confusion with the
`room_version` (from `m.room.create`).
2025-05-21 17:26:52 +02:00
Benjamin Bouvier 726111b073 refactor(timeline): group all the parameters for a remote echo in TimelineAction::from_content 2025-05-21 16:59:41 +02:00
Benjamin Bouvier 298fa7d5d2 feat(timeline): pass the encryption info for a bundled edit when constructing the edit aggregation
This should properly update shields in case the bundled edit was
correctly decrypted.
2025-05-21 16:59:41 +02:00
Benjamin Bouvier 4a627baae8 feat(timeline): indicate when the replied-to event is not a standalone item
If the replied-to event is an aggregation, the
`RepliedToEvent::try_from_timeline_event` will now return `Ok(None)`,
and the caller may handle this as they please.

In the FFI layer, this will be filled with an error message indicating
that the event is unsupported.
2025-05-21 15:52:31 +02:00
Benjamin Bouvier 560e33c27b refactor(timeline): deduplicate parsing of events when making a RepliedToEvent 2025-05-21 15:52:31 +02:00
Stefan Ceriu d84cf0614d change(ffi): return errors if the client or utd delegates were already set 2025-05-21 16:38:20 +03:00
Stefan Ceriu 3f1bc2591e chore(ffi): report an error if the timeline is set to report UTDs but the hook manager isn't configured 2025-05-21 16:38:20 +03:00
Stefan Ceriu 8e83b724da chore(ffi): rename the utd_hook to utd_hook_manager 2025-05-21 16:38:20 +03:00
Stefan Ceriu 767b10f5e2 change(ffi): remove now unused ClientDelegate did_refresh_tokens callback (dropped in favor of the ClientSessionDelegate) 2025-05-21 16:38:20 +03:00
Stefan Ceriu 4e2b5562f1 change(ffi): use a OnceLock to guard against multiple settings of the ClientDelegate 2025-05-21 16:38:20 +03:00
Stefan Ceriu 8ef471b492 chore(multiverse): simplify marking a room as read 2025-05-21 16:38:20 +03:00
Stefan Ceriu de6998dbe0 change(ffi): move the UTD delegate directly to the client for ease of use 2025-05-21 16:38:20 +03:00
Stefan Ceriu e48b1f6056 change(ffi): stop retrieving room list last messages from through the timeline
As per the plan defined in #4718:

```
the room_list_service::room::RoomInner shouldn't make use of its inner timeline;
it's only used in a direct getter, or to compute the latest room event, but it's not working
as intended, since local echoes aren't properly displayed in the room list.
This non-working feature can be removed, in favor of #4112
```
2025-05-21 16:38:20 +03:00
Stefan Ceriu 7074110780 change(ffi): remove the UTD manager from the sync service, room list service and room list items 2025-05-21 16:38:20 +03:00
Stefan Ceriu c90d272374 change(ffi): add timeline configuration option for reporting UTDs 2025-05-21 16:38:20 +03:00
Stefan Ceriu e6dc203c4d change(ffi): pass the client utd manager down into the timeline builder 2025-05-21 16:38:20 +03:00
Stefan Ceriu 195ee35eea change(ffi): move the utd hook from the sync service to the client 2025-05-21 16:38:20 +03:00
Stefan Ceriu 70122a4407 change(ui): stop relying on a room's stored timeline for marking it as read 2025-05-21 16:38:20 +03:00
Stefan Ceriu f2ca0697af chore(ui): remove the timeline's builder method and make the builder's constructor public 2025-05-21 16:38:20 +03:00
Kévin Commaille 9f196be2f6 Fix doc link
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-05-21 13:51:48 +02:00
Kévin Commaille 254d86bc45 refactor(ui): Set room as variable instead of calling self.room() every time
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-05-21 13:51:48 +02:00
Kévin Commaille a8dcea6931 refactor(sdk): Make Room::set_unread_flag() a no-op if the unread flag already has the wanted value
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-05-21 13:51:48 +02:00
Kévin Commaille eed04ecdb3 chore: Add changelog entries for #5055
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-05-21 13:51:48 +02:00
Kévin Commaille c131e0cfe5 test(ui): Add tests for unsetting the unread flag when sending receipts
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-05-21 13:51:48 +02:00
Kévin Commaille 5f01c72a48 refactor(ui): Use the new mock endpoints in tests
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-05-21 13:51:48 +02:00
Kévin Commaille bf7d5e7841 feat(ui): Unset the unread flag when sending unthreaded receipts in Timeline
Updates the unread flag or the room in `Timeline::send_single_receipt()`
and `Timeline::send_multiple_receipts()` if the room is marked as unread
and the receipts are unthreaded.

Updates it also in `Timeline::mark_as_read()`, even if there is no
latest event ID.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-05-21 13:51:48 +02:00
Kévin Commaille dff1886015 test(sdk): Add tests for unsetting the unread flag when sending receipts
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-05-21 13:51:48 +02:00
Kévin Commaille 14a73dc932 refactor(sdk): Use new mock endpoints for tests
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-05-21 13:51:48 +02:00
Kévin Commaille 51be581d48 feat(test): Add RoomAccountDataTestEvent::MarkedUnread
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-05-21 13:51:48 +02:00
Kévin Commaille 0a41febe15 test(sdk): Mock endpoints for sending receipts and room account data
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-05-21 13:51:48 +02:00
Kévin Commaille 63eb429843 feat(sdk): Unset the unread flag when sending unthreaded receipts in Room
Updates the unread flag or the room in `Room::send_single_receipt()` and
`Room::send_multiple_receipts()` if the room is marked as unread and the
receipts are unthreaded.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-05-21 13:51:48 +02:00
Mauro 91815ab678 feat(bindings): added APIs to get the media preview config from the store 2025-05-21 12:15:17 +02:00
Benjamin Bouvier a98f71ed0c feat(timeline): handle live aggregations on non-live timelines (#5060)
This makes it possible to handle reactions/redactions/edits/etc. on
non-live timelines. As a result, the pinned and focused timelines will
now get live reactions/redactions and so on. This makes it possible to
also have the thread timelines handle those live events, although it's
unclear how it will pane out in the end, when the event cache is also
involved.
2025-05-20 18:03:57 +02:00
Benjamin Bouvier 6f8b744c24 refactor(timeline): address review comments 2025-05-20 13:50:02 +02:00
Benjamin Bouvier 5e9c76f476 refactor(timeline): avoid cloning of relates_to for room messages
This can be done by splitting the handling of the msgtype/mentions from
the handling of the `relates_to` field, requiring a few API changes here
and there.
2025-05-20 13:50:02 +02:00
Benjamin Bouvier 4fd0f2a32c refactor(timeline): slightly optimize flow for saving a bundled edit
We only need the edit_json if we're about to save the edit aggregation.
Likewise, if there's no current event id (i.e. the event being handled
is a local echo), then we don't need to even try to extract anything
from the bundle information.
2025-05-20 13:50:02 +02:00
Benjamin Bouvier 74d5d6e265 refactor(timeline): group extracting the reply and thread root
The poll events's `related_to` field is a `RelationWithoutReplacement`,
while the two others are `Relation<C>`, where `C` is the event content
type (in case it was replacement). As a matter of fact, we try
converting the `Relation<C>` into a `RelationWithoutReplacement` (which
unfortunately requires cloning, which is wasteful if the relation was a
replacement indeed), and then we can use a single function to extract
the reply information and thread root info, for all three.
2025-05-20 13:50:02 +02:00
Benjamin Bouvier caa53be00e optimize(timeline): find the position of an event by starting from the end 2025-05-20 13:50:02 +02:00
Benjamin Bouvier b3086accd5 refactor(timeline): hey, i can actually remove this pending_edits field now 2025-05-20 13:50:02 +02:00
Benjamin Bouvier f010587201 chore: make clippy happy
bleh.
2025-05-20 13:50:02 +02:00
Benjamin Bouvier 2af751d8c6 optimize(timeline): avoid one clone if a new item has pending aggregations 2025-05-20 13:50:02 +02:00
Benjamin Bouvier f36f9915d1 refactor(timeline): simplify a few functions as a result of not providing the edit json ahead of time 2025-05-20 13:50:02 +02:00
Benjamin Bouvier e8f8e7bfd6 fix(timeline): properly update encryption info upon edit
To be fair, this is a regression from a previous commit in this PR.
2025-05-20 13:50:02 +02:00
Benjamin Bouvier dbe23777a0 refactor(timeline): use the aggregations manager for handling edits 2025-05-20 13:50:02 +02:00
Benjamin Bouvier b7191e3dc2 refactor(timeline): have Aggregations::try_remove_aggregation also take care of updating the item 2025-05-20 13:50:02 +02:00
Benjamin Bouvier 56ce93ce72 refactor(timeline): simplify a bit mark_aggregation_as_sent by having it do more work
This avoids another struct definition, and items are going to be needed
for edits anyways.
2025-05-20 13:50:02 +02:00
Benjamin Bouvier 9cbc674cf2 optimize(timeline): don't eagerly clone an EventTimelineItem before applying an aggregation onto it 2025-05-20 13:50:02 +02:00
Benjamin Bouvier 16fe53c40d refactor!(timeline): have TimelineItem::reactions() return an Option<&ReactionsByKeyBySender> instead of owned non-optional 2025-05-20 13:50:02 +02:00
Benjamin Bouvier 1b581d52e8 refactor(timeline): rename Aggregations::apply to Aggregations::apply_all
It applies *all* the stashed aggregations for a given event timeline
item.
2025-05-20 13:50:02 +02:00
Benjamin Bouvier dd6604e9f3 feat(timeline): handle redaction in the pending aggregations
This allows having a redaction event come before the related event, and
still handle it when the redacted event shows up.
2025-05-20 13:50:02 +02:00
Yousef Moazzam f173510482 test: Replace sync_timeline_event! with EventFactory for beacon events in room integration tests 2025-05-20 12:31:23 +02:00
Benjamin Bouvier 905d9b9aba refactor(timeline): don't reload a pinned event timeline that hasn't changed since the previous time 2025-05-20 10:49:08 +02:00
Benjamin Bouvier 810056cd4d refactor(timeline): add logging for the pinned events task 2025-05-20 10:49:08 +02:00
Richard van der Hoff 4d2c261ff8 crypto: rewrite check_sender_trust_requirement to use VerificationState (#5044)
There are two reasons for this.

Firstly. we've already done a bunch of work to map `SenderData` into a
`VerificationState`, and the decision tree from `VerificationState` to
allow/reject is simpler than going from `SenderData`, even if we have to
fudge it a bit to get the "legacy" flag. (Note that it allows us to get
rid of an `unreachable!` panic.)

Secondly, `VerficationState` represents the state of an *event*, whereas
`SenderData` is about the session as a whole. A session can be fine,
whilst events (claiming to be) encrypted with it can be suspect. What we
want here is to check a specific message. Currently, this doesn't make
any functional difference, but conceptually it's cleaner to check the
`VerificationState`.

Note that there are a bunch of tests for this method in
`matrix-sdk-crypto/src/machine/tests/decryption_verification_state.rs`,
called `test_decryption_trust_requirement`.
2025-05-19 18:35:21 +01:00
Ivan Enderlin d4626835bb chore(base): Fix formatting. 2025-05-19 16:09:32 +02:00
Ivan Enderlin f684883902 chore(base): Move RoomInfo inside another module.
This patch moves the `RoomInfo` type and its implementations inside the
`room_info` module.
2025-05-19 16:09:32 +02:00
Ivan Enderlin cf6316e290 chore(base): Move BaseRoomInfo into its own module.
This patch extracts the `BaseRoomInfo` type and its implementations
inside its own module.
2025-05-19 16:09:32 +02:00
Ivan Enderlin 12caf12d8a doc(base): Fix a typo. 2025-05-19 16:09:32 +02:00
Ivan Enderlin 9809e1b53c doc(sdk): Fix a typo in an inline comment. 2025-05-19 15:58:58 +02:00
Ivan Enderlin 80b7eed14b task(sdk): Fix warnings error without e2e-encryption. 2025-05-19 15:58:58 +02:00
Ivan Enderlin 6980dc5628 doc(sdk): Add #5047. 2025-05-19 15:58:58 +02:00
Ivan Enderlin 9366bc85e9 refactor(sdk): Remove FrozenSlidingSync.
This patch removes `FrozenSlidingSync`. Its unique field is supposed to
be stored in the crypto store.
2025-05-19 15:58:58 +02:00
Ivan Enderlin 0c193500d2 refactor(sdk): Remove SlidingSyncRoom \o/.
This patch FINALLY removes `SlidingSyncRoom`, youhou!
2025-05-19 15:58:58 +02:00
Ivan Enderlin 6c68cef6e0 refactor(sdk): Remove SlidingSync::rooms.
This patch removes the `SlidingSync::rooms` field. A cascade of removal
happens, and many part of the code is simplified. The most notable is
`FrozenSlidingSync`.
2025-05-19 15:58:58 +02:00
Ivan Enderlin 5a7a42cde3 refactor(sdk): Stop maintaining SlidingSync::rooms.
This patch stops maintaining/updating `SlidingSync::rooms`. The goal is
to remove `SlidingSyncRoom`.
2025-05-19 15:58:58 +02:00
Ivan Enderlin 0ad88842cc refactor(sdk): Remove SlidingSync::get_rooms and get_all_rooms.
This patch removes `SlidingSync::get_rooms` and `get_all_rooms`. The
goal is to remove `SlidingSyncRoom`.
2025-05-19 15:58:58 +02:00
Ivan Enderlin 7c0f7f4715 refactor(sdk): Remove SlidingSync::get_number_of_rooms.
This patch removes `SlidingSync::get_number_of_rooms`. The goal is to
remove `SlidingSyncRoom`.
2025-05-19 15:58:58 +02:00
Ivan Enderlin 52c38ec44d refactor(base): Remove SlidingSync::get_room.
This patch removes the `SlidingSync::get_room` method. The goal is to
remove `SlidingSyncRoom`.
2025-05-19 15:58:58 +02:00
Valere Fedronic 21de891ea5 feat(sdk): Add the encrypt_and_send_raw_to_device method
This method allows users to encrypt and send custom to-device events to a set of devices of their choosing.
2025-05-19 11:20:25 +00:00
Mauro 154f29e5a0 feat(sdk): implement and observe MSC4278 config value
This patch
- Updates Ruma to use the improved MediaPreviewConfig event type that
also supports a `Default` for the content type
- Implemented a way to observe the stable and unstable values of the
event and return the used one accordingly, if no one is present the
default will be used
- Set the value (will only use unstable type for now)
2025-05-19 12:35:50 +02:00
Ivan Enderlin 7f07731471 doc(changelog): Add #5054. 2025-05-19 11:42:09 +02:00
Ivan Enderlin 1a0a4d7905 chore(base): Remove RoomInfo::prev_room_state.
This patch removes the `RoomInfo::prev_room_state` field, along with the
`RoomInfo::prev_state` method.

This data was introduced during the knocking project but was never used,
and is not used nowadays. Let's remove it.
2025-05-19 11:42:09 +02:00
Jonas Platte e3bcd4d5b2 chore: Upgrade dirs to 6.0 in examples 2025-05-19 09:23:02 +02:00
Timo ea4c9a41f8 feat(widgets): Add the controlledMediaOutput url parameter to the VirtualElementCallWidgetOptions.
This is used to configure EC on devices that need to control media outputs on their own (android, ios).
If set, EC will display a list of devices provided by the app.
2025-05-16 15:48:49 +02:00
Ivan Enderlin ac2c7f431c feat(ui): Add m.room.tombstone to the room list required_state.
This patch adds the `m.room.tombstone` state event to the list of
events in `required_state` used by the `RoomListService`. The goal is to
offer the possibility for the consumers to know whether a room has been
tombstoned or not.
2025-05-16 15:11:11 +02:00
Ivan Enderlin c0d6e87c99 task(base): Fix conflicts with a previous patch. 2025-05-16 14:43:45 +02:00
Ivan Enderlin 6162600bda refactor(base): Remove &mut Context argument from response processors when unused.
This patch removes the `_context: &mut Context` argument from response
processors when it's unused.
2025-05-16 14:43:45 +02:00
Ivan Enderlin 1187539ea4 chore(base): Remove the useless PreviousEventsProvider. 2025-05-16 14:43:45 +02:00
Ivan Enderlin 2649587d2f refactor(sdk): Use the Event Cache for read_receipts::compute_unread_counts.
The `read_receipts::compute_unread_counts` function needs the _previous
events_ to compute the read receipt correctly. These previous events
were store in `SlidingSyncRoom::timeline_queue`. Since the removal of
`timeline_queue` in the previous patches, this patch uses the Event
Cache to fetch them. It only uses events that are loaded in memory.
This is as correct as the prior behaviour, even this is still incorrect
since it doesn't back-paginate to get a better view. This is for
later. The goal of this patch is to restore the same behaviour, without
`timeline_queue`.

The main problem is that read receipts are computed in
`matrix-sdk-base`, and that the Event Cache lives in `matrix-sdk`. Thus,
we change the `SlidingSyncResponseProcessor` to handle read receipt
in particular.

The
`matrix_sdk_base::response_processors::rooms::msc4186::extensions::dispa
tch_ephemeral_events` function has been split in
two methods `dispatch_typing_ephemeral_events`, and
`dispatch_receipt_ephemeral_event_for_room`. The workflow has been a
little bit redesigned to fit in the new `SlidingSyncResponseProcessor`
constraints.

This patch moves one test from `matrix-sdk-base` into `matrix-sdk`,
because to compute the read receipt, the Event Cache must be
enabled/listening to sync updates.
2025-05-16 14:43:45 +02:00
Ivan Enderlin 68651aac1f feat(sdk): Add RoomEventCache::events to avoid ::subscribe.
`RoomEventCache::subscribe` returns the set of events + the
`RoomEventCacheListener`. However, creating this listener isn't
cheap, especially dropping it. That's why this patch creates
`RoomEventCache::events` to replace `subscribe` when the listener is
not necessary.
2025-05-16 14:43:45 +02:00
Ivan Enderlin 2c8f48fabb doc(base): Fix inline comment typos. 2025-05-16 14:43:45 +02:00
Ivan Enderlin c426c03624 refactor(sdk): Remove timeline and prev_batch from SlidingSyncRoom. 2025-05-16 14:43:45 +02:00
Ivan Enderlin eeaa091024 chore(ffi): Justify the allow(clippy::large_enum_variant). 2025-05-16 14:27:49 +02:00
Ivan Enderlin 7ef962f931 chore(labs): Allow clippy::large_enum_variant in multiverse.
This is development-, debug-oriented tool. Let's allow
`clippy::large_enum_variant` for the moment.
2025-05-16 14:27:49 +02:00
Ivan Enderlin 8480e0fc55 chore(ui): Allow clippy::large_enum_variant on TimelineAction.
This enum is large, but it's used in a short period of time, not
collected somewhere, so it's safe to accept a large size here.
2025-05-16 14:27:49 +02:00
Ivan Enderlin bf5e0124ab refactor(ui): Reduce the size of NotificationEvent.
This patch reduces the size of `NotificationEvent` from 576 bytes to
16 bytes.
2025-05-16 14:27:49 +02:00
Ivan Enderlin d56ad64cc2 refactor(ui): Reduce the size of NotificationStatus.
This patch reduces the size of `NotificationStatus` from 216 bytes to
16 bytes.
2025-05-16 14:27:49 +02:00
Ivan Enderlin ab3f22c212 refactor(sdk): Reduce the output size of get_header.
This patch reduces the size of the output's `Result::Err` variant of
`get_header` from 160 bytes to 8 bytes.
2025-05-16 14:27:49 +02:00
Ivan Enderlin e41dbd6300 refactor(sdk): Reduce size of HttpError.
This patch reduces the size of `HttpError` from 160 bytes to 24 bytes.
2025-05-16 14:27:49 +02:00
Ivan Enderlin 29a8556f10 refactor(sdk): Reduce size of ReplyContent.
This patch reduces the size of `ReplyContent` from 448 bytes to
16 bytes.
2025-05-16 14:27:49 +02:00
Ivan Enderlin 8d0d920808 refactor(sdk): Replace iter().any() by contains().
This is faster for scalars, but it falls back to a regualar
`iter().any()` for other types. It's the same, but at least Clippy
doesn't complain.
2025-05-16 14:27:49 +02:00
Ivan Enderlin 1cdc9ff6e4 refactor(sdk): Use IoError::other.
This patch replaces `IoError::new(IoErrorKind::Other, …)` by
`IoError::other(…)`.
2025-05-16 14:27:49 +02:00
Ivan Enderlin 9541123fcf refactor(crypto): Reduce the size of SasState.
This patch reduces the size of `SasState` from 288 bytes to 88 bytes.
2025-05-16 14:27:49 +02:00
Ivan Enderlin ed4b789f87 refactor(crypto): Reduce the size of OutgoingContent.
This patch reduces the size of `OutgoingContent` from 160 bytes to
24 bytes.
2025-05-16 14:27:49 +02:00
Ivan Enderlin 18a3c37554 refactor(crypto): Reduce sizes of Verification and VerificationRequestState.
This patch reduces the sizes of `Verification` from 376 bytes to
16 bytes, and `VerificationRequestState` from 424 bytes to 96 bytes.

It also reduces the size of a couple of other types in the same vain.
2025-05-16 14:27:49 +02:00
Ivan Enderlin 92b4b03a8d refactor(crypto): Reduce the size of OutgoingContent and RoomMessageRequest.
This patch reduces the sizes of `OutgoingContent` from 464 bytes to
160 bytes, and `RoomMessageRequest` from 480 bytes to 40 bytes.
2025-05-16 14:27:49 +02:00
Ivan Enderlin 192c50dcad refactor(crypto): Reduce the size of OutgoingVerificationRequest.
This patch reduces the size of `OutgoingVerificationRequest` from
480 bytes to 64 bytes.
2025-05-16 14:27:49 +02:00
Ivan Enderlin 7b34eaabe5 refactor(crypto): Reduce the size of AnyOutgoingRequest.
This patch reduces the size of `AnyOutgoingRequest` from 488 bytes to
72 bytes.
2025-05-16 14:27:49 +02:00
Ivan Enderlin 2eb4278835 refactor(crypto): Reduce the size of RoomIdentityChange.
This patch reduces the size of `RoomIdentityChange` from 576 bytes to
72 bytes.
2025-05-16 14:27:49 +02:00
Ivan Enderlin 293d4ee08c refactor(crypto): Reduce the size of MaybeEncryptedRoomKey.
This patch reduces the size of `MaybeEncryptedRoomKey` from 336 bytes
to 32 bytes.
2025-05-16 14:27:49 +02:00
Ivan Enderlin 87066a127e refactor(crypto): Use IoError::other.
This patch uses `IoError::other(…)` as a shortcut of
`IoError::new(ErrorKind::Other, …)`.
2025-05-16 14:27:49 +02:00
Kévin Commaille 4847a3135b feat(base-sdk): Ignore marked_unread room account data with unstable prefix after seeing one with stable prefix
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-05-16 09:55:02 +02:00
Kévin Commaille af02e0c472 feat(sdk): Send stable m.marked_unread room account data
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-05-16 09:55:02 +02:00
Stefan Ceriu da2dda0e45 fix(ui): populate the thread_root and in_reply_to fields for stickers and polls
They have never been set and there was no way of telling if stickers and polls belong on a thread or come in reply of any other message.

This patch also exposes methods for setting these relations on the event factory level.
2025-05-15 16:45:34 +03:00
Benjamin Bouvier 69b8878890 fix(sdk): mark rooms joined with join_room_by_id or join_room_by_id_or_alias as DMs if needs be
This moves the logic from `Room::join()` to these two methods. This is
isofunctional, because `Room::join()` does call into
`Client::join_room_by_id()` internally.
2025-05-15 14:19:31 +02:00
Damir Jelić 7893e55a8d refactor(crypto): Make the room key importing logic more generic 2025-05-15 12:23:23 +02:00
Damir Jelić c0e45a2e0f refactor(widgets): Rename the then() function into add_response_handler() (#5037)
The then() function can be used with booleans and futures to execute a
piece of code if the boolean is true or once the future is completed.

In contrast, the then() function in the widget driver is not executed
immediately. Instead it only adds a callback that is later on executed
by the widget driver.
2025-05-14 16:16:04 +02:00
Stefan Ceriu 13a65c8dfe feat(ui): add new Thread timeline focus mode and associated events loader (#5032)
… that allows building a timeline instance specific to a particular
thread root.

Creating a timeline in this mode will start by backpaginating root event
relations with `num_events` and automatically insert the thread root
event when reaching the end. It will include
`RelationsOfType(RelationType::Thread)` but also recurse over the
retrieved events to fetch reactions.
It will not however react to new events received over sync or that the
user sends (for now).

This patch will also help incrementally deliver the upstream client
support for creating such a timeline.

Part of #4833 (meta #4869).
2025-05-14 14:14:29 +00:00
Kévin Commaille 36667c1298 chore: Get rid of cargo-deny errors due to new advisories
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-05-14 15:42:46 +02:00
Benjamin Bouvier e4f2299785 refactor(timeline): get rid of FullEventMeta and replace it with EventMeta + function parameters 2025-05-14 13:30:24 +02:00
Benjamin Bouvier 81a2679bb8 refactor(timeline): only compute the TimelineEventContext when it's needed 2025-05-14 13:30:24 +02:00
Benjamin Bouvier 9087263da4 refactor(timeline): rewrite how failed-to-parse items are added 2025-05-14 13:30:24 +02:00
Benjamin Bouvier d58111fa04 refactor(timeline): isolate computation of should_add in its own function 2025-05-14 13:30:24 +02:00
Benjamin Bouvier ab87ea5770 refactor(timeline): simplify computation of should_add a bit 2025-05-14 13:30:24 +02:00
Benjamin Bouvier 6b62b41a60 refactor(timeline): get rid of the return value of find_item_and_apply_aggregation
As it's now unused.
2025-05-14 13:30:24 +02:00
Benjamin Bouvier b1f088277d refactor(timeline): get rid of HandleEventResult 2025-05-14 13:30:24 +02:00
Benjamin Bouvier 277eae75d9 refactor(timeline): make item_added a local variable instead of deeply-stored context 2025-05-14 13:30:24 +02:00
Benjamin Bouvier 20559e3f2c refactor(timeline): get rid of unused HandleEventResult::items_updated 2025-05-14 13:30:24 +02:00
Benjamin Bouvier 341f9c267d feat(state store): enable the busy timeout to automatically retry operations on busy dbs
Under some very particular circumstances, the "database is locked" error
can still happen, even in WAL mode, even if the database connection is
not being upgraded from a read transaction to a write transaction.

We *think* this might be the reason behind errors like
github.com/element-hq/element-x-ios/issues/3582, so we're enabling the
sqlite busy_timeout, which will retry the operation after a short sleep,
until the busy timeout is being hit.
2025-05-14 13:27:54 +02:00
Timo af3039abde docs(WidgetDriver): Add module documentation
Co-authored-by: Robin <robin@robin.town>
Signed-off-by: Damir Jelić <poljar@termina.org.uk>
2025-05-14 11:42:55 +02:00
Benjamin Bouvier 4d027ec405 doc: add a changelog entry for the persistent storage of the event cache 2025-05-13 15:50:21 +02:00
dependabot[bot] 3cd64ac03b chore(deps): Bump tokio from 1.43.0 to 1.43.1
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.43.0 to 1.43.1.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.43.0...tokio-1.43.1)

---
updated-dependencies:
- dependency-name: tokio
  dependency-version: 1.43.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-13 14:58:30 +02:00
Jorge Martín 44e103c0e3 feat(ffi): expose ffi::RoomInfo::tombstone
This replaces `ffi::RoomInfo::is_tombstoned`, including the needed extra info for the migration UI.
2025-05-13 14:41:41 +02:00
dependabot[bot] 7d992d1af8 chore(deps): Bump ring from 0.17.8 to 0.17.14
Bumps [ring](https://github.com/briansmith/ring) from 0.17.8 to 0.17.14.
- [Changelog](https://github.com/briansmith/ring/blob/main/RELEASES.md)
- [Commits](https://github.com/briansmith/ring/commits)

---
updated-dependencies:
- dependency-name: ring
  dependency-version: 0.17.14
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-13 14:37:12 +02:00
Benjamin Bouvier ac42953524 doc(timeline): add extra documentation for HandleAggregationKind 2025-05-13 14:15:00 +02:00
Benjamin Bouvier 06759359af refactor(timeline): simplify lightly computation of should_add_new_items for local events 2025-05-13 14:15:00 +02:00
Benjamin Bouvier a3a30885c0 refactor(timeline): rename TimelineEventKind into TimelineAction and add comments 2025-05-13 14:15:00 +02:00
Benjamin Bouvier b448c4ac39 refactor(timeline): get rid of TimelineEventKind::Message 2025-05-13 14:15:00 +02:00
Benjamin Bouvier 6a874fec2d refactor(timeline): use TimelineEventKind::AddItem for most room messages 2025-05-13 14:15:00 +02:00
Benjamin Bouvier 998019b8b8 refactor(timeline): use TimelineEventKind::AddItem for poll starts 2025-05-13 14:15:00 +02:00
Benjamin Bouvier 5a0b33fcd1 refactor(timeline): use TimelineEventKind::AddItem for call invite/notify/sticker 2025-05-13 14:15:00 +02:00
Benjamin Bouvier ec55d7cb58 refactor(timeline): use TimelineEventKind::HandleAggregation for poll edits/responses/ends 2025-05-13 14:15:00 +02:00
Benjamin Bouvier 706f78d5b3 refactor(timeline): use TimelineEventKind::HandleAggregation for edits 2025-05-13 14:15:00 +02:00
Benjamin Bouvier 1218cb4c28 refactor(timeline): use TimelineEventKind::HandleAggregation for redactions 2025-05-13 14:15:00 +02:00
Benjamin Bouvier 31bd8a3587 refactor(timeline): introduce TimelineEventKind::HandleAggregation for non-items
This variant will be used to cause side-effects to existing timeline
items, because the event they relate to is for an aggregation
(edit/reaction/etc.).

This is used here for reactions.
2025-05-13 14:15:00 +02:00
Benjamin Bouvier 9e70cc5dde refactor(timeline): add a small helper function to create TimelineEventKind::AddItem items 2025-05-13 14:15:00 +02:00
Benjamin Bouvier d78a4927fd refactor(timeline): use TimelineEventKind::AddItem for room member and state events 2025-05-13 14:15:00 +02:00
Benjamin Bouvier bf246b6c09 refactor(timeline): use TimelineEventKind::AddItem for redacted messages 2025-05-13 14:15:00 +02:00
Benjamin Bouvier 5da2235973 refactor(timeline): use TimelineEventKind::AddItem for UTDs 2025-05-13 14:15:00 +02:00
Benjamin Bouvier a9de4709f9 refactor(timeline): use TimelineEventKind::AddItem to insert failed-to-parse items 2025-05-13 14:15:00 +02:00
Benjamin Bouvier 3b8d7ffacd refactor(timeline): introduce a new TimelineEventKind::AddItem variant
The intent is that the `TimelineItemContent` can be constructed in a
single place, avoiding the need to create it in multiple places.
2025-05-13 14:15:00 +02:00
Benjamin Bouvier 3eee5a4a92 refactor(timeline): bury one use of TimelineEventKind 2025-05-13 14:15:00 +02:00
Benjamin Bouvier 6fed5747bb refactor(timeline): remove another spurious is_own_event
Once again, this information is available in the `TimelineMetadata`.
2025-05-13 14:15:00 +02:00
Benjamin Bouvier 442094a725 refactor(timeline): remove spurious FullEventMeta::is_own_event
This information can be accessed via the `TimelineMetadata::own_user_id`
field, which is instantiated only once.
2025-05-13 14:15:00 +02:00
Benjamin Bouvier de66047eee refactor(event cache): simplify Deduplicator so it only operates with the store impl 2025-05-13 10:17:21 +02:00
Benjamin Bouvier 48da03a148 refactor(event cache): don't make the store optional in the event cache 2025-05-13 10:17:21 +02:00
Benjamin Bouvier 13a2a8757e feat(event cache): enable storage by default \o/ 2025-05-13 10:17:21 +02:00
Benjamin Bouvier 1d901ec12a refactor(room list): get rid of the sliding sync in room_list_service::Room
This was only used to retrieve events cached in the timeline_queue().
2025-05-13 10:17:21 +02:00
Benjamin Bouvier 7115203a90 feat(event cache): get rid of add_initial_events() entirely 2025-05-13 10:17:21 +02:00
Jorge Martín 68fb60f223 test(ui): add more tests for fetching invite notifications in sliding sync 2025-05-13 09:10:44 +02:00
Jorge Martín 1f064fe474 test(ui): migrate notification fetching tests to use the batched methods
Also add one for the sliding sync + /context case
2025-05-13 09:10:44 +02:00
Jorge Martín 008c6f6d6c feat(ui): allow retrieving push notification events in batches 2025-05-13 09:10:44 +02:00
Yousef Moazzam 1afad3ab78 test: remove import of unused sync_timeline_event! macro 2025-05-13 09:04:55 +02:00
Yousef Moazzam d1802086ad test: create room canonical alias events with EventFactory 2025-05-13 09:04:55 +02:00
Yousef Moazzam cabe9632af test: add room canonical alias event method to EventFactory 2025-05-13 09:04:55 +02:00
Ivan Enderlin 08aa9c8614 doc(ui): Fix a typo in a comment. 2025-05-12 17:15:43 +02:00
Ivan Enderlin 831bba5cf0 test(ui): Add tests for push_local and push_date_divider. 2025-05-12 17:15:43 +02:00
Ivan Enderlin d727111a51 doc(ui): Add #5000 in the CHANGELOG.md. 2025-05-12 17:15:43 +02:00
Ivan Enderlin 8d785b762e chore(ui): Make Clippy happy. 2025-05-12 17:15:43 +02:00
Ivan Enderlin ad4ae230d5 refactor(ui): EventHandler uses regions to improve the code and avoid bugs.
This patch updates `EventHandler` to use the correct regions where
appropriate, thus reducing the complexity of the code, and removing
classes of bugs.

In the case of `Flow::Remote { position: TimelineItemPosition::At { …
}}`, we no longer need to skip the local timeline items, and to handle
the presence of the `TimelineStart` timeline item. The code is less
complex.

In the case of `Flow::Remote { position: TimelineItemPosition::End { …
}}`, that's exactly the same at the previous case.

In the case of `recycle_local_or_create_item`, the `try_fold` approach
is replaced entirely with a simple `iter_locals_region`, reducing the
size of the comments explaining the code, reducing the complexity of the
code, and reducing the surface of bugs.
2025-05-12 17:15:43 +02:00
Ivan Enderlin 54f7963152 refactor(ui): TimelineStateTransaction works on _remotes_ and _all_ regions.
This patch updates `TimelineStateTransaction` to work on the correct
regions, _remotes_ in one place, and all regions in another place.
2025-05-12 17:15:43 +02:00
Ivan Enderlin b59c0b671e refactor(ui): TimelineMetadata works on the _remotes_ region.
This patch updates `TimelineMetadata` to work on the _remotes_ region
only, excluding the _start_ and the _locals_ regions. It helps to reduce
the risk of inserting items in an incorrect regions.

This patch also removes on more `rfind_event_by_id` usage, which is
nice.
2025-05-12 17:15:43 +02:00
Ivan Enderlin c6453a4cb3 refactor(ui): ReadReceiptTimelineUpdate works on _remotes_ region.
This patch updates `ReadReceiptTimelineUpdate` to work on the _remotes_
region only, excluding the _start_ and the _lcoals_ regions. It helps
to reduce the risk of inserting a `ReadMarker` inside the _start_ or the
_locals_ regions.
2025-05-12 17:15:43 +02:00
Ivan Enderlin 74bf699615 refactor(ui): DateDividerAdjuster works on _remotes_ and _locals_ regions.
This patch updates `DateDividerAdjuster` to work on _remotes_ and
_locals_ regions only, excluding the _start_ region. It helps to reduce
the risk of inserting a `DateDivider` inside the _start_ region.

This patch also uses the new `push_date_divider` method, which provides
a couple of invariants.
2025-05-12 17:15:43 +02:00
Ivan Enderlin e1f94bf9c4 feat(ui): Define _regions_ in the Timeline.
This patch defines a new concept in the `Timeline`: Regions.

The `ObservableItems` holds all the invariants about the _position_ of the
items. It defines three regions where items can live:

1. the _start_ region, which can only contain a single `TimelineStart`,
2. the _remotes_ region, which can only contain many `Remote` timeline
   items with their decorations (only `DateDivider`s and `ReadMarker`s),
3. the _locals_ region, which can only contain many `Local` timeline items
   with their decorations (only `DateDivider`s).

The `iter_all_regions` method allows to iterate over all regions.
`iter_remotes_region` will restrict the iterator over the _remotes_
region, and so on. These iterators provide the absolute indices of the
items, so that it's harder to make mistakes when manipulating the indices of
items with operations like `insert`, `remove`, `replace` etc.

Other methods like `push_local` or `push_date_divider` insert the items
in the correct region, and check a couple of invariants.
2025-05-12 17:15:43 +02:00
Ivan Enderlin 44a0745110 chore(base): Move the bitflags dependency in the workspace. 2025-05-12 17:15:43 +02:00
Ivan Enderlin 94b76168e8 refactor(ui): Add ObservableItemsTransaction::has_local.
This patch implements the `has_local` method on
`ObservableItemsTransaction`, which is way faster than the previous the
previous solution which was to iterate over all items to find at least
one local timeline item.
2025-05-12 17:15:43 +02:00
Ivan Enderlin 55ea80b485 refactor(ui): Add ObservableItemsTransaction::push_local.
This patch adds the `push_local` method on `ObservableItemsTransaction`
to add semantics and hardcode the invariant in a single place for the
different timeline items.
2025-05-12 17:15:43 +02:00
Ivan Enderlin 4e501e88ee refactor(ui): Add ObservableItemsTransaction::push_timeline_start_if_missing.
This patch adds the `push_timeline_start_if_missing` method on
`ObservableItemsTransaction` to add semantics and hardcode the
invariant in a single place for the different timeline items.
2025-05-12 17:15:43 +02:00
Ivan Enderlin afc02781e9 test(ui): Add a regression test.
This patch adds a regression test ensuring [this bug][4976] cannot
happen anymore.

[4976]: https://github.com/matrix-org/matrix-rust-sdk/issues/4976
2025-05-12 17:15:43 +02:00
Ivan Enderlin c2072e1cc2 test(ui): Support index [$nth] --- date divider --- in assert_timeline_stream!. 2025-05-12 17:15:43 +02:00
Ivan Enderlin eef99b2679 test: Add assert messages in the assert_timeline_stream macro.
This patch improves the `assert_timeline_stream` macro by adding a bunch
of assert messages in case it fails.
2025-05-12 17:15:43 +02:00
Ivan Enderlin 581d54f65f fix(ui): Offset the timeline index in the presence of a TimelineStart.
This patch fixes the insertion of a new `TimelineItem` in the presence
of a `TimelineStart` that shifts/offsets the timeline index of 1.
2025-05-12 17:15:43 +02:00
dependabot[bot] 5f5ea69a32 chore(deps): Bump tj-actions/changed-files
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 4168bb487d5b82227665ab4ec90b67ce02691741 to 480f49412651059a414a6a5c96887abb1877de8a.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](https://github.com/tj-actions/changed-files/compare/4168bb487d5b82227665ab4ec90b67ce02691741...480f49412651059a414a6a5c96887abb1877de8a)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-version: 480f49412651059a414a6a5c96887abb1877de8a
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-12 17:00:46 +02:00
Johannes Marbach 08800f7d60 Reduce boilerplate further 2025-05-12 10:56:57 +02:00
Johannes Marbach eb51a7f145 Use macros to reduce boilerplate 2025-05-12 10:56:57 +02:00
Johannes Marbach 8cf09217d6 Switch to structs in yet more places 2025-05-12 10:56:57 +02:00
Johannes Marbach f81945ad7e Fix build error 2025-05-12 10:56:57 +02:00
Johannes Marbach c21f97274c Switch to struct 2025-05-12 10:56:57 +02:00
Johannes Marbach da67bacfbf Add changelog 2025-05-12 10:56:57 +02:00
Johannes Marbach 175d854a9b feat(ffi): Add methods for observing account data changes
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-05-12 10:56:57 +02:00
Ivan Enderlin afabfb97b6 task(ui): Log when the room name is empty when filtering the room list. 2025-05-12 10:24:52 +02:00
Yousef Moazzam 8e4554d3c0 test: create room server ACL event with EventFactory 2025-05-12 09:30:36 +02:00
Yousef Moazzam f1ea47f0b6 test: add room server ACL event method to EventFactory 2025-05-12 09:30:36 +02:00
Yousef Moazzam 26f1282c6a test: create room power level events with EventFactory 2025-05-10 20:35:57 +02:00
Yousef Moazzam 4053321cd0 test: add room power levels event method to EventFactory 2025-05-10 20:35:57 +02:00
Robin 561158c7bb fix(tests): Avoid depending on the local time zone in tests
This test fails when run in the Americas, because over here the Unix epoch took place in 1969 =D
2025-05-09 20:47:48 +02:00
Mauro Romito c023745dcf crates: update Ruma to support MSC 4278
This will later be handled once the account data observation PR is ready
2025-05-09 14:41:17 +02:00
Yousef Moazzam 6ba68fe87e test: add timeline events in bulk to room builder 2025-05-09 11:51:45 +02:00
Yousef Moazzam 7afb46cc0c test: create room create event with EventFactory 2025-05-09 11:51:45 +02:00
Yousef Moazzam 93dcd07073 test: add room create event method to EventFactory 2025-05-09 11:51:45 +02:00
Yousef Moazzam cf2f507951 test: create member event with EventFactory 2025-05-09 11:51:45 +02:00
Damir Jelić 35a2ce97d8 refactor(widget): Use streams to streamline the action processing logic 2025-05-09 11:07:41 +02:00
Ivan Enderlin f042084bd2 doc: Generate doc with --generate-link-to-definition.
This patch adds the `--generate-link-to-definition`
argument to `rustdoc` for `docs.rs`. This is using
https://github.com/rust-lang/rust/pull/84176 to add links in the source
code page.
2025-05-08 13:08:32 +02:00
Doug be6d5f9bd9 ffi: Add support for the login hints with OIDC. 2025-05-08 12:10:16 +02:00
Doug 506060f23d sdk: Add support for generic OAuth login hints.
See https://github.com/element-hq/matrix-authentication-service/pull/4512
2025-05-08 12:10:16 +02:00
Richard van der Hoff 55d475df04 Merge pull request #4988 from matrix-org/rav/history_sharing/better_sender_data
crypto: improve SenderData stored with room key bundle data
2025-05-07 22:31:48 +01:00
Richard van der Hoff f349a66292 crypto: improve SenderData stored with room key bundle data
If we already have cross-signing details for the owner of the device at the
point we receive the to-device message, we should store that rather than just
the device info.
2025-05-07 22:16:57 +01:00
Richard van der Hoff 3742bdc7cf crypto: move some logic from SenderDataFinder to SenderData
create a new method `SenderData::from_device` which does the last few steps of
`SenderDataFinder`: turns out we want it elsewhere. Add some tests to test that
functionality in isolation.
2025-05-07 22:16:57 +01:00
Doug b5b2450eac ffi: Expose the QrCodeData server name. 2025-05-07 13:40:29 +02:00
Denis Kasak fc071bafb2 docs: Various fixes for store-related comments.
- Doc comment for the SQLite-based state store incorrectly referred to
  it as a "cryptostore".
- Consistent capitalisation of SQLite.
- Consistent use of indefinite article "an" before SQLite.
- Fix line length.
2025-05-06 13:55:03 +02:00
Yousef Moazzam e4ce1790cd test: replace sync_timeline_event! with EventFactory in notification test 2025-05-06 13:34:00 +02:00
Ivan Enderlin 3461b13ec7 doc(sqlite): Add entry in the CHANGELOG.md. 2025-05-06 09:17:54 +02:00
Ivan Enderlin 83e4314645 fix(sqlite): Fix a UNIQUE constraint violation with Update::RemoveItem.
Imagine we have the following events:

| event_id | room_id | chunk_id | position |
|----------|---------|----------|----------|
| $ev0     | !r0     | 42       | 0        |
| $ev1     | !r0     | 42       | 1        |
| $ev2     | !r0     | 42       | 2        |
| $ev3     | !r0     | 42       | 3        |
| $ev4     | !r0     | 42       | 4        |

`$ev2` has been removed, then we end up in this state:

| event_id | room_id | chunk_id | position |
|----------|---------|----------|----------|
| $ev0     | !r0     | 42       | 0        |
| $ev1     | !r0     | 42       | 1        |
|          |         |          |          | <- no more `$ev2`
| $ev3     | !r0     | 42       | 3        |
| $ev4     | !r0     | 42       | 4        |

We need to shift the `position` of `$ev3` and `$ev4` to `position - 1`,
like so:

| event_id | room_id | chunk_id | position |
|----------|---------|----------|----------|
| $ev0     | !r0     | 42       | 0        |
| $ev1     | !r0     | 42       | 1        |
| $ev3     | !r0     | 42       | 2        |
| $ev4     | !r0     | 42       | 3        |

Usually, it boils down to run the following query:

```sql
UPDATE event_chunks
SET position = position - 1
WHERE position > 2 AND …
```

Okay. But `UPDATE` runs on rows in no particular order. It means that
it can update `$ev4` before `$ev3` for example. What happens in this
particular case? The `position` of `$ev4` becomes `3`, however `$ev3`
already has `position = 3`. Because there is a `UNIQUE` constraint
on `(room_id, chunk_id, position)`, it will result in a constraint
violation.

There is **no way** to control the execution order of `UPDATE` in
SQLite. To persuade yourself, try:

```sql
UPDATE event_chunks
SET position = position - 1
FROM (
    SELECT event_id
    FROM event_chunks
    WHERE position > 2 AND …
    ORDER BY position ASC
) as ordered
WHERE event_chunks.event_id = ordered.event_id
```

It will fail the same way.

Thus, we have 2 solutions:

1. Remove the `UNIQUE` constraint,
2. Be creative.

The `UNIQUE` constraint is a safe belt. Normally, we have
`event_cache::Deduplicator` that is responsible to ensure there is no
duplicated event. However, relying on this is “fragile” in the sense it
can contain bugs. Relying on the `UNIQUE` constraint from SQLite is more
robust. It's “braces and belt” as we say here.

So. We need to be creative.

Many solutions exist. Amongst the most popular, we see _dropping and
re-creating the index_, which is no-go for us, it's too expensive. I
(@hywan) have adopted the following one:

- Do `position = position - 1` but in the negative space, so
 `position = -(position - 1)`. A position cannot be negative; we are
  sure it is unique!
- Once all candidate rows are updated, do `position = -position` to move
  back to the positive space.

'told you it's gonna be creative.

This solution is a hack, **but** it is a small number of operations, and
we can keep the `UNIQUE` constraint in place.

This patch updates the `test_linked_chunk_remove_item` to handle
6 events. On _my_ system, with _my_ SQLite version, it triggers the
`UNIQUE` constraint violation without the bug fix.
2025-05-06 09:17:54 +02:00
dependabot[bot] c726bc5904 chore(deps): Bump tj-actions/changed-files
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 5426ecc3f5c2b10effaefbd374f0abdc6a571b2f to 4168bb487d5b82227665ab4ec90b67ce02691741.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](https://github.com/tj-actions/changed-files/compare/5426ecc3f5c2b10effaefbd374f0abdc6a571b2f...4168bb487d5b82227665ab4ec90b67ce02691741)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-version: 4168bb487d5b82227665ab4ec90b67ce02691741
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-05 18:35:37 +03:00
dependabot[bot] 970af0de7c chore(deps): Bump crate-ci/typos from 1.31.2 to 1.32.0
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.31.2 to 1.32.0.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.31.2...v1.32.0)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.32.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-05 17:31:29 +02:00
mpeter50 8be0a7df95 Update logging of device verification request timestamp valdiation
In element-hq/element-web#29625 it was found to be useful to give more visibility to this kind of verification error.

Signed-off-by: mpeter50 <83356418+mpeter50@users.noreply.github.com>
2025-05-05 09:46:29 +03:00
Michael Goldenberg 8fd122c431 style(indexeddb): cargo fmt
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-05-05 08:44:54 +02:00
Michael Goldenberg f661b82f18 refactor(indexeddb): rename module (indexeddb_serializer -> serializer)
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-05-05 08:44:54 +02:00
Michael Goldenberg 77ee7f1d19 refactor(indexeddb): change indexeddb_serializer::Result to use IndexeddbSerializerError
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-05-05 08:44:54 +02:00
Michael Goldenberg af90b7ac4e refactor(indexeddb): add conversions into IndexeddbSerializerError
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-05-05 08:44:54 +02:00
Michael Goldenberg 3f3daef01c refactor(indexeddb): add conversion IndexeddbSerializerError -> IndexeddbCryptoStoreError
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-05-05 08:44:54 +02:00
Michael Goldenberg c2e859273d refactor(indexeddb): add enum for general IndexedDB serialization errors
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-05-05 08:44:54 +02:00
Richard van der Hoff 3b84b2c5e7 crypto-ffi: fix error message for MissingRoomKey (#4997)
This error does not necessarily mean that the session was *withheld*.
2025-05-02 15:52:15 +01:00
Stefan Ceriu 284db61540 feat(ffi): expose a new get_room method on the NotificationClient that will fetch it from its inner in-memory store backed client instead of the parent one.
This is necessary because the `NotificationClient` runs a sliding sync loop and the retrieved data isn't pushed back into the parent client stores (because of cross process locking shenanigans).
This will be used with the previously introduced `org.matrix.msc3401.call.member` required state to check whether a room still has an ongoing call before showing the ringing screen.
2025-04-30 12:48:41 +03:00
Stefan Ceriu 8e19a5eb33 change(notification_client): request the org.matrix.msc3401.call.member state events resolving notification payloads
- this will be used to check whether a room still has an active call (`has_active_room_call`) before showing the ringing screen
2025-04-30 12:48:41 +03:00
Jorge Martín ef4cb79cde fix(sdk): Upload encrypted media with application/octet-stream mime type
This is apparently the right way to do it, both because some HS expect only this mimetype and also so we don't leak the mime type of the encrypted media.
2025-04-30 09:10:27 +02:00
Timo 9fbb9cbe9b WidgetDriver: refactor Filter
This commit simplifies the filter public api.

Rethinking the public api we only need:
 - to know if events can be sent based on the capabilities
 - to know if events can be sent to the widget (read) based on the capabilities
 - if it even makes sense to sent a cs api read request or if all possibly returned events
   would not match the type.

To simplify the code in the machine it also made sense to add `From` implementation
to the FilterInputs instead of gathering the relevant data from all kinds of Raw events.

The new api is simpler:
All possible events we need to check can be converted into filter inputs (using `into()`).
`capabilites` has two allow_read/allow_send that consume filter inputs.
`capabilites` can be asked if there is any filter for specific event types
to allow not send unnecassary requests.
2025-04-29 18:15:07 +02:00
Timo 4e64f28318 WidgetDriver: filter event_type change from dedicated ...EventType -> String
This is (sadly) required since we cannot do `as_str()` for `TimlineEventType` and other `ruma` event types.
So we need to use `String`. We also never used them more specifically than strings.
2025-04-29 18:15:07 +02:00
Timo 5e2f775b2b WidgetDriver: rename EventFilter->Filter & MatrixEventFilterInput -> FilterInput
This is a simple devtool refactor rename. Nothing fancy here.
2025-04-29 18:15:07 +02:00
procr1337 0856f4e6b0 refactor(crypto): Properly encapsulate internal OutboundGroupSession state
Previously, the `share_strategy` was breaking the abstraction provided
by `OutboundGroupSession` by accessing its internal fields in an
inconsistent and adhoc way. Now all fields are private and a proper
abstraction was added to access the required state in a consistent API.
2025-04-29 17:39:21 +02:00
Benjamin Bouvier ae4cdda939 feat(sdk): add a room method to retrieve all related events 2025-04-29 15:01:31 +02:00
Benjamin Bouvier 0db273bf38 test(sdk): add a mocking endpoint for listing relations and test Room::relations 2025-04-29 15:01:31 +02:00
Benjamin Bouvier a912a7584f test(sdk): add a mocking endpoint for listing threads and test Room::list_threads() 2025-04-29 15:01:31 +02:00
Benjamin Bouvier fa1aa57581 feat(sdk): add a room method to retrieve a list of threads 2025-04-29 15:01:31 +02:00
Richard van der Hoff b22bb3fa86 crypto: Move some test helpers out from sender_data_finder 2025-04-29 12:36:32 +01:00
Michael Goldenberg c3ed8b9e7b docs(ffi): update changelog
Signed-of-by: Michael Goldenberg <m@mgoldenberg.net>
2025-04-29 12:35:31 +02:00
Michael Goldenberg 6e442d9046 feat(ffi): rename fields in UploadSource to match AttachmentSource
Signed-of-by: Michael Goldenberg <m@mgoldenberg.net>
2025-04-29 12:35:31 +02:00
Michael Goldenberg 79c5edd319 refactor(ffi): add conversion from UploadSource to AttachmentSource
Signed-of-by: Michael Goldenberg <m@mgoldenberg.net>
2025-04-29 12:35:31 +02:00
Michael Goldenberg bd6361e23a feat(ffi): replace file-related fields with UploadSource in UploadParameters
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-04-29 12:35:31 +02:00
Michael Goldenberg 02fdf8c0d3 feat(ffi): add UploadSource for representing upload data
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-04-29 12:35:31 +02:00
Michael Goldenberg 1e835b24fb feat(ffi): update changelog
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-04-29 12:35:31 +02:00
Michael Goldenberg 1a12ba3ad4 feat(ffi): allow file data to be passed through bindings when sending attachment
Signed-of-by: Michael Goldenberg <m@mgoldenberg.net>
2025-04-29 12:35:31 +02:00
dependabot[bot] d9f2588561 chore(deps): Bump tj-actions/changed-files
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from c34c1c13a740b06851baff92ab9a653d93ad6ce7 to 5426ecc3f5c2b10effaefbd374f0abdc6a571b2f.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](https://github.com/tj-actions/changed-files/compare/c34c1c13a740b06851baff92ab9a653d93ad6ce7...5426ecc3f5c2b10effaefbd374f0abdc6a571b2f)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-version: 5426ecc3f5c2b10effaefbd374f0abdc6a571b2f
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-29 12:31:49 +02:00
dependabot[bot] 5268bc35db chore(deps): Bump crate-ci/typos from 1.31.1 to 1.31.2
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.31.1 to 1.31.2.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.31.1...v1.31.2)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.31.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-29 12:20:08 +02:00
Valere Fedronic ff32840387 refactor(crypto): Move session_id from EncryptionInfo to AlgorithmInfo as it is megolm specific
This patch moves the `session_id` field from EncryptionInfo to
AlgorithmInfo::MegolmV1AesSha2 as it is specific to Megolm. We provide
transparent migration of the serialized data from one format to the other.

In the future we plan to reuse `EncryptionInfo` for to_device decryption
(using olm not megolm). So megolm session_id should move to algorithm
specific data.
2025-04-29 08:07:03 +00:00
Mauro Romito b4afb91de5 feat(bindings): check if report room api is supported 2025-04-29 09:48:29 +03:00
Richard van der Hoff d800d3c324 crypto: clean up confusing method (#4983)
This method had a confusing name: it didn't receive a key bundle, but
rather the data *about* a key bundle.

Remove the unused `sender_key` parameter while we are at it: we use the
embedded (and already-checked) `event.sender_device_keys` here.
2025-04-28 13:55:51 +01:00
Richard van der Hoff 7c84ab2701 Merge pull request #4982 from matrix-org/rav/random_fix_1
sdk: remove redundant variable
2025-04-28 13:51:12 +01:00
procr1337 6e119c737c fix(crypto): Take into account pending to-device room key sharing requests when collecting devices that have already received a session
This avoids conditions where a key may be shared with a device only
after we decided that it is fine to reuse (and not rotate) the session
based on the wrong assumption that that particular device does not have
the keys.

Signed-off-by: Niklas Baumstark
[niklas.baumstark@gmail.com](mailto:niklas.baumstark@gmail.com)
2025-04-25 15:51:41 +02:00
Valere Fedronic 237c0256a2 fix(tests): tweak a flaky test to make it more stable + logs (#4968)
Tentative fix for
https://github.com/matrix-org/matrix-rust-sdk/issues/4832

Tweaked a bit the timings and added more logging in the UTD manager to
see what is happening exactly in case it is still flaky.

Signed-off-by: Damir Jelić <poljar@termina.org.uk>
Co-authored-by: Damir Jelić <poljar@termina.org.uk>
Co-authored-by: Benjamin Bouvier <benjamin@bouvier.cc>
2025-04-25 10:42:55 +00:00
Benjamin Bouvier f763d3690d docs(common): add a comment explaining how to use the timer! macro 2025-04-24 18:21:00 +02:00
Andy Balaam 91d085c41b task(tests): Ignore test_new_users_first_messages_dont_warn_about_insecure_device_if_it_is_secure because it is flaky 2025-04-24 15:14:58 +01:00
procr1337 afb6627bef fix(crypto): Fixed a bug where room keys would be rotated unecessarily
Previously, `is_session_overshared_for_user` did not take into account
that `shared_with_set` also contains withheld device IDs who explicitly
have never received the session keys. This would lead to it mistakenly
determining oversharing for those devices for every event being sent in
the presence of blacklisted/withheld devices in the room, and rotating
the group session accordingly.

The fix is to correctly exclude devices with `ShareInfo::Withheld` from
the enumeration.

Signed-off-by: Niklas Baumstark niklas.baumstark@gmail.com
2025-04-24 14:39:02 +02:00
Benjamin Bouvier 8c3f55456f refactor(widget): reduce indent in a few places thanks to early returns 2025-04-24 14:07:27 +02:00
Benjamin Bouvier 03d9e9b368 refactor(widget): avoid complicated combinators and make decisions more local and explicit 2025-04-24 14:07:27 +02:00
Benjamin Bouvier 75c4af5f4e chore(widget): make some names more explicit 2025-04-24 14:07:27 +02:00
Benjamin Bouvier c9f6938cb7 refactor(widget): get rid of WidgetDriverRequestHandle::null too 2025-04-24 14:07:27 +02:00
Benjamin Bouvier 939af521f3 refactor(widget): simplify further the MatrixDriverRequestHandle 2025-04-24 14:07:27 +02:00
Benjamin Bouvier bb9d481d88 refactor(widget): get rid of the null MatrixDriverRequestHandle 2025-04-24 14:07:27 +02:00
Benjamin Bouvier 3df336ab1c refactor(widget): get rid of function used only once 2025-04-24 14:07:27 +02:00
Benjamin Kampmann 12e358a54f fix(sdk): Don't overwrite previously added state events in state_event processing
Fixes #4952 .

Signed-off-by: Benjamin Kampmann <ben@acter.global>
2025-04-24 13:49:22 +02:00
Richard van der Hoff 468e7c35f6 Merge pull request #4932 from matrix-org/rav/history_sharing/save_key_bundle_data
crypto: store received room key bundle data information

Add hooks to the memory store and sqlite store to stash the information about room key data.
2025-04-24 12:22:14 +01:00
Richard van der Hoff a3cb1cd6b5 Merge branch 'main' into rav/history_sharing/save_key_bundle_data 2025-04-24 12:07:21 +01:00
Johannes Marbach 1554e05d8a refactor(send_queue): generalize SentRequestKey::Media and DependentQueuedRequestKind::UploadFileWithThumbnail to prepare for MSC4274 gallery uploads (#4897)
This was broken out of
https://github.com/matrix-org/matrix-rust-sdk/pull/4838 and is a
preliminary step towards implementing
[MSC4274](https://github.com/matrix-org/matrix-spec-proposals/pull/4274).
`SentRequestKey::Media` and
`DependentQueuedRequestKind::UploadFileWithThumbnail` are generalized to
allow chaining dependent media uploads and accumulating sent media
sources.

- [x] Public API changes documented in changelogs (optional)

---------

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
Co-authored-by: Benjamin Bouvier <benjamin@bouvier.cc>
2025-04-24 09:52:33 +00:00
Richard van der Hoff 85e0626d5f indexeddb: fudge implementation of get_received_room_key_bundle_data 2025-04-23 19:59:24 +01:00
Richard van der Hoff e89c45ba42 sqlite: store data on received room key bundles 2025-04-23 19:59:24 +01:00
Richard van der Hoff 6173aef064 memorystore: store received room key bundle data 2025-04-23 19:59:24 +01:00
Richard van der Hoff 00364d95af crypto: add methods for room key bundles to store traits 2025-04-23 19:59:24 +01:00
Richard van der Hoff 3aa0983a5c crypto: add received room key bundles to store changes list
After we receive a to-device message holding room key bundle info, add the data
to the store's Changes structure
2025-04-23 19:59:24 +01:00
Richard van der Hoff 4be4d39851 crypto: add types to support decryption of RoomKeyHistoryBundle to-device
events
2025-04-23 19:59:24 +01:00
Benjamin Bouvier 884775086a chore: add an intermittent test failure policy 2025-04-23 15:34:26 +02:00
Damir Jelić a60e336f85 feat(crypto): Start using the stable identifier for the sender device keys
This patch updates the sending side of the `sender_device_keys` field
introduced in MSC4147.

Since the MSC got merged, we're switching from the unstable identifier
to the stable one.

A couple of snapshot tests were added modified to make this happen.
2025-04-23 15:25:11 +02:00
Benjamin Bouvier 426a4ff1bf chore(ci): make clippy happy on all configurations 2025-04-23 14:49:49 +02:00
Benjamin Bouvier 9492614ea6 refactor(sdk): rename a few push_action_ctx variables back into push_ctx 2025-04-23 14:49:49 +02:00
Benjamin Bouvier 234e0be337 refactor(timeline): reuse the same push context for all the events we're trying to re-decrypt 2025-04-23 14:49:49 +02:00
Benjamin Bouvier b6d71a3875 refactor(timeline): make use of PushContext in the RoomDataProvider trait 2025-04-23 14:49:49 +02:00
Benjamin Bouvier 4c8e2fd4ae refactor(sdk): no need to recompute push actions from /messages, since try_decrypt_event does it for us 2025-04-23 14:49:49 +02:00
Benjamin Bouvier 55342a84fa refactor(sdk): explicit the case where we can't compute the push actions 2025-04-23 14:49:49 +02:00
Benjamin Bouvier 9950268164 refactor(sdk): rename Room::push_action_ctx to Room::push_context() (and associated type too) 2025-04-23 14:49:49 +02:00
Benjamin Bouvier f17c9fb2d5 refactor(sdk): rename Room::push_context() to Room::push_condition_room_ctx() 2025-04-23 14:49:49 +02:00
Benjamin Bouvier 93c961d673 refactor(sdk): avoid recomputing the push context and ruleset for every single event when decrypting a batch 2025-04-23 14:49:49 +02:00
Benjamin Bouvier 6e786e0ede refactor(sdk): use Room::try_decrypt_event in an extra location
The previous code was wrong, in that it wouldn't properly compute push
actions for events that were not encrypted and received with /messages.
2025-04-23 14:49:49 +02:00
Timo 6c4a4382d7 WidgetDriver: Use matrix_sdk_common::executor::spawn instead of tokio::spawn to make it wasm compatible. (#4959)
This PR is a revived version of:
https://github.com/matrix-org/matrix-rust-sdk/pull/4707 since it is now
possible to easily add wasm support thanks to:
https://github.com/matrix-org/matrix-rust-sdk/pull/4572

Using the `executer::spawn` will select `tokio::spawn` or
`wasm_bindgen_futures::spawn_local` based on what platform we are on.

~~They behave differently in that `tokio` actually starts the async
block inside the spawn but the wasm `spawn` wrapper will only start the
part that is not inside the `async` block and the join handle needs to
be awaited for it to work.~~

This has now changed with:
https://github.com/matrix-org/matrix-rust-sdk/pull/4572.
Now they behave the same and this PR becomes a very simple change.
2025-04-23 14:39:34 +02:00
Richard van der Hoff 7272a347fa Merge pull request #4961 from matrix-org/rav/history_sharing/deflake_integ_test
test: attempt to deflake history-sharing integ test
2025-04-23 11:49:41 +01:00
Richard van der Hoff 6d1c24f6fb test: attempt to deflake history-sharing test
Rather than attempting to trigger Alice's encryption sync loop with an incoming
message from Bob, instead have Alice send a message once Bob has joined the
room.
2025-04-23 11:33:06 +01:00
Richard van der Hoff 4300148663 test: factour out new helper wait_until_some. 2025-04-23 11:33:06 +01:00
Richard van der Hoff 75cde02283 Merge pull request #4946 from matrix-org/rav/history_sharing/share_on_invite
sdk: share room history when we send an invite, subject to an experimental feature flag.
2025-04-23 11:15:57 +01:00
Damir Jelić 59ecb1edbd fix(multiverse): Add a shortcut to mark rooms as read back 2025-04-23 11:39:59 +02:00
Richard van der Hoff 6e963917d6 sdk: clean up imports 2025-04-23 09:51:01 +01:00
Valere 7adf60d2c6 fixup: Cleaner ProcessedToDevice snapshot serialization 2025-04-22 16:30:53 +02:00
Valere bd576c22c0 fixup: test, redact snapshot value that is dependent of feature flag 2025-04-22 16:30:53 +02:00
Valere 35023ceb0b fixup: invalid tag in doc 2025-04-22 16:30:53 +02:00
Valere f1e7894c01 fixup: insta use shorter names 2025-04-22 16:30:53 +02:00
Valere ef44631fc6 review: remove outdated changelog line 2025-04-22 16:30:53 +02:00
Valere 541586f6cc review: add snapshot test with proper redaction 2025-04-22 16:30:53 +02:00
Valere f89150d3ee review: quick doc improvements 2025-04-22 16:30:53 +02:00
Valere b27770801c review: refactor, rename NotProcessed variant to Invalid 2025-04-22 16:30:53 +02:00
Valere a49bffac4c review: refactors ProcessedToDeviceEvent to tuple variants
Simplifies the `ProcessedToDeviceEvent` enum by converting its variants to tuple variants.

This change improves code readability and conciseness by removing the need for named fields within the variants.
2025-04-22 16:30:53 +02:00
Valere d4a0c2882d review: Move ProcessedToDeviceEvent to crypto types mod 2025-04-22 16:30:53 +02:00
Valere 031f4ec329 review: Remove encryption_info. Will be part of another PR 2025-04-22 16:30:53 +02:00
Valere 4bf103db38 test: Add more olm decryption encryption_info tests 2025-04-22 16:30:53 +02:00
Valere 4363105976 crypto: Add variants for plain text and encrypted to-device events
fixup: post rebase
2025-04-22 16:30:53 +02:00
Doug 3b133865f0 chore: Remove unused contacts field from OidcConfiguration. 2025-04-22 16:25:07 +02:00
Richard van der Hoff 82a0708b4e SDK: rename confusing-named Room::query_keys_for_untracked_users
This also sends out the query for dirty users, so the name was misleading.
2025-04-22 15:19:51 +01:00
dependabot[bot] 3eafefcf37 chore(deps): Bump tj-actions/changed-files
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 9b4bb2bedb217d3ede225b6b07ebde713177cd8f to c34c1c13a740b06851baff92ab9a653d93ad6ce7.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](https://github.com/tj-actions/changed-files/compare/9b4bb2bedb217d3ede225b6b07ebde713177cd8f...c34c1c13a740b06851baff92ab9a653d93ad6ce7)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-version: c34c1c13a740b06851baff92ab9a653d93ad6ce7
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-22 10:02:57 +02:00
Andy Balaam d2874afb75 fix(integration-tests): Fixes #4871 (hopefully). Repeatedly sync in a test after other user cross-signs 2025-04-17 13:51:32 +02:00
Damir Jelić a848506669 feat(multiverse): Add a /invite command 2025-04-17 12:03:51 +02:00
Damir Jelić f0e49c2adf feat(multiverse): Render membership changes 2025-04-17 12:03:51 +02:00
Ivan Enderlin 74e2e767dd fix(base): Add RoomNotableUpdateReasons::NONE to… fix a possible regression.
This patch introduces a temporary hack.

So here is the thing. Ideally, we DO NOT want to emit this reason.
It does not makes sense. However, all notable update reasons
are not clearly identified so far. Why is it a problem? The
`matrix_sdk_ui::room_list_service::RoomList` is listening this stream
of [`RoomInfoNotableUpdate`], and emits an update on a room item if
it receives a notable reason. Because all reasons are not identified,
we are likely to miss particular updates, and it can feel broken.
Ultimately, we want to clearly identify all the notable update reasons,
and remove this one.
2025-04-17 09:13:41 +03:00
Ivan Enderlin 3ec831f5da test(base): Update tests after the previous patch.
This patch updates the tests, which now break because of the previous
batch that fixes a bug.
2025-04-17 09:13:41 +03:00
Ivan Enderlin ab34330e47 fix(base): Do not emit an RoomInfoNotableUpdate with empty reasons.
In case an empty `RoomInfoNotableUpdateReasons` is empty, let's not send
a `RoomInfoNotableUpdate`.
2025-04-17 09:13:41 +03:00
Ivan Enderlin 35e5cca3fb fix(base): room::display_name emits a RoomInfoNotableUpdateReasons::DISPLAY_NAME.
This patch updates the `room::display_name` response processor to emits
a `RoomInfoNotableUpdateReasons::DISPLAY_NAME`.
2025-04-17 09:13:41 +03:00
Ivan Enderlin 80a7aadf9f feat(base): changes::save_only will also broadcast room info notable updates.
This patch changes the `changes::save_only` and
`changes::save_and_apply` response processors to both broadcast the
`RoomInfoNotableUpdates`.
2025-04-17 09:13:41 +03:00
Ivan Enderlin 4837add55e test(base): Test that sliding sync persists the room (cached) display name. 2025-04-17 09:13:41 +03:00
Ivan Enderlin abf0bbb1a6 fix(base): Use the room::display_name processor for sync v2.
This patch uses `room::display_name` and `changes::save_only` to compute
the new display name for all rooms and save them, for sync v2 only.
2025-04-17 09:13:41 +03:00
Ivan Enderlin c1d885f913 fix(base): Create the room::display_name response processor.
This patch creates the `room::display_name::update_for_rooms` response
processor. It also creates the `changes::save_only` response processor.
Finally, this patch uses both to compute the new display name for all
rooms and save them, for sliding sync only.
2025-04-17 09:13:41 +03:00
Ivan Enderlin 568e60b434 refactor(base): Extract the “save” part of save_and_apply into its own function.
This patch extracts the “save” part of the `save_and_apply` response
processor into its own function. Thus we have `save_changes` (new) and
`apply_changes` that are used by `save_and_apply`.
2025-04-17 09:13:41 +03:00
Ivan Enderlin d05796d8cb task(base): Room::compute_display_name returns an UpdatedRoomDisplayName.
This patch updates `Room::compute_display_name` to return an
`UpdatedRoomDisplayName`. This is useful to know if the display name has
changed or not.
2025-04-17 09:13:41 +03:00
Ivan Enderlin adb7cd33d1 task(base): Introduce UpdatedRoomDisplayName.
This patch introduces a new enum: `UpdatedRoomDisplayName`, which
will be used to know if a room display name is different or not when
computing the room display name.
2025-04-17 09:13:41 +03:00
Richard van der Hoff 18f20a7e29 sdk: only share history if cross-signing is set up
... otherwise, it fails with an error, which makes the integ tests fail
2025-04-16 16:53:31 +01:00
Richard van der Hoff 96bdd91bad sdk: share room history when we send an invite
... subject to an experimental feature flag.
2025-04-16 16:53:27 +01:00
Damir Jelić bc50cae35f feat(multiverse): Add support to join rooms you've been invited to 2025-04-16 11:35:57 +02:00
Stefan Ceriu d36b2a6869 feat(ffi): introduce a ThreadSummary type within MsgLikeContent (#4933)
…that holds information on the thread the given item is the root of

- it holds the latest event content and sender at the moment but will
hold more information in the future e.g. number of replies, if it's
unread etc.
- the field is not currently being populate but is delivered earlier so
it can power shipping the UI side on the embedders
2025-04-16 09:11:31 +03:00
Richard van der Hoff ed232df0b6 Merge pull request #4864 from matrix-org/rav/history_sharing/upload_bundle
crypto: encrypt, upload and share keys for room history
2025-04-15 18:00:03 +01:00
Richard van der Hoff 1a4f6effda Merge branch 'main' into rav/history_sharing/upload_bundle
Signed-off-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
2025-04-15 17:43:01 +01:00
Richard van der Hoff dc6fe93d1e crypto: fix changelog for 0.10.0 and 0.11.0 (#4939)
PR #4670 didn't land until 0.11.0.
2025-04-15 17:12:32 +01:00
Benjamin Bouvier a5537a8f24 fix(event cache): don't ditch a previous-batch token when we didn't have initial events (#4936)
See #4891 that shows a case where we should have saved the
previous-batch token, and instead ditched it, in the previous version.

Changes include:
- moving the code deciding to keep or ditch the `previous-batch` token
into `append_events_locked`.
- tweak the condition to ditch, so that the `previous-batch` token is
ditched only if we didn't have events in the event cache in the first
place, in addition to having storage + the timeline not being marked as
limited explicitly.

Credits to @zecakeh for the test case.
2025-04-15 15:29:11 +00:00
Benjamin Bouvier a27d6e2655 multiverse: prefer rendering back-paginated events instead of timeline's tail
This is useful to observe the virtyual start of timeline item in manual
testing.
2025-04-15 16:31:20 +02:00
Richard van der Hoff bce6c19bba Merge branch 'main' into rav/history_sharing/upload_bundle
Signed-off-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
2025-04-15 14:32:42 +01:00
Ivan Enderlin 22d092b83c chore(base): Rename ephemeral_events to dispatch_ephemeral_events. 2025-04-15 14:17:07 +02:00
Ivan Enderlin ee5671bef5 chore(base): Rename Room to RoomCreationData. 2025-04-15 14:17:07 +02:00
Ivan Enderlin 0b58b9112d chore(base): Make Clippy happy. 2025-04-15 14:17:07 +02:00
Ivan Enderlin 0fed5147b9 chore(base): Use Entry::or_default() to simplify code. 2025-04-15 14:17:07 +02:00
Ivan Enderlin b06234149f chore(base): Fix imports and e2e-encryption. 2025-04-15 14:17:07 +02:00
Ivan Enderlin 739f306bf0 refactor(base): Create the room::msc4186::extensions::room_account_data response processor.
This patch is twofold:

1. it transforms the `&mut` to a `&` for the room account data in the
   sliding sync flow, which allows to remove one big clone!
2. it adds the `room_account_data` response processor.
2025-04-15 14:17:07 +02:00
Ivan Enderlin 5f3c96607f refactor(base): Create the room::msc4186::extensions::ephemeral_events response processor.
This patch creates the new `room::msc4186::extensions::ephemeral_events`
response processor.

Ideally we would like to merge this with `ephemeral_events`, but they
are a bit different. Let's see how it evolves in the future.
2025-04-15 14:17:07 +02:00
Ivan Enderlin 235facb793 refactor(base): room::sync_v2::update_(joined|left)_room use room::Room.
This patch uses the `room::Room` structure in `room::sync_v2` to
reduce the number of arguments of the `update_joined_room` and the
`update_left_room` processors.
2025-04-15 14:17:07 +02:00
Ivan Enderlin 9adb0deaa5 refactor(base): room::msc4186::update_any_room uses room::Room.
This patch uses the `room::Room` structure in
`room::msc4186::update_any_room` to reduce its number of arguments. It
results in the removal of te `allow(clippy::too_many_arguments)`.
2025-04-15 14:17:07 +02:00
Ivan Enderlin 61b711ce76 chore(base): Create the room::Room structure.
This patch creates the `room::Room` structure to group common arguments
in the `room` processors.
2025-04-15 14:17:07 +02:00
Ivan Enderlin 9e6dc71609 refactor(base): Remove the BaseStateStore argument in room:sync_v2::* processors.
This patch removes the `BaseStateStore` argument in `room::sync_v2::*`
response processors as it can be fetched from the `Notification`
argument.
2025-04-15 14:17:07 +02:00
Ivan Enderlin 33b1c02873 chore(base): Rename room_data to room_response.
This patch tries to clarify that the `room_data` _is_ the HTTP response.
2025-04-15 14:17:07 +02:00
Ivan Enderlin f4ad575090 refactor(base): Remove SessionMeta from update_any_room.
This patch removes the `SessionMeta` argument of
`room::msc4186::update_any_room`. Tracking its usage, it reveals that
providing a `UserId` is sufficient. Happily, we already provide a
`UserId`, hence making `SessionMeta` useless here.
2025-04-15 14:17:07 +02:00
Ivan Enderlin f55730716a chore(base): Rename a variable room_type to room_state. 2025-04-15 14:17:07 +02:00
Ivan Enderlin bad1c683f8 chore(base): Rename fields of RoomUpdate.
This patch renames the fields in `RoomUpdate`: `join` becomes `joined`,
`leave` becomes `left`, `invite` becomes `invited`, to match their
associate type.
2025-04-15 14:17:07 +02:00
Ivan Enderlin 2479339c46 refactor(base): Simplify the flow of membership.
This patch simplifies the flow of `membership` when the room is an
invited room.

Previously, we were creating the room as `Invited`. Then, later
overriding it to `Knocked` if we found it was such a room. Otherwise, we
were overriding it again to `Invited`. Well. Now the flow is simpler.
2025-04-15 14:17:07 +02:00
Ivan Enderlin 45d5d1a802 refactor(base): Remove the numerous Option<…> returned by update_any_room.
This patch removes the 4 `Option<…>` returned by `update_any_room`: they
are replaced by a single new `RoomUpdateKind` enum.
2025-04-15 14:17:07 +02:00
Ivan Enderlin a218105aca refactor(base): Create the room::msc4186::update_any_room response processor.
This patch extracts the `BaseClient::process_sliding_sync_room` method
into the new `msc4186::update_any_room` response processor. So far, this
is purely a code move, without any changes (modulo method-to-function
transformation).

With `BaseClient:process_sliding_sync_room` comes
`BaseClient::process_sliding_sync_room_membership`,
`BaseClient::handle_own_room_membership`, `process_room_properties` and
`cache_latest_events`.
2025-04-15 14:17:07 +02:00
Ivan Enderlin 6bbb7fb498 chore(base): Remove unnecessary comments.
This patch removes comments that are irrelevant today. There is nothing
to fix.
2025-04-15 14:17:07 +02:00
Richard van der Hoff 80db096be8 crypto: fix changelog 2025-04-15 12:55:55 +01:00
Richard van der Hoff 9ef1a040dd test: add the start of an integration test for room history sharing
This is only a partial test, since we haven't yet implemented the receiver side
of the history-sharing messages.
2025-04-15 12:55:55 +01:00
Richard van der Hoff f11158ab6c sdk: send out the to-device requests created by Room::share_history 2025-04-15 12:55:55 +01:00
Richard van der Hoff 84a030aed0 crypto: Support for encrypting and sending room key history bundle data
For each device belonging to the user, encrypt and send to-device messages
containing the bundle data
2025-04-15 12:55:55 +01:00
Richard van der Hoff 7b25a50a51 sdk: add Room::share_history
The next step in our work on sharing encrypted room history. Add a method to
`matrix_sdk::room::Room` which will upload an encrypted key bundle.
2025-04-15 12:55:55 +01:00
Ivan Enderlin f32d0099fc chore(base): Make Clippy happy. 2025-04-15 11:57:39 +02:00
Ivan Enderlin 6d9cf861f6 chore(base): Remove the re-exports in timeline::builder.
This patch removes the re-exports of `Notification` and `E2EE` in
`timeline::builder`. Let's make things simple now that they are used
outside the `timeline` processors.
2025-04-15 11:57:39 +02:00
Ivan Enderlin 13e565a4dc refactor(base): Create the room::sync_v2::update_knocked_room response processor.
This patch creates the `update_knocked_room` processor, extracted from
the sync v2 flow.
2025-04-15 11:57:39 +02:00
Ivan Enderlin aeca1f1495 refactor(base): Use Notification wherever possible.
This patch groups arguments behind the `Notification` struct
if it makes sense. This patch also uses the new method
`Notification::push_notification_from_event_if` method to replace
duplicated code.
2025-04-15 11:57:39 +02:00
Ivan Enderlin c16bc6b435 refactor(base): Move and improve the Notification struct in response processors.
This patch moves the `timeline::builder::Notification` into
its own module, `notification`. This patch adds two methods:
`push_notification` and `push_notification_from_event_if`.
2025-04-15 11:57:39 +02:00
Ivan Enderlin 846fcfb408 feat(base): Add From implementations to build a RawAnySyncOrStrippedTimelineEvent.
This patch adds two `From` implementations on
`RawAnySyncOrStrippedTimelineEvent` to create the correct variant of
this enum. This is useful when we get generic types and want to build
this type.
2025-04-15 11:57:39 +02:00
Ivan Enderlin 1141b7db1a refactor(base): Create the room::sync_v2::update_invited_room response processor.
This patch creates the `update_invited_room` processor by extracting the
code for the sync v2 flow.
2025-04-15 11:57:39 +02:00
Ivan Enderlin b85a1a0998 refactor(base): Create the room::sync_v2::update_left_room response processor.
This patch creates the `update_left_room` processor, extracted from the
sync v2 flow.

I've noticed that `new_user_ids` is not used, so it has been put behind
a `_` variable for the moment, but some computations have been removed.
We need to clean this user ID flow.
2025-04-15 11:57:39 +02:00
Ivan Enderlin e62313d7ba reefactor(base): Create the room::sync_v2::update_joined_room response processor.
This patch extracts the logic to handle a `JoinedRoom` in a response
processor.
2025-04-15 11:57:39 +02:00
Ivan Enderlin 71510de602 refactor(base): Extract timeline::builder::E2EE into e2ee, and use it more.
This patch moves the `timeline::builder::E2EE` type into the `e2ee`
module. Gathering these 3 E2EE values in the same type was a good idea,
and is now applied to more places in the response processors.
2025-04-15 11:57:39 +02:00
Ivan Enderlin 28888a414b refactor(base): Compute now if and only if Level::INFO is enabled.
This patch computes the `now` variable if and only if it is required.
It is used by an `info!` log, so let's condition the creation of `now`
to that.
2025-04-15 11:57:39 +02:00
Ivan Enderlin 3538bb91e3 refactor(base): Context derives Default.
This patch makes `Context` to derive `Default`. The `new` constructor
also no longer takes a `RoomInfoNotableUpdates`, since it was always
used with a default value, it generates this value by itself.
2025-04-15 11:57:39 +02:00
Ivan Enderlin fdcc6dbeda chore(base): Replace an unwrap by an expect.
This patch updates a comment and replaces an `unwrap` by an `expect`.
2025-04-15 11:57:39 +02:00
dependabot[bot] f1cd8120a8 chore(deps): Bump tj-actions/changed-files
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 6f67ee9ac810f0192ea7b3d2086406f97847bcf9 to 9b4bb2bedb217d3ede225b6b07ebde713177cd8f.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](https://github.com/tj-actions/changed-files/compare/6f67ee9ac810f0192ea7b3d2086406f97847bcf9...9b4bb2bedb217d3ede225b6b07ebde713177cd8f)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-version: 9b4bb2bedb217d3ede225b6b07ebde713177cd8f
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-15 11:31:54 +02:00
Richard van der Hoff 473852c7d5 Merge pull request #4922 from matrix-org/rav/check_sender_device_keys
crypto: check `sender_device_keys` on incoming Olm messages
2025-04-15 10:18:50 +01:00
Ivan Enderlin f19f8b25db fix(sdk): Remove include_heroes in sliding sync requests.
This patch removes all mentions of `include_heroes`. This field isn't
part of the MSC4186. We have the `test_compute_heroes_from_sliding_sync`
which continues to pass, everything fine on that side. There is no
mention of `include_heroes` in Synapse at the time of writing. It seems
to be an artifact from the sliding sync proxy experiment.
2025-04-14 18:26:27 +02:00
Jonas Richard Richter 4de202fc64 doc(changelog): add entry for room topic string in StateEventContent 2025-04-14 15:43:17 +02:00
Jonas Richard Richter 7b206d33f0 feat(ffi): update RoomTopic AnySyncStateEvent variant to include topic string 2025-04-14 15:43:17 +02:00
Ivan Enderlin 085de8bdae refactor(base): Create the ephemeral_events response processor.
This patch creates the `ephemeral_events` response processors:
`dispatch` and `dispatch_one`.

This remove duplicated code.
2025-04-14 08:48:18 +02:00
Ivan Enderlin fe0b954019 refactor(base): Create the dispatch_invite_and_knock response processor.
This patch creates the
`state_events::stripped::dispatch_invite_and_knock`, that is migrated
from the `BaseClient::handle_invite_state` method.
2025-04-14 08:48:18 +02:00
Ivan Enderlin e21ae2ae53 chore(base): Move dispatch_and_get_new_users inside the sync module.
This patch moves the `state_events::dispatch_and_get_new_users`
processor inside the `state_events::sync` module. Why? Because a similar
processor for `state_events::stripped` is about to be created.
2025-04-14 08:48:18 +02:00
Jorge Martín 0306683cbf feat(ffi): Add extra details to ClientError.
Also split `ClientError::new` into `ClientError::from_str` and `ClientError::from_err` so we can automatically get the details from the 2nd one.
2025-04-11 15:49:20 +02:00
Richard van der Hoff e020ba1023 crypto: check sender_device_keys on incoming Olm messages
MSC4147 added a `sender_device_keys` property to olm-encrypted to-device
messages, with recommendations about checking the values in that propety. We do
(most of?) those checks for `m.room_key` messages today, but not other types of
to-device message.
2025-04-11 12:00:18 +01:00
Richard van der Hoff 0697e0705b crypto: support MSC4147 device keys on custom to-device events
MSC4147 added a `sender_device_keys` property to the plaintext of *all*
olm-encrypted events. 03d4a30eb added the field to `DecryptedOlmV1Event`, but
due to Reasons, there is an almost-parallel struct `ToDeviceCustomEvent` which
is used for event types other than the 4 we have content types for.

To complete the set, let's add the field to `ToDeviceCustomEvent`.
2025-04-11 11:01:22 +01:00
Ivan Enderlin 1e938df90d Merge pull request #4921 from Hywan/release-0.11.0
chore: Release matrix-sdk version 0.11.0
2025-04-11 11:06:43 +02:00
Ivan Enderlin 5d55bb4955 chore: Release matrix-sdk version 0.11.0 2025-04-11 10:51:30 +02:00
Ivan Enderlin 8abd9fb303 doc(releasing): Fixing markdown.
Remove unbreakable spaces, and add indentations.
2025-04-11 10:42:37 +02:00
Ivan Enderlin d5ee644443 refactor(base): Reorganize the state_events processors.
This patch tries to clarify the processors in `state_events` by
putting them in two modules: `sync` and `stripped`, along with a bit
of renaming:

- `collect_sync` becomes `sync::collect`,
- `collect_sync_from_timeline` becomes `sync::collect_from_timeline`,
- `collect_stripped` becomes `stripped::collect`.

I believe this is an improvement.
2025-04-11 10:32:08 +02:00
Ivan Enderlin 06022aa23a chore(base): Remove a _ in front of a variable.
This ptach removes a `_` in front of a variable: it is used, if and only
if the `e2e-encryption` feature is enabled. Make it stand out.
2025-04-11 10:32:08 +02:00
Ivan Enderlin 8ea0d9542d chore(base): Rename process_if_candidate to process_if_relevant. 2025-04-11 10:32:08 +02:00
Ivan Enderlin 63938bf2c7 chore(base): Remove an unnecessary allow(clippy…). 2025-04-11 10:32:08 +02:00
Ivan Enderlin acff6c2e1d doc(base): Fix an intra-link in the documentation. 2025-04-11 10:32:08 +02:00
Ivan Enderlin 1013843071 refactor(base): Create the timeline::build response processor.
This patch does the following things:

1. moves the `BaseClient::handle_timeline` method as the
   `timeline::build` response processor.
2. moves the `BaseClient::update_push_room_context` method as a private
   function of the `timeline` response processor module,
3. moves the `BaseClient::get_push_room_context` method as a public
   function of the `timeline` response processor module.

The scope of the `timeline::build` processor is a bit too broad, but at
least the number of methods on `BaseClient` are greatly reduced.
2025-04-11 10:32:08 +02:00
Ivan Enderlin a0bfd3e21e chore(clippy): Make Clippy happy. 2025-04-11 10:32:08 +02:00
Ivan Enderlin 68352e8339 chore(base): Rename variables for the sake of clarity.
Don't know what `event`, `e`, `e`, `e` or `r` represent? Me neither.
This patch tries to fix that.
2025-04-11 10:32:08 +02:00
Ivan Enderlin 6a5e24a64a refactor(base): Create the verification::process_if_candidate response processor.
This patch creates a new `verification::process_if_candidate` response
process. The idea is the check whether a `AnySyncTimelineEvent` suits to
be a verification event that must be processed.

This piece of code was repeated in three different places in the code.
Now it's unique.
2025-04-11 10:32:08 +02:00
Ivan Enderlin db6d1b4cd6 refactor(base): BaseClient::handle_timeline no longer handle state events.
This patch removes the `ignore_state_events` argument from
`BaseClient::handle_timeline`. This method no longer handles state
events. Consequently, the `user_ids` and `ambiguity_cache` arguments are
also removed.

This patch updates the flow to use the new
`state_events::collect_sync_from_timeline` processor, and to re-use the
`state_events::dispatch_and_get_new_users` processor.

The positive impact of this patch is that it re-uses existing code
(hurray). However, the downside is that state events are deserialized
twice for the moment.
2025-04-11 10:32:08 +02:00
Ivan Enderlin b0c1eda682 refactor(base): Create the state_events::dispatch_and_get_new_users response processor.
This patch extracts the `BaseClient::handle_state` method as a new
response processor named `state_events::dispatch_and_get_new_users`.
It appears that we can do something more elegant with the returned new
users. See the next patch.
2025-04-11 10:32:08 +02:00
Ivan Enderlin afecd6d508 chore(base): Simplify code by calling iter::zip instead of Iterator::zip.
This patch uses `iter::zip` to replace `Iterator::zip`. It does exactly
the same, it's just shorter.
2025-04-11 10:32:08 +02:00
Ivan Enderlin 52b490f5b6 chore(base): Remove allow(unused_mut) for the context.
This is used everytime now :-).
2025-04-11 10:32:08 +02:00
Ivan Enderlin 097558ca1b chore(base): Rewrite code to avoid an allocation and calling twice the same function.
This patch rewrites `e2e::decrypt::sync_timeline_event` to avoid calling
a `verification` twice with the same arguments, and to avoid a string
allocation (the `to_string`).

Also, I wasn't comfortable with the `starts_with` which wasn't included
a trailing `.`. At least now we rely on strongly typed event type.
2025-04-11 10:32:08 +02:00
Ivan Enderlin 2194a81d74 refactor(base): Create the e2ee::decrypt::sync_timeline_event response processor.
This patch extracts the `BaseClient::decrypt_sync_room_event` method
into the new `e2ee::decrypt::sync_timeline_event` response processor.
2025-04-11 10:32:08 +02:00
Ivan Enderlin 37146da27a chore(base): Rename from_sync_v3 to …_v2.
The version of something is 3 but the sync version is 2. Confusing.
2025-04-11 10:32:08 +02:00
Mauro Romito 9300f47b40 feat(bindings): join_rule in NotificationItem 2025-04-11 10:29:59 +02:00
Mauro Romito 52f0aafb1e feat(bindings): expose is_public in notifications 2025-04-11 10:29:59 +02:00
dependabot[bot] bfbbe89989 chore(deps): Bump crossbeam-channel from 0.5.13 to 0.5.15
Bumps [crossbeam-channel](https://github.com/crossbeam-rs/crossbeam) from 0.5.13 to 0.5.15.
- [Release notes](https://github.com/crossbeam-rs/crossbeam/releases)
- [Changelog](https://github.com/crossbeam-rs/crossbeam/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crossbeam-rs/crossbeam/compare/crossbeam-channel-0.5.13...crossbeam-channel-0.5.15)

---
updated-dependencies:
- dependency-name: crossbeam-channel
  dependency-version: 0.5.15
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-10 16:52:10 +02:00
Richard van der Hoff d25e236a75 Merge pull request #4917 from matrix-org/rav/tracing_improvements
Cleanups and extensions to tracing span logic
2025-04-10 11:47:53 +01:00
Damir Jelić ab90c1b945 refactor(sdk): Migrate away from the backoff crate to the backon crate
Since backon also has WASM support, this should mean that we can get rid
of the WASM specific HTTP client implementation.
2025-04-10 11:58:06 +02:00
Jorge Martín 005e506c9b refactor(ffi): expose Room::member_with_sender_info.
Reuse this for `RoomPreview` too, removing `RoomMembershipDetails`, which contained the same info.
2025-04-10 09:48:15 +02:00
Jorge Martín 3fe457db83 refactor(room): change Room::own_membership_details to member_with_sender_info.
The function will now return the previously added `RoomMemberWithSenderInfo` struct.
2025-04-10 09:48:15 +02:00
Jorge Martín 69ab855efa refactor(room): add RoomMemberWithSenderInfo struct.
This will hold the info for both the room member whose sender id was provided and the sender of the `m.room.member` event from which the `RoomMember` is built.
2025-04-10 09:48:15 +02:00
Richard van der Hoff 66c7ba60a9 sdk: reduce clobber attached to tracing spans for http requests
We don't need to list the `server_versions` every time we do an http
request. Further, `config` is listed under both `skip` and `fields`, which ends
up being a no-op. I don't think it's very useful (and is quite noisy), so
let's remove it.
2025-04-09 19:55:38 +01:00
Richard van der Hoff 3e320b8289 sdk: propagate tracing span for outgoing requests
In parallel to a sliding sync request, we also check for and send pending outgoing
requests from the crypto stack. Currently, these drop the tracing span, which
loses valuable data. We should propagate the span.

We also add an extra layer of instrumentation so that we can differentiate
between the results of the sliding sync itself, and the outgoing requests.
2025-04-09 19:55:38 +01:00
Richard van der Hoff 2922389037 UI: Allow attaching a parent tracing span for sync service
Currently, if you have two clients both syncing away inside the same process,
it's approximately impossible to see which logs belong to which client. It
would be much better if we could use a tracing span to distinguish the two
clients.

I expect this to be mostly useful in integration tests, but it might be useful
elsewhere too.
2025-04-09 19:54:01 +01:00
Damir Jelić 68e3cdebdd chore: Bump the blake3 version 2025-04-09 16:37:40 +02:00
Damir Jelić 511cf78d51 chore: Bump the rusqlite version 2025-04-09 16:37:40 +02:00
Damir Jelić f4eea708fa chore: Update the ByteSize crate version we're using 2025-04-09 16:37:40 +02:00
Richard van der Hoff 1d3107ebcb multiverse: reduce redraw rate
This reduces the framerate from ~62fps to 10fps.

This is a workaround for a problem in my terminal, so apologies for inflicting
it on everyone else, but here we are, and 10fps seems like it should be enough
for anyone.

The problem in question is specifically when I try to select some text by
dragging the mouse (eg, to copy a generated recovery key). If I start a drag,
but a redraw happens before the mouse has moved [a certain distance?], then the
drag doesn't work, and nothing gets selected. By reducing the framerate, I have
a much better chance of successfully starting a drag.
2025-04-09 16:10:31 +02:00
Richard van der Hoff 073b4bae03 multiverse: allow recovery keys with h or l in them
Currently, `h` and `l` are intercepted by the parent view to change tab,
meaning it's impossible to enter a recovery key which contains those
characters.

The fix here is very blunt: it just disables `h` and `l` for tab-changing. I
considered making it dependent on which tab is open, or what's going on in the
'Encryption' tab, but given you need to know about the alternatives (tab/cursor
keys) to switch away from the Encryption tab, I don't think that makes sense.
2025-04-09 14:53:36 +02:00
Richard van der Hoff 35e13b8730 Merge pull request #4903 from matrix-org/rav/history_sharing/upload_bundle_prep
crypto: prep work for sharing room key history bundles
2025-04-09 10:40:42 +01:00
Damir Jelić f266fb9c38 chore(ffi): Update the changelog so it conforms to our cargo-release setup 2025-04-09 11:26:59 +02:00
Damir Jelić 2dfd334ade chore: Include the matrix-sdk-ffi crate in the release process 2025-04-09 11:26:59 +02:00
Damir Jelić 89d0cc5a76 chore: Add a missing PR link in one of the changelogs 2025-04-09 09:16:53 +02:00
Stefan Ceriu 628440632d chore(ffi): move reactions from EventTimelineItem to MsgLikeContent to keep in line with the UI crate types 2025-04-08 17:47:17 +03:00
Stefan Ceriu 16a3d9d78b chore(ffi): introduce MsgLike Content and Kind and move mappings 2025-04-08 17:47:17 +03:00
Stefan Ceriu d89a7d6c18 chore(ffi): move MsgLike related types to their own file 2025-04-08 17:47:17 +03:00
Stefan Ceriu 87c70789fe chore(ffi): move replies to their own file 2025-04-08 17:47:17 +03:00
Damir Jelić 9cde7f46bc docs: Use the github background color for the logo on dark themes 2025-04-08 16:13:59 +02:00
Damir Jelić 3e2b95cdb9 doc: Update the main readme
Now with a logo.
2025-04-08 14:26:20 +02:00
Richard van der Hoff dc3bd69af1 crypto: rename MaybeEncryptedRoomKey::Withheld to MissingSession
This is only used to indicate missing sessions, and the `code` is no longer
used, so let's rename the variant and remove the redundant field.
2025-04-08 12:47:08 +01:00
Richard van der Hoff da14cef6c1 crypto: Factor out a EncryptForDevicesResultBuilder
We want to re-use all this logic, so putting it in a separate type will
help. Plus I think it's cleaner.
2025-04-08 12:47:08 +01:00
Ivan Enderlin a1ac363383 refactor(base): Create account_data::for_room response processor.
This patch moves the `handle_room_account_data` method as the new
`account_data::for_room` response processor.

Instead of taking the `BaseClient` to fetch the room in `on_room_info`,
it now takes a `BaseStateStore`, which is safer and more straight to
the point.
2025-04-08 13:20:31 +02:00
Ivan Enderlin 9e5ef57a5f refactor(base): AccountDataProcessor becomes a response processor.
This patch changes `AccountDataProcessor` to
`processors::account_data::global`. The next patch will introduce a
per-room account data processor, hence the renaming to `Global` to make
the difference between the twos.
2025-04-08 13:20:31 +02:00
Ivan Enderlin da86552648 refactor(base): Remove a pub(crate).
This patch removes a `pub(crate)`. This is a private method only.
2025-04-08 13:20:31 +02:00
Ivan Enderlin c0f4e90965 refactor(base): Rename a couple of variables.
This patch renames a couple of variables because it's important
to understand that those stripped state events are coming from the
`invite_state` value of a sliding sync response. This is a pretty
important detail.
2025-04-08 13:20:31 +02:00
Ivan Enderlin cf393bf8e1 refactor(base): verification must be behind e2e-encryption. 2025-04-08 13:20:31 +02:00
Ivan Enderlin 2ee0e175fa refactor(base): Create the state_events::collect_* response processors.
This patch creates the `state_events::collect_sync`
and `collect_stripped` response processors. It
removes the `BaseClient::deserialize_state_events` and
`deserialize_stripped_state_events` methods. It also removes a couple of
`Vec` allocations and a couple of clones.
2025-04-08 13:20:31 +02:00
Ivan Enderlin 5c011a8400 refactor(base): Create the changes::save_and_apply response processor.
This patch creates the `changes::save_and_apply` response
processor. It consists of moving `BaseClient::apply_changes` plus
the code that is always around. This patch helps to remove the
`BaseClient::load_previous_ignored_user_list` method.
2025-04-08 13:20:31 +02:00
Ivan Enderlin b6b0c556b9 doc(crypto): Fix typos. 2025-04-08 13:20:31 +02:00
Ivan Enderlin 0c5f0b8d26 chore(base): Remove the with_e2ee internal module.
Just because it's clearer.
2025-04-08 13:20:31 +02:00
Ivan Enderlin e7e15ca280 refactor(base): Create the tracked_users response processor.
This patch creates the new `tracked_users::update_if_necessary` response
processor, and uses it in two places where the code was duplicated.
2025-04-08 13:20:31 +02:00
Ivan Enderlin 222cffd502 chore(base): Move e2ee.rs to e2ee/to_device.rs. 2025-04-08 13:20:31 +02:00
Ivan Enderlin cbef772eaa refactor(base): Move handle_room_member_event_for_profiles as a processor.
This patch moves the `handle_room_member_event_for_profiles`
function inside the collection of response processors under the name
`profiles::upsert_or_delete`.
2025-04-08 13:20:31 +02:00
Ivan Enderlin e7e9c7bcf2 refactor(base): Use response_processors::Context everywhere.
This patch updates codes in `BaseClient` to use `Context` as much as
possible.
2025-04-08 13:20:31 +02:00
Richard van der Hoff f1ea3e64d0 crypto: new result type for encrypt_session_for
Reduce the size of the tuple that this thing returns by defining a new result
type.

I haven't put the `share_infos` in the struct, because I'm going to reuse the
same struct for another method where `share_infos` aren't needed.
2025-04-08 12:18:21 +01:00
Richard van der Hoff 257deb4b94 crypto: factor out collect_recipients_for_share_strategy
For cases where we don't have a whole `EncryptionSettings`, we need
a finer-grained method.
2025-04-08 12:18:21 +01:00
Jorge Martín 3ece8e62b5 refactor(ffi): export NotificationItem::thread_id in the FFI layer 2025-04-08 11:53:39 +02:00
Jorge Martin Espinosa f7f07e7389 fix(ui): extract NotificationEvent::thread_id to its own function (#4899) 2025-04-08 08:30:14 +00:00
dependabot[bot] 8d0928ff7c chore(deps): Bump tj-actions/changed-files
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 27ae6b33eaed7bf87272fdeb9f1c54f9facc9d99 to 6f67ee9ac810f0192ea7b3d2086406f97847bcf9.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](https://github.com/tj-actions/changed-files/compare/27ae6b33eaed7bf87272fdeb9f1c54f9facc9d99...6f67ee9ac810f0192ea7b3d2086406f97847bcf9)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-version: 6f67ee9ac810f0192ea7b3d2086406f97847bcf9
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 09:13:50 +01:00
maan2003 39212db9ce fix(wasm): don't kill task on drop
Signed-off-by: Manmeet Singh <manmeetmann2003@gmail.com>
2025-04-08 09:10:24 +02:00
Johannes Marbach 056d4a79d0 refactor(send_queue): vectorize media handles on SendHandle (#4898)
This was broken out of
https://github.com/matrix-org/matrix-rust-sdk/pull/4838 and is a
preliminary step towards implementing
[MSC4274](https://github.com/matrix-org/matrix-spec-proposals/pull/4274).
The `media_handles` field on `SendHandle` is turned into a vector so
that it can hold handles for several media when upload a gallery later.

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-04-08 08:53:08 +02:00
Jorge Martin Espinosa 5753ca3a64 misc(ffi): add thread_id to NotificationItem (#4895)
This is needed to identify the event as being in a thread, and to reply
to it inside the thread instead of in the room's timeline.

<!-- description of the changes in this PR -->

- [ ] Public API changes documented in changelogs (optional)

<!-- Sign-off, if not part of the commits -->
<!-- See CONTRIBUTING.md if you don't know what this is -->
Signed-off-by:
2025-04-07 17:53:44 +02:00
Damir Jelić f3291d15c5 feat(multiverse): Move the details view to be beside the timeline
This allows us to view the debug screen at the same time as the
timeline. The details view can be to the right of the timeline or bellow
the timeline and we can switch using ALT-t.
2025-04-07 14:17:10 +02:00
Damir Jelić 413070aecb feat(multiverse): Shrink the room list to 25% of the screen 2025-04-07 14:17:10 +02:00
Damir Jelić 288251b1b5 feat(multiverse): Keep the timeline scrolled to the bottom 2025-04-07 14:17:10 +02:00
Damir Jelić 6c3e3bb519 feat(multiverse): Tweak the keybindings for the details view a bit 2025-04-07 14:17:10 +02:00
Damir Jelić 2e8b396c09 feat(multiverse): Add global keybindings to go directly into a details view 2025-04-07 14:17:10 +02:00
Damir Jelić ed048af903 chore(multiverse): Fix some clippy warnings 2025-04-07 14:17:10 +02:00
Damir Jelić 28e475b1fc chore(multiverse): Get rid of an unused dependency 2025-04-07 14:17:10 +02:00
Damir Jelić 5a13bd5e76 feat(multiverse): Allow to backpaginate while looking at the room details 2025-04-07 14:17:10 +02:00
Damir Jelić 6966302467 fix(multiverse): Properly document the shortcuts on the help screen 2025-04-07 14:17:10 +02:00
Damir Jelić fd6ce02d70 feat(multiverse): Select the first item in the developer settings 2025-04-07 14:17:10 +02:00
Damir Jelić 2debfd4c4d fix(multiverse): The encryption settings don't need a separate block anymore 2025-04-07 14:17:10 +02:00
Damir Jelić 19c40fd2da fix(multiverse): Better rendering for the OAuth approval URL when resetting recovery 2025-04-07 14:17:10 +02:00
Damir Jelić d5d0368ba8 feat(multiverse): Add support to reset your identity 2025-04-07 14:17:10 +02:00
Damir Jelić 899eb04f05 feat(multiverse): Use F8 to open the details view instead of CTRL-D
CTRL-D might become important once we add scrolling to the timeline
2025-04-07 14:17:10 +02:00
Damir Jelić 187280d573 feat(multiverse): Show an exit screen instead of printing things to stdout 2025-04-07 14:17:10 +02:00
Damir Jelić 036d14e9e3 feat(multiverse): Settings view 2025-04-07 14:17:10 +02:00
Damir Jelić 226229d63b refactor(multiverse): Move the widgets of the details mode into the details module 2025-04-07 14:17:10 +02:00
Damir Jelić 0cf018cc1b feat(multiverse): Allow the help screen to be closed with ESC as well 2025-04-07 14:17:10 +02:00
Damir Jelić c3d7a760f7 feat(multiverse): Add an input line so we can send messages 2025-04-07 14:17:10 +02:00
Damir Jelić d51cf1e76e refactor(multiverse): Change the keybinding for the reaction sending feature 2025-04-07 14:17:10 +02:00
Damir Jelić 71b6b213c4 refactor(multiverse): Move the details mode into a tab-based popout 2025-04-07 14:17:10 +02:00
Damir Jelić ec23638567 refactor(multiverse): Move some global shortcuts behind modifiers 2025-04-07 14:17:10 +02:00
Damir Jelić 688a56a077 feat(multiverse): Always render the timeline, despite showing some details 2025-04-07 14:17:10 +02:00
Damir Jelić 8413e856fe refactor(multiverse): Move the timeline rendering into a separate widget 2025-04-07 14:17:10 +02:00
Damir Jelić 3f16e77686 refactor(multiverse): Don't unwrap when subscribing to the event cache 2025-04-07 14:17:10 +02:00
Damir Jelić a4779299f6 refactor(multiverse): Move the details views under the room view module 2025-04-07 14:17:10 +02:00
Damir Jelić 50934f5bc9 refactor(multiverse): Move the room view into a separate widget 2025-04-07 14:17:10 +02:00
Damir Jelić fad63b9a64 feat(multiverse): Recovery support 2025-04-07 14:17:10 +02:00
Damir Jelić d999cf9180 refactor(multiverse): Convert the status widget into a stateful widget
The status widget depends on the main app state, instead of cloning the
app state let's just use a StatefulWidget instead.
2025-04-07 14:17:10 +02:00
Damir Jelić 3cd60c5b01 doc(multiverse): Document the status widget a bit better 2025-04-07 14:17:10 +02:00
Damir Jelić 2e4587f824 refactor(multiverse): Move all the widgets into a separate module 2025-04-07 14:17:10 +02:00
Damir Jelić 3996f7c0d6 feat(multiverse): Add a help screen 2025-04-07 14:17:10 +02:00
Damir Jelić 774bff00a0 refactor(multiverse): Events view 2025-04-07 14:17:10 +02:00
Damir Jelić d6196e6c5c refactor(multiverse): Move the linked chunk view into a popout widget 2025-04-07 14:17:10 +02:00
Damir Jelić b49fd2b473 feat(multiverse): Only open the read receipt screen if a room is selected 2025-04-07 14:17:10 +02:00
Damir Jelić f31119a013 refactor(multiverse): Turn the read receipt rendering logic into a widget 2025-04-07 14:17:10 +02:00
Damir Jelić fb4caf40aa refactor(multiverse): Make the Status struct a true widget 2025-04-07 14:17:10 +02:00
Damir Jelić 238fbdbe82 refactor(multiverse): Use the Mutex from the common crate to avoid calling unwrap 2025-04-07 14:17:10 +02:00
Damir Jelić a1d42cdf06 refactor(multiverse): Shorten some overly long lines 2025-04-07 14:17:10 +02:00
Damir Jelić 1c134a78de refactor(multiverse): Use a mpsc channel to propagate status messages to the status widget 2025-04-07 14:17:10 +02:00
Damir Jelić b5d1c14e29 refactor(multiverse): Rename set_status_message to set_message 2025-04-07 14:17:10 +02:00
Damir Jelić a28ec70816 refactor(multiverse): Move the set_status_message under the Status widget 2025-04-07 14:17:10 +02:00
Damir Jelić e1b393c39f refactor(multiverse): Move the status message into a separate module 2025-04-07 14:17:10 +02:00
Damir Jelić a345c47a31 refactor(multiverse): Merge the two App impl blocks 2025-04-07 14:17:10 +02:00
Damir Jelić 64feee41ef refactor(multiverse): Move the room subscription logic into the RoomList 2025-04-07 14:17:10 +02:00
Damir Jelić 9f947e019f refactor(multiverse): Split the get_selected_room_id method into two methods 2025-04-07 14:17:10 +02:00
Damir Jelić be74cb4a16 refactor(multiverse): Move the get_selected_room_id under the RoomList 2025-04-07 14:17:10 +02:00
Damir Jelić 409f08dc2b refactor(multiverse): Simplify the constructor 2025-04-07 14:17:10 +02:00
Damir Jelić a94a03766b refactor(multiverse): Move the closure listening for new data into a separate method 2025-04-07 14:17:10 +02:00
Damir Jelić 988fd18b78 refactor(multiverse): Move the RoomList widget into a separate module 2025-04-07 14:17:10 +02:00
Damir Jelić 68b848602a refactor(multiverse): Turn the RoomList struct into a widget 2025-04-07 14:17:10 +02:00
Damir Jelić f7d6fe2dbf refactor(multiverse): Rename StatefulList to RoomList
While we're at it, move the impl block closer to the struct.
2025-04-07 14:17:10 +02:00
Damir Jelić c2a9523cbb refactor(multiverse): Remove the generics from the StatefulList struct 2025-04-07 14:17:10 +02:00
Ivan Enderlin ee879354b7 doc(sqlite,ffi): Add #4894 in the CHANGELOG.mds. 2025-04-07 14:05:40 +02:00
Ivan Enderlin c3fd571623 feat(ffi): Add ClientBuilder::system_is_memory_constrained().
This patch adds `ClientBuilder::system_is_memory_constrained`
so that the client can be built with that in mind.
Behind the scene, for the moment, it only calls
`SqliteStoreConfig::with_low_memory_config` instead of
`SqliteStoreConfig::new`, but this flag can be used for other use cases.
2025-04-07 14:05:40 +02:00
Ivan Enderlin 52ec6a4539 feat(sqlite): Add SqliteStoreConfig::with_low_memory_config.
This patch adds a new constructor for `SqliteStoreConfig`, which sets
some defaults tailored for low memory usage.

This patch adds tests asserting the defaults for `new` and
`with_low_memory_config`.
2025-04-07 14:05:40 +02:00
Ivan Enderlin a57322466c refactor(base): Simplify the e2ee response processor.
This patch explores a way to simplify the call sites of the `e2ee`
response processor by creating one response processor for `/v3/sync` and
one for MSC4186. The idea is to:

- simplify the call site by having less code,
- isolating the “dispatch” of a the response values into the `e2ee`
  response processor,
- make it easier to test this response processor based on a `Response`
  structs directly.
2025-04-07 13:52:29 +02:00
Ivan Enderlin 90ce6e85ad refactor(base): Centralise processors that require e2e-encryption. 2025-04-07 13:52:29 +02:00
Ivan Enderlin e94fd64276 refactor(base): BaseClient uses response processors and remove duplicated code.
This patch updates
`BaseClient::receive_sync_repsonse_with_requested_required_states` to
use the `response_processors`. This patch also removes duplicated code
with the processors.
2025-04-07 13:52:29 +02:00
Ivan Enderlin 0c7cf58d4d refactor(base): BaseClient::process_sliding_sync_e2ee uses response processors. 2025-04-07 13:52:29 +02:00
Ivan Enderlin e3b2e0fa3e feat(base): Add the Verification request processor. 2025-04-07 13:52:29 +02:00
Ivan Enderlin 4619221429 feat(base): Add the DecryptLatestEvents response processor. 2025-04-07 13:52:29 +02:00
Ivan Enderlin 9b316ed405 feat(base): Add the E2EE response processor. 2025-04-07 13:52:29 +02:00
Ivan Enderlin 0a633ca75c feat(base): Add the Context struct.
This patch introduces a new `Context` type that holds the state changes
and the room info notable updates. Processors will exchange this
context. That's the only data that is mutable and exchangeable between
processors.
2025-04-07 13:52:29 +02:00
Ivan Enderlin 2e57733f05 refactor(base): Move response_processors.rs into response_processors/account_data.rs.
This patch creates the `response_processors` module and
moves the existing `response_processors.rs` file into
`response_processors/account_data.rs`.
2025-04-07 13:52:29 +02:00
Kévin Commaille b94be8d509 Upgrade tokio
To get rid of advisory

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-04-07 12:04:06 +02:00
Kévin Commaille 24e6d780fc Upgrade Ruma to version 0.12.2
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-04-07 12:04:06 +02:00
Ivan Enderlin d9157e5b83 refactor(ffi): Call methods on Room instead of redoing the same work.
This patch updates `Room::report_content` and `Room::report_room` to
call the same methods on `matrix_sdk` instead of re-implementing them.
2025-04-07 11:54:25 +02:00
dependabot[bot] b4a8089b40 chore(deps): Bump openssl from 0.10.70 to 0.10.72 2025-04-04 23:28:09 +02:00
Kévin Commaille a736dc9f96 doc(sdk): Cleanup changelog entries for Oidc/OAuth API changes
There were a lot of changes, and it was hard to follow, especially with
methods and types that changed and were then removed.

This groups everything under a single entry, with a short summary of
the changes, followed by a list of per-PR changes.

The detailed list is reorganized to put the biggest changes first, like
the renaming, and a few entries were cleaned up or removed, because
they mention a method or type that is removed in another entry.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-04-04 15:00:54 +02:00
Kévin Commaille dd094ea38e doc(oauth): Update docs
Make sure they are up-to-date.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-04-04 15:00:54 +02:00
Kévin Commaille b2cd81a992 test(sdk): Fix compilation error
Introduced by merging #4886 and #4887 around the same time.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-04-04 14:45:06 +02:00
Ivan Enderlin d5ceb5f99a refactor(sdk): Reduce the size of Error::CrossProcessLockStore.
This patch boxes the error in `Error::CrossProcessLockStore` to reduce
the size of this variant (from 24 bytes to 16 bytes).
2025-04-04 13:25:55 +02:00
Ivan Enderlin 956386a3ed refactor(sdk): Reduce the size of Error::SendQueueWedgeError.
This patch boxes the error in `Error::SendQueueWedgeError` to reduce the
size of this variant (from 32 bytes to 16 bytes).
2025-04-04 13:25:55 +02:00
Ivan Enderlin b2a4032432 refactor(sdk): Reduce the size of Error::EventCache.
This patch boxes the error in `Error::EventCache` to reduce the size of
this variant (from 32 bytes to 16 bytes).
2025-04-04 13:25:55 +02:00
Ivan Enderlin 38378f7bae refactor(sdk): Reduce the size of Error::OAuth.
This patch boxes the error in `Error::OAuth` to reduce the size of this
variant (from 160 bytes to 16 bytes).
2025-04-04 13:25:55 +02:00
Ivan Enderlin e12264bcb6 refactor(sdk): Reduce the size of Error::WrongRoomState.
This patch boxes the error in `Error::WrongRoomState` to reduce the
size of this variant (from 24 bytes to 16 bytes).
2025-04-04 13:25:55 +02:00
Ivan Enderlin 385df955c3 refactor(sdk): Reduce the size of Error::SlidingSync.
This patch boxes the error in `Error::SlidingSync` to reduce the size of
this variant (from 72 bytes to 16 bytes).
2025-04-04 13:25:55 +02:00
Ivan Enderlin 47a1db9e16 refactor(sdk): Reduce the size of Error::QrCodeScanError.
This patch boxes the error in `Error::QrCodeScanError` to reduce the
size of this variant (from 72 bytes to 16 bytes).
2025-04-04 13:25:55 +02:00
Ivan Enderlin ed82f07d7d refactor(sdk): Reduce the size of Error::EventCacheStore.
This patch boxes the error in `Error::EventCacheStore` to reduce the size of
this variant (from 32 bytes to 16 bytes).
2025-04-04 13:25:55 +02:00
Ivan Enderlin 4ddd1468c2 refactor(sdk): Reduce the size of Error::StateStore.
This patch boxes the error in `Error::StateStore` to reduce the size of
this variant (from 40 bytes to 16 bytes).
2025-04-04 13:25:55 +02:00
Ivan Enderlin b274d36e11 refactor(sdk): Reduce the size of Error::MegolmError.
This patch boxes the error in `Error::MegolmError` to reduce the size of this
variant (from 72 bytes to 16 bytes).
2025-04-04 13:25:55 +02:00
Ivan Enderlin 7ae82f3afb refactor(sdk): Reduce the size of Error::OlmError.
This patch boxes the error in `Error::OlmError` to reduce the size of this
variant (from 80 bytes to 16 bytes).
2025-04-04 13:25:55 +02:00
Ivan Enderlin 111306b411 refactor(sdk): Reduce the size of Error::CryptoStoreError.
This patch boxes the error in `Error::CryptoStoreError` to reduce the
size of this variant (from 72 bytes to 16 bytes).
2025-04-04 13:25:55 +02:00
Ivan Enderlin c8fb8ad9ca refactor(sdk): Reduce the size of Error::Http.
This patch boxes the error in `Error::Http` to reduce the size of this
variant (from 160 bytes to 16 bytes).
2025-04-04 13:25:55 +02:00
Ivan Enderlin 2f7525c3c8 chore(base): Reduce the size of DependentQueuedRequestKind.
This patch boxes `local_echo` in
`DependentQueuedRequestKind::FinishUpload`, this variant' size is 512
bytes, compared to the second largest variant which is 104 bytes. To
reduce the global size of this enum, `local_echo` is now a `Box<_>`.
2025-04-04 13:25:55 +02:00
Ivan Enderlin 511fc96835 chore(base): Replace iter().any() by contains().
This is usually faster to use `contains()` than `iter().any()` on
a slice.

See https://rust-lang.github.io/rust-clippy/master/index.html#manual_contains.
2025-04-04 13:25:55 +02:00
Ivan Enderlin cb539bc72b chore(base): Reduce the size of AnySyncOrStrippedState.
This patch reduces the size of `anySyncOrStrippedState`. The `Sync`
variant is 528 bytes, the `Stripped` variant is 232. First off, there is
a non-negligible difference in size between the two, but still, we can
reduce the size of the enum by boxing all values.
2025-04-04 13:25:55 +02:00
Kévin Commaille 2b450a0a6a chore: Add changelog for Client::logout()
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-04-04 13:14:55 +02:00
Kévin Commaille fc93690d1f test(sdk): Add test for Client::logout()
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-04-04 13:14:55 +02:00
Kévin Commaille 43431b88da feat(sdk): Add Client::logout() to log out regardless of the auth API
It simplifies code for users, and avoids to have to match on
`AuthApi`, which is a non-exhaustive enum.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-04-04 13:14:55 +02:00
Ivan Enderlin 13ca27d66a chore(cargo): Update eyeball and imbl.
This patch updates `eyeball`, `eyeball-im` and `eyeball-im-util` from
`main` branch to latest releases.

This patch also updates `imbl` to 5.0.
2025-04-04 10:09:12 +02:00
Stefan Ceriu 9f7179263a fix(ffi): correctly populate all audio content fields when converting from FFI types to Ruma
- fixes forwarding audio and voice messages that would previously show up as files because of missing fields
2025-04-03 15:50:17 +03:00
Kévin Commaille c8da9cb462 refactor(oauth): Remove the issuer from OAuthAuthData
It is actually unused, and now that we only need homeserver URLs for
static registrations, users don't need to access it easily.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-04-03 12:52:17 +03:00
Kévin Commaille 678938951e chore: Update changelog for OAuthRegistrationStore removal
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-04-03 12:52:17 +03:00
Kévin Commaille 8883e081af refactor(oauth): Remove OAuthRegistrationStore
MSC2966 was updated, clients should re-register for every log in, so we
don't need to store the client IDs between logins.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-04-03 12:52:17 +03:00
Johannes Marbach c4d9ec98c3 feat!(ffi): merge send_reply and send_thread_reply (#4880)
This pushes down the `Reply` struct to be provided when sending a reply, and merges the `send_reply` and `send_thread_reply` FFI functions.

This is a small follow-up on https://github.com/matrix-org/matrix-rust-sdk/pull/4852/files#r2016594024.

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-04-03 11:19:21 +02:00
Johannes Marbach dccd836dc6 feat!(timeline): allow sending media as (thread) replies (#4852)
This makes it possible to reply with a media, as part of a thread or not.

Fixes #4835.

---------

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-04-02 12:25:06 +00:00
Benjamin Bouvier c719cd11f3 fix(event cache): properly clear all rooms, including those sleeping in the store backend 2025-04-02 13:54:43 +02:00
Benjamin Bouvier 42133a60c8 fix(ffi): use EventCache::clear_all_rooms() to clear the events caches
Clearing only the store backend, while not emptying the in-memory linked
chunks, will lead to out-of-sync state across the in-memory caches and
the database. As a result, it's safer to call the existing
`EventCacheInner::clear_all_rooms`, which will clear all the rooms
manually.
2025-04-02 13:54:43 +02:00
Benjamin Bouvier d30dc7177f refactor(sqlite): rename gaps to gap_chunks / events_chunks to event_chunks
And some comments have been tweaked too.
2025-04-02 13:26:15 +02:00
Benjamin Bouvier e42be87798 test(event cache): add tests for save_event() and find_event_relations()
And fix the sql backend \o/
2025-04-02 13:26:15 +02:00
Benjamin Bouvier 524040b33c refactor(event cache): have EventCacheStore::clear_all_rooms_chunks delete all the events' contents 2025-04-02 13:26:15 +02:00
Benjamin Bouvier 0227b3f554 refactor(event cache): regroup code to compute the filter strings 2025-04-02 13:26:15 +02:00
Benjamin Bouvier 6a076b0989 refactor(event cache): don't return the event itself, in find_event_with_relations
And rename it `find_event_relations`.
2025-04-02 13:26:15 +02:00
Benjamin Bouvier 9b1a5b7102 refactor(event cache): don't have find_event_with_relations return redaction events
A redaction event would be either applied a priori (by the server, when
returning the sync response), or the event cache would handle it, and
redact it in the database; in any case, we'd never see the original
event in its non-redacted form, so there's no point in returning the
redaction event itself.
2025-04-02 13:26:15 +02:00
Benjamin Bouvier 0bb72064b5 refactor(event cache): don't have find_event_with_relations return replies
It's unclear whether it's useful, especially in the case where it would
return an entire reply chain. It's not possible to filter in replies
only, using the function either, which is a sign that replies shouldn't
be indexed, IMO. In any case, that's something we can add back in the
future, if we want to.
2025-04-02 13:26:15 +02:00
Benjamin Bouvier 8af68d7389 feat(event cache): get the transitive closure of related events when getting an event's relations 2025-04-02 13:26:15 +02:00
Benjamin Bouvier cde0a9e24b refactor(event cache): get rid of the AllEventsCache 2025-04-02 13:26:15 +02:00
Benjamin Bouvier 9423e41a06 refactor(event cache): use the store methods to retrieve an event by id, with or without its relations 2025-04-02 13:26:15 +02:00
Benjamin Bouvier 3489cbd5d7 feat(event cache): allow retrieving an event and all its relations 2025-04-02 13:26:15 +02:00
Benjamin Bouvier 65be779bb0 refactor(event cache): move relation extraction into common store helpers 2025-04-02 13:26:15 +02:00
Benjamin Bouvier 45f1dca6a3 refactor(event cache): don't return a position in find_event
Getting the position when reading an event is no longer required:

- the only use case for reading the position out of the event cache was
when we wanted to replace a redacted item into the linked chunk; now
with save_event(), we can replace it without having to know its
position.

As an extra measure of caution, I've also included the room_id in the
`events` table, next to the event_id, so that looking for an event is
still restricted to a single room.
2025-04-02 13:26:15 +02:00
Benjamin Bouvier 913b2a5f78 feat(event cache): allow to persist an out-of-band event into storage 2025-04-02 13:26:15 +02:00
Benjamin Bouvier 627e2ca5a6 refactor(linked chunk): rejigger the relational linked chunk so it can handle indexed items
This is necessary to save out-of-band items into the relational linked
chunk. I'm not quite sure of the value to keep it generic, at this
point, but at least it makes testing easy.
2025-04-02 13:26:15 +02:00
Benjamin Bouvier 3d8af1b972 feat(event cache): extract an event's relationship before inserting it into the database 2025-04-02 13:26:15 +02:00
Benjamin Bouvier 03f5d0222e refactor(event cache): store the events' content independently of their position in a chunk 2025-04-02 13:26:15 +02:00
dependabot[bot] af85447328 chore(deps): bump tj-actions/changed-files
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 0fee5fb278312d962ff465bb38dc4cae9f446de2 to 27ae6b33eaed7bf87272fdeb9f1c54f9facc9d99.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](https://github.com/tj-actions/changed-files/compare/0fee5fb278312d962ff465bb38dc4cae9f446de2...27ae6b33eaed7bf87272fdeb9f1c54f9facc9d99)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-02 09:07:21 +02:00
Andy Balaam d34e11b9f6 Fixes #4871 (hopefully). In test code, sync after other user cross-signs 2025-04-01 14:56:13 +02:00
Benjamin Bouvier 231073c9c3 chore(sqlite): log underlying errors in many OpenStoreError variants
This would help understanding what's the underlying error each time.
2025-04-01 14:51:32 +02:00
Andy Balaam 34a3fb4efb Fix typo decodeable -> decodable 2025-04-01 12:07:29 +01:00
dependabot[bot] a38c3b5dc5 chore(deps): bump crate-ci/typos from 1.30.2 to 1.31.1
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.30.2 to 1.31.1.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.30.2...v1.31.1)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-01 12:07:29 +01:00
Ivan Enderlin 1a1310e205 doc(ffi,sqlite,sdk): Update CHANGELOG.mds. 2025-04-01 11:50:20 +02:00
Ivan Enderlin d7b6fae2a3 feat(ffi): Add session_pool_max_size, …_cache_size and …_journal_size_limit.
This patch adds 3 methods on `ClientBuilder`:

1. `session_pool_max_size`,
2. `session_cache_size`,
3. `session_journal_size_limit`.

Respective fields are also added.

These values control the `SqliteStoreConfig`, used to control the
stores, especially their memory consumption.
2025-04-01 11:50:20 +02:00
Ivan Enderlin bb87a728ac doc(sqlite): Fix a broken link. 2025-04-01 11:50:20 +02:00
Ivan Enderlin d60810c2af refactor(ffi): Rename ClientBuilder::passphrase to session_passphrase.
This patch renames the `passphrase` method of `ClientBuilder` to
`session_passphrase` for more consistency with the `session_paths`
method.
2025-04-01 11:50:20 +02:00
Ivan Enderlin a6a4579ef9 feat(sdk): Add ClientBuilder::sqlite_store_with_config_and_cache_path.
This patch a new `sqlite_store_with_config_and_cache_path` method on
`ClientBuilder`.
2025-04-01 11:50:20 +02:00
Ivan Enderlin a084a5b08b feat(sdk): BuilderStoreConfig::Sqlite embeds SqliteStoreConfig.
This patch removes the `path` and `passphrase` fields from
`BuilderStoreConfig::Sqlite`, and replaces them by `SqliteStoreConfig`.

This patch then opens the stores with `open_with_config` instead of
`open`.
2025-04-01 11:50:20 +02:00
Ivan Enderlin 4112162092 feat(sqlite): Add SqliteStoreConfig::path() to override the path.
This patch adds `SqliteStoreConfig::path()` to override the path passed
to the constructor `new`.
2025-04-01 11:50:20 +02:00
Ivan Enderlin f341572616 feat(sqlite): SqliteStoreConfig implements Clone and Debug.
This patch implements `Clone` and `Debug` for `SqliteStoreConfig`.
2025-04-01 11:50:20 +02:00
Ivan Enderlin a047784278 feat(ffi): Add Client::restore_session_with and RoomLoadSettings.
This patch adds `Client::restore_session_with` along with the
`RoomLoadSettings` enum.
2025-04-01 10:50:18 +02:00
Ivan Enderlin 7ac3fa1f4a refactor(ffi): Inline restore_session_inner.
This patch inlines the `restore_session_inner` into its unique call
site.
2025-04-01 10:50:18 +02:00
Kevin Boos c30ec0ed8a chore(sdk): Reduce log verbosity from info -> trace in a few functions (#4747)
At the default `INFO` log level, the log gets inundated with thousands
of emitted statements, primarily related to
`is_display_name_ambiguous()`. The execution of this tiny function
certainly doesn't need to be traced every time, at least not at the info
log level.

Note: some of these could be debatably reduced to debug level rather
than trace level, but I went with "trace" because they all seem to be
trace statements rather than actual debug dump outputs (there is no
actual program state dumped out).

Signed-off-by: Kevin Boos
[kevinaboos@gmail.com](mailto:kevinaboos@gmail.com)

---------

Signed-off-by: Kevin Boos <1139460+kevinaboos@users.noreply.github.com>
2025-03-31 15:11:06 +00:00
Ivan Enderlin d6f2fd4304 test(base): Test BaseStateStore::load_rooms.
This patch adds tests for `BaseStateStore::load_rooms`. This patch also
updates the `test_derive_from_other` test.
2025-03-31 16:47:58 +02:00
Ivan Enderlin cf5b8d3b33 test(base): Test StateStore::get_room_infos with the RoomLoadSettings arguments.
This patch tests the new behaviour of `StateStore::get_room_infos`.
2025-03-31 16:47:58 +02:00
Ivan Enderlin 2a67d7472a feat(base,sqlite,indexeddb): Use RoomLoadSettings to load all or one room.
This patch updates `BaseStateStore` and the `StateStore` trait along
with its implementors, to return all rooms or a single room from
`StateStore::get_room_infos`.

See the previous patch for more context.
2025-03-31 16:47:58 +02:00
Ivan Enderlin 9f74be26c3 feat(base): Introduce RoomLoadSettings.
This patch introduces the `RoomLoadSettings` enum. It is helpful to load
either all rooms or one room when activating a `BaseClient`, i.e. when a
session is initialized or restored.

It addresses a broader problem where, for large accounts with large
caches, creating a `BaseClient` takes many resources. In a resource
constrainted context, like a push notification process, it can eat all
resources up to the point the process is killed (then notifications can
be missed).

The idea is then to force the `BaseClient` to load a single room.

This patch installs the `RoomLoadSettings` argument everywhere it needs
to be. The next patch will use `RoomLoadSettings` to load either all
rooms or a single one.
2025-03-31 16:47:58 +02:00
Damir Jelić fb04539418 refactor(encryption): Simplify the parsing of OAuthCrossSigningResetInfo
Now that OAuth isn't behind a feature flag a bunch of things can be
simplified.

Seems that we don't need a Client object either.
2025-03-31 13:06:28 +02:00
Richard van der Hoff 192cf0154a integration-test: Remove postgres container (#4858)
Followup on https://github.com/matrix-org/matrix-rust-sdk/pull/3983: now
that we don't have a sliding sync proxy, we don't need a postgres
container.
2025-03-28 16:51:12 +00:00
Damir Jelić 9acd649742 chore: Remove the RSA security advisory from our deny config
Since we don't depend on mas anymore, we don't depend on RSA either.

Let's remove the exception, lest we reintroduce the dependency and
security issue.
2025-03-28 17:08:42 +01:00
Ivan Enderlin e8fcdf4360 doc(base): Update the description in the CHANGELOG.md. 2025-03-28 16:11:44 +01:00
Ivan Enderlin 43dbb6a021 refactor(base): Split BaseStateStore::set_or_reload_session into 3 distinct methods.
This patch splits `BaseStateStore::set_or_reload_session` into 3
distinct methods:

1. `set_session_meta` (hello again!),
2. `load_rooms`,
3. `load_sync_token`.

This patch also renames
`BaseStateStore::set_or_reload_session_from_other` into
`derive_from_other` to clarify its semantics. It calls these 3 methods
above as a combo.

Finally, this patch also updates `BaseClient::activate` to call these 3
methods above individually.
2025-03-28 16:11:44 +01:00
Ivan Enderlin de615f2ffe refactor(base): Rename BaseClient::set_or_reload_session and ::logged_in.
This patch renames `BaseClient::set_or_reload_session`
to `BaseClient::activate`, and `BaseClient::logged_in` to
`BaseClient::is_activated`.

The idea behind these renamings is to introduce a “state” for the
`BaseClient`: it is activated when is has a `SessionMeta`, has loaded
its data from the storages, and has an `OlmMachine`. Consequently, the
`logged_in` method is renamed `is_activated` for the symmetry. If one
wants to know if the client is logged in, it can use `is_activated`, or
also `MatrixAuth::logged_in`.
2025-03-28 16:11:44 +01:00
Ivan Enderlin f11eec4caf doc(base): Update CHANGELOG.md. 2025-03-28 16:11:44 +01:00
Ivan Enderlin f445a5ca57 test(base): Add tests for Store::set_or_reload_session*.
This patch adds tests for the `set_or_reload_session` and
`set_or_reload_session_from_other` methods.
2025-03-28 16:11:44 +01:00
Ivan Enderlin 68605de596 feat(base): Add Store::set_or_reload_session_from_other.
This patch adds the `Store::set_or_reload_session_from_other` method to
isolate the behaviour of deriving a store from another one.
2025-03-28 16:11:44 +01:00
Ivan Enderlin db97d616f6 refactor(base): Rename set_session_meta to set_or_reload_session.
This patch renames the various `set_session_meta` methods to
`set_or_reload_session`. The idea is to highlight that the method is not
a simple setter: it sets but it _also_ updates the store' state.

The private shortcut method in `matrix_sdk::Client::set_session_meta`
is removed, and caller uses `client.base_client().set_or_reload_session`
instead. Why removing this `Client::set_session_meta` shortcut
method? Because it was creating confusion with another method:
`Client::restoring_session`. This private shortcut method wasn't used in
a lot of places, then I believe it's a nice improvement.
2025-03-28 16:11:44 +01:00
Damir Jelić e2e5b39afa test(sdk): Test that resetting cross-signing with an invalid password errors out 2025-03-28 15:23:52 +01:00
Damir Jelić 6fec953ff0 test(sdk): Use the MatrixMockServer for the cross-signing reset test 2025-03-28 15:23:52 +01:00
Damir Jelić 95befc9a25 fix(encryption): Return the uiaa error if we have one in the identity reset loop 2025-03-28 15:23:52 +01:00
Stefan Ceriu cb92971657 chore(ui): move TimelineItemContent::UnableToDecrypt to MsgLikeKind::UnableToDecrypt 2025-03-28 15:30:47 +02:00
Stefan Ceriu 5a35fec894 chore(ui): rename RedactedMessage to just Redacted 2025-03-28 15:30:47 +02:00
Stefan Ceriu 43b8f83e4b chore(ui): move TimelineItemContent::RedactedMessage to MsgLikeKind::RedactedMessage 2025-03-28 15:30:47 +02:00
Stefan Ceriu 0952255a50 chore(ui): reoder MsgLike helpers so that as_foo and is_foo are grouped 2025-03-28 15:30:47 +02:00
Ivan Enderlin 8738c4dbfd test(sqlite): Add test for cache_size and journal_size_limit.
This patch adds tests for checking the `PRAGMA cache_size` and `PRAGMA
journal_size_limit`.
2025-03-28 13:36:32 +01:00
Ivan Enderlin 99436f8e79 test(sqlite): Test the new RuntimeConfig type. 2025-03-28 13:36:32 +01:00
Ivan Enderlin 339b220488 feat(sqlite): Introduce RuntimeConfig which includes cache_size
This patch updates `StoreOpenConfig` to hold a new type: `RuntimeConfig`.

This `RuntimeConfig` type is passed to a new `SqliteAsyncConnExt`
method, named `apply_runtime_config`. Depending on the values passed
here, the `optimize`, `cache_size` (new!) and `journal_size_limit`
methods will be called automatically.

The goal of this type is to automate a flow we keep repeating in
all the stores. This is error-prone. This type brings uniformity and
consistency.

This patch also makes all `open_with_pool` methods on the stores private
(they were public before):

1. they were never used as far as I know because getting a `SqlitePool`
   isn't possible since the `pool` attribute is private…
2. it's better to keep control of this flow.
2025-03-28 13:36:32 +01:00
Ivan Enderlin 661f381e34 chore: Run rustfmt with an older nightly version. 2025-03-28 10:54:48 +01:00
Ivan Enderlin 8d4ccf6442 doc(sdk,base): Update CHANGELOG.mds. 2025-03-28 10:54:48 +01:00
Ivan Enderlin bd6b7c2ce1 refactor(base): Rename BaseClient::store to state_store.
This patch renames `BaseClient::store` to `state_store`, and
inevitably `Client::store` to `state_store` too.
2025-03-28 10:54:48 +01:00
Ivan Enderlin 9152d84b06 refactor(base): Rename BaseClient::store to state_store.
This patch pursues the same goal as the previous one: `Store` has
been renamed `BaseStateStore`, so the `store` field holding this
`BaseStateStore` is renamed `state_store`.
2025-03-28 10:54:48 +01:00
Ivan Enderlin c044f81d7b refactor(base): Rename Store to BaseStateStore.
This patch renames `Store` to `BaseStateStore`. Ideally, I would
like to rename to `StateStore` but that's already a trait name
(`traits::StateStore`).

Why this renaming? To clarify what store it is.
2025-03-28 10:54:48 +01:00
Stefan Ceriu 5730f0e00e chore(ui): introduce an as_message MsgLikeContent helper and use in tests 2025-03-27 15:53:31 +02:00
Stefan Ceriu 76f92ba9af chore(ui): rename AggregatedTimelineItemContent to MsgLikeContent and AggregatedTimelineItemContentKind to MsgLikeKind.
- we decided on this naming convention to keep consistent with Ruma which uses `MsgLike` as well
2025-03-27 15:53:31 +02:00
Stefan Ceriu d599c72278 chore(ui): use a newer version of as_variant to match nested types. 2025-03-27 15:53:31 +02:00
Stefan Ceriu e5243e32be chore(ui): simplify the fetch_replied_to_event method 2025-03-27 15:53:31 +02:00
Stefan Ceriu db18e7fd74 chore(ui): simplify the test by using more of the TimelineItemContent helpers 2025-03-27 15:53:31 +02:00
Johannes Marbach f3baf7efd2 refactor(timeline): push the reply logic down into matrix_sdk (#4842)
This achieves step 2 of #4835.

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-03-27 13:47:51 +01:00
Benjamin Bouvier f6e223edf6 refactor(timeline): bump as_variant and make more use of its pattern matching form 2025-03-27 10:42:22 +01:00
Stefan Ceriu 4f3b40d6fb feat!(timeline): Introduce an extra TimelineItemContent layer that holds aggregations (#4839)
In preparation for threads we have realised that the `reactions`,
`thread_root` and `in_reply_to` were only available on `Message` types,
which doesn't play well with Stickers and Polls.

This PR introduces a new `Aggregated` `TimelineItemContent` variant
which holds the message `kind` (Message, Sticker, Poll) as well as well
as any related aggregated data. it will help treat them all in a similar
fashion as well as account for future changes.

There are no functional changes, it's mostly about moving code around
and the FFI interfaces haven't changed.

Part of #4833.
2025-03-26 16:27:03 +00:00
Richard van der Hoff 6e480271d3 crypto: get_most_recent_session: return None for unknown device (#4846)
If we have a device whose Curve25519 key we don't know, then
self-evidently we can't have any active Olm sessions with that device.
Currently, we return an `EventError::MissingSenderKey` in this case, but
(a) the definition of that error doesn't match this situation, and (b)
it complicates handling in methods that call `DeviceData::encrypt`
(currently only `DeviceData::maybe_encrypt_room_key`, but I want to add
a second).

Other than `DeviceData::encrypt`, the only place where
`get_most_recent_session` is called is `mark_device_as_wedged`. In that
case, we have just looked up the device by its Curve25519 key, so we
know it must have one.

We can therefore be reasonably certain that this change is a no-op.
2025-03-26 14:42:24 +00:00
Ivan Enderlin e60cf18337 refactor(base): Rename BaseClient::with_store_config to new.
This renames the `BaseClient::with_store_config` constructor to `new`:

1. there is no alternative constructor, it's the only one,
2. the `with_` prefix is usually reserved to (i) builders or (ii)
   alternative constructors,
3. I believe it clarifies the code.
2025-03-26 15:34:08 +01:00
Ivan Enderlin 6409adb879 doc(base): Remove an empty line in an example. 2025-03-26 15:34:08 +01:00
Ivan Enderlin 915e0e83bc doc(base): Fix documentation of BaseClient::with_store_config.
The documentation is outdated, `config` is now a `StoreConfig`.
2025-03-26 15:34:08 +01:00
Ivan Enderlin 8323ecdc8b doc(base): Improve documentation of BaseClient.
This patch:

* fixes the mention of “no IO”, it lacks the “network” part,
* adds an example of how to build a `BaseClient`,
* mentions that it is better be used via `matrix_sdk`.
2025-03-26 15:34:08 +01:00
Kévin Commaille e0e9c06ca4 Don't use serde to avoid returning an error
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-26 15:26:55 +01:00
Kévin Commaille eb313efdeb Don't qualify error! macro
It's already imported.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-26 15:26:55 +01:00
Kévin Commaille bc22ff1221 refactor(oauth): Introduce AccountManagementUrlBuilder
It allows to reuse the URL for different actions more easily than having
to call `OAuth::account_management_url` every time for a different
action.

It also adds a method with fallback if we want to ignore action
serialization errors, to always present a URL.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-26 15:26:55 +01:00
Benjamin Bouvier 8c988beaf2 doc(linked chunk): tweak comments related to clearing/dropping a linked chunk 2025-03-26 11:57:48 +01:00
Benjamin Bouvier 752c9baf7c refactor(timeline): simplify removal of duplicated local echo item 2025-03-26 11:33:20 +01:00
Benjamin Bouvier 766772f654 feat(timeline): insert the start of the timeline in places where it's required 2025-03-26 11:33:20 +01:00
Benjamin Bouvier f5b6767253 feat(timeline): add a timeline start virtual item 2025-03-26 11:33:20 +01:00
Benjamin Bouvier 5ce045ee02 refactor(timeline): avoid unnecessary shadowing variables 2025-03-26 11:33:20 +01:00
Benjamin Bouvier b83889dcba fix(ffi): propagate initial values before the future is picked by a runtime 2025-03-26 11:18:23 +01:00
Ivan Enderlin cfc839f71b doc(sqlite) Add entry to the changelog. 2025-03-26 11:01:52 +01:00
Ivan Enderlin 660d4e7ccb feat(sqlite): Add StoreOpenConfig and open_with_config for all stores.
This patch adds a new `StoreOpenConfing` type to configure the store
when opening it and when creating the pool of connections to SQLite via
`deadpool_sqlite`.

This patch also adds a new `open_with_config` constructor on all
stores, namely `SqliteCryptoStore`, `SqliteEventCacheStore` and
`SqliteStateStore`.
2025-03-26 11:01:52 +01:00
Benjamin Bouvier 404dd3949f test: remove unused helpers 2025-03-26 11:01:14 +01:00
Benjamin Bouvier 693c8df8d0 test(timeline): more of the same, with a new EventFactory method to add a receipt with a given timestamp 2025-03-26 11:01:14 +01:00
Benjamin Bouvier d587d5f145 test(timeline): make more use of MatrixMockServer / EventFactory 2025-03-26 11:01:14 +01:00
Benjamin Bouvier be6daa5930 test: add EventFactory::typing for typing notifications and get rid of more old cruft 2025-03-26 11:01:14 +01:00
Benjamin Bouvier acee5415c5 test: get rid of a few global statics for read receipts event contents 2025-03-26 11:01:14 +01:00
Benjamin Bouvier 6399a99452 test(timeline): use the EventFactory a bit more 2025-03-26 11:01:14 +01:00
Benjamin Bouvier b5aa2113db test: add a way to create ephemeral read receipts events in the EventFactory 2025-03-26 11:01:14 +01:00
Benjamin Bouvier 1585b0c32e test(timeline): use more MatrixMockServer 2025-03-26 11:01:14 +01:00
Benjamin Bouvier 3c01f88ab8 benchmark(linked chunk): add a benchmark for reading events out of the store 2025-03-26 10:29:01 +01:00
Benjamin Bouvier 785312856e fix(linked chunk): don't leak upon drop
The previous code in `LinkedChunk::drop()` would call `clear()`, which
would reset the chunk to an empty events chunk; preinitializing a vector
of 128 items. But this item chunk would never be dropped, so this would
cause a leak.

The solution is to split the semantics of *resetting* a linked chunk
(what was called `clear` before), from *clearing* it: clearing will put
it in an dangling state, and it's the caller's responsibility to do
something about it; as such, it's marked unsafe. `LinkedChunk::drop()`
may now call `clear()` (which is fine; last use of the linked chunk);
and `LinkedChunk::reset()` will call `clear()` and reset the first
chunk, which is fine too.
2025-03-26 10:29:01 +01:00
Benjamin Bouvier fc8a6dc9b1 refactor(ffi): always try to load InReplyToDetails from the event cache or network 2025-03-25 11:06:31 +01:00
Benjamin Bouvier 7b8694e465 refactor(timeline): better encapsulate RepliedToEvent's fields 2025-03-25 11:06:31 +01:00
Benjamin Bouvier 655f62c331 refactor(timeline): don't ever look into the timeline's items to fetch the replied-to event content
This is a nice simplification, because this means that:

1. we use a single way to get the event (event-cache-or-network)
2. we don't have to reconstruct a `RoomMessageEventContent` from a
timeline's message, which seems a bit error prone
3. there's a single way to get the replied-to info for an event,
4. and that's actually independent of the timeline, so we can improve
the code for #4835
2025-03-25 11:06:31 +01:00
Benjamin Bouvier 53732e0ff2 refactor(timeline): use load_or_fetch_event when fetching a reply's content 2025-03-25 11:06:31 +01:00
Benjamin Bouvier 4cae122854 refactor(room): use load_or_fetch_event when creating an edit event 2025-03-25 11:06:31 +01:00
Benjamin Bouvier 2a11494c33 feat(room): introduce a new method to load from cache or network 2025-03-25 11:06:31 +01:00
Benjamin Bouvier 1dddd97d96 refactor(timeline): simplify ReplyContent::Message 2025-03-25 11:06:31 +01:00
Johannes Marbach f8236a8b96 feat(timeline): add functions for sending messages in threads
`send_reply` is geared towards clients that don't render threads themselves. It sends a reply and optionally forwards it onto any existing thread.

This PR adds `send_thread_reply` for clients that do render threads themselves. This sends a message onto a thread, regardless of whether a thread existed before, and includes the fallback for non-threaded clients only if the message is not itself a reply on the thread.

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-03-24 14:31:20 +01:00
Kévin Commaille aa07108c98 fix(oauth): Put cross-process module behind e2e-encryption feature
Since it requires the crypto store.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-24 14:04:04 +02:00
Kévin Commaille 9aed0cc933 chore: Add changelog about experimental-oidc feature removal
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-24 14:04:04 +02:00
Kévin Commaille f6c5addf55 refactor(sdk): Remove experimental-oidc feature
Now that is compiles under WASM and that the API was cleaned up, it
should be okay.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-24 14:04:04 +02:00
Kévin Commaille 9434a112d9 fix(oauth): Don't run OAuth tests under WASM
They almost all require a mock server.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-24 14:04:04 +02:00
Kévin Commaille c4ec32cb78 fix(sdk): Gate QR login imports and methods for WASM
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-24 14:04:04 +02:00
Kévin Commaille c5b8c812b3 fix(oauth): Use WASM-compatible types from matrix-sdk-common
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-24 14:04:04 +02:00
Kévin Commaille fdc2ca0c9e fix(oauth): Do not expose OAuthRegistrationStore under wasm32
It usually won't be possible to write data to a file.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-24 14:04:04 +02:00
Kévin Commaille dcd0e078f6 docs(qr-login): Update docs
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-24 09:32:05 +00:00
Kévin Commaille 78b79a758f feat(oauth-cli): Use OAuthRegistrationStore
It's probably the recommended way to do registration when the client can create files.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-24 09:32:05 +00:00
Kévin Commaille 29f6606d99 refactor(examples): Rename oidc_cli to oauth_cli
And update the docs.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-24 09:32:05 +00:00
Kévin Commaille 94f0beec51 chore: Add changelog for login with registration methods
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-21 19:17:13 +01:00
Kévin Commaille 590d1d7890 test(oauth): Add test for OAuth::use_registration_method
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-21 19:17:13 +01:00
Kévin Commaille 400c92fc89 refactor(oauth): Reuse the AuthorizationServerMetadata when possible
Avoids repeated calls to the same endpoint in the same flow.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-21 19:17:13 +01:00
Kévin Commaille b3e82a05db refactor(oauth): Merge OAuth::login_with_oidc_callback() and OAuth::finish_login()
Accept a URL or a query string for simplicity.

That way we don't need to expose AuthorizationResponse.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-21 19:17:13 +01:00
Kévin Commaille a8aa364757 refactor(oauth): Allow to use any registration method with OAuth::login
Gets rid of OAuth::url_for_oidc since it can be replaced by a call to
OAuth::login now.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-21 19:17:13 +01:00
Kévin Commaille 7457ecb1a8 feat(oauth): Allow to use any registration method with login_with_qr_code
Introduces the ClientRegistrationMethod type

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-21 19:17:13 +01:00
Kévin Commaille 01caf56edc refactor(oauth): Rename OAuth::configure to restore_or_register_client
The name is more explicit about what the function does.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-21 19:17:13 +01:00
Kévin Commaille 6f07d008c9 refactor(oauth): Inline store_client_registration and load_client_registration
They are one- or two-liners and are only used once.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-21 19:17:13 +01:00
Kévin Commaille b408087320 refactor(oauth): OAuth::login doesn't return a Result
There is actually no way to get an error.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-21 19:17:13 +01:00
Kévin Commaille 22cbce82ce refactor(oauth): Use tokio::fs APIs instead of spawn_blocking
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-21 10:49:46 +01:00
Kévin Commaille ecdc68aa1c chore: Add changelog for OidcRegistrations changes
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-21 10:49:46 +01:00
Kévin Commaille 4a0bf80ab0 test(oauth): Add checks that client ID is written to OAuthRegistrationStore
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-21 10:49:46 +01:00
Kévin Commaille 095425f664 refactor(oauth): Do not take static registrations in default OAuthRegistrationStore constructor
It complicates the constructor and most clients will probably not need to use it.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-21 10:49:46 +01:00
Kévin Commaille ca4e212e98 refactor(oauth): Make OAuthRegistrationStore methods async
Since they perform blocking I/O we probably don't want to block a thread on that.

We use spawn_blocking, the alternative would be to use tokio::fs functions, which do the same thing and would require to load the whole file content in memory before (de)serialization.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-21 10:49:46 +01:00
Kévin Commaille 0b0f84b784 refactor(oauth): Don't ignore errors when reading the file of the OAuthRegistrationStore
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-21 10:49:46 +01:00
Kévin Commaille fbd4a7dc38 refactor(oauth): Get rid of OAuthError::UnknownError
Instead add a variant for OAuthRegistrationStoreError to
OAuthClientRegistrationError.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-21 10:49:46 +01:00
Kévin Commaille cb90d7fee6 refactor(oauth): Avoid impossible error
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-21 10:49:46 +01:00
Kévin Commaille 1a79ea94ed refactor(oauth): Improve OAuthRegistrationError variants
Make them more precise instead of wrapping several error types into a single variant.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-21 10:49:46 +01:00
Kévin Commaille c3328a03f6 refactor(oauth): Avoid unnecessary allocations when accessing OAuthRegistrationStore data
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-21 10:49:46 +01:00
Kévin Commaille 6803538c2e refactor(oauth): Rename OidcRegistrations to OAuthRegistrationStore
Use the same prefix as the other types in the OAuth 2.0 API, and use the
same suffix as other data-persisting APIs for consistency.

It also avoids to have two modules with very similar names, the only
difference being a trailing `s`.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-21 10:49:46 +01:00
Kévin Commaille 9d27e9b379 refactor(oauth): Make registrations module private
Since it only contains 2 types, it doesn't seem worth it to expose it,
we can just expose the types elsewhere.
2025-03-21 10:49:46 +01:00
Kévin Commaille 8683ca4d13 refactor(oauth): Re-export ClientID from the oauth module
Since it is now used everywhere, there is no reason to reexport it from
the registrations module.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-21 10:49:46 +01:00
Benjamin Bouvier d4f5ac152a feat(ffi): log the log targets and levels 2025-03-21 09:37:25 +01:00
Benjamin Bouvier 31a1724390 feat(ffi): add support for log bundles 2025-03-21 09:37:25 +01:00
Kévin Commaille c034818c92 chore: Add changelog for OAuth::login changes
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-20 16:02:18 +00:00
Kévin Commaille e1fe479008 refactor(oauth): Get rid of OAuthError::MissingDeviceId
Since we are the ones generating the device ID, we have a way to avoid this error. Even if in practice, it's probably always included in the server's response.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-20 16:02:18 +00:00
Kévin Commaille 530659b59d feat(oauth): Allow user to log into the same session again
Can be useful with soft logouts, without requiring the user to recreate a new Client to log in again.

Returns an error if the new session is different from the current one.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-20 16:02:18 +00:00
Kévin Commaille 45dd96e30a refactor(oauth): Merge finish_authorization and finish_login
That way users only need to call finish_login, since there is no other
reason to call finish_authorization currently.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-20 16:02:18 +00:00
Hugh Nimmo-Smith 3f4c1fd1bb feat(widgets, element-call): Update the widget url generation
Fixes: #4793

There was a previous PR https://github.com/matrix-org/matrix-rust-sdk/pull/4802 which attempted to implement this, but missed some backwards compatibility needs.

This updated PR has the original commit and then additional commits to add the compatibility (along with tests for the new intent param generally).
2025-03-20 14:28:45 +00:00
Benjamin Bouvier 5acaaf5865 fix(ffi): call the back-pagination status callback immediately 2025-03-20 10:26:54 +01:00
Benjamin Bouvier 156501dbbd chore(event cache): add logs here and there 2025-03-20 10:26:54 +01:00
Hugh Nimmo-Smith a0eb9340d5 Revert "feat(widgets, element-call) Update the widget url generation (#4802)"
This reverts commit 3b9ae3e65e.
2025-03-19 17:49:21 +01:00
Jonas Richard Richter dbdbfd0b38 feat(notification): Add support for custom conditional push rules (#4587)
---
Signed-off-by: Jonas Richard Richter <jonas-richard.richter@telekom.de>
2025-03-19 12:50:02 +01:00
Ivan Enderlin 1d9d4d3b3a chore(sdk): Annotate RoomEventCacheState::remove_events with #[instrument].
This patch annotates `RoomEventCacheState::remove_events` with the
`#[instrument]` proc-macro so that it is logged when called.
2025-03-19 12:15:59 +01:00
Ivan Enderlin 8d16b3265c refactor(sdk): RoomEventCacheState checks if events to remove aren't empty.
This patch updates `RoomEventCacheState::remove_events` to check whether
the set of events are not empty before removing them. When removing
`in_memory_events`, it avoids taking a write lock on the `RoomEvents`
for nothing for example.
2025-03-19 12:15:59 +01:00
Ivan Enderlin 9c37a0393c fix(base): Check the lazy_previous of the first chunk matches the new first chunk.
This patch adds a new check when inserting a new first chunk. It makes
some tests to fail but because they were not realistic. This patch then
updates these tests.
2025-03-19 11:18:05 +01:00
Ivan Enderlin 82ef6232e7 doc(sdk): Precise in which context a variable can or cannot be used. 2025-03-19 11:02:09 +01:00
Timo 3b9ae3e65e feat(widgets, element-call) Update the widget url generation (#4802)
Fixes: https://github.com/matrix-org/matrix-rust-sdk/issues/4793

Co-authored-by: Valere <bill.carson@valrsoft.com>
2025-03-19 11:29:07 +02:00
Kévin Commaille a539518cd4 chore: Add changelog for Oidc renaming
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-18 17:18:34 +01:00
Kévin Commaille f61cd60147 refactor(oauth): Change the Oauth prefix in test utils with OAuth
For consistency.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-18 17:18:34 +01:00
Kévin Commaille b9c970dc43 refactor(oauth): Rename OauthGrantType to OAuthGrantType
For consistency.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-18 17:18:34 +01:00
Kévin Commaille ba5e395a59 refactor(oauth): Change Oauth prefix for error types to OAuth
For consistency.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-18 17:18:34 +01:00
Kévin Commaille c46e6623fe refactor(oauth): Rename OauthClient and OauthHttpClient to OAuthClient and OAuthHttpClient
For consistency

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-18 17:18:34 +01:00
Kévin Commaille 7ad1b113dc doc(oauth): Change mentions of OpenID Connect to OAuth 2.0
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-18 17:18:34 +01:00
Kévin Commaille c0d3ed1a90 refactor(oauth): Rename provider_metadata to server_metadata
"Provider" is an OpenID Connect term. OAuth 2.0 uses the "authorization
server" term.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-18 17:18:34 +01:00
Kévin Commaille 00d7a77ebe refactor(encryption): Rename OidcCrossSigningResetInfo to OAuthCrossSigningResetInfo
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-18 17:18:34 +01:00
Kévin Commaille f29d3fd666 refactor(oauth): Rename OidcAuthCodeUrlBuilder to OAuthAuthCodeUrlBuilder
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-18 17:18:34 +01:00
Kévin Commaille 47204830a9 refactor(oauth): Rename OidcError to OAuthError
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-18 17:18:34 +01:00
Kévin Commaille f4bb14a30e refactor(oauth): Rename OidcSession to OAuthSession
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-18 17:18:34 +01:00
Kévin Commaille 0a345a3124 refactor(oauth): Rename OidcAuthData to OAuthAuthData
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-18 17:18:34 +01:00
Kévin Commaille 450a66ad11 refactor(oauth): Rename OidcCtx to OAuthCtx
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-18 17:18:34 +01:00
Kévin Commaille 6f3694cfa9 refactor(oauth): Rename Oidc API to OAuth
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-18 17:18:34 +01:00
Kévin Commaille 1658610f93 refactor(sdk): Rename oidc module to oauth
Since we mostly use OAuth 2.0 now.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-18 17:18:34 +01:00
Kévin Commaille f9b1bdb22d chore: Add changelog for LocalServerBuilder
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-18 09:55:01 +01:00
Kévin Commaille f8abb85e9e refactor(oidc_cli): Use LocalServerBuilder
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-18 09:55:01 +01:00
Kévin Commaille 1b5e6462ee refactor(sdk): Use LocalServerBuilder with SsoLoginBuilder
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-18 09:55:01 +01:00
Kévin Commaille fbdd8839e6 feat(sdk): Expose a local server builder
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-18 09:55:01 +01:00
dependabot[bot] d86117ac70 chore(deps): bump crate-ci/typos from 1.30.1 to 1.30.2
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.30.1 to 1.30.2.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.30.1...v1.30.2)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-17 20:24:01 +01:00
dependabot[bot] 914b7125cf chore(deps): bump tj-actions/changed-files
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from dcc7a0cba800f454d79fff4b993e8c3555bcc0a8 to 0fee5fb278312d962ff465bb38dc4cae9f446de2.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](https://github.com/tj-actions/changed-files/compare/dcc7a0cba800f454d79fff4b993e8c3555bcc0a8...0fee5fb278312d962ff465bb38dc4cae9f446de2)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-17 18:32:00 +02:00
Richard van der Hoff ad0223cafb Merge pull request #4775 from matrix-org/rav/history_sharing/room_key_bundle
crypto: support for building key bundles
2025-03-17 11:45:38 +00:00
Richard van der Hoff a870c02eab test: snapshot test for HistoricRoomKey::debug 2025-03-17 11:24:52 +00:00
Richard van der Hoff 002e77616d crypto: support for building key bundles
Add a method to CryptoStore which will construct a key bundle, ready for
encrypting and sharing with invited users.

Part of https://github.com/matrix-org/matrix-rust-sdk/issues/4504
2025-03-17 11:24:52 +00:00
Michael Telatynski d777e68c4a Pin tj-actions/changed-files
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-17 10:26:15 +00:00
Ivan Enderlin cabb345a1c fix(xtask): Add --limit 100 to gh pr list.
Because yes, some weeks, we are very productive!

This patch adds `--limit 100` to `gh pr list` so that we are sure to not
miss pull requests if there are many.
2025-03-14 18:04:33 +01:00
Kévin Commaille 2f08f27b59 chore: Add changelog about removing mas-oidc-client
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-14 18:03:34 +01:00
Kévin Commaille 3a7b0e9404 refactor(oidc): Remove dependency on mas-oidc-client
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-14 18:03:34 +01:00
Kévin Commaille 7713ce768a refactor(oidc): Create ClientMetadata type
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-14 18:03:34 +01:00
Kévin Commaille aea573d001 refactor(oidc): Import code to register a client
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-14 18:03:34 +01:00
Kévin Commaille 7ca6494efa refactor(oidc): Remove support for software statement
It is not mentionned in MSC2966
2025-03-14 18:03:34 +01:00
Kévin Commaille 6f44853bf7 refactor(oidc): Use Url for the issuer
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-14 18:03:34 +01:00
Kévin Commaille 2c6c818005 refactor(oidc): Use ruma's server metadata type
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-14 18:03:34 +01:00
Kévin Commaille abc4fbc2f7 refactor(oidc): Import code to discover OIDC provider configuration
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-14 18:03:34 +01:00
Kévin Commaille 9adff21f78 refactor(oidc): Import code for building the account management URL
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-14 18:03:34 +01:00
Kévin Commaille 91f9ef85ae refactor(oidc): Add type alias for oauth2 errors
To have less verbose and more predictable error types.
2025-03-14 18:03:34 +01:00
Ivan Enderlin 17c6ad6b70 test(ui): Ensure Timeline runs a redecryption for all UTD.
This patch adds two tests, ensuring UTD stored in the event cache are
decrypted, whether they come from the initial items or paginated items.
2025-03-14 14:18:21 +01:00
Ivan Enderlin 8f61bdb046 fix(ui): Events received from the event cache trigger a decryption.
This patch fixes a bug where events coming from the event cache might be
encrypted, see https://github.com/matrix-org/matrix-rust-sdk/issues/4762
to learn more.

This patch updates the `room_event_cache_updates_task` to call
`TimelineController::retry_event_decryption` if the origin is `Cache`.
2025-03-14 14:18:21 +01:00
Ivan Enderlin 53c36226cb refactor(ui): TimelineController::retry_event_decryption no longer needs a &Room.
This patch removes the room `&Room` argument of
`TimelineController::retry_event_decryption`. The `TimelineController`
already has the room with its `room(&self) -> &Room` method. This method
was always used to fetch the room, let's expect `retry_event_decryption`
to do that by itself.

It also prevents passing the “wrong” room. This is more robust this way.
2025-03-14 14:18:21 +01:00
Ivan Enderlin 5a22944f52 chore(ui): Move an inline comment.
This patch moves an inline comment in its correct place. Code
was inserted between the comment and the part of the code it was
documenting.
2025-03-14 10:51:49 +01:00
Ivan Enderlin 494f93d2a4 chore(ui): Move the RoomKeyInfo task inside its own function.
This patch moves the task responsibles to handle the `RoomKeyInfo`
updates into its own function.

The goal is to get simpler code.
2025-03-14 10:51:49 +01:00
Ivan Enderlin d7849a1aa5 chore(ui): Move the BackupState task inside its own function.
This patch moves the task responsibles to handle the `BackupState`
updates into its own function.

The goal is to get simpler code.
2025-03-14 10:51:49 +01:00
Ivan Enderlin bc9adaab06 chore(ui): Move the rooms keys from backups task inside its own function.
This patch moves the task responsibles to handle the room keys from
backups into its own function.

The goal is to get simpler code.
2025-03-14 10:51:49 +01:00
Ivan Enderlin 1ec47ca24f chore(ui): Move the RoomSendQueueUpdate task inside its own function.
This patch moves the task responsibles to handle the
`RoomSendQueueUpdate`s into its own function.

The goal is to get simpler code.
2025-03-14 10:51:49 +01:00
Ivan Enderlin faa2fa2ef0 chore(ui): Move the pinned event IDs stream task inside its own function.
This patch moves the task responsibles to handle the pinned event IDs
stream into its own function.

The goal is to get simpler code.
2025-03-14 10:51:49 +01:00
Ivan Enderlin 33e8b453ee chore(ui): Move the RoomEventCacheUpdate task inside its own function.
This patch moves the task responsibles to handle the
`RoomEventCacheUpdate`s into its own function.

The goal is to get simpler code.
2025-03-14 10:51:49 +01:00
Ivan Enderlin b9fadd0a10 fix(base): Do not define the RoomInfoNotableUpdate's channel based on number of rooms.
When `BaseClient` is created, the rooms aren't loaded yet. Then,
calculating the size of the channel for `RoomInfoNotableUpdate` is
useless, as it will always be zero, and we will fallback to 500 every
time. By the way, 500 is a nice default. This patch uses this value as
the only channel's size.
2025-03-14 10:43:12 +01:00
Richard van der Hoff 294fd79947 multiverse: support passing a server URL
Allow use of a server url (eg `http://localhost:8008`), enabling connection to
a local server rather than one which supports well-known, TLS, and the rest.
2025-03-13 20:16:54 +01:00
Ivan Enderlin 8b6096729c fix(sdk): event_cache::Room::replace_all_events_by takes an EventsOrigin.
This patch updates `replace_all_events_by` to take an `EventsOrigin`.
It is called in two places: by `add_initial_events`, in which case it
is `EventsOrigin::Cache`, and by `handle_timeline`, in which case it is
`EventsOrigin::Sync`.
2025-03-13 09:23:24 +01:00
Ivan Enderlin 6f370daaed fix(sdk): Replace assert! by debug_assert!.
This patch replaces a call to `assert_eq` and `assert` by
`debug_assert_eq` and `debug_assert`. We don't want to panic in
production :-].
2025-03-13 09:23:24 +01:00
Ivan Enderlin 3c694e7909 fix(sdk): event_cache::Room::clear sends EventsOrigin::Cache.
This patch updates the `EventsOrigin` value sent by `Room::clear` to be
`Cache` instead of `Sync`. No events are added, but the `VectorDiff`s
are generated from the event cache itself, not from a sync.
2025-03-13 09:23:24 +01:00
Ivan Enderlin c3245a4f22 fix(sdk): Events loaded from the cache have EventsOrigin::Cache.
This patch fixes the `EventsOrigin` value for events loaded from the
cache, it was `Pagination`, now it is `Cache`.
2025-03-13 09:23:24 +01:00
Ivan Enderlin da89a53605 feat(ui): Add EventItemOrigin::Cache.
With more and more events coming from the event cache, it's nice to know
that an event actually from the cache instead of having `None`.
2025-03-13 09:23:24 +01:00
Ivan Enderlin 968582af01 chore(ui): Update a log message.
This patch changes the log message. A `TimelineItemPosition::UpdateAt`
can only happen for remote event, not local event. The log message
was talking about _decryption_ but an `UpdateAt` can also happen for
redaction. This was misleading.
2025-03-13 09:23:24 +01:00
Ivan Enderlin 07c7b6ab2a fix(ui): Pass the correct RemoteEventOrigin to replace_with_initial_remote_events.
This patch updates the `RemoteEventOrigin` value passed to
`TimelineController::replace_with_initial_remote_events` when there is
a lag with the event cache. The previous value was `Sync`, but it should
be `Cache` since these events come from the event _cache_ (it was in the
name, easy).
2025-03-13 09:23:24 +01:00
Ivan Enderlin 5aae0cbcd9 test: Fix a test in NotificationClient.
With the previous patch, `NotificationClient` with sliding sync now
knows immediately when the room is encrypted or not.
2025-03-12 16:53:59 +01:00
Ivan Enderlin 3ea842dae4 test(base): Test that room encryption is correctly computed. 2025-03-12 16:53:59 +01:00
Ivan Enderlin 31e0bfa400 feat: Install RequestedRequiredStates and add handle_encryption_state.
This patch updates the sync code to include the
`RequestedRequiredStates` type.

This patch also adds `RoomInfo::handle_encryption_state` which
is able to mark an encryption state as synced depending of
`RequestedRequiredStates` (read the comment in the code).

This patch also updates the documentation of
`RoomInfo::handle_state_event` to clarify the impact of a
`m.room.encryption` state event.
2025-03-12 16:53:59 +01:00
Ivan Enderlin d32b10de80 feat: Introduce RequestedRequiredStates.
This patch introduces a new type: `RequestedRequiredStates`, which keeps
track of all `required_states` passed to a sync request. So far, there
is only a `From` implementation for MSC4186.
2025-03-12 16:53:59 +01:00
Kévin Commaille 215853cf67 chore: Upgrade ruma
To pull in`GrantType::DeviceCode` and a fix for the generated `DeviceId`
length.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-12 13:41:40 +01:00
Jorge Martín a941cc824d fix(ffi): Restore some needed OIDC prompts in the FFI layer
These prompts were used in the Element X app, probably in some other clients too.

Since Ruma removed these cases, we're just passing them as `_Custom(value)` ones, which do work.
2025-03-12 13:12:09 +01:00
Benjamin Bouvier d3daa18bf8 feat!(ffi): rename setup_tracing into init_platform (#4790)
And make it take a boolean indicating whether we want to set up a
lightweight tokio runtime or not, instead of having
`setup_lightweight_tokio_runtime` as a public function + another
function, both of which would have to be called anyways.

cc @stefanceriu @jmartinesp
2025-03-12 09:41:01 +00:00
Ivan Enderlin 2ac3b6e9a2 chore(ffi): Remove useless block_on.
This patch removes the last `block_on` calls in `matrix-sdk-ffi`. Those
are artifacts of the past.
2025-03-11 16:37:44 +01:00
Benjamin Bouvier e81817c1b2 chore(ci): add new exceptions for cargo-deny 2025-03-11 16:05:52 +01:00
Benjamin Bouvier 01bb8093d0 feat(ffi): add a function to setup a lightweight tokio runtime
Creating many threads may use a bit of memory: on a machine with N
devices, exactly N*2 MB of memory may be consumed.

That might be a lot for a NSE process on iOS, which can only have up to
16 MB of RAM allocated for it. For this case, we introduce a new FFI
method `setup_lightweight_tokio_runtime` which will spawn at most 4
worker threads and 1 blocking thread. This should be sufficient for most
use cases.
2025-03-11 16:05:52 +01:00
Ivan Enderlin 1565067cee doc(ffi): Update the CHANGELOG.md. 2025-03-11 15:39:50 +01:00
Ivan Enderlin ecc603171b feat(ffi): Add RoomInfo::encryption_state.
This patch adds the `EncryptionState` onto the new
`RoomInfo::encryption_state` field.
2025-03-11 15:39:50 +01:00
Benjamin Bouvier 7f3308bd2b feat(sdk): don't trigger the ignored user list change if it hasn't changed since the previous time 2025-03-11 15:03:42 +01:00
Benjamin Bouvier 06d5fdb5ff fix(event cache): enable foreign keys on a connection basis
As opposed to WAL mode, foreign keys must be enabled for each database
connection, according to
https://www.sqlite.org/foreignkeys.html#fk_enable

Unfortunately, we can't track which connection objects have already
executed the pragma, so the safer we can do is enable it everytime we
try to acquire a connection from the pool.

Fixes #4785.
2025-03-11 15:03:42 +01:00
Benjamin Bouvier 6047d369a6 refactor(event cache): call clear() instead of doing it manually in clear_all_rooms 2025-03-11 15:03:42 +01:00
Benjamin Bouvier 961a893b8c test(event cache): double-check cascading happened in the clear linked chunk test 2025-03-11 15:03:42 +01:00
Benjamin Bouvier 2927974396 fix(event cache): don't try to remove a previous gap if it's the only chunk in memory
A linked chunk never wants to be empty. However, after a limited gap
that doesn't contain events, it may be shrunk to the latest chunk that's
a gap.

If later we decide to remove the gap (because it's been resolved with no
events), then we would try to remove the last chunk, which is not
correct.

Ideally, we'd keep an events chunk around; but if we have an events
chunk *before* a gap, that may look like missing events to the user, at
least until the gap has been resolved.

The fix to this problem is to *not* optimize / remove the gap, if it's
the only chunk kept in memory. This was only a memory optimization, but
it's not absolutely required per se.
2025-03-11 15:03:42 +01:00
Benjamin Bouvier 8c780fc5d5 chore(event cache): don't make use of .not() when it's not useful
`.not()` is useful in assertions, at best, but using it where `!` would
suffice is a bad code smell.
2025-03-11 15:03:42 +01:00
Benjamin Bouvier 8867d203e7 chore(event cache): add spans for RoomEventCache methods
So we know which room some logs messages correspond to.
2025-03-11 15:03:42 +01:00
Ivan Enderlin cf5f14ef5d feat(base): Reduce memory usage of BaseClient::room_info_notable_update_sender.
This patch reduces the memory usage of the broadcast channel used by
`BaseClient::room_info_notable_update_sender`. So far, its size was
`u16::MAX`. Considering `RoomInfoNotableUpdate` is 24 bytes, the channel
was allocating 1.5Mb of memory, which is way too much. It is creating
problems on systems where the process has limited resources, like the
Notification Service Extension on iOS.

For a regular users with 200 rooms, the memory usage becomes 24Kb, which
is 65'536 times less.
2025-03-11 14:47:53 +01:00
Ivan Enderlin 132f063769 feat(base): Add ObservableMap::len.
This patch implements `ObservableMap::len`, which is useful to count of
values it contains.
2025-03-11 14:47:53 +01:00
Ivan Enderlin 915cb13d45 fix(ffi): Remove Room::is_encrypted.
This API is now deprecated.
2025-03-11 14:03:42 +01:00
Kévin Commaille 0089da10cc refactor(ffi): Use methods on OidcConfiguration to construct parts
Changing the `TryInto` implementation into a method makes the code easier to follow.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-11 13:55:12 +01:00
Kévin Commaille 28293d0f2b chore: Add changelog for url_for_oidc changes
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-11 13:55:12 +01:00
Kévin Commaille d3e64295cf refactor(oidc): Add redirect URI as an argument of url_for_oidc
Being able to always use the first redirect URI in the client metadata
seems to be very specific to the FFI bindings.

For example clients that need to bind a port on localhost need to
provide a custom redirect URI each time.

 So we ask for the redirect URI, and keep the current behavior only for
the bindings.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-11 13:55:12 +01:00
Kévin Commaille 6cd3217c2e refactor(oidc): Don't take the client metadata as an argument of url_for_oidc
The OidcRegistrations already hold the metadata. We can just clone it lazily when we need it.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-11 13:55:12 +01:00
Ivan Enderlin eba2a7a6e3 doc(ffi): Update the CHANGELOG.md. 2025-03-11 12:28:16 +01:00
Ivan Enderlin a98b822eeb feat(ffi): Replace Room::is_encrypted by encryption_state and latest_encryption_state. 2025-03-11 12:28:16 +01:00
Ivan Enderlin 0a80021742 doc: Update the CHANGELOG.mds. 2025-03-11 12:28:16 +01:00
Ivan Enderlin 63e8fc84a3 test(sdk): Test encryption_state() vs latest_encryption_state(). 2025-03-11 12:28:16 +01:00
Ivan Enderlin fe0fb641f3 test(base): Test EncryptionState helpers. 2025-03-11 12:28:16 +01:00
Ivan Enderlin 1c43bc7e29 test(base): Test EncryptionState::NotEncrypted. 2025-03-11 12:28:16 +01:00
Ivan Enderlin d03ed3063c feat: Introduce EncryptionState.
This patch introduces the new `EncryptionState` to represent the 3
possible states: `Encrypted`, `NotEncrypted` or `Unknown`. All the
`is_encrypted` methods have been replaced by `encryption_state`.
The most noticable change is in `matrix_sdk::Room` where `async fn
is_encrypted(&self) -> Result<bool>` has been replaced by `fn fn
encryption_state(&self) -> EncryptionState`. However, a new `async
fn latest_encryption_state(&self) -> Result<EncryptionState>` method
“restores” the previous behaviour by calling `request_encryption_state`
if necessary.

The idea is that the caller is now responsible to call
`request_encryption_state` if desired, or use `latest_encryption_state`
to automate the call if necessary. `encryption_state` is now non-async
and infallible everywhere.

`matrix-sdk-ffi` has been updated but no methods have been added for
the moment.
2025-03-11 12:28:16 +01:00
Stefan Ceriu ea8664c487 Merge pull request #4780 from matrix-org/stefan/invitesRoomSummaryFallback
Invites room summary fallback
2025-03-11 11:02:23 +02:00
Stefan Ceriu ca025f8cca feat(ffi): forget the room when rejecting invites
- we're doing this as an extra layer of protection against spam attacks.
2025-03-11 10:16:27 +02:00
Stefan Ceriu 78e19fce32 chore(sdk): rewrite the room summary fallback test on top of the MatrixMockServer 2025-03-11 09:22:29 +02:00
Andy Balaam c8536e9e46 fix(crypto): Redecrypt non-UTD messages to remove no-longer-relevant warning shields (#4644)
Fixes https://github.com/element-hq/element-meta/issues/2697
Fixes https://github.com/element-hq/crypto-internal/issues/398

I'm sorry it's a big change. I've tried to break it into decent commits,
and I did a couple of preparatory PRs to make it less painful, but it's
still a bit to get your head around.

The basic idea is that when a session is updated and we call
`retry_event_decryption`, we don't only look at UTDs any more - now we
also look at decrypted events, and re-request their `EncryptionInfo`, in
case it has improved.

---------

Signed-off-by: Andy Balaam <mail@artificialworlds.net>
Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
Co-authored-by: Benjamin Bouvier <benjamin@bouvier.cc>
2025-03-11 07:01:54 +00:00
Benjamin Bouvier 1caa6069db refactor(timeline): move is_utd() to TimelineItemContent
It's unusual to have the method on the parent type when the field type
could also hold the method. In fact, this was the only bool getter
inspecting the timeline's content, so let's move the method next to as
its siblings, for consistency, and let's spell it out fully for clarity.
2025-03-11 07:43:53 +01:00
Stefan Ceriu abe8338e5c chore(ffi): expose a method for retrieving rooms based on their identifier 2025-03-10 19:11:59 +02:00
Stefan Ceriu 5373e39ce5 chore(ffi): remove now unnecessary invited_room and inviter methods as those should be retrieved through the room preview 2025-03-10 19:11:58 +02:00
Stefan Ceriu 5875973c13 feature(ffi): have previews for invited rooms fallback to cached client data if fetching the preview fails
- relates to element-hq/element-x-ios/issues/3713
- this will allow us to interact with them even if the given homeserver doesn't have MSC3266 enabled
2025-03-10 19:11:58 +02:00
dependabot[bot] 3fbf159d0e chore(deps): bump crate-ci/typos from 1.30.0 to 1.30.1
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.30.0 to 1.30.1.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.30.0...v1.30.1)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 16:38:20 +01:00
Kévin Commaille b5c4fe3f7d test(sdk): Allow any MockEndpoint to override the expected access token
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-10 10:52:53 +01:00
Kévin Commaille 516d066d4c test(sdk): Add a constructor for MockEndpoint on MatrixMockServer
Allows to reduce duplication and will allow to add common logic.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-10 10:52:53 +01:00
Kévin Commaille fbcd5a71aa test(sdk): Always call MockEndpoint::respond_with
Instead of MockBuilder::respond_with. This reduces duplcation and will
allow to add some common logic when building the endpoints.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-10 10:52:53 +01:00
Ivan Enderlin b5a23086fd test(sdk): Add test for maybe_apply_new_redaction.
This patch adds a test for `maybe_apply_new_redaction` when the redacted
event is not loaded in-memory, i.e. when it lives in the store only.
2025-03-10 09:45:41 +01:00
Kévin Commaille a9ce3f6963 chore: Add changelog for merging SessionTokens
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-10 09:12:38 +01:00
Kévin Commaille a27f8f79a4 refactor(sdk): Move the session tokens into the AuthCtx
To avoid duplicating the code between both authentication APIs.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-10 09:12:38 +01:00
Kévin Commaille dd01479c6b refactor(sdk): Use a single SessionTokens type
Since MatrixSessionTokens and OidcSessionTokens are identical.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-10 09:12:38 +01:00
dependabot[bot] e7f85ba545 chore(deps): bump ring from 0.17.8 to 0.17.13
Bumps [ring](https://github.com/briansmith/ring) from 0.17.8 to 0.17.13.
- [Changelog](https://github.com/briansmith/ring/blob/main/RELEASES.md)
- [Commits](https://github.com/briansmith/ring/commits)

---
updated-dependencies:
- dependency-name: ring
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 09:41:43 +02:00
Andy Balaam 48767da6cc refactor(test): Make use of is_utd method in integration test 2025-03-07 14:20:45 +00:00
Andy Balaam 73754399be feat(timeline): Provide is_utd on EventTimelineItem 2025-03-07 14:20:45 +00:00
Kévin Commaille 18f5668e3e Add assertion messages
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-07 13:10:52 +01:00
Kévin Commaille bc92e55b53 Improve tests
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-07 13:10:52 +01:00
Kévin Commaille 230feff430 test(sdk): Add tests for handle_refresh_tokens and Oidc
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-07 13:10:52 +01:00
Kévin Commaille 8bb4387dc4 fix(oidc): Match the proper error type for invalid refresh token
Since we do not use mas-oidc-client anymore, the error to match has changed.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-07 13:10:52 +01:00
Kévin Commaille 2506ba8364 refactor(oidc): Use oauth2 for token revocation
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-07 12:50:58 +01:00
Damir Jelić daad6d662f fix(multiverse): Don't wait for sync service state changes when shutting down
The SyncService::stop method guarantees that the sync service will be
stopped after it has completed so there's no need to wait for state
changes.

The state change might not even come, if you pressed `S` to stop the
sync service manually.
2025-03-06 16:16:11 +01:00
Damir Jelić 53853c2d9a refactor(multiverse): Put the login logic into a separate function 2025-03-06 15:46:07 +01:00
Damir Jelić 40de714e81 refactor(multiverse): Use clap to simplify the CLI argument parsing 2025-03-06 15:46:07 +01:00
Damir Jelić 27bde16843 refactor(multiverse): Simplify the terminal and panic hook setups 2025-03-06 15:46:07 +01:00
Damir Jelić 5e8f8d5513 refactor(multiverse): Simplify the tracing setup 2025-03-06 15:46:07 +01:00
Damir Jelić 120970c4ea chore(multiverse): Bump the deps 2025-03-06 15:46:07 +01:00
Kévin Commaille 740e729606 docs(oidc): Document the arguments of url_for_oidc
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-06 12:27:30 +01:00
Kévin Commaille 60b140b684 chore: Add changelog for using oauth2
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-06 12:27:30 +01:00
Kévin Commaille 9a165468eb test(oidc): Add more checks for the authorization URL
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-06 12:27:30 +01:00
Kévin Commaille e15897b3f1 refactor(oidc): Use oauth2 for authorization code grant
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-06 12:27:30 +01:00
Kévin Commaille 52f98582f1 refactor(oidc): Use oauth2 client for refreshing access tokens
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-06 12:27:30 +01:00
Kévin Commaille 2e72c23868 refactor(oidc): Move error types to the error module
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-06 12:27:30 +01:00
Kévin Commaille 0967027feb refactor(oidc): Use ClientId type from oauth2
Avoids to use 2 similar types with the same name.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-06 12:27:30 +01:00
Andy Balaam 6c9b1ef3c1 fix(common): Rename all snapshots in deserialized_responses to have shorter names 2025-03-05 15:29:44 +00:00
Damir Jelić 8cceded0ae refactor(oidc): Move the fallback issuer discovery logic into a separate method 2025-03-05 15:37:04 +01:00
Jorge Martín ff181475a0 fix(client): Add handle_verification_events field to BaseClient.
This is done to fix an issue with these events being received and processed twice when `NotificationProcessSetup` is `SingleProcess`, causing issues with user verification.

This can be used to ignore verification requests in this sliding sync instance, preventing issues found where several sliding sync instances with the same client process events simultaneously and re-process the same verification request events during their initial syncs.
2025-03-05 15:09:31 +01:00
Andy Balaam 074c0e59e0 fix(common): Shorten the name of the snapshot_test_encryption_info 2025-03-05 14:03:51 +00:00
Kévin Commaille 1d7c60c46a chore: Add changelog about ID tokens support removal
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-05 14:19:27 +01:00
Kévin Commaille 377f34fae2 refactor(oidc): Get rid of OidcBackend
Now that we don't use it for tests, we don't need it anymore.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-05 14:19:27 +01:00
Kévin Commaille 26cb805e0f test(oidc): Use MatrixMockServer in the remaining tests
Gets rid of the MockImpl for OidcBackend.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-05 14:19:27 +01:00
Kévin Commaille 81dbe2060c refactor(oidc): Remove support for ID tokens
ID tokens are a feature of OpenID Connect, we don't need them to support OAuth 2.0.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-05 14:19:27 +01:00
Ivan Enderlin fd0fca436b chore(sdk): Remove the request_body instrument's field.
Many fields here are not argument of the `send` method, but are set
later with `Span::record`. Grepping all these fields reveal they are all
set except `request_body` apparently.
2025-03-05 14:15:29 +01:00
Ivan Enderlin 3d653d3fdc fix(sqlite): Design a new schema to get faster insertions.
This patch is twofold. First off, it provides a new schema allowing to
improve the performance of `SqliteEventCacheStore` for 100_000 events
from 6.7k events/sec to 284k events/sec on my machine.

Second, it now assumes that `EventCacheStore` does NOT store invalid
events. It was already the case, but the SQLite schema was not rejecting
invalid event in case some were handled. It's now explicitely forbidden.
2025-03-05 13:57:08 +01:00
Ivan Enderlin b22bb3ee9f fix(sqlite): Use a prepared statement to insert events.
This patch uses a prepared statement to insert events in the linked
chunks. It offers more predictable performance, and SQLite prefers that.
2025-03-05 13:57:08 +01:00
Ivan Enderlin 7f17b4be7b bench: Add a benchmark for the LinkedChunk with the EventCacheStore. 2025-03-05 13:57:08 +01:00
Benjamin Bouvier fa3a9d81e3 refactor(event cache): use Ruma's is_redacted() method instead of original_content()
This is cheaper, as it doesn't require cloning the content and
immediately throw it away. This method was introduced recently, thanks
to Kevin for it.
2025-03-05 12:14:04 +01:00
Ivan Enderlin 892c99f0f3 test(sqlite): Improve a test to check uniqueness constraint. 2025-03-05 12:02:30 +01:00
Ivan Enderlin 8d8846a259 chore(sdk): Remove EventsPostProcessing.
This patch removes the `EventsPostProcessing` type, it assumes
`with_events_muts` will always return events that will be post-process.
The case where `EventsPostProcessing::None` becomes a `vec![]`.
2025-03-05 11:30:55 +01:00
Ivan Enderlin 9d63af6271 chore(sdk): maybe_apply_new_redaction no longer takes a &RoomVersionId.
This patch updates `maybe_apply_new_redaction` to remove the first
`&RoomVersionId` argument. Indeed, due to the refactoring, it's now
possible for `maybe_apply_new_redaction` to read this value directly
from `Self::room_version`.
2025-03-05 11:30:55 +01:00
Ivan Enderlin 37ad82adfc doc(sdk): Add missing documentation. 2025-03-05 11:30:55 +01:00
Ivan Enderlin 57953b9ae9 chore(sdk): Make Clippy happy. 2025-03-05 11:30:55 +01:00
Ivan Enderlin 777fb920f6 fix(sdk): maybe_apply_new_redaction updates in-store events.
This patch updates `maybe_apply_new_redaction` so that it is able to
update/redact an event found in the store.
2025-03-05 11:30:55 +01:00
Ivan Enderlin 05750e871b task(sdk): maybe_apply_new_redaction uses find_event.
This patch updates `maybe_apply_new_redaction` to use `find_event`, so
that the target event is looked up in memory or in the store.

The case where it is in the store is a simple `todo!()` for the moment.
I wanted to separate the update of the `maybe_apply_new_redaction`
signature from the `InStore` implementation. The method is now async and
returns a `Result`.
2025-03-05 11:30:55 +01:00
Ivan Enderlin 5a11b8b836 task(sdk): RoomEventCacheState::find_event returns the event location.
This patch introduces `EventLocation` to know if an event has been found
in the memory (in `RoomEvents`) or in the store (in `EventCacheStore`).

This is used by the `RoomEventCacheState::find_event`.
2025-03-05 11:30:55 +01:00
Ivan Enderlin 8a785ea855 task(sdk): Move maybe_apply_new_redaction from RoomEvents inside RoomEventCacheState.
This patch moves the `maybe_apply_new_redaction` method from
`RoomEvents` inside `RoomEventCacheState` so that it has an access
to the store (necessary for the next patch). This patch creates a new
`RoomEvents::replace_event_at` method, which is a thin wrapper around
`LinkedChunk::replace_item_at`.
2025-03-05 11:30:55 +01:00
Ivan Enderlin 1874a76f67 task(sdk): Rename a variable, and AllEventsCache only stores valid events.
This patch renames `sync_timeline_events` into `timeline_events`.
Moreover, this change has spotted a possible improvement
in `AllEventsCache` where it now receives events from
`collect_valid_and_duplicated_events`, which allows to only store valid
events in it.
2025-03-05 11:30:55 +01:00
Ivan Enderlin 0b2b528962 task(sdk): Callback in with_events_mut returns an EventsPostProcessing.
This patch updates the callback passed to `with_events_mut`. It now
returns an `EventsPostProcessing` which can automatically run the, now
inlined, `on_new_events`.

This patch updates where the `RoomVersionId` is also stored. It's not
held by `RoomEventCacheState` instead of `RoomEventCacheInner`.
2025-03-05 11:30:55 +01:00
Ivan Enderlin 2036c3da9d doc(sdk): Fix documentation of with_events_mut. 2025-03-05 11:30:55 +01:00
Benjamin Bouvier 7694b016da chore: disable LTO on release builds
I have to disable LTO every time I'm building any final binary using the
SDK, because otherwise, the builds can take easily more than 10 minutes
to complete, killing iteration times, and making it almost impractical
to use the programs (benchmarks, or multiverse).

I think it should be the decision of the final embedder to enable or
disable LTO, and that for the purpose of our own binaries hosted in the
SDK repository, we don't need the absolute best performance (or, for the
sake of benchmarking, we can tweak the profiling profile).
2025-03-05 10:55:15 +01:00
Benjamin Bouvier 6fdd59157a fix(timeline): steal hiddden receipts from the previous item, when inserting in the middle 2025-03-05 09:42:14 +01:00
Benjamin Bouvier 0d7096fa94 test(timeline): add regression test for duplicate read receipts 2025-03-05 09:42:14 +01:00
Benjamin Bouvier a94dc4e89b chore(timeline): add more logs for read receipts 2025-03-05 09:42:14 +01:00
Benjamin Bouvier bf965b2a17 feat(timeline): add an invariant check that there's no duplicate read receipts 2025-03-05 09:42:14 +01:00
Benjamin Bouvier 9c87625910 chore(event cache): include the room id in the sending linked chunk updates to the store log 2025-03-05 08:56:37 +01:00
Benjamin Bouvier 3f1543504a test(timeline): add an equivalent test when storage's enabled 2025-03-05 08:56:37 +01:00
Benjamin Bouvier 3773968d19 fix(timeline): remove events when back-paginating too
The previous strategy was incorrect, see the new doc comment explaining
why with the example taken from the regression test.
2025-03-05 08:56:37 +01:00
Kévin Commaille d28d4ce799 test(timeline): add a regression test for the incorrect timeline ordering 2025-03-05 08:56:37 +01:00
Benjamin Bouvier bffb19b23a refactor!(sdk): bump the MSRV, yay for async closures 🥳 2025-03-04 18:10:59 +01:00
Benjamin Bouvier 6aea4c827a feat(ffi): allow setting the media retention policy from the FFI layer 2025-03-04 18:10:59 +01:00
Benjamin Bouvier ac3250c58b refactor(event cache): use u64 instead of usize in MediaCachePolicy
This is more predictible and we're still far from 128-bits wide cpu,
right? RIGHT?
2025-03-04 18:10:59 +01:00
Benjamin Bouvier 6fe0880e11 feat(ffi): add a method to clear all the non-critical caches of a client 2025-03-04 18:10:59 +01:00
Stefan Ceriu 78282bf1e1 chore(sdk-base): fix typos following typos crate bump to 1.30.0 2025-03-04 11:28:36 +02:00
dependabot[bot] 43d25127c3 chore(deps): bump crate-ci/typos from 1.29.7 to 1.30.0
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.29.7 to 1.30.0.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.29.7...v1.30.0)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-04 11:28:36 +02:00
bitfriend c33c61a256 feat(ci): Implement CI to detect long path in pushed commit 2025-03-03 16:57:36 +00:00
Benjamin Bouvier def4be5a9f refactor(event cache): make use of Chunk::num_items when deciding whether to drop a previous empty event chunk 2025-03-03 16:04:15 +01:00
Benjamin Bouvier 9bc0d8b0d9 refactor(event cache): rename Chunk::len to Chunk::num_items 2025-03-03 16:04:15 +01:00
Benjamin Bouvier 0924b2e343 refactor(event cache): get rid of EmptyChunkRule::Keep which is only used in testing
This isn't useful to keep, since it's only used in testing. Worst case,
we can revert this commit in the future.
2025-03-03 16:04:15 +01:00
Benjamin Bouvier 8b6e75980b refactor(event cache): don't keep an empty events chunk before a gap
The linked chunk always starts with an empty events chunk. If we receive
a gap from sync, then we will immediately push a gap chunk; in this
case, it might be better to replace the events chunk with a gap chunk.
This is equivalent to removing the empty events chunk, after pushing
back the first one (we can't do it before, otherwise we might get rid of
the only chunk in the linked chunk, which breaks the invariant that a
linked chunk is never empty).
2025-03-03 16:04:15 +01:00
dependabot[bot] 5fd0cb0ddb chore(deps): bump bnjbvr/cargo-machete from 0.7.1 to 0.8.0
Bumps [bnjbvr/cargo-machete](https://github.com/bnjbvr/cargo-machete) from 0.7.1 to 0.8.0.
- [Release notes](https://github.com/bnjbvr/cargo-machete/releases)
- [Changelog](https://github.com/bnjbvr/cargo-machete/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bnjbvr/cargo-machete/compare/v0.7.1...v0.8.0)

---
updated-dependencies:
- dependency-name: bnjbvr/cargo-machete
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-03 15:38:57 +01:00
Benjamin Bouvier b5edc86a52 refactor(event cache): rename clear to clear_pending 2025-03-03 14:56:33 +01:00
Benjamin Bouvier d09655989d feat(event cache): don't include useless updates when clearing/resetting a linked chunk 2025-03-03 14:56:33 +01:00
Benjamin Bouvier 83415ac6ca refactor(event cache): clear all pending updates when resetting/shrinking a linked chunk 2025-03-03 14:56:33 +01:00
Kévin Commaille cc7fb63c6d refactor(sdk): Remove clone_request method
http::Request implements Clone since http 1.0.0
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-03-03 14:32:34 +01:00
Benjamin Bouvier f5195222a7 refactor(ffi): move the TimelineEventTypeFilter to timeline/configuration
Pure code motion, nothing else.
2025-03-03 12:40:54 +01:00
Benjamin Bouvier cecf15a34a refactor(ffi): unify a bit more Room::timeline_with_configuration and RoomListItem::init_timeline
The two last missing pieces will be the UTD hook and loading events from
the persistent storage.
2025-03-03 12:40:54 +01:00
Damir Jelić 95b53d7e01 chore: Tweak the weekly-report command to include PR numbers 2025-02-28 15:51:38 +01:00
Andy Balaam 8cd70854ba refactor(crypto): Keep a long-lived DecryptionRetryTask in TimelineController 2025-02-28 12:35:04 +00:00
Andy Balaam dbaa36ec3e refactor(timeline): Share should_retry logic between the two places we use it 2025-02-28 12:35:04 +00:00
Andy Balaam 8976233905 refactor(timeline): Move the code to find which events to redecrypt into the async task 2025-02-28 12:35:04 +00:00
Andy Balaam 82d47d800c refactor(timeline): Pass requests to retry decryption through a channel, instead of spawning a task directly 2025-02-28 12:35:04 +00:00
Andy Balaam e84ad97edf refactor(timeline): Adjust tests that retry decryption to wait with timeouts 2025-02-28 12:35:04 +00:00
Andy Balaam d447342cbd refactor(timeline): Split finding retry indices into its own function 2025-02-28 12:35:04 +00:00
Andy Balaam c74ecff3f0 refactor(timeline): Move finding retry indices into DecryptionRetryTask 2025-02-28 12:35:04 +00:00
Andy Balaam a0282ec71b refactor(timeline): Move the decryption retrying into a separate struct 2025-02-28 12:35:04 +00:00
Ivan Enderlin a67f9d5bbf chore(sdk): Log all updates. 2025-02-28 13:07:45 +01:00
Benjamin Bouvier f7297edd61 refactor(event cache): get rid of get_or_wait_for_token
Also the tests, they were not quite useful to port to the new mechanism
because they made little sense.
2025-02-28 13:00:35 +01:00
Benjamin Bouvier 87a6037924 refactor(event cache): consolidate logic around returning the previous gap token 2025-02-28 13:00:35 +01:00
Benjamin Bouvier ee710e34dd test(event cache): turn test into failing regression test 2025-02-28 13:00:35 +01:00
Benjamin Bouvier 55143e1790 refactor(event cache): call /messages directly in the room pagination
And don'y rely on the `Paginator`. This simplifies the code a bit,
avoids a few methods on the `Paginator`, and makes it more
straightforward the pagination happens.
2025-02-28 12:24:59 +01:00
Damir Jelić 7a0bf9b9b9 chore(sdk): Don't repeat a log line about the list of users for a /keys/query
The crypto crate already logs this, so no need to repeat the whole list
of users in the main crate.
2025-02-27 16:33:13 +01:00
Damir Jelić b422b93c78 chore(crypto): Lower a very noisy log line
This partially reverts: 66fcaeb2bad125dcdf884aaec6528633d2f1ec32
2025-02-27 16:33:13 +01:00
Benjamin Bouvier 4742aa298a fix(event cache): wait for the initial previous-batch token, if there wasn't any 2025-02-27 10:28:02 +01:00
Benjamin Bouvier f9f389d9ec chore(event cache): remove unused errors 2025-02-26 17:20:45 +01:00
Hanadi 7dba05f4c5 feat(sdk): Add Room::report_room
solves this https://github.com/matrix-org/matrix-rust-sdk/issues/4681

- add room report_room api from
https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3roomsroomidreport
- expose report_room on room ffi

---------

Signed-off-by: hanadi92 <hanadi.tamimi@gmail.com>
2025-02-26 16:55:57 +01:00
Kévin Commaille f02a7d15ab test(sdk): Run integration tests for experimental-oidc feature too
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-26 16:08:00 +01:00
Kévin Commaille 54ab46dcb4 test(oidc): Use MatrixMockServer for cross-signing test
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-26 14:31:46 +01:00
Kévin Commaille 9b406cff87 test(oidc): Use mock server and client as much as possible
We keep the mock backend for endpoints that require an ID token for now,
as it would involve generating them on the fly.
And since support for ID tokens is going to be removed, it is not worth
it to implement that.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-26 14:31:46 +01:00
Kévin Commaille 5791ac9b76 test(oidc): Add an OauthMockServer and use it for qrcode login tests
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-26 14:31:46 +01:00
Kévin Commaille 6026b0c4b7 test(oidc): Use MatrixMockServer for qrcode login tests
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-26 14:31:46 +01:00
Kévin Commaille 52909b0eeb test(oidc): Simplify qrcode login tests
Since it only uses OAuth 2.0 now, we can remove the ID token and JWKS.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-26 14:31:46 +01:00
Benjamin Bouvier 1feb77bbef doc(event cache): tweak paginate_backwards_with_network doc comment 2025-02-26 14:09:08 +01:00
Benjamin Bouvier 2e1b051a4d refactor(event cache): split handling a successful network pagination into its own function 2025-02-26 14:09:08 +01:00
Benjamin Bouvier 15fd892b63 chore(event cache): remove the sync_ prefix from timeline_events_diff
And simplify pluralization, use more natural terms (new instead of
next), etc.
2025-02-26 14:09:08 +01:00
Benjamin Bouvier 4833403d65 refactor(event cache): simplify signature of RoomEventCacheState::with_events_mut 2025-02-26 14:09:08 +01:00
Benjamin Bouvier 061a2f739a refactor(event cache): handle pagination status in a single location 2025-02-26 14:09:08 +01:00
Benjamin Bouvier 86b5cb4dba refactor(event cache): split disk and network paginations into smaller functions 2025-02-26 14:09:08 +01:00
Benjamin Bouvier 74bc3dfb6e refactor(event cache): don't hold onto a live instance of the paginator in RoomEventCache
Instead of keeping state for the `Paginator` instance, we create one
when needs be, in the `run_backwards_impl` method, and initialize it
with a previous-batch token. This is simpler than keeping one alive, and
making sure that we reset it in the right places.
2025-02-26 14:09:08 +01:00
Benjamin Bouvier 7841ed8637 refactor(event cache): remove RoomPagination::hit_timeline_end()
The event cache doesn't use the paginator for forwards pagination, so it
doesn't make sense to expose this method. Moreover, the event cache
always listens to sync in real-time, so technically it's always hit the
timeline end.

As a proof of this, this method wasn't even tested.
2025-02-26 14:09:08 +01:00
Doug 19df945155 fix(ffi): Correctly indicate OIDC support when fetching metadata fails. 2025-02-25 19:24:07 +02:00
Damir Jelić 3e3bff76de fixup! feat(crypto): Add support for the shared_history flag defined in MSC3061 2025-02-25 16:52:23 +01:00
Damir Jelić ea073f55f0 doc(crypto): Document all the arguments of the InboundGroupSession::new method 2025-02-25 16:52:23 +01:00
Damir Jelić c1e28aa156 test(crypto): Add a snapshot test for the inbound group session pickle 2025-02-25 16:52:23 +01:00
Damir Jelić af62f09e37 test(crypto): Test that the shared history flag gets set when we ourselves crate a session 2025-02-25 16:52:23 +01:00
Damir Jelić 9a33385697 test(crypto): Test that the shared history flag gets set when creating sessions 2025-02-25 16:52:23 +01:00
Damir Jelić bfa89bc73f feat(crypto): Add support for the shared_history flag defined in MSC3061
This patch adds support for the `shared_history` flag from MSC3061 to
the `m.room_key` content, exported room keys, and backed-up room keys.

The flag is now persisted in our `InboundGroupSession`. Additionally,
when creating a new `InboundGroupSession`, we ensure the
`shared_history`  flag is set appropriately.

MSC3061: https://github.com/matrix-org/matrix-spec-proposals/pull/3061
2025-02-25 16:52:23 +01:00
Damir Jelić e1d05fa53c refactor(crypto): Use struct destructuring instead of separate field access in some places
In several places, we access almost all the fields of a struct to
create an `InboundGroupSession` from a pure data struct.

When new fields are added to these data structs, it's easy to
overlook updating the `InboundGroupSession` accordingly.

By using struct destructuring, we ensure that newly added fields  are
explicitly considered, making it harder to forget one of the newly added
fields.
2025-02-25 16:52:23 +01:00
Damir Jelić b0ccc94b26 refactor(crypto): Simplify some tests by using a session created from a helper function 2025-02-25 16:52:23 +01:00
Damir Jelić b2356a0232 doc(crypto): Improve the documentation for the encrypted Content serialization
Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
Signed-off-by: Damir Jelić <poljar@termina.org.uk>
2025-02-25 15:58:42 +01:00
Damir Jelić 3bb883387e refactor(crypto): Use the DecryptedOlmV1Event type when encrypting to-device events
This ensures that new fields that are added to the
`m.olm.v1.curve25519-aes-sha` content need to be added in a single
place.
2025-02-25 15:58:42 +01:00
Damir Jelić 506a36b210 fix(crypto): Fix the serialization of DecryptedOlmV1Event 2025-02-25 15:58:42 +01:00
Damir Jelić 8c1966a237 refactor(crypto): Don't require event_type to return a static string 2025-02-25 15:58:42 +01:00
Kévin Commaille 09513eaa5e refactor(oidc): Only support authorization URL parameters defined in MSCs
`prompt=create` is defined in MSC2964, and
`login_hint=mxid:@user:server.name` is defined in MSC4198.
The other parameters came from OpenID Connect.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-25 15:34:36 +01:00
Kévin Commaille fda9177a70 refactor(oidc): Remove support for Pushed Authorization Requests
It is a small optimization which makes the URL smaller, but it is not
part of the next-gen auth MSCs and is not supported by the oauth2 crate,
so let's drop it.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-25 15:34:36 +01:00
Benjamin Bouvier 21960a5ba2 chore(event cache): add more logs to the auto-shrinking mechanism 2025-02-25 14:13:33 +01:00
Benjamin Bouvier 0819ab1dad refactor(event cache): apply review comments of #4708 2025-02-25 12:28:28 +01:00
Ivan Enderlin 475ad79360 fix(sdk): RoomEventCache::event looks inside the store.
This patch fixes `RoomEventCache::event` to look inside `RoomEvents` but
also inside the `EventCacheStore` to look for an event. It ultimately
fallbacks to `AllEventsCache` because we can't get rid of it for the
moment.
2025-02-25 12:07:17 +01:00
Ivan Enderlin 7b52306ff2 feat(base): Add EventCacheStore::find_event.
This patch adds the method `find_event` on the `EventCacheStore` trait.
It helps to find a single event from the store.
2025-02-25 12:07:17 +01:00
Benjamin Bouvier e5f6d026ff ci: use an hardcoded version of cargo-machete in CI (#4710)
Should resolve the CI issues around cargo-machete.

See also:
https://github.com/bnjbvr/cargo-machete/issues/156#issuecomment-2681308436
2025-02-25 11:35:18 +01:00
Benjamin Bouvier 5dd5710758 feat(event cache): auto-shrink a room event cache's chunk after all listeners are left 2025-02-24 17:40:50 +01:00
Ivan Enderlin 37b62dfed1 test(sdk): Add a big test for a deduplication + event removals.
This patch adds a test for deduplication that covers unloaded and loaded
chunk with event removals in both, with a finaly backwards pagination.
Yummy.
2025-02-24 17:37:47 +01:00
Ivan Enderlin d21a4152de chore(sdk): Code cleanup.
This patch puts the `Ok` outside the `match` for a better ergonomics.
2025-02-24 17:37:47 +01:00
Ivan Enderlin 8c2dcd7b5d task(sdk): Make the code more robust around event removals.
This patch makes the code more robust around event removals. Sorting
events by their position is no longer done in the `Deduplicator` but in
a new `RoomEventCacheState::remove_events` method, which removes events
in the store and in the `RoomEvents`. This method is responsible to sort
events, this stuff is less fragile like so.
2025-02-24 17:37:47 +01:00
Ivan Enderlin 019b4a20f6 chore(common): Rename EmptyChunk to EmptyChunkRule.
This patch renames `EmptyChunk` into `EmptyChunkRule`. Name suggested by
@stefanceriu, it makes a lot more sense, thanks!
2025-02-24 17:37:47 +01:00
Ivan Enderlin 30a9a972ce tes(sdk): Deduplicator dispatches duplicated events in memory vs in store.
This patch tests that `Deduplicator` dispatches duplicated events in the
correct field of `DeduplicationOutcome`.
2025-02-24 17:37:47 +01:00
Ivan Enderlin 22ba1684b2 chore(sdk): Rename a test helper and some variables.
Nothing fancy here. Just regular chore tasks.
2025-02-24 17:37:47 +01:00
Ivan Enderlin 0b12ec2b38 test(sdk): Deduplicator excludes invalid events.
This patch adds a test ensuring that `Deduplicator` excludes invalid
events, i.e. event with no ID.
2025-02-24 17:37:47 +01:00
Ivan Enderlin a71f5bf21f test(sdk): Test Deduplicator filters events in the input.
This patch adds a test ensuring that `Deduplicator` is able to find
duplicates in its own inputs.
2025-02-24 17:37:47 +01:00
Ivan Enderlin 9bd7cfda5f test(sdk): Rename a test. 2025-02-24 17:37:47 +01:00
Ivan Enderlin c1a13f7f98 test(sdk): Test sort_events_by_position_descending.
This patch adds a test for `sort_events_by_position_descending`. It
also updates this function so that events are sorted by their chunk
identifier from newest to oldest, it makes no difference but it matches
the order of the position indices too. Everything “dimension” is
descending.
2025-02-24 17:37:47 +01:00
Ivan Enderlin a362584bb3 task(sdk): Use DeduplicationOutcome to remove events in their correct place.
This patch uses `DeduplicationOutcome` to remove events either in
memory, or in the store, when required. The `remove_events_by_id` method
has been renamed `remove_events_by_position`.
2025-02-24 17:37:47 +01:00
Ivan Enderlin f9ce7628ff task(sdk): Redesign Deduplicator::filter_duplicate_events.
This patch redesigns `Deduplicator::filter_duplicate_events`.

First off, `filter_duplicate_events` does remove events with no valid
ID. At the same time, it removes duplicate events within the new events
(`events`). This check was done in the `BloomFilterDeduplicator` but
not in the `StoreDeduplicator`. Now it's done at the front of these
implementations, directly inside `Deduplicator`.

Second, this patch introduces `DeduplicationOutcome` to replace the
return type `(Vec<Event>, Vec<OwnedEventId>)`, especially because
now it would have become `(Vec<Event>, Vec<(OwnedEventId, Position)>,
Vec<(OwnedEventId, Position)>)`. Why?

1. Because the positions of the duplicated events are returned,
2. We differentiate between in-memory vs. in-store duplicated events.

Third, now there are positions associated to duplicated events, events
must be sorted. It's the role of `sort_events_by_position_descending`.

This way, `DeduplicatorOutcome` brings guarantees and less checks are
required.
2025-02-24 17:37:47 +01:00
Ivan Enderlin 43c066e837 task(base): EventCacheStore::filter_duplicated_events returns Position.
This patch changes `EventCacheStore::filter_duplicated_events` to return
the `Position` of the duplicated event.
2025-02-24 17:37:47 +01:00
Benjamin Bouvier f3f37a33fd fix(event cache): override reached_start when there's a mismatch between network and disk
It could be that we have a mismatch between network and disk, after
running a back-pagination:

- network indicates start of the timeline, aka there's no previous-batch
token
- but in the persisted storage, we do have an initial empty events chunk

Because of this, we could have weird transitions from "I've reached the
start of the room" to "I haven't actually reached it", if calling the
`run_backwards()` method manually.

This patch rewrites the logic when returning `reached_start`, so that
it's more precise:

- when reloading an events chunk from disk, rely on the previous chunk
property to indicate whether we've reached the start of the timeline,
thus avoiding unnecessary calls to back-paginations.
- after resolving a gap via the network, override the result of
`reached_start` with a boolean that indicates 1. there are no more gaps
and 2. there's no previous chunk (actual previous or lazily-loaded).

In the future, we should consider NOT having empty events chunks, if we
can.
2025-02-24 14:47:21 +01:00
Benjamin Bouvier 39c6481f96 feat(event cache): include the lazy previous chunk in the debug string, if available 2025-02-24 14:47:21 +01:00
Benjamin Bouvier 66b9d334ef feat(event cache): shrink the linked chunk upon gappy syncs 2025-02-24 14:47:21 +01:00
Benjamin Bouvier e64cb2c4f1 feat(event cache): implement RoomEventCacheState::shrink_to_last_chunk 2025-02-24 14:47:21 +01:00
Benjamin Bouvier 4f47868930 feat(linked chunk): allow replacing a linked chunk's content with a raw chunk 2025-02-24 14:47:21 +01:00
Benjamin Bouvier 4c115b6ad5 feat(event cache): don't store a gap if we've deduplicated all events during sync 2025-02-24 14:47:21 +01:00
Benjamin Bouvier 242a1047bd doc(event cache): clarify that RoomEvents::updates() is only for storage updates
And rename it accordingly to `RoomEvents::store_updates`.

Note: no changelog, because this is an internal API only.
2025-02-24 14:47:21 +01:00
Kévin Commaille 2f3cab431f chore: Add changelog for Oidc::logout
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-24 14:19:48 +01:00
Kévin Commaille 55f514897b refactor(oidc): Only revoke one token for logout
The server is supposed to revoke any token associated with the token that we revoke.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-24 14:19:48 +01:00
Kévin Commaille d4b92de8e4 refactor(oidc): Remove support for OIDC RP-Initiated logout
Token revocation was split out from MSC2964 to MSC4254, and RP-Initiated
logout is now mentioned only as an alternative.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-24 14:19:48 +01:00
Kévin Commaille 25d39997a4 chore: Add changelog for moving qrcode module
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-24 13:39:23 +01:00
Kévin Commaille 254ce8923b refactor(oidc): Use OidcBackend with LoginWithQrCode
Will allow to share code when the backend is switched to the oauth2 crate too.

It will also allow to expose the device authorization grant directly in Oidc, if necessary.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-24 13:39:23 +01:00
Kévin Commaille 0a4db305b9 refactor(oidc): Move qrcode module inside oidc
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-24 13:39:23 +01:00
Benjamin Bouvier 90ac2181e9 test: rename some MatrixMockServer helpers functions to make it clear they're matchers
The `.from()`, `.with_delay()` and `.limit()` functions are not very
explicit about what they did, and the `with_delay` one in particular led
me to think that it would introduce a delay *in the response*, while it
indicated a delay was expected as part of the matched URL.

Instead, this patch proposes to prefix all matchers with `match_`, to
make it clearer that… they will match the incoming query: match_from,
match_delay, match_limit.

Thanks to this change, the `RoomMessagesResponseTemplate` can be renamed
from `delayed` to `with_delay`, which was my original intent, before I
noticed another `with_delay` with totally different semantics.
2025-02-24 11:09:25 +01:00
Ivan Enderlin bdf5fad992 chore(common): Remove LinkedChunkBuilderTest.
This patch removes `LinkedChunkBuilderTest` and updates tests
accordingly.
2025-02-21 12:06:08 +01:00
Ivan Enderlin 05be62183a task: Remove all usages of LinkedChunkBuilderTest.
This patch replaces all usages of `LinkedChunkBuilderTest` by
`from_all_chunks`.
2025-02-21 12:06:08 +01:00
Ivan Enderlin d545419684 test(common): Add lazy_loader::from_all_chunks.
This patch adds the new `from_all_chunks` function in the
`linked_chunk::lazy_loader` module. It is only used for testing
purposes. It aims at replacing `LinkedChunkBuilderTest` (see next
patches). Why? Because `from_all_chunks` uses `from_last_chunk` and
`insert_new_first_chunk`: if `from_all_chunks` is able to find all
errors that `LinkedChunkBuilderTest` finds, it's a bingo. Transitively,
it proves that `from_last_chunk` and `insert_new_first_chunk` are
correct!
2025-02-21 12:06:08 +01:00
Jonas Platte f900db49dd feat(sdk): Re-export the base crate's store module
Fixes a broken intra-doc link.
2025-02-21 09:36:50 +01:00
Jonas Platte 1373f99288 refactor: Use NonZeroUsize::new + unwrap in const contexts
Make clippy happy.
2025-02-21 09:36:50 +01:00
Jonas Platte f56bc4c0d6 chore: Bump nightly 2025-02-21 09:36:50 +01:00
Benjamin Bouvier 60efcbc55d refactor(event cache): use Option<&mut> in Chunk::insert_before 2025-02-20 16:28:22 +01:00
Benjamin Bouvier 30589ca899 refactor(event cache): use Option<&mut> in Chunk::unlink 2025-02-20 16:28:22 +01:00
Damir Jelić 61fa339163 refactor(crypto): Add a constructor to create an InboundGroupSession from a m.room_key event 2025-02-20 12:28:45 +01:00
Damir Jelić 3f5efc1ff6 docs(crypto): Update some docs for the InboundGroupSession 2025-02-20 12:28:45 +01:00
Ivan Enderlin 23f72ba15f test(common): Test ability for AsVector to generate several VectorDiff::Remove.
This patch adds a test ensuring that `AsVector` generates the correct
`VectorDiff::Remove` when a non-empty chunk is removed.
2025-02-20 10:52:59 +01:00
Ivan Enderlin a25acf7e62 feat(common): Update::RemoveChunk emits VectorDiff::Remove.
This patch updates `Update::RemoveChunk` to emit `VectorDiff::Remove`.
Until now, `RemoveChunk` was expecting the chunk to be
empty, because it is how it is used so far. However, with
https://github.com/matrix-org/matrix-rust-sdk/pull/4694, it can change
rapidly.
2025-02-20 10:52:59 +01:00
Benjamin Bouvier c3fc310f29 refactor(event cache): simplify back-pagination 2025-02-20 10:51:06 +01:00
Benjamin Bouvier b9c7ffe7c3 doc(timeline): tweak comment in pagination to explain why it's correct 2025-02-20 10:51:06 +01:00
Benjamin Bouvier 017a947fc1 doc(timeline): fix a broken link to all_remote_events 2025-02-20 10:01:57 +01:00
Benjamin Bouvier 5c57631a6c refactor(event cache): simplify flow when deciding to resolve a gap
The code before this patch was doing this:

- look if there's any prev-batch token available right now, aka look if
there's a gap in the in-memory linked chunk
- look at the first chunk; if it's a gap, return to the caller so it
resolves it

The check is done twice at two different levels, which is confusing.
Instead, this patch rewrites it so that the chunk is done only in
`load_more_event_backwards()`.

Note this is also correct for the case storage is disabled; in this
case, we early return and always try to resolve the gap anyways.
2025-02-19 15:39:58 +01:00
Ivan Enderlin 3495cab7ad refactor(common): builder::LinkedChunkBuilder::* becomes lazy_loader::*.
This patch renames the `builder` module to `lazy_loader`.
The `LinkedChunkBuilder`'s methods are now functions.
The `LinkedChunkBuilder` struct is removed. Finally,
`LinkedChunkBuilderError` is renamed `LazyLoaderError`.

The `LinkedChunkBuilderTest` struct is kept for the moment. It's going
to be replaced soon.
2025-02-19 14:38:56 +01:00
bitfriend 7a06bdb695 Rename snapshots to reduce filename length (#4625) 2025-02-19 13:29:51 +00:00
Ivan Enderlin 6c57003d17 feat(sqlite) Add an index on events.event_id and .room_id.
This patch adds an index on `events.event_id` and on `events.room_id`
so that queries on this column are faster. It mostly happens for the
`Deduplicator`, which runs for every backwards pagination or sync.

This patch also updates the query in `filter_duplicated_events` to
sort event by their `chunk_id` and `position` so that the results are
constant, it helps when testing.
2025-02-19 11:50:23 +01:00
Kévin Commaille 2eb2ae7959 refactor(oidc): Use the GET /auth_metadata Matrix endpoint (#4673)
This is the method to get the server metadata in the latest draft of
[MSC2965](https://github.com/matrix-org/matrix-spec-proposals/pull/2965).

We still keep the old behavior with `GET /auth_issuer` as fallback for
now because it has wider server support.

There are some pre-main commit cleanups to simplify the main commit.
This can be reviewed commit by commit.

The changes were tested with the oidc_cli example on beta.matrix.org.

Closes #4550.

---------

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-18 17:41:48 +01:00
Ivan Enderlin a055aa3e57 chore(sdk): Fix comments and rename variables. 2025-02-18 17:13:12 +01:00
Ivan Enderlin 5c7a733f49 task(common): LinkedChunkBuilder detects cycles. 2025-02-18 17:13:12 +01:00
Ivan Enderlin 00ae386b74 test(sdk): Test RoomEvents::debug_string.
This patch moves `chunk_debug_string` from `rooms/mod.rs` to
`rooms/event.rs`. In addition, it restores (and rewrites) a test,
initially for `chunk_debug_string`, now for `RoomEvents::debug_string`
whichh is the public API.
2025-02-18 17:13:12 +01:00
Ivan Enderlin 9ad7ca8f11 test(sdk): Write a full integration test for the event cache lazy-loading. 2025-02-18 17:13:12 +01:00
Ivan Enderlin a43ce05200 task(sdk): LinkedChunkBuider::load_previous_chunk supports lazy_previous.
This patch installs `lazy_previous` in the `LinkedChunkBuilder`.
2025-02-18 17:13:12 +01:00
Ivan Enderlin 7d4dfb5c2d fix(sdk): Change semantics about duplicated events for a backwards pagination.
This patch changes the semantics regarding what to do in case of
duplicated events received during a backwards pagination.

Previously, the strategy for both sync and backwards pagination was
the same: With the new received events, when duplicated events are
detected, the old events are removed and the new ones are kept.

The strategy is reversed for backwards pagination: the old events are
always kept, and the duplicated events are removed from the new events.

The rest of the patch is about removing dead code because of this
change.
2025-02-18 17:13:12 +01:00
Ivan Enderlin d9e9006e61 feat(sdk): event_cache::Pagination::run_backwards paginates in the event cache.
This patch updates `Pagination::run_backwards_impl` to paginate in the
event cache. The flow is now as follow:

- backwards pagination tries to load and to insert a new previous chunk
   from the store
  - if the new chunk contains events, they are returned, pagination done
  - if the new chunk is a gap, the flow continues
- (as previously) check for a prev batch token (it exists in the newly
  inserted gap)
- (as previously) run a network request, replace the gap by the new
  events
- etc.

The new part is to load and to insert a new previous chunk. The rest
is stays the same. The code has been moved in code to keep the lock
releases happy and to clarify the code.
2025-02-18 17:13:12 +01:00
Ivan Enderlin bfec34db20 task(sdk) Add RoomEventCacheState::load_more_events_backwards.
This patch adds the `RoomEventCacheState::load_more_events_backwards`
method to load a new chunk and to insert it at the beginning of the
`LinkedChunk`.

It uses the new `EventCacheStore::load_previous_chunk` method, along
with the new `LinkedChunkBuilder::insert_new_first_chunk` method.
2025-02-18 17:13:12 +01:00
Ivan Enderlin 0aae72c161 feat(sdk): The EventCache loads only the last chunk when initialised.
This patch updates `RoomEventCacheState::new` to load a single chunks
of events instead of all events. It solves bugs where all events were
loaded, while removing the gaps in between, thus the `Timeline` wasn't
able to load the missing events to fill the gaps.
2025-02-18 17:13:12 +01:00
Ivan Enderlin fbd8b9c816 chore(sdk): Remove useless indentations. 2025-02-18 17:13:12 +01:00
Ivan Enderlin d6120a5985 doc(sdk): Events from BackPaginationOutcome are deduplicated…
This patch removes a `TODO` in `BackPaginationOutcome`.
Events it contains are deduplicated by the `EventCache` (see
`event_cache::Deduplicator`) when inserted inside `RoomEventCache`.
2025-02-18 17:13:12 +01:00
Ivan Enderlin a8f7939126 doc(ui): Fix a typo. 2025-02-18 17:13:12 +01:00
Ivan Enderlin 217429c3fe test(common): Add tests for LinkedChunkBuilder::insert_new_first_chunk.
This patch adds tests for the
`LinkedChunkBuilder::insert_new_first_chunk` method.
2025-02-18 17:13:12 +01:00
Ivan Enderlin 716958bb86 test(common): Add tests for LinkedChunkBuilder::from_last_chunk.
This patch adds tests for the `LinkedChunkBuilder::from_last_chunk`
method.
2025-02-18 17:13:12 +01:00
Ivan Enderlin 1319558eb6 test(base): Add test_linked_chunk_incremental_loading.
This patch adds a test for all event cache store
implementations that tests a linked chunk incremental
loading, i.e. the `EventCacheStore::load_last_chunk` and
`EventCacheStore::load_previous_chunk` methods.
2025-02-18 17:13:12 +01:00
Ivan Enderlin 096d478593 chore(base): Split LinkedChunkBuilder and create LinkedChunkBuilderTest.
Most of the methods on `LinkedChunkBuilder` are now only used
for testing. This patch splits `LinkedChunkBuilder` and creates
`LinkedChunkBuilderTest`. This new type is part of the public
API because it's used in other crates, but it's hidden from the
documentation.
2025-02-18 17:13:12 +01:00
Ivan Enderlin 8fcd5a91c4 test(sdk): Update tests.
This patch updates 3 tests that were expected to read all events: a
pagination is now necessary.
2025-02-18 17:13:12 +01:00
Ivan Enderlin 155042e46c task(common): LinkedChunkBuilder gains from_last_chunk and insert_new_first_chunk.
This patch adds two methods on `LinkedChunkBuilder`: `from_last_chunk`
to build a new `LinkedChunk` with a single provided chunk, and
`insert_new_first_chunk` to insert a new chunk at the beginning of the
provided `LinkedChunk`.
2025-02-18 17:13:12 +01:00
Ivan Enderlin e03d40e946 chore(sdk): Rename RoomEvents::with_initial_chunks.
This patch renames `RoomEvents::with_initial_chunks` to
`with_initial_linked_chunk`. It avoids a confusion between several
chunks, like `RawChunk`s, and `LinkedChunk` which represents several
`Chunk`s.
2025-02-18 17:13:12 +01:00
Benjamin Bouvier 2671769d9f fix(compilation): fix benchmark compilation on non-linux platforms 2025-02-18 13:58:23 +01:00
Jorge Martín 8d9d83f15f feat(ffi): add history_visibility_override param to the create room fn 2025-02-18 13:08:02 +01:00
Ivan Enderlin 6bc9dc5c6a test(sdk): Improve a test.
This patch adds an event to be sure it is removed later during the error
recovery.
2025-02-18 11:38:24 +01:00
Ivan Enderlin d6566484a1 doc(sqlite): Fix typos in comments. 2025-02-18 11:38:24 +01:00
Ivan Enderlin 0e4d8ec62f feat(sqlite): Detect cycles when loading last chunk of LinkedChunk.
This patch updates `SqliteEventCacheStore::load_last_chunk` to detect
cycle for the last chunk only.
2025-02-18 11:38:24 +01:00
Ivan Enderlin f9c6f897c8 feat(common): Detect cycles when loading last chunk of LinkedChunk.
This patch updates `RelationalLinkedChunk::load_last_chunk` to detect
cycle for the last chunk only.
2025-02-18 11:38:24 +01:00
Ivan Enderlin 7252a685a6 test(common): Test RelationalLinkedChunk::load_last_chunk and load_previous_chunk.
This patch adds tests for the `load_last_chunk` and
`load_previous_chunk` methods on `RelationalLinkedChunk`.
2025-02-18 11:38:24 +01:00
Ivan Enderlin bed4d5034e test(sqlite): Test SqliteEventCacheStore::load_last_chunk and load_previous_chunk.
This patch adds tests for the `SqliteEventCacheStore::load_last_chunk`
and `load_previous_chunk` methods.
2025-02-18 11:38:24 +01:00
Ivan Enderlin e2a2f32e82 task(sqlite): Implement load_last_chunk and last_previous_chunk.
This patch replaces `todo!()` by real implementations for the
`load_last_chunk` and `last_previous_chunk` methods.
2025-02-18 11:38:24 +01:00
Ivan Enderlin 334c66b0a0 task(base): Update EventCacheStore to add load_last_chunk and load_previous_chunk.
This patch update the `EventCacheStore` trait to:

1. rename `reload_linked_chunk` into `load_all_chunks` and put this
   method behind `#[cfg(test)]` so that it is removed from the public API,
2. add `load_last_chunk`,
3. add `load_previous_chunk`.

These 2 new methods are implemented inside the `MemoryStore` (with its
real implementation in the `RelationalLinkedChunk`), but `todo!()` are
added for the SQLite implementation.
2025-02-18 11:38:24 +01:00
Kévin Commaille ca392b08c9 chore: Add changelog for UserSession deserialization
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-18 11:32:44 +01:00
Kévin Commaille e9a34f6359 refactor(oidc): Remove support for deserializing previous UserSession format
The format changed 10 months ago and since it contains the tokens, it should have be reserialized already in that time.

Afaict EX clients do not serialize that type, the bindings have their own `Session` type for that.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-18 11:32:44 +01:00
dependabot[bot] 6411d27096 chore(deps): bump crate-ci/typos from 1.29.5 to 1.29.7
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.29.5 to 1.29.7.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.29.5...v1.29.7)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-17 16:47:15 +01:00
Ivan Enderlin 07f0017d30 feat(common): Introduce Chunk::lazy_previous in the LinkedChunk.
This patch introduces `Chunk::lazy_previous` which is a key feature to
support lazy-loading of a `LinkedChunk`. When a chunk is loaded, if it
is the first, it keeps in memory whether it has a previous chunk or not.
Thus, it is possible to insert new chunk in front of the `LinkedChunk`,
and `Update`s will correctly continue to link chunks between them (with
`NewItemsChunk` and `NewGapChunk`).

Example, imagine the following chunks: [0] <-> [1] <-> [2]. If [2] is
the only one being loaded. Then its previous chunk, [1], is loaded from
the store (because [2]'s previous is [1] in the store). Then [1] is
replaced by [3] and [4]. We get this: [4] <-> [3] <-> [1] <-> [2]. If
the `Update::New*Chunk` for [4] doesn't contain a `previous`, the store
is out of sync: in the store, [4] has no previous, but [0] still has [1]
for its `next`.

With this `lazy_previous`, the links are correctly computed.
2025-02-17 14:09:17 +01:00
Benjamin Bouvier 59f9d12da5 perf(timeline): make replacing replies much faster by indexing replies 2025-02-17 12:24:19 +01:00
Kévin Commaille 1c114978e4 refactor(oidc): Remove method to authorize arbitrary scope
Only the scopes necessary during login are specified in MSC2967 now.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-17 12:02:43 +01:00
Stefan Ceriu 629421214f change(crypto): have the RoomIdentityProvider return processing changes when identities transition to IdentityState::Verified too (#4670)
While implementing user verification on Element X I realized that the
`IdentityStatusChanges` stream does not notify us when an user becomes
`Verified`, which is a shame as it's perfect for powering app updates
after verifying users (i.e. all the decorations we show throughout the
app: room header, room details, room member list, room member profile
etc.)

Had a look at the existing code and, while that seems completely
intentional, there is no reason why we can't expand its purview.
2025-02-17 11:48:35 +02:00
Jorge Martín 97d772dd05 test: reverse the logic behind test_focused_timeline_reacts so it checks no reactions are received 2025-02-17 09:47:23 +01:00
Jorge Martín f28c64ba21 test: modify pinned event tests to make sure we don't add events from paginations or syncs to the timeline 2025-02-17 09:47:23 +01:00
Jorge Martín 20dd15e256 fix(timeline): Don't add events to the pinned events timeline when we receive them on paginations/syncs 2025-02-17 09:47:23 +01:00
Ivan Enderlin f33d10468d refactor(base): Remove the sliding_sync::http re-export.
This patch removes the `pub use ruma::api::client::sync::sync_events::v5
as http` re-export in `matrix_sdk_base::sliding_sync`.
2025-02-14 14:00:39 +01:00
Ivan Enderlin d3b3b4db10 chore(base): Remove the sliding_sync module.
This patch inlines `sliding_sync::http` inside `sliding_sync`. Then, the
`sliding_sync/mod.rs` file is renamed to `sliding_sync.rs`.
2025-02-14 14:00:39 +01:00
Ivan Enderlin 38e28643f1 fix: Remove support for MSC3575. 2025-02-14 14:00:39 +01:00
Valere 9de6d28270 logging(crypto): Add more logs when identity or devices change 2025-02-14 10:43:42 +01:00
Benjamin Bouvier 9f47201bab bench: add a profiling profile that doesn't enable LTO for quick rebuilds 2025-02-14 09:02:38 +01:00
Benjamin Bouvier 0b7140c123 bench: add a benchmark to measure how long it takes to fill a timeline with lots of initial items 2025-02-14 09:02:38 +01:00
Damir Jelić 28a4918ff6 chore(test): Increase the timeout for the sync service offline mode test 2025-02-13 17:00:42 +01:00
Benjamin Bouvier 534cd599f4 doc: remove internal links to macro
Those worked fine until now, but it seems they started to fail after
including the `testing` feature in the benchmark repository. Oh well.
2025-02-13 16:02:10 +01:00
Benjamin Bouvier 910a5ce90a ci: add a task to compile benchmarks
This adds a task to compile the benchmarks in CI, without running them,
and with the lowest level of optimization that's available (the `dev`
profile).
2025-02-13 16:02:10 +01:00
Benjamin Bouvier dadd01a4ea chore: fix benchmarks and use the MatrixMockServer in there too 2025-02-13 16:02:10 +01:00
Damir Jelić c4a9059814 Merge branch 'oidc_e2e' 2025-02-13 15:59:20 +01:00
Damir Jelić 51a1cd3c67 Merge pull request #4604 from zecakeh/qr-login-oauth2
refactor(auth-qrcode): Use oauth2 crate instead of openidconnect
2025-02-13 15:42:51 +01:00
Kévin Commaille c6d2ab4637 chore: Fix changelog location
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-13 15:26:11 +01:00
Kévin Commaille c6c7307d6e Merge branch 'main' into qr-login-oauth2 2025-02-13 15:20:10 +01:00
Kévin Commaille 9c9944aa0c chore: Update changelog for TLS 1.2
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-13 14:21:25 +01:00
Kévin Commaille b311197d41 feat(sdk): Only allow TLS 1.2 or newer
As recommended by BCP 195.

It shouldn't be a problem with rustls that only supports TLS 1.2 and 1.3, but with native-tls it depends on the implementation.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-13 14:21:25 +01:00
Benjamin Bouvier 1068d88c3e fix(event cache store): shortcut when there's no duplicate events to check at all
Otherwise this causes a panic when repeating the events variable, when
generating the SQL query below.
2025-02-13 13:43:49 +01:00
Damir Jelić 861078a95e feat: Add a memoized variant of Oidc::fetch_account_management_url 2025-02-13 12:32:42 +01:00
Damir Jelić aa9aef44f7 refactor: Rename Oidc::account_management_url to fetch_account_management_url 2025-02-13 12:32:42 +01:00
Damir Jelić f2ad11a56a refactor(client): Create a common struct for client caches 2025-02-13 12:32:42 +01:00
Damir Jelić 12c327292f feat(common): Add a simple TTL cache implementation 2025-02-13 12:32:42 +01:00
Kévin Commaille 31e78c2a1b refactor(oidc): Only support public clients (#4634)
This should be the most common case, and is already the only case
supported by the higher level APIs like `url_for_oidc` and
`login_with_qr_code`. It simplifies the API because we can call
`restore_registered_client` directly from `register_client`, which was a
TODO.

- [x] Public API changes documented in changelogs (optional)

---------

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-13 11:40:17 +01:00
Benjamin Bouvier 8a64922130 test(event cache): address review comments and add a test for storage deduplication 2025-02-13 10:53:20 +01:00
Benjamin Bouvier 2ae142f257 refactor(event cache): get rid of a few generics
The YAGNI crew strikes again.
2025-02-13 10:53:20 +01:00
Benjamin Bouvier faa0e6e554 feat(event cache): allow using the bloom filter OR the store to deduplicate events 2025-02-13 10:53:20 +01:00
Benjamin Bouvier b95cf79a6d refactor(event cache): move the gist of deduplication into BloomFilterDeduplicator 2025-02-13 10:53:20 +01:00
Benjamin Bouvier 28cd8beb77 refactor(event cache): rename Deduplicator to BloomFilterDeduplicator 2025-02-13 10:53:20 +01:00
Ivan Enderlin 1918bd5f6b chore(base): Rename variables. 2025-02-12 16:50:14 +01:00
Ivan Enderlin d45addee10 feat(base): Add EventCacheStore::filter_duplicated_events.
This patch adds and implements the
`EventCacheStore::filter_duplicated_events` method. It is implemented on
the `MemoryStore` and the `SqliteEventCacheStore`.

This method remove the unique events and reutrn the duplicated events.
2025-02-12 16:50:14 +01:00
Ivan Enderlin ed16e91aed fix: RoomEventCache::subscribe is now infallible.
This patch updates `RoomEventCache::subscribe` to be infallible. This
method wasn't able to return something else than an `Ok`. The return
type has been updated from `Result<T>` to `T`.
2025-02-12 16:35:03 +01:00
Ivan Enderlin 714caae545 chore(sqlite): Remove a useless indentation.
This patch removes a useless indentation.
2025-02-12 16:13:40 +01:00
Andy Balaam 25bb607b27 feat(crypto): Allow fetching encryption info using session ID 2025-02-12 14:00:14 +00:00
Andy Balaam c9a6ae9549 fix(tests): Prevent test flake by using different names for the test stores 2025-02-12 13:45:20 +00:00
Benjamin Bouvier 58099fd6b5 test(timeline): add new tests for fetching replies that are UTD or sticker or polls 2025-02-12 14:28:17 +01:00
Benjamin Bouvier c5856a33f0 test(timeline): rewrite and comment fetch_details() test 2025-02-12 14:28:17 +01:00
Benjamin Bouvier a5f115f21f test(timeline): use the MatrixMockServer in integration/timeline/replies 2025-02-12 14:28:17 +01:00
Benjamin Bouvier ddd84e231b feat(timeline): support more timeline item content kinds in replied-to details 2025-02-12 14:28:17 +01:00
Benjamin Bouvier 51e9df87f5 chore(timeline): add some logs when fetching a reply details 2025-02-12 14:28:17 +01:00
Benjamin Bouvier aec4d37a2e refactor(event cache): fold all_deduplicated computation into collect_valid_and_duplicated_events 2025-02-12 14:10:14 +01:00
Benjamin Bouvier ceafc2155f refactor(event cache): move the Deduplicator instance to RoomEventCacheState
The `RoomEvents` doesn't hold the `Deduplicator` instance now, it's the
role of the `RoomEventCacheState`. This slightly simplifies the code, in
a few cases.
2025-02-12 14:10:14 +01:00
Benjamin Bouvier 4a37d6ebe2 refactor(event cache): move remove_events_and_update_insert_position to the public impl block
Only code motion.
2025-02-12 14:10:14 +01:00
Benjamin Bouvier 10095f8627 refactor(event cache): rename RoomEvents::remove_events to remove_events_by_id
And move it to the public implementation.

Only code motion and renaming, no changes in functionality.
2025-02-12 14:10:14 +01:00
Benjamin Bouvier 84bb1ab595 refactor(event cache): extract deduplication outside the RoomEvents events methods 2025-02-12 14:10:14 +01:00
Ivan Enderlin fce7999890 chore(common) Split the test_insert_items_at tests.
This patch splits the `test_insert_items_at` test into 5 tests.
2025-02-11 17:32:20 +01:00
Ivan Enderlin 10b72ef4b4 test(common): Update test_replace_item.
This patch updates the `test_replace_item` test to ensure
`Update::ReplaceItem` is correct.
2025-02-11 17:32:20 +01:00
Ivan Enderlin bfbb354c39 chore(common): Split a test into 3 tests.
This patch splits the `test_replace_at` test into 3 smaller tests.
2025-02-11 17:32:20 +01:00
Ivan Enderlin 9db137af44 refactor(common): LinkedChunk can start by a gap.
This patch removes the invariant stating that a `LinkedChunk` must start
by a chunk of type items. This has never been really useful but it's now
annoying to have this (with iterative loading of a `LinkedChunk` via the
`EventCache`, it's now possible to get a gap as the first chunk). Let's
remove this invariant.
2025-02-11 17:32:20 +01:00
Stefan Ceriu 2999d10fb9 fix(ffi): check that our own device is cross-signed before responding to incoming user verification requests 2025-02-11 16:39:26 +02:00
Kévin Commaille 654885a925 fix(ui): Demote aggregation target not found log to TRACE level
We encountered this warning a lot in the logs after upgrading the SDK today.

My understanding is that this path is expected if the event is not yet in the timeline, so it's nothing to warn about.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-11 15:37:35 +01:00
Damir Jelić 8042abe5f5 fix(recovery): Delete the known secrets from 4s when disabling recovery 2025-02-11 15:28:33 +01:00
Kévin Commaille 65ee18a52d feat(sqlite): Run VACUUM operation after removing a room
A room can be associated to a lot of data, depending on the number of members in the room.
So freeing space on the filesystem should be worth it in some cases.

An (extreme) example: I have a test account that is in ~60 rooms, a few of those big public rooms, including Matrix HQ. The size of the matrix-sdk-state.sqlite3 file is 542 MB. Using this PR and leaving, then forgetting Matrix HQ brings the DB down to 255 MB.
2025-02-11 14:13:25 +00:00
Benjamin Bouvier 69588d5266 test(timeline): use a builder pattern to create a TestTimeline 2025-02-11 11:26:47 +01:00
Benjamin Bouvier 7b77b19bc0 refactor(timeline): get rid of the optional in is_room_encrypted field
The `EventTimelineItem` would get from a bool to an `Option<bool>`. If
the metadata's `is_room_encrypted` was set to `None`, then it would use
`false` as the value, before wrapping it again into an `Option`.

Since the only reader of `EventTimelineItem::is_room_encrypted()`
doesn't really care about the difference between `Some(false)` and
`None`, in `EventTimelineItem::get_shield()`, we can use a plain `bool`
instead of an `Option`, and not distinguish `Some(false)` from `None`.
At worst, it means that sometimes we don't know the room encryption
status yet, and consider the room unencrypted; as soon as we'll get an
update about encryption state, all the items will be marked as encrypted
anyways, if needs be.
2025-02-11 11:26:47 +01:00
Benjamin Bouvier 357b36b287 refactor(timeline): simplify transition from unencrypted -> encrypted 2025-02-11 11:26:47 +01:00
Benjamin Bouvier a5f0473e1b test(timeline): use the MatrixMockServer and EventFactory in integration/timeline/mod.rs 2025-02-11 11:26:47 +01:00
Stefan Ceriu 8d74d46d80 chore(ffi): expose UserIdentity was_previously_verified and has_verification_violation methods 2025-02-10 18:28:55 +02:00
Benjamin Bouvier 9f2c572709 fix(timeline): maintain aggregations when an event is deduplicated (#4576)
## Some context

An aggregation is an event that relates to another event: for instance,
a
reaction, a poll response, and so on and so forth.
                               
## Some requirements
                                              
Because of the sync mechanisms and federation, it can happen that a
related
event is received *before* receiving the event it relates to. Those
events
must be accounted for, stashed somewhere, and reapplied later, if/when
the
related-to event shows up.
In addition to that, a room's event cache can also decide to move events
around, in its own internal representation (likely because it ran into
some
duplicate events, or it managed to decrypt a previously UTD event).
When that happens, a timeline opened on the given room
will see a removal then re-insertion of the given event. If that event
was
the target of aggregations, then those aggregations must be re-applied
when
the given event is reinserted.
                                                                       
## Some solution
      
To satisfy both requirements, the [`Aggregations`] "manager" object
provided
by this PR will take care of memoizing aggregations, **for the entire
lifetime of the timeline** (or until it's clear'd by some
caller). Aggregations are saved in memory, and have the same lifetime as
that of a timeline. This makes it possible to apply pending aggregations
to cater for the first use case, and to never lose any aggregations in
the
second use case.

## Some points for the reviewer

- I think the most controversial point is that all aggregations are
memoized for the entire lifetime of the timeline. Would that become an
issue, we can get back to some incremental scheme, in the future:
instead of memoizing aggregations for the entire lifetime of the
timeline, we'd attach them to a single timeline item. When that item is
removed, we'd put the aggregations back into a "pending" stash of
aggregations. If the item is reinserted later, we could peek at the
pending stash of aggregations, remove any that's in there, and reapply
them to the reinserted event. This is what the [first version of this
patch](https://github.com/matrix-org/matrix-rust-sdk/pull/4576/commits/ec64b9e0bcc9a2d05dd8c910a8710b72466ef51c)
did, in a much more adhoc way, for reactions only; based on the current
PR, we could do the same in a simpler manner
- while the PR has small commits, they don't quite make sense to review
individually, I'm afraid, as I was trying to find a way to make a
general system that would work not only for reactions, poll responses
and ends. As a matter of fact, the first commits may have introduced
code that is changed in subsequent commits, making the review a bit
hazardous. Happy to have a live reviewing party over Element Call, if
that helps, considering the size of the patch.
- future work may include using the aggregations manager for edits too,
leading to more code removal.
2025-02-10 15:38:25 +00:00
Jorge Martín 4b6dd5c857 fix(ffi): Client::resolve_room_alias was mapping the wrong error type
This is used to check if the alias is resolved or not.
2025-02-10 09:39:00 +01:00
Stefan Ceriu 83dd11ea7d chore(ffi): expose the whole sender profile when receiving a verification request 2025-02-07 11:47:59 +02:00
Jorge Martín 6c2a88cdc0 test: Fix flaky test_publishing_room_alias 2025-02-07 09:00:07 +01:00
Benjamin Bouvier e00d57fee2 test: wait for the redact endpoint to be hit in test_abort_before_being_sent
The test ends up with checking that the redact endpoint has been hit
once. It's actually the send queue doing the redaction as a dependent
send request, and it doesn't provide any notification mechanism in this
case, so we can't really know when it's done doing it.

One solution would be to not check the number of calls to the redact/
endpoint, but that means checking for fewer things. Instead, I made it
so that when hit, the endpoint will signal it to the main task using a
oneshot channel; then the main task waits with a long timeout for the
receiving end to get the notification it's been sent, which should be
sufficient.
2025-02-06 17:20:04 +01:00
Stefan Ceriu ce44c6e4e7 chore(base): don't show timeline verification requests as last messages 2025-02-06 14:01:05 +02:00
Stefan Ceriu f9ff4fff50 feat(ffi): add support for starting and responding to user verification requests 2025-02-06 14:01:05 +02:00
Benjamin Bouvier 2291a61379 ci: add a new feature set to test experimental-oidc too
This would help find test failures specific to experimental-oidc, as
well as doctests failing (which would have prevented the failures fixed
in https://github.com/matrix-org/matrix-rust-sdk/pull/4614 to happen in
the first place).
2025-02-06 11:21:31 +01:00
Stefan Ceriu d8f37509af chore(ffi): reduce the verbosity of the store locks and ambiguity map 2025-02-05 17:45:57 +02:00
Jorge Martín dddbcfbabb fix(ffi): Align RoomList::preview_room with Client::get_room_preview_* functions.
This removes the restriction applied in the FFI layer so only invited and knocked rooms can return room previews.
2025-02-05 14:06:50 +01:00
Ivan Enderlin ed8c1d543a doc(sdk): Add #4627 in the CHANGELOG.md. 2025-02-05 13:29:08 +01:00
Ivan Enderlin 3e02d90a27 chore(sdk): Remove RoomEventCacheUpdate::Clear.
This patch removes the `Clear` variant of the `RoomEventCacheUpdate`
enum. This one is not needed anymore since we have
`UpdateTimelineEvents` which contains updates as `Vec<VectorDiff<_>>`.
`VectorDiff` _has_ a `Clear` variant. It resulted in a double clear
every time.

This patch updates `RoomEventCacheInner::reset` and
`RoomEventCacheInner::with_events_mut` to annotate them with a
`#[must_use]`. Since they return the updates as `VectorDiff`s,
they **must** be broadcasted/propagated somewhere, likely with
`RoomEventCacheUpdate`. This mechanism ensures to not miss updates.
2025-02-05 13:29:08 +01:00
Ivan Enderlin 954b16ad39 Merge pull request #4603 from zecakeh/media-cache-auto-cleanup
feat(base): Add automatic media cache cleanups to MediaService
2025-02-05 13:24:20 +01:00
Kévin Commaille ed18c5113f fix: Fix changelogs for new media service feature after new release
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-05 00:28:56 +01:00
Kévin Commaille 0f4b3aa187 Merge branch 'main' into media-cache-auto-cleanup 2025-02-05 00:12:18 +01:00
Ivan Enderlin 8a7658745d chore: Fallback to jplatte/eyeball instead of Hywan/eyeball's fork. 2025-02-04 19:43:38 +01:00
Ivan Enderlin 2ea39877cc fix: Add github.com/Hywan/eyeball in the allow-git list. 2025-02-04 19:43:38 +01:00
Ivan Enderlin 4212691cf0 fix(ui): Don't use 0 as the initial value for Skip.
This patch fixes an issue where 0 was used as the initial value for
the `Skip` higher-order stream in the `TimelineSubscriber`. This is
wrong, as the `SkipCount` value may have been modified before the
`TimelineSubscriber` is created.

This patch provides a test to reproduce the problem.
2025-02-04 19:43:38 +01:00
Ivan Enderlin d66fe79579 task(ui): Create TimelineSubscriber.
This patch gathers the logic of the `Timeline::subscribe` into a single
type: `TimelineSubscriber`.

The `TimelineController::subscribe_batched_and_limited` method is
renamed `subscribe` to match `Timeline::subscribe`. Things are simpler
to apprehend.

The `TimelineSubscriber` type configures the subscriber/stream in a
single place. It takes an `&ObservableItems` and a `&SkipCount`, and
configures everything. It also provides a single place to document the
behaviour of the subscriber, with the `Skip` higher-order stream.
2025-02-04 19:43:38 +01:00
Ivan Enderlin 88caf11842 chore(ui): Rename Timeline::subscribe to subscribe_raw.
This patch renames the (test only) `Timeline::subscribe` method to
`Timeline::subscribe_raw`.
2025-02-04 19:43:38 +01:00
Ivan Enderlin 5320d952e5 test(ui): Test the Timeline lazy backwards pagination. 2025-02-04 19:43:38 +01:00
Ivan Enderlin 7eae832b8c test: set_timeline_prev_batch takes anything that implements Into<String>. 2025-02-04 19:43:38 +01:00
Ivan Enderlin 1d18ab03d7 test(ui): Improve the assert_timeline_stream macro. 2025-02-04 19:43:38 +01:00
Ivan Enderlin 337bc2c097 doc(ui): Fix a typo. 2025-02-04 19:43:38 +01:00
Ivan Enderlin 7e59ae99d0 featui): Timeline::subscribe() supports lazy backwards pagination.
This patch updates the pagination mechanism of the `Timeline` to support
lazy backwards pagination.

`Timeline::paginate_backwards` already does different things whether the
timeline focus is live or focused. When it's live, the method will first
try to paginate backwards lazily, by adjusting the `count` value of the
`Skip` stream used by the `Timeline::subscribe` method. If there is not
enough items to provide, the greedy pagination will run.
2025-02-04 19:43:38 +01:00
Ivan Enderlin 51feda1042 task(ui): TimelineStateTransaction::commit adjusts the Skip's count.
This patch updates `TimelineStateTransaction::commit` to adjust the
count value of the `Skip` higher-order stream.

This patch also creates `TimelineStateTransaction::new` to simplify the
creation of a state transaction.
2025-02-04 19:43:38 +01:00
Ivan Enderlin 0a520e4f9f task(ui): Apply the Skip higher-order stream for the Timeline.
This patch applies the `Skip` higher-order stream on the `Timeline`
subscriber. The method `TimelineController::subscribe_batched` is
renamed `subscribe_batched_and_limited` at the same time.

The `Skip` stream uses the `TimelineMetadata::subscriber_skip_count`
observable value as the `count_stream`. The initial count value is 0.

No test needs to be changed so far.
2025-02-04 19:43:38 +01:00
Ivan Enderlin e07212d356 task(ui): Add TimelineMetadata::subscriber_skip_count.
This patch adds the `subscriber_skip_count` field to `TimelineMetadata`.
It's going to be used to define the `count` value of the `Skip`
higher-order stream that is going to be applied to the `Timeline`
subscriber.
2025-02-04 19:43:38 +01:00
Ivan Enderlin 1d52073b45 task(ui): Add the SkipCount type to help computing the count for Skip.
This patch adds functions like `compute_next`,
`compute_next_when_paginating_backwards` and
`compute_next_when_paginating_forwards`, which are necessary to
correctly compute the `count` value for the `Skip` higher-order stream.

This patch adds the associated test suite.
2025-02-04 19:43:38 +01:00
Ivan Enderlin d85d6cfbca refactor(ui): Rename TimelineStream to TimelineWithDropHandle.
This patch renames `TimelineStream` to `TimelineWithDropHandle`, as the
former name was too vague and was not clarify what the type was doing.
The new name makes it clear that it attaches a `TimelineDropHandle` to a
subscriber (since it is part of the `subscriber` module!).
2025-02-04 19:43:38 +01:00
Ivan Enderlin 892cb9116c refactor(ui): Move TimelineStream into the new subscriber module.
This patch moves the `TimelineStream` type into the new `subscriber`
module. The idea is to add more code related to subscribers in the next
patches.
2025-02-04 19:43:38 +01:00
Ivan Enderlin 5e9d291ca3 chore(ui): Clone items only once when subscribing.
This patch updates `TimelineController::subscribe` to use
`VectorSubscriber::into_values_and_batched_stream`. It returns
the cloned items along with the stream. It saves the need to call
`ObservableItems::clone_items`, thus saving the clone of all items.

tl;dr: `clone_items()` clones… items… and `subscribe()` also clones the
items. There is 2 clones. With `into_values_and_batched_stream()`, there
is 1 clone.
2025-02-04 19:43:38 +01:00
Benjamin Bouvier b74d64a456 test(timeline): use the MatrixMockServer in integration/timeline/edit 2025-02-04 17:20:02 +01:00
Benjamin Bouvier cd8f5cf5d4 test(timeline): use the MatrixMockServer in integration/timeline/echo 2025-02-04 17:20:02 +01:00
Benjamin Bouvier 1dc20aa9aa test(timeline): use the MatrixMockServer in integration/timeline/reactions 2025-02-04 17:20:02 +01:00
Ivan Enderlin bdab9951af doc(changelog): Add entries for the performance improvement patches.
This patch adds #4601, #4608, #4612 and #4616 in their respective
`CHANGELOG.md`s.
2025-02-04 16:59:13 +01:00
Damir Jelić 4c46e42201 chore: Release matrix-sdk version 0.10.0 2025-02-04 16:32:55 +01:00
Damir Jelić 0d4bc65e28 chore: Enable releases for the test crates 2025-02-04 16:32:55 +01:00
Jorge Martín 5e1bae02fe feat(ffi): Add RoomPreview::forget action in the FFI layer 2025-02-04 16:26:15 +01:00
Ivan Enderlin 77a67de7df fix(ui): Fix performance of ReadReceiptTimelineUpdate::apply.
This patch improves the performance of
`ReadReceiptTimelineUpdate::apply`, which does 2 things: it calls
`remove_old_receipt` and `add_new_receipt`. Both of them need an
timeline item position. Until this patch, `rfind_event_by_id` was used
and was the bottleneck. The improvement is twofold as is as follows.

First off, when collecting data to create `ReadReceiptTimelineUpdate`,
the timeline item position can be known ahead of time by using
`EventMeta::timeline_item_index`. This data is not always available, for
example if the timeline item isn't created yet. But let's try to collect
these data if there are some.

Second, inside `ReadReceiptTimelineUpdate::remove_old_receipt`, we use
the timeline item position collected from `EventMeta` if it exists.
Otherwise, let's fallback to a similar `rfind_event_by_id` pattern,
without using intermediate types. It's more straightforward here: we
don't need an `EventTimelineItemWithId`, we only need the position.
Once the position is known, it is stored in `Self` (!), this is the
biggest improvement here. Le't see why.

Finally, inside `ReadReceiptTimelineUpdate::add_new_receipt`, we use
the timeline item position collected from `EventMeta` if it exists,
similarly to what `remove_old_receipt` does. Otherwise, let's fallback
to an iterator to find the position. However, instead of iterating over
**all** items, we can skip the first ones, up to the position of the
timeline item holding the old receipt, so up to the position found by
`remove_old_receipt`.

I'm testing this patch with the `test_lazy_back_pagination` test in
https://github.com/matrix-org/matrix-rust-sdk/pull/4594. With 10_000
events in the sync, the `ReadReceipts::maybe_update_read_receipt` method
was taking 52% of the whole execution time. With this patch, it takes
8.1%.
2025-02-04 16:02:29 +01:00
JoFrost f27eb4d1c8 fix[oidc]: fix docstring in oidc module 2025-02-04 15:33:28 +01:00
Jorge Martín 05814c5559 refactor(ffi): Map client API errors to ClientError::MatrixApi, containing the error kind, their error code and the associated message 2025-02-04 12:25:51 +01:00
Kévin Commaille d5d9898fb4 feat: Upgrade Ruma to 0.12.1
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-04 12:00:40 +01:00
Kévin Commaille f641a639cd Merge branch 'main' into media-cache-auto-cleanup 2025-02-04 11:56:32 +01:00
Ivan Enderlin 3f71d9a379 fix(sdk): Improve performance of RoomEvents::maybe_apply_new_redaction.
This patch improves the performance of
`RoomEvents::maybe_apply_new_redaction`. This method deserialises
all the events it receives, entirely. If the event is not an
`m.room.redaction`, then the method returns early. Most of the time,
the event is deserialised for nothing because most events aren't of kind
`m.room.redaction`!

This patch first uses `Raw::get_field("type")` to detect the type of
the event. If it's a `m.room.redaction`, then the event is entirely
deserialized, otherwise the method returns.

When running the `test_lazy_back_pagination` from
https://github.com/matrix-org/matrix-rust-sdk/pull/4594 with 10'000
events, prior to this patch, this method takes 11% of the execution
time. With this patch, this method takes 2.5%.
2025-02-04 09:49:58 +01:00
Jorge Martín b077f45e78 test(room_preview): Add tests for where get_room_preview gets its data from for each room state 2025-02-04 09:33:31 +01:00
Jorge Martín 648d527f2f fix(room_preview): Return room preview info based on local data for banned rooms too
Any remote endpoint would just return a `403` status code so we have no other choice than trusting the local room info we already have.
2025-02-04 09:33:31 +01:00
Jorge Martín 8513547e92 feat(ffi): Add FFI bindings for Room::forget.
Also make sure rooms the user has been banned from can also be forgotten, not only left ones.
2025-02-03 19:48:27 +01:00
dependabot[bot] d18669e8d9 chore(deps): bump crate-ci/typos from 1.29.4 to 1.29.5
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.29.4 to 1.29.5.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.29.4...v1.29.5)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 16:52:27 +01:00
Benjamin Bouvier a0426251a3 test(timeline): test that editing a replied-to doesn't lose the latest edit JSON 2025-02-03 16:52:12 +01:00
Benjamin Bouvier 2739c5bf27 test(timeline): test that adding a response or ending a poll doesn't clear the latest edit JSON 2025-02-03 16:52:12 +01:00
Benjamin Bouvier 381f4d419f fix(timeline): don't clear the latest_edit_json under certain conditions 2025-02-03 16:52:12 +01:00
Kévin Commaille 049021fe27 refactor(auth-qrcode): Inline declaration of oauth2 client type
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-03 11:30:57 +01:00
Kévin Commaille 4db32b15ba chore(auth-qrcode): Update copyright date
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-03 11:27:58 +01:00
Ivan Enderlin 9ab5547065 fix(ui): Fix performance of AllRemoteEvents::(in|de)crement_all_timeline_item_index_after.
This patch fixes the performance of
`AllRemoteEvents::increment_all_timeline_item_index_after` and
`decrment_all_timeline_item_index_after`.

It appears that the code was previously iterating over all items. This
is a waste of time. This patch updates the code to iterate over all
items in reverse order:

- if `new_timeline_item_index` is 0, we need to shift all items anyways,
  so all items must be traversed, the iterator direction doesn't matter,
- otherwise, it's unlikely we want to traverse all items: the item has
  been either inserted or pushed back, so there is no need to traverse
  the first items; we can also break the iteration as soon as all
  timeline item index after `new_timeline_item_index` has been updated.

I'm testing this patch with the `test_lazy_back_pagination` test in
https://github.com/matrix-org/matrix-rust-sdk/pull/4594. With 10_000
events in the sync, the `ObservableItems::push_back` method (that uses
`AllRemoteEvents::increment_all_timeline_item_index_after`) was taking
7% of the whole execution time. With this patch, it takes 0.7%.
2025-02-03 11:25:48 +01:00
Kévin Commaille df3cb002a5 chore: Add changelog entries
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-03 11:22:23 +01:00
Kévin Commaille 6ebd4295b9 feat(sqlite): Limit size of WAL file
The WAL file can grow depending on the transactions that are run. A
critical case is VACUUM which basically writes the content of the DB
file to the WAL file before writing it back to the DB file.

SQLite doesn't try to reduce the size of the file after that unless we
set an explicit limit,
so we could end up taking twice the size of the database on the
filesystem.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-03 11:22:23 +01:00
Kévin Commaille c5104d68fd feat(sqlite): Run PRAGMA optimize regularly
As recommended by the SQLite docs.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-03 11:22:23 +01:00
Kévin Commaille 0064839283 fix(sqlite): Vaccum the SqliteStateStore
It should have been done in the migration of version 7, to reduce the
size of the database on the filesystem after the media cache was moved
to the SqliteEventCacheStore. Better late than never.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-03 11:22:23 +01:00
Kévin Commaille 2727d72916 fix(timeline): Do not filter out own receipts in load_read_receipts_for_event
Fixes #4517.

It turns out that the bugs found in that test were due to 2 causes:

- First commit: `TestRoomDataProvider` didn't use `initial_user_receipts` but returned hardcoded values.
- Second commit: Our own read receipts were ignored in `TimelineStateTransaction::load_read_receipts_for_event`, although we need to process all read receipts via `ReadReceipts::maybe_update_read_receipt` because it knows how to filter out our own read receipts were needed.
2025-02-03 10:15:25 +00:00
Ivan Enderlin 33a2cc3031 chore(cargo): Bump the minimum stable rust version (MSRV). 2025-02-03 10:27:45 +01:00
Ivan Enderlin 38097f90b2 fix(ui): Fix performance of TimelineEventHandler::deduplicate_local_timeline_item.
This patch drastically improves the performance of
`TimelineEventHandler::deduplicate_local_timeline_item`.

Before this patch, `rfind_event_item` was used to iterate over all
timeline items: for each item in reverse order, if it was an event
timeline item, and if it was a local event timeline item, and if it was
matching the event ID or transaction ID, then a duplicate was found.

The problem is the following: all items are traversed.

However, local event timeline items are always at the back of the items.
Even virtual timeline items are before local event timeline items. Thus,
it is not necessary to traverse all items. It is possible to stop the
iteration as soon as (i) a non event timeline item is met, or (ii) a non
local event timeline item is met.

This patch updates
`TimelineEventHandler::deduplicate_local_timeline_item` to replace to
use of `rfind_event_item` by a custom iterator that stops as soon as a
non event timeline item, or a non local event timeline item, is met, or
—of course— when a local event timeline item is a duplicate.

To do so, [`Iterator::try_fold`] is probably the best companion.
[`Iterator::try_find`] would have been nice, but it is available on
nightlies, not on stable versions of Rust. However, many methods in
`Iterator` are using `try_fold`, like `find` or any other methods that
need to do a “short-circuit”. Anyway, `try_fold` works pretty nice here,
and does exactly what we need.

Our use of `try_fold` is to return a `ControlFlow<Option<(usize,
TimelineItem)>, ()>`. After `try_fold`, we call
`ControlFlow::break_value`, which returns an `Option`. Hence the need
to call `Option::flatten` at the end to get a single `Option` instead of
having an `Option<Option<(usize, TimelineItem)>>`.

I'm testing this patch with the `test_lazy_back_pagination` test in
https://github.com/matrix-org/matrix-rust-sdk/pull/4594. With 10_000
events in the sync, the test was taking 13s to run on my machine. With
this patch, it takes 10s to run. It's a 23% improvement. This
`deduplicate_local_timeline_item` method was taking a large part of the
computation according to the profiler. With this patch, this method is
barely visible in the profiler it is so small.

[`Iterator::try_fold`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.try_fold
[`Iterator::try_find`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.try_find
2025-02-03 10:27:45 +01:00
Ivan Enderlin 47d08683a2 fix(security): Update OpenSSL.
See this note https://rustsec.org/advisories/RUSTSEC-2025-0004.

This patch updates OpenSSL to 0.10.70.
2025-02-03 09:42:58 +01:00
Kévin Commaille 619346acad chore: Add changelog entry for oauth2
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-02 19:48:50 +01:00
Kévin Commaille 525f9866a4 refactor(auth-qrcode): Rename everything OIDC to OAuth 2.0
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-02 19:41:04 +01:00
Kévin Commaille d7dc1c9b5b refactor(auth-qrcode): Use oauth2 crate instead of openidconnect
The MSCs are now only based on OAuth 2.0, which is simpler than OpenID Connect.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-02 18:42:54 +01:00
Kévin Commaille 7da3aaaa8a chore: Add new PR links to MediaRetentionPolicy changelog entries
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-02 12:28:43 +01:00
Kévin Commaille 5aaa6bf187 feat(base): Add automatic media cache cleanups to MediaService
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-02 12:20:13 +01:00
Kévin Commaille af9a5edd59 feat(executor): Expose JoinHandle::is_finished method on wasm32
For parity with tokio's JoinHandle.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-02 12:10:31 +01:00
Kévin Commaille 6e764644b3 feat(base): Require that EventCacheStoreMedia implementations implement Clone
We want to be able to send the store to a new task, so the easiest way is to be able to clone it.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-02 12:09:18 +01:00
Kévin Commaille 8dc2ec9dc4 feat(base): Allow to clone MediaService
We want to be able to send it to a new task, so the easiest way is to be able to clone it.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-02 12:03:12 +01:00
Kévin Commaille 4e1ae3d5e9 feat(base): Store last media cleanup time with EventCacheStoreMedia
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-02 11:52:42 +01:00
Kévin Commaille 582b3a91d6 refactor(sqlite): Add methods to get and set values in the kv table by (de)serializing them
Since it's a common occurrence, it will reduce duplication.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-02 11:47:43 +01:00
Kévin Commaille f7467ff57a feat(base): Add setting for frequency of automatic media cache cleanups
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-01 17:51:47 +01:00
Damir Jelić 57919f5480 chore: Bump most of our deps 2025-01-31 17:14:37 +01:00
Damir Jelić b8949cfe26 chore: Bump vodozemac 2025-01-31 17:14:37 +01:00
Damir Jelić 8d27b0c811 test: Simplify some tests using the assert_next_with_timeout macro 2025-01-31 14:15:18 +01:00
Damir Jelić 3707d2fb81 test: Make the timeout parameter in the assert_next_with_timeout macro optional 2025-01-31 14:15:18 +01:00
Damir Jelić eaaa5e17a0 chore: Fix a doc example in the MatrixMockServer 2025-01-31 14:15:18 +01:00
Ivan Enderlin e3958b754c chore(crypto-ffi): Done is a unit type, no need for { .. }. 2025-01-31 14:07:43 +01:00
Ivan Enderlin 78d9e1292f chore(sdk): Do not iterate over the entire iterator when we can reach back.
This patch uses `next_back()` instead of `last()`, which is equivalent
but `last()` requires to iterate over the entire iterator, while
`next_back()` is a single operation.
2025-01-31 14:07:43 +01:00
Ivan Enderlin d594b4dad7 chore(sdk): Remove a useless type conversion.
This patch removes a useless type conversion. The `Room::event()` method
returns a `TimelineEvent`, so calling `Into::into` is useless: we map
`TimelineEvent` to `TimelineEvent`.
2025-01-31 14:07:43 +01:00
Ivan Enderlin 3f40ad83a5 chore(sdk): Remove a useless type conversion.
This patch removes a useless type conversion. The iterator produces
`TimelineEvent`, so mapping to `TimelineEvent::from` is useless: we map
`TimelineEvent` to `TimelineEvent`.
2025-01-31 14:07:43 +01:00
Ivan Enderlin 5049d1a3b6 chore(sqlite): Use repeat_n(…, n) instead of repeat(…).take(n).
Thanks Clippy!
2025-01-31 14:07:43 +01:00
Damir Jelić 29862fc9bd refactor: Add the assert_next_eq_with_timeout test helper
This test helper is the same as the assert_next_eq helper, but it waits
for the stream to be ready for a certain amount of time instead of
expecting it to be ready right away.
2025-01-31 09:58:55 +01:00
Damir Jelić 585224b2fa chore(ui): Replace the unit type with an empty block 2025-01-31 09:58:55 +01:00
Damir Jelić 0dc5e69ace fix: Retry the sync even in case of network errors 2025-01-31 09:58:55 +01:00
Damir Jelić b323802ab0 fix(ui): Enable retries for network failures of the /versions check in the SyncService 2025-01-31 09:58:55 +01:00
Damir Jelić 252786d2ef refactor(ui): Make SyncService::stop infallible
The `SyncService::stop()` method could fail for the following reasons:

1. The supervisor was not properly started up, this is a programmer error.
2. The supervisor task wouldn't shut down and instead it returns a JoinError.
3. We couldn't notify the supervisor task that it should shutdown due the channel being closed.

All of those cases shouldn't ever happen and the supervisor task will be
stopped in all of them.

1. Since there is no supervisor to be stopped, we can safely just log an
   error, our tests ensure that a `SyncService::start()` does create a
   supervisor.

2. A JoinError can be returned if the task has been cancelled or if the
   supervisor task has panicked. Since we never cancel the task, nor
   have any panics in the supervisor task, we can assume that this won't
   happen.

3. The supervisor task holds on to a reference to the receiving end of
   the channel, as long as the task is alive the channel can not be
   closed.

In conclusion, it doesn't seem to be useful to forward these error cases
to the user.
2025-01-31 09:58:55 +01:00
Damir Jelić 97cbe57d3f docs: Document the offline mode for the SyncService 2025-01-31 09:58:55 +01:00
Damir Jelić 0d8ad159c3 test(ui): Write tests for the SyncService offline mode 2025-01-31 09:58:55 +01:00
Damir Jelić 9d732395ce feat(ui): Introduce a "offline" mode for the SyncService 2025-01-31 09:58:55 +01:00
Damir Jelić 6a772d1c56 test: Add a method to mock the /versions endpoint to the MatrixMockServer 2025-01-31 09:58:55 +01:00
Damir Jelić b71499ffe6 refactor(ui): Move the start/stop implementations under the inner SyncService
This will allow us to more easily implement a restart method.
2025-01-31 09:58:55 +01:00
Damir Jelić 4dadf8581a refactor(ui): Move the creation of the child tasks in the SyncService around
This patch moves the creations of the child tasks of the SyncService
into the supervisor tasks itself. This should make it easier to let the
supervisor recreate tasks.

This will become useful once we introduce a offline mode where the
supervisor task becomes responsible to restart syncing once we notice
that the server is back online.
2025-01-31 09:58:55 +01:00
Benjamin Bouvier 4d4cd61363 chore: update copyright years for files newly introduced 2025-01-31 08:50:41 +01:00
Richard van der Hoff 6f42b0a67b crypto: withhold outgoing messages to unsigned dehydrated devices
Per https://github.com/matrix-org/matrix-rust-sdk/issues/4313, we should not
send outgoing messages to dehydrated devices that are not signed by the current
pinned/verified identity.
2025-01-29 17:37:36 +00:00
Richard van der Hoff e3b348761e test: test helpers in share_strategy tests
Split the existing `set_up_test_machine` into two parts, so we can set up
the test OlmMachine without importing data for other users
2025-01-29 17:37:36 +00:00
Ivan Enderlin d755a8a3aa chore(ui): Move EventMeta inside the metadata module.
This patch moves the `EventMeta` type from `state.rs` to `metadata.rs`.
2025-01-29 11:44:27 +01:00
Ivan Enderlin 66e3ddec47 chore(ui): Move TimelineMetadata into its own module.
This patch moves `TimelineMetadata`, its implementation and companion
types (like `RelativePosition`) into its own module. The idea is to
reduce the size of the `state.rs` module.
2025-01-29 11:44:27 +01:00
Ivan Enderlin 720d443452 chore(ui): Move TimelineStateTransaction into its own module.
This patch moves `TimelineStateTransaction` and its implementation into
its own module. The idea is to reduce the size of the `state.rs` module.
2025-01-29 11:44:27 +01:00
Ivan Enderlin 33d691a58e Merge pull request #4571 from zecakeh/media-retention-policy
feat: Add MediaRetentionPolicy to the EventCacheStore, take 2
2025-01-28 17:27:02 +01:00
Kévin Commaille c2f39c1086 Merge branch 'main' into media-retention-policy 2025-01-28 16:53:52 +01:00
Kévin Commaille df9c355aed Merge branch 'main' into media-retention-policy
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-01-28 15:46:55 +01:00
Kévin Commaille 0334ff3f64 chore(sdk): Add changelog entry for MediaRetentionPolicy
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-01-28 15:44:44 +01:00
Kévin Commaille 8262726369 chore(sqlite): Add changelog entry for EventCacheStore changes
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-01-28 15:44:44 +01:00
Kévin Commaille a4a6bf540d chore(base): Add changelog entry for MediaRetentionPolicy and associated changes
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-01-28 15:44:44 +01:00
Kévin Commaille 9b38f38aea fix(base): Organize changelog the same way as other crates
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-01-28 15:44:44 +01:00
Kévin Commaille 2e05cc74bf feat(sdk): Add methods to Media to interact with MediaRetentionPolicy
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-01-28 15:44:44 +01:00
Kévin Commaille eb9b86971a feat(base): Add methods for MediaRetentionPolicy to EventCacheStore
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-01-28 15:44:42 +01:00
Ivan Enderlin 31e7ec182c chore(ui): Remove TimelineNewItemPosition.
This patch removes the `TimelineNewItemPosition` type a sit is no longer
used.
2025-01-28 15:43:06 +01:00
Ivan Enderlin 5e8f3b2bc8 chore(ui): Remove HandleManyEventsResult.
This patch removes the `HandleManyEventsResult` type as it is no longer
used.
2025-01-28 15:43:06 +01:00
Ivan Enderlin c0b91c4b0e chore(ui): Remove TimelineState(Transaction)::add_remote_events_at.
This patch removes `TimelineState::add_remote_events_at` and
`TimelineStateTransaction::add_remote_events_at` as they are no longer
used.
2025-01-28 15:43:06 +01:00
Ivan Enderlin 46d90afa9c refactor(ui): Use handle_remote_events_with_diffs instead of add_remote_events.
This patch replaces a call to `add_remote_events` by
`handle_remote_events_with_diffs`.

The idea is to remove all calls to `add_remote_events`.
2025-01-28 15:43:06 +01:00
Ivan Enderlin 29e19b729b chore(ui): Remove TimelineController::add_events_at.
This patch removes `TimelineController::add_events_at` since it's a
non-public method and it's unused.
2025-01-28 15:43:06 +01:00
Ivan Enderlin 20c1eff391 refactor(ui): The Timeline no longer use add_events_at.
This patch replaces all uses of `TimelineController::add_events_at` by
`TimelineController::handle_remote_events_with_diffs` in the `Timeline`
itself.

The idea is to remove `add_events_at`, as we currently have 2 ways to
add or to handle remote events in the `Timeline`. We want a single one,
because it's simpler!
2025-01-28 15:43:06 +01:00
Ivan Enderlin 3e40db3d7f test(ui): Tests no longer use add_events_at.
This patch replaces all uses of `TimelineController::add_events_at` by
`TimelineController::handle_remote_events_with_diffs` in the tests.

The idea is to remove `add_events_at`, as we currently have 2 ways to
add or to handle remote events in the `Timeline`. We want a single one,
because it's simpler!
2025-01-28 15:43:06 +01:00
Kévin Commaille 8ca5983093 feat(sqlite): Implement EventCacheStoreMedia for SqliteEventCacheStore
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-01-28 15:38:18 +01:00
Kévin Commaille 144f568a5c feat(base): Implement EventCacheStoreMedia for MemoryStore
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-01-28 15:37:30 +01:00
Kévin Commaille 34c7dd48ae refactor(base): Use struct for media content in the MemoryStore
It is already a 3-tuple and we want to add more data so it will be clearer to use a stuct with named fields.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-01-28 15:36:16 +01:00
Kévin Commaille 6c7d8c16bb feat(base): Add macro for integration tests of EventCacheStoreMedia
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-01-28 15:36:16 +01:00
Kévin Commaille 2c930df8aa feat(base): Add MediaService
This is an API to handle the MediaRetentionPolicy with a lower level
EventCacheStoreMedia trait.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-01-28 15:36:06 +01:00
Kévin Commaille 834bed2b1a feat(base): Add MediaRetentionPolicy
This will be used as a configuration to decide whether or not to keep
media in the cache, allowing to do periodic cleanups to avoid to have
the size of the media cache grow indefinitely.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-01-28 15:35:26 +01:00
Valere 8d530ef220 Merge pull request #4558 from matrix-org/valere/memory_store_consistent_with_other_stores
refactor(crypto): Make memory store behave more like other stores
2025-01-28 15:27:03 +01:00
Valere 542d68dcda fix(test): Properly set up test crypto memory store by saving own device 2025-01-28 15:02:30 +01:00
Valere 50696a0d74 refact(mem_store) Add a global save change lock similar to other stores 2025-01-28 15:02:30 +01:00
Valere 182fc6fd8f refact(mem_store): Ser/Deser inbound group sessions 2025-01-28 15:02:30 +01:00
Valere fe85cddf88 refact(mem_store): Serialize/Deserialise olm sessions 2025-01-28 15:02:30 +01:00
Valere 9ff3761cac refact(mem_store): Serialize account and cache static account data 2025-01-28 15:02:30 +01:00
Valere a311dcbd3e refact(mem_store): Replace unwrap with expect 2025-01-28 15:02:30 +01:00
Andy Balaam 447bd67fe1 feat(crypto): Ignore to-device messages from dehydrated devices 2025-01-28 12:57:30 +00:00
Andy Balaam d8ba2b521c refactor(crypto): extract a method from a test that I will re-use later 2025-01-28 12:57:30 +00:00
Damir Jelić 8de15429fb chore: Fix a typo 2025-01-28 12:48:55 +01:00
Damir Jelić 3f398d8934 chore(ui): Move the SyncService stop logic out of the State::Running branch 2025-01-28 12:48:55 +01:00
Damir Jelić f8ec957193 doc(ui): Reword the doc comment for the is_supervisor_task_running method 2025-01-28 12:48:55 +01:00
Damir Jelić 7cc121ab38 docs(ui): Clarify that the supervisor encapsulates the child tasks in its own task 2025-01-28 12:48:55 +01:00
Damir Jelić 8a4918309a refactor(ui): Rename the abortion sender in the SyncService
Termination aligns better with the existing terminology.
2025-01-28 12:48:55 +01:00
Damir Jelić 30d7fac927 refactor(ui): Remove some unneeded references from the SyncServiceInner 2025-01-28 12:48:55 +01:00
Damir Jelić be71c6df56 docs: Document that the SyncService requires MSC4186 2025-01-28 12:48:55 +01:00
Damir Jelić 06ad67f99c docs(ui): Polish the documentation of the SyncService a bit 2025-01-28 12:48:55 +01:00
Damir Jelić 28fb6f7c27 fix(ui): Shutdown the child tasks if the channel got closed in the supervisor 2025-01-28 12:48:55 +01:00
Damir Jelić 842d32d41b refactor(ui): Prettify the two sync tasks a bit 2025-01-28 12:48:55 +01:00
Damir Jelić b52cf8327a refactor(ui): Remove some early returns from the sync service
Now that the various match branches in the start and stop method of the
SyncService are minimized we can remove the early returns.

This should allow us to more easily add new branches.
2025-01-28 12:48:55 +01:00
Damir Jelić d14526f161 refactor(ui): Move the task spawning functions under the supervisor 2025-01-28 12:48:55 +01:00
Damir Jelić 1b8a6b705c refactor(ui): Move the expiration of the sync services closer to the action 2025-01-28 12:48:55 +01:00
Damir Jelić 7c2b15fe86 refactor(ui): Move the spawning of the child tasks into the supervisor 2025-01-28 12:48:55 +01:00
Damir Jelić 3085f05d51 refactor(ui): Create a Supervisor for the SyncService
The supervisor is defined as two optional fields that are set and
removed at the same time.

This patch converts the two optional fields into a single optional
struct. The fields inside the struct now aren't anymore optional. This
ensures that they are always set and destroyed at the same time.
2025-01-28 12:48:55 +01:00
Damir Jelić 4344e06707 refactor(ui): Rename the scheduler task to supervisor task
From cambridge a scheduler is defined as:
    > someone whose job is to create or work with schedules

While supervisor is defined as:
    > a person whose job is to supervise someone or something

Well ok, that doesn't tell us much, supervise is defined as:
    > to watch a person or activity to make certain that everything is done correctly, safely, etc.:

In conclusion, supervising a task is the more common and better
understood terminology here I would say.
2025-01-28 12:48:55 +01:00
Damir Jelić 173ec75bb3 refactor(ui): Move common data of the SyncService under a lock
Previously we had a lock protecting an empty value, but the logic wants
to protect a bunch of data in the SyncService.

Let's do the usual thing and create a SyncServiceInner which holds the
data and protect that with a lock.
2025-01-28 12:48:55 +01:00
Ivan Enderlin 1d3f8bf898 doc(ui): Update the CHANGELOG. 2025-01-28 09:54:31 +01:00
Ivan Enderlin 5b3b87d3e2 chore(ui): Rename Timeline::subscribe_batched to ::subscribe.
This patch renames `Timeline::subscribe_batched` to
`Timeline::subscribe`. Since the `Timeline::subscribe` method has been
removed because unused, it no longer makes sense to have a “batched”
variant here. Let's simplify things!
2025-01-28 09:54:31 +01:00
Ivan Enderlin 6dc5b33d87 chore(ui): Remove useless trace!.
This patch removes useless `trace!` calls.
2025-01-28 09:54:31 +01:00
Richard van der Hoff 408b843156 test: fix cross-signing in legacy dehydrated device test
We missed a call to `sign_device_keys`. Pull out a test helper to make it more
obvious where this code is coming from.
2025-01-27 17:49:47 +00:00
Ivan Enderlin 0820170261 doc(ui): Update the CHANGELOG. 2025-01-27 17:02:09 +01:00
Ivan Enderlin 254ac6f2ce refactor(ui): Unify the Timeline pagination API.
This patch simplifies the `Timeline` pagination API as follows:

- a unique `paginate_backwards` method (no more
  `focused_paginate_backwards` and `live_paginate_backwards`),
- a unique `paginate_forwards` method (no more
  `focused_paginate_forwards`, the `live` variant was absent).

The idea is to unify pagination by hiding the `live` and `focused` mode.
It was already partially the case with `paginate_backards`, but the
`live` and `focused` variants were also present. I believe it creates
an unnecessary confusion.
2025-01-27 17:02:09 +01:00
Ivan Enderlin 468a7ac883 doc(ffi): Remove useless #[cfg(doc)] imports.
This patch removes 2 useless imports that are behind a `#[cfg(doc)]` but
never used.
2025-01-27 17:02:09 +01:00
Richard van der Hoff 3e610c80e1 Merge pull request #4581 from matrix-org/rav/refactor_share_keys
crypto: refactor the room key sharing strategies
2025-01-27 15:51:04 +00:00
Richard van der Hoff f43edbd31f refactor(crypto): split up split_devices_for_user_for_error_on_verified_user_problem_strategy
to make it easier to grok, I hope
2025-01-27 15:34:43 +00:00
Richard van der Hoff 7c57f2cee4 crypto: split out new device collection strategies
Rather than a bunch of flags on `DeviceBasedStrategy`, separate the strategies
properly.
2025-01-27 15:34:43 +00:00
Richard van der Hoff 8d612eca46 crypto: break up split_devices_for_user
handle the separate flags with separate methods.
2025-01-27 15:34:43 +00:00
Richard van der Hoff 98f4d55aa0 test: check serialization format of DeviceBasedStrategy 2025-01-27 15:34:43 +00:00
Richard van der Hoff 709b09c4ec test: factor out test helpers in share_strategy tests
Some helpers for creating common `EncryptionSettings`
2025-01-27 15:34:43 +00:00
Richard van der Hoff 818876a22e crypto: factor out common code between device collection cases
Firstly, build a `CollectRecipientsResult` as we go, rather than building its
components separately and then assembling it at the end.

Then, factor the common code between the two code paths into a method to update
the `CollectRecipientsResult`.
2025-01-27 15:34:43 +00:00
Stefan Ceriu 2657eb7866 feat(ui): expose a method for checking whether a message contains only emojis and should be boosted (use a bigger font size) (#4577)
- supports only text room message types
- enumerates through their body's grapheme clusters and check that every
single one of them is an emoji
- part of the `LazyTimelineItemProvider` so that it can be opt in
2025-01-27 14:00:01 +00:00
torrybr aaecbf07f2 refactor: dont panic if beacon_info is not found 2025-01-27 11:05:24 +01:00
torrybr f336638a17 refactor: move subscribe into arc 2025-01-27 11:05:24 +01:00
torrybr 839fbe477c feat(beacons): expose ffi functions to start, stop and subscribe 2025-01-27 11:05:24 +01:00
Richard van der Hoff 35ad5441d3 crypto: split common struct out of device collection results
`split_devices_for_user` returns a superset of the results of
`split_recipients_withhelds_for_user_based_on_identity`: let's reflect that in
the return types so we can start to share code.

Also, rename `split_recipients_withhelds_for_user_based_on_identity` to
`split_devices_for_user_for_identity_based_strategy` while we are here.
2025-01-26 22:50:15 +00:00
Kevin Boos 756dec264d Upgrade imbl, eyeball-im, eyeball-im-util to fix bounds check
A bounds check was recently relaxed in `imbl`'s `Focus::narrow()`
function: https://github.com/jneem/imbl/pull/89,
which fixed a bug that would cause a panic if the downstream user
of `matrix-sdk-ui` attempted to narrow a focus of Timeline items
using a range that included the last item in the Timeline.
Example: https://github.com/project-robius/robrix/issues/330

This fix has been incorporated in `eyeball-im` and `eyeball-im-util`
and has been tested by me to no longer trigger upon the aforementioned
conditions.
2025-01-25 02:13:22 -05:00
Doug 87983ab610 chore: Remove an old todo
This was already done by moving the methods into Client.
2025-01-24 18:06:05 +01:00
Neil Johnson 66ffc3448e Update README.md
style

Signed-off-by: Neil Johnson <neil@matrix.org>
2025-01-24 11:20:20 +01:00
Neil Johnson c6e308717d update maturity and contribution call to action 2025-01-24 10:56:05 +01:00
maan2003 4c4dd03411 fix(wasm): don't use tokio::time::{timeout,sleep} (#4573)
Tokio timeout and sleep don't work on wasm so provide alternative versions

---------

Signed-off-by: Manmeet Singh <manmeetmann2003@gmail.com>
Signed-off-by: Andy Balaam <andy.balaam@matrix.org>
Co-authored-by: Andy Balaam <andy.balaam@matrix.org>
2025-01-23 08:57:11 +00:00
Benjamin Bouvier 2d0f873342 refactor: send respects to multiple automated lints and checks 2025-01-22 20:24:48 +01:00
Benjamin Bouvier 041627ec4a test: rename EventFactory::into_sync to into_event 2025-01-22 20:24:48 +01:00
Benjamin Bouvier da4b8004f2 docs: add changelog entries 2025-01-22 20:24:48 +01:00
Benjamin Bouvier 3428494468 refactor: rename SyncTimelineEvent to TimelineEvent 2025-01-22 20:24:48 +01:00
Benjamin Bouvier 0c2046f93b refactor: have SyncTimelineEvent::push_actions be optional 2025-01-22 20:24:48 +01:00
Benjamin Bouvier eb31f035e6 refactor: turn TimelineEvent into SyncTimelineEvent
As the comment noted, they're essentially doing the same thing. A
`TimelineEvent` may not have computed push actions, and in that regard
it seemed more correct than `SyncTimelineEvent`, so another commit will
make the field optional.
2025-01-22 20:24:48 +01:00
Kévin Commaille df51404a14 chore(sdk): Add changelog for move of matrix_auth and oidc
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-01-22 20:22:13 +01:00
Kévin Commaille 3e78e441d4 refactor(sdk): Move oidc module to authentication::oidc
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-01-22 20:22:13 +01:00
Kévin Commaille 02c2e55855 refactor(sdk): Move matrix_auth module to authentication::matrix
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-01-22 20:22:13 +01:00
Stefan Ceriu a528624274 chore(ffi): replace all the different timeline builder methods with one taking a configuration (#4561) 2025-01-22 13:56:53 +02:00
Ivan Enderlin 1d83d42e9f chore(ui): Remove Timeline::subscribe.
This patch removes `Timeline::subscribe`. There is
`Timeline::subscribe_batched`, which is the only useful API.
`subscribe` was only used by our tests, and `matrix-sdk-ffi` uses
`subscribe_batched` only.
2025-01-22 11:55:23 +01:00
Ivan Enderlin 4684cfb780 chore: Replace Timeline::subscribe by Timeline::subscribe_batched.
This patch changes all calls to `Timeline::subscribe` to replace them by
`Timeline::subscribe_batched`. Most of them are in tests. It's the first
step of a plan to remove `Timeline::subscribe`.

The rest of the patch updates all the tests to use
`Timeline::subscribe_batched`.
2025-01-22 11:55:23 +01:00
Stefan Ceriu 991c9ad610 chore(ci): simplify formatting checks by using xtask instead 2025-01-22 12:20:47 +02:00
Hubert Chathi e826c54a42 Use the dehydrated device format implemented by vodozemac (#4421)
Signed-off-by: Hubert Chathi <hubertc@matrix.org>
2025-01-22 09:38:48 +01:00
Kévin Commaille 9ae658c1b9 feat(sdk): Enable HTTP/2 support
It became an optional default feature in reqwest 0.12, and we disable
the default features,
so I don't think it was meant to be disabled when the crate was
upgraded.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-01-22 09:08:00 +01:00
Benjamin Bouvier 4341aaf65c test: remove some uses of sync_timeline_event! in the base and sdk crates (#4565)
Part of #3716.
2025-01-21 14:33:22 +00:00
Jorge Martín ad847a82c8 refactor: Remove TestHelper in pinned_events.rs
Use helper functions instead.
2025-01-21 12:56:19 +01:00
Jorge Martín dad3e6839f test: update the code in pinned_events integration tests
This is done so the tests there use the new APIs based on `MatrixMockServer`.
2025-01-21 12:56:19 +01:00
Jorge Martín d078ef6155 fix: fix mock_room_event not being able to properly filter out event ids since the regex was incorrectly parsed by wiremock 2025-01-21 12:56:19 +01:00
Benjamin Bouvier 210c5749f1 test: minimize usage of EventFactory::state_key
It was used in places where we could make use of other helpers, in some
cases. Also introduces the `room_avatar` helper to create the room
avatar state event.
2025-01-21 10:50:29 +01:00
Benjamin Bouvier 0c74abbc50 test: get rid of EventBuilder (#4560)
This gets rid of `EventBuilder`, and makes more usage of the
`EventFactory`, which is more ergonomic to create test events.

A large part of
https://github.com/matrix-org/matrix-rust-sdk/issues/3716.
2025-01-21 09:23:03 +00:00
Jorge Martín dbadfe19b0 fix(timeline): avoid adding non-pinned events to the pinned events timeline when back paginating 2025-01-20 17:38:50 +01:00
dependabot[bot] f3e43dbfa4 chore(deps): bump malinskiy/action-android/install-sdk@release/0.1.4
Bumps [malinskiy/action-android/install-sdk@release/0.1.4](https://github.com/malinskiy/action-android) from 0.1.4 to 0.1.7.
- [Release notes](https://github.com/malinskiy/action-android/releases)
- [Commits](https://github.com/malinskiy/action-android/compare/release/0.1.4...release/0.1.7)

---
updated-dependencies:
- dependency-name: malinskiy/action-android/install-sdk@release/0.1.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-20 16:22:38 +01:00
dependabot[bot] b846a6dd81 chore(deps): bump crate-ci/typos from 1.28.3 to 1.29.4
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.28.3 to 1.29.4.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.28.3...v1.29.4)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-20 16:22:31 +01:00
Ivan Enderlin c82e469fc3 chore(cargo): Update Ruma to include https://github.com/ruma/ruma/pull/1995. 2025-01-20 15:02:53 +01:00
Kévin Commaille f2c9a8f723 fix(ui): Fix latest_edit_json for live edits
This was a regression introduced in
f0d98602a9.

`latest_edit_json` was first set by the call to
`EventTimelineItem::with_content()`.
It was overwritten in the next section because of the
`if let EventTimelineItemKind::Remote(remote_event)`
that uses `item` instead of `new_item`.
It means that the updated `RemoteEventTimelineItem`
inside`new_item` was replaced by the outdated one inside `item`,
so `latest_edit_json` goes back to its previous value.

I believe that part of why that went unnoticed is that the code looks
more
complicated due to the need to set an inner field, so I decided to
change the API and
move `with_encryption_info` to `EventTimelineItem`, which makes the code
look cleaner.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-01-20 13:07:13 +01:00
Daniel Kilimnik 2e16021f14 fix(oidc): remove unnecessary e2e-encryption imports 2025-01-19 16:08:51 +01:00
torrybr 47fc073b70 refactor(live_location_share): exclude live location events of own user (#4535)
This change ensures that the user's own live location events are
excluded from the location sharing stream. Since the user's location is
already represented on the map by the blue dot, processing their own
events is redundant and unnecessary.
2025-01-17 14:56:10 +00:00
Jonas Platte 160600e8c0 chore(ui): Copy some attributes from matrix-sdk
The others should likely be copied at some point as well, but including
the readme as crate documentation is not currently useful since the
readme for the ui crate is empty, and warning about missing docs or
debug implementations would make CI fail without substantial extra work.
2025-01-17 15:35:28 +01:00
Jonas Platte 993c103270 ci: Add wasm job for matrix-sdk-ui 2025-01-17 15:35:28 +01:00
Jonas Platte e077980ba2 ci: Shorten job name
The extra 'wasm-flags' does not seem to add clarity.
2025-01-17 15:35:28 +01:00
Jonas Platte 63d14b798b ci: Reorder workflow matrix items to match ci.rs 2025-01-17 15:35:28 +01:00
Jonas Platte 077d63a9fc chore(ui): Re-export matrix-sdk's js feature 2025-01-17 15:35:28 +01:00
Ivan Enderlin 453c4e12db doc(sdk): Simplify documentation of RoomEventCache::subscribe.
This patch removes a `XXX` (which I believe is a TODO) in the
documentation of `RoomEventCache::subscribe`. We are not going to change
this API anytime soon.
2025-01-17 15:32:00 +01:00
Jonas Platte f7db52e069 Use Send-less BoxFuture for HttpClient Service impl on wasm 2025-01-17 12:21:21 +01:00
Richard van der Hoff 2bd8c56e64 crypto: add some more documentation to DeviceKeys
This confused me for a while, so I thought more documentation might help.
2025-01-16 15:13:25 +00:00
Richard van der Hoff f231c74314 test: simplify examples for KeyQueryResponseTemplate
Generating keys from slices rather than base64 is easier.

Also, s/builder/template/.
2025-01-16 15:13:11 +00:00
Stefan Ceriu 2cb6ee8e6d chore(ffi): silence useless logs coming out of the ffi crate
Setting the default log level to `debug` results in logs like:

```
log: log_event
log: latest_event
log: log_event
log: log_event
log: room_info
log: latest_event
log: log_event
log: room_info
```

Presumably they're coming out of the custom tracing configuration and we definitely don't need them.
2025-01-16 15:17:24 +02:00
Richard van der Hoff c24770a774 test: add support for dehydrated devices to KeyQueryResponseTemplate (#4540)
#4476 added some test helpers to generate `/keys/query` responses. We're
going to need to test dehydrated devices, so this PR adds support for
that.
2025-01-16 11:26:52 +00:00
Benjamin Bouvier 7fa06cb028 refactor(timeline): rename TimelineItemPosition::UpdateDecrypted to UpdateAt 2025-01-16 12:26:32 +01:00
Benjamin Bouvier 50383098ff feat(event cache): redact events in the database whenever they're redacted 2025-01-16 12:26:32 +01:00
Benjamin Bouvier 6f780a499c test(timeline): use assert_let_timeout more in the timeline's code 2025-01-16 12:26:32 +01:00
Benjamin Bouvier 425e48a46d feat(linked chunk): add LinkedChunk::replace_item_at to replace an item from a given position 2025-01-16 12:26:32 +01:00
Richard van der Hoff 3dd81fbe2c test: rename snapshots not to contain :
Windows doens't allow you to have `:` in its filenames
2025-01-16 11:11:38 +00:00
Benjamin Bouvier 6a0333e812 test: replace Option::default() by None 2025-01-16 11:34:42 +01:00
Benjamin Bouvier b3a789af90 test: get rid of the synced_client helper
Not running a large sync with many events make for simpler test cases,
with a more focused scope.
2025-01-16 11:34:42 +01:00
Benjamin Bouvier 560e582e41 test: get rid of mock_redaction and replace it with the holy MatrixMockServer 2025-01-16 11:34:42 +01:00
Benjamin Bouvier de7397a20e feat(event cache): handle redacted redactions in the AllEventsCache
This is unlikely that it will affect us, so not worth adding a test IMO,
but for the sake of completeness: this handles redacted redactions in
the `AllEventsCache` too.
2025-01-16 10:05:45 +01:00
Andy Balaam 8bd94318c0 fix(tests): Fix a flaky test by marking a room's members as synced.
This is intended to prevent the test
`test_when_user_in_verification_violation_becomes_verified_we_report_it`
flaking. I found that sometimes when it called `Room::members` the
result was empty due to it trying to fetch the answer from the server.
This change prevents that behaviour. I don't know why the behaviour was
inconsistent before.
2025-01-15 15:24:49 +00:00
Richard van der Hoff fe3cc09ae0 test: add examples for the new builder 2025-01-15 10:42:21 +00:00
Richard van der Hoff 3a3cc54067 test: generate dan's data dynamically 2025-01-15 10:42:21 +00:00
Richard van der Hoff 47f8b32ea1 test: give Dan new keys
Regenerate Dan's data with new cross-signing and device keys, for which I know
the private keys.

The signatures are manually calculated for now; this will be improved in a
later commit.
2025-01-15 10:42:21 +00:00
Richard van der Hoff 49748dbd4b test: factor out common parts of dan_keys_query_response{_loggedout} 2025-01-15 10:42:21 +00:00
Richard van der Hoff 25ea5fdd73 test: use builder for some more test data 2025-01-15 10:42:21 +00:00
Richard van der Hoff 5fadde5a6d test: implement test user data builder type
... and use it for some simple data
2025-01-15 10:42:21 +00:00
Richard van der Hoff b6be4d5170 test: remove redundant sig on master key
Our test helper won't do this, and it's redundant
2025-01-15 10:42:21 +00:00
Richard van der Hoff c9bac4ff2b test: snapshot the generated object rather than the JSON
We're going to be switching away from JSON-twiddling, so let's snapshot the
real object rather than the JSON.
2025-01-15 10:42:21 +00:00
Richard van der Hoff fedf7d214f test: add some snapshot tests before we change anything 2025-01-15 10:42:21 +00:00
Valere c969f903b7 Merge pull request #4526 from matrix-org/valere/test_encrypted_crypto_sql_snapshot
tests: Add an encrypted snapshot of a SQLite db for regression tests
2025-01-15 09:37:27 +01:00
Jorge Martín bd5d7aafee feat(ffi): Add FFI bindings for fn Room::own_membership_details.
Also add `membership_change_reason` field to `ffi::RoomMember`.
2025-01-14 16:23:51 +01:00
Jorge Martín e015a531da feat(room): Add fn Room::own_membership_details
This will retrieve the room member info of both the current user and the info for the sender of the current user's room member event.
2025-01-14 16:23:51 +01:00
Benjamin Bouvier b9014a5e2a test: keep a single sync in test_delayed_invite_response_and_sent_message_decryption()
This removes one sync that happens in the background, because it's
likely spurious and may be confusing the server about what's been seen
by the current client.
2025-01-14 15:07:10 +01:00
Jorge Martín e9487b0851 fix(timeline): Add UTDs to the timeline conditionally 2025-01-14 12:25:49 +01:00
Damir Jelić c60bfb877a chore: Add some missing links to the changelog 2025-01-13 19:27:44 +01:00
Valere ee32b1f600 tests: Add an encrypted snapshot of a SQLite db for regression tests 2025-01-13 17:50:50 +01:00
Daniel Salinas 9641aa9082 feat(send queue): Add an enqueued time to to-be-sent events (#4385)
Add a new created_at to the send_queue_events and
dependent_send_queue_events stored records. This will allow clients to
understand how stale a pending message might be in the event that the
queue encounters and error and becomes wedged.

This change is exposed through the FFI on the `EventTimelineItem` struct
as a new optional field named `local_created_at`. It will be `None` for
any Remote event, and `Some` for Local events (except for those that
were enqueued before the migrations were run).

Signed-off-by: Daniel Salinas

---------

Signed-off-by: Daniel Salinas <zzorba@users.noreply.github.com>
Co-authored-by: Daniel Salinas <danielsalinas@daniels-mbp-2.myfiosgateway.com>
Co-authored-by: Benjamin Bouvier <benjamin@bouvier.cc>
Co-authored-by: Daniel Salinas <danielsalinas@Daniels-MBP-2.attlocal.net>
2025-01-13 16:41:05 +00:00
Benjamin Bouvier a8ca77f4fc feat(base): remove cached events when forgetting about a room 2025-01-13 17:36:33 +01:00
Benjamin Bouvier e647ff935e feat(event cache store): allow removing an entire room at once 2025-01-13 17:36:33 +01:00
Benjamin Bouvier 7f04a9a18b fix(memory chunk): only remove a given room's events when clearing a roo 2025-01-13 17:36:33 +01:00
Damir Jelić 67d2cb790d chore: Fix a couple of typos 2025-01-13 17:25:00 +01:00
Benjamin Bouvier 279c78b3e2 chore!(encryption): rename are_we_the_last_man_standing to is_last_device
While the former name is arguably more fun, the latter is more
descriptive of what the function does.
2025-01-13 16:51:33 +01:00
Benjamin Bouvier 9514388108 tests: rename RoomMessagesResponse to RoomMessagesResponseTemplate 2025-01-13 14:50:21 +01:00
Benjamin Bouvier e6dc10933c tests: add helper to delay a room /messages response
This removes a few manual uses of `ResponseTemplate`, which is sweet and
guarantees some better typing for those responses overall.
2025-01-13 14:50:21 +01:00
Benjamin Bouvier c456356424 tests: add a helper to create a room /messages response 2025-01-13 14:50:21 +01:00
Benjamin Bouvier 5af326b36e fix(event cache): keep the previous-batch token when we haven't enabled storage 2025-01-13 14:50:21 +01:00
Jorge Martín 5548f38393 feat(ffi): Add FFI bindings for the new room privacy settings feature. 2025-01-13 11:29:10 +01:00
Jorge Martín d9c1188f87 test(room): Add integration tests for publishing and removing room aliases 2025-01-13 11:29:10 +01:00
Jorge Martín 588702756b feat(room): Add fn RoomPrivacySettings::remove_room_alias_in_room_directory. 2025-01-13 11:29:10 +01:00
Jorge Martín d6a74d389d feat(room): Add fn RoomPrivacySettings::publish_room_alias_in_room_directory.
This also needs some new mocks for resolving room aliases.
2025-01-13 11:29:10 +01:00
Jorge Martín d807d71e22 feat(room): Add fn RoomPrivacySettings::update_room_visibility. 2025-01-13 11:29:10 +01:00
Jorge Martín 587545ae82 feat(room): Add fn RoomPrivacySettings::get_room_visibility. 2025-01-13 11:29:10 +01:00
Jorge Martín 49985e5476 feat(room): Add fn RoomPrivacySettings::update_join_rule. 2025-01-13 11:29:10 +01:00
Jorge Martín 4fbe79a27d feat(room): Add fn RoomPrivacySettings::update_room_history_visibility. 2025-01-13 11:29:10 +01:00
Jorge Martín f61ad19ae6 feat(room): Add RoomPrivacySettings helper struct.
This can be accessed through `fn Room::privacy_settings` and will wrap the functionality related to a room's access and privacy settings.

This commit includes the `fn RoomPrivacySettings::update_canonical_alias` to modify the canonical alias of a room.
2025-01-13 11:29:10 +01:00
Benjamin Bouvier c9a49006f6 chore(xtask): tweak the TWiM report to include only merged PRs, not created PRs
As an outsider, I am mostly interested in features and new developments
that have happened, not those that *may* happen. An open-but-not-merged
PR may not get merged in the end, or it may not get merged any time
soon, creating false expectations. Merged PRs, on the other hand, have
definitely happened (even if they get undone, that happens via other PRs
that will get merged later). As such, I think it brings more value to
outsiders.
2025-01-13 11:03:08 +01:00
Kévin Commaille ca9eb70db5 Add PR link to changelog
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-01-13 08:46:10 +01:00
Kévin Commaille f173aea6e4 feat(sdk): Expose Client::server_versions publicly
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-01-13 08:46:10 +01:00
Jonas Platte e37ad11b47 refactor(ui): Use RPITIT / AFIT for RoomDataProvider 2025-01-11 13:19:16 -05:00
Damir Jelić d6c2a63f5c refactor: Use the simplified locks in the encryption tasks 2025-01-11 09:33:33 +01:00
Damir Jelić 4ebf5056be chore: Remove our ancient upgrade guide 2025-01-10 20:22:27 +01:00
Valere a79d409f9d task(bindings): Expose withdraw_verification in UserIdentity 2025-01-10 15:18:12 +01:00
Kévin Commaille 5941495e68 feat(sdk): Implement Default for AttachmentInfo types
Since all of their fields are optional, it simplifies their
construction.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-01-10 14:37:56 +01:00
Kévin Commaille b3491582d0 feat(sdk): Allow to set and check whether an image is animated
Using MSC4230.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-01-10 14:37:56 +01:00
Damir Jelić def4bbbed2 fix(store-encryption): Remove an unwrap that snuck in (#4506) 2025-01-10 14:13:10 +01:00
Valere 1dd2b2c9e8 test: Test the KnownSenderData migration with optimised [u8] serialization 2025-01-10 14:12:32 +01:00
Damir Jelić e4b269e0de fix: Implement visit_bytes for the Ed25519PublicKey deserialization
This fixes the deserialization of the SenderData since it switched to
the base64 encoding for serialization of the master key in one of its
variants.

The issue was introduced in 5ff556f6c3.
2025-01-10 14:12:32 +01:00
Kévin Commaille cb72d4375f chore: Upgrade Ruma
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-01-10 11:31:56 +01:00
Andy Balaam 7ec384c61a fix: Fix incorrect debug_struct calls in several places 2025-01-10 09:27:31 +01:00
Jonas Platte 7466f77eae refactor(ui): Replace tokio::spawn with matrix_sdk::executor::spawn 2025-01-10 03:02:37 -05:00
Jonas Platte 526b5c4630 refactor(ui): Relax some Send constraints on WASM 2025-01-10 03:02:37 -05:00
Jonas Platte 4043f9bf5d refactor(sdk): Un-cfg SendAttachment::with_send_progress_observable
It (now) compiles on WASM just fine.
2025-01-10 03:02:37 -05:00
Jonas Platte ff5dcbf631 refactor(common): Warn if LinkedChunk::updates() return value is not used 2025-01-09 16:20:51 -05:00
Jonas Platte 6c053a86bf chore: Fix new nightly warnings 2025-01-09 16:20:51 -05:00
Benjamin Bouvier 0cae54cc3f chore(ui): rename "utils" to "algorithms"
It only contains functions used to search items in the timeline now \o/
2025-01-09 17:27:53 +01:00
Benjamin Bouvier 692aceba50 chore(ui): move RelativePosition in timeline/controller 2025-01-09 17:27:53 +01:00
Benjamin Bouvier c4a86a3d0a chore(ui): move timeline/read_receipts to timeline/controller/read_receipts
Read receipts only make sense in the context of the timeline controller.
2025-01-09 17:27:53 +01:00
Benjamin Bouvier 5f5aa81174 chore(ui): move date and timestamp functionality to timeline/date_dividers.rs
`util.rs` files are… not the best thing. These types and functions were
only used by the date dividers file, so let's move them there.
2025-01-09 17:27:53 +01:00
Benjamin Bouvier 6e0f258a39 chore(sdk): move send_queue.rs to send_queue/mod.rs 2025-01-09 17:27:53 +01:00
Stefan Ceriu c4bfbd0f44 feat(ffi): move tracing setup from the final client to the ffi layer (#4492)
Having the final clients define the tracing filters / log levels proved
to be tricky to keep in check resulting missing logs (e.g. recently
introduced modules like the event cache) or unexpected behaviors (e.g.
missing panics because we don't set a global filter). As such we decided
to move the tracing setup and default definitions over to the rust side
and let rust developers have full control over them.

We will now take a general log level and optional extra targets and 
apply them on top of the rust side defined defaults. Targets that log
more than the requested by default will remain unchanged while the
others will increase their log levels to match. Certain targets like
`hyper` will be ignored in this step as they're too verbose others 
like `matrix_sdk` because they're too generic.
2025-01-09 18:08:44 +02:00
Benjamin Bouvier 8e0ee47637 refactor(event cache): eliminate intermediate function append_events_locked
and replace it with an inlined call to `append_events_locked_impl`,
that's then renamed `append_events_locked`.
2025-01-09 15:36:36 +01:00
Benjamin Bouvier 0915eeed51 chore(event cache): simplify and add logs to RoomEventCacheState::propagate_changes 2025-01-09 15:36:36 +01:00
Benjamin Bouvier fb54e869e9 chore(event cache): add more logs when the event cache tasks are shutting down 2025-01-09 15:36:36 +01:00
Benjamin Bouvier 9e97ed3134 test(event cache): add a regression test for not deleting a gap that wasn't inserted 2025-01-09 12:12:12 +01:00
Benjamin Bouvier b926c4287a refactor(event cache): use a more fine-grained check for the gap removal 2025-01-09 12:12:12 +01:00
Ivan Enderlin ddf4d575b7 fix(sdk): Ensure a gap has been inserted before removing it.
This patch fixes a bug where the code assumes a gap has been inserted,
and thus, is always present. But this isn't the case. If `prev_batch`
is `None`, a gap is not inserted, and so we cannot remove it. This patch
checks that `prev_batch` is `Some(_)`, which means the invariant is
correct, and the code can remove the gap.
2025-01-09 12:12:12 +01:00
Damir Jelić 2a954e3ce3 fix(base): Correctly name the LeftRoomUpdate in its debug implementation (#4487)
Signed-off-by: Damir Jelić <poljar@termina.org.uk>
Co-authored-by: Benjamin Bouvier <benjamin@bouvier.cc>
2025-01-09 11:10:33 +00:00
Jonas Platte b837865226 refactor(ui): Inherit Send / Sync bounds on RoomDataProvider from super traits 2025-01-09 09:26:55 +01:00
Jonas Platte eac5a5eb35 refactor(ui): Fix unused import on wasm 2025-01-09 09:26:55 +01:00
Jonas Platte d6c64027f6 refactor(sdk): Un-cfg Client::rooms_stream
It compiles on WASM too.
2025-01-09 09:26:55 +01:00
Ivan Enderlin df4b69666c chore: Make Clippy and wasm-pack happy. 2025-01-08 21:30:41 +01:00
Ivan Enderlin 5675ac7f46 refactor(sdk): Remove SlidingSyncRoomInner::client.
This patch removes `SlidingSyncRoomInner::client` because, first
off, it's not `Send`, and second, it's useless. Nobody uses it, it's
basically dead code… annoying dead code… bad dead code!
2025-01-08 21:30:41 +01:00
Ivan Enderlin 6b2233f8c4 fix(sdk): Use spawn from matrix_sdk_common to make it compatible with wasm32-u-u. 2025-01-08 21:30:41 +01:00
Ivan Enderlin 61dd560499 feat: Remove the experimental-sliding-sync feature flag.
Sliding sync is no longer experimental. It has a solid MSC4186, along
with a solid implementation inside Synapse. It's time to consider it
mature.

The SDK continues to support the old MSC3575 in addition to MSC4186.
This patch only removes the `experimental-sliding-sync` feature flag.
2025-01-08 21:30:41 +01:00
Damir Jelić 62567ca6eb refactor(crypto): Use the simplified locks across the crypto crate 2025-01-08 18:59:22 +01:00
Damir Jelić 46dc2a9c5e refactor: Use the simplified locks in the failures cache 2025-01-08 18:59:22 +01:00
Damir Jelić 891583b70e refactor: Add Mutex and RwLock wrappers that panic on poison 2025-01-08 18:59:22 +01:00
Ivan Enderlin e19bdbfd59 test(ui): Adjust tests according to the new Timeline behaviour. 2025-01-08 17:04:58 +01:00
Ivan Enderlin 14d0cc1935 refactor(ui): Remove TimelineSettings::vectordiffs_as_inputs.
From now on, this patch considers that `VectorDiff`s are the official
input type for the `Timeline`, via `RoomEventCacheUpdate` (notably
`::UpdateTimelineEvents`).

This patch removes `TimelineSettings::vectordiffs_as_inputs`. It thus
removes all deduplication logics, as it is supposed to be managed by the
`EventCache` itself.
2025-01-08 17:04:58 +01:00
Ivan Enderlin b8d0384da7 refactor: Remove RoomEventCacheUpdate::AddTimelineEvents.
This patch removes the `AddTimelineEvents` variant from
`RoomEventCacheUpdate` since it is replaced by `UpdateTimelineEvents`
which shares `VectorDiff`.

This patch also tests all uses of `UpdateTimelineEvents` in existing
tests.
2025-01-08 17:04:58 +01:00
Ivan Enderlin 4e0a6d15ca chore(sdk): Merge imports for the sake of clarity. 2025-01-08 17:04:58 +01:00
Ivan Enderlin 251433382f chore(test): Remove a warning.
`owned_user_id` is only used by a test behind the
`experimental-sliding-sync` feature flag.
2025-01-08 17:04:58 +01:00
Benjamin Bouvier 34e993435d fix(ffi): ensure the log level for panic is always set (#4485)
If it's present, we just let it untouched. Otherwise, we set it to
`error` if it's missing. See code comment explaining why we need this.

This makes sure we log panics at the FFI layer, since the `log-panics`
crate will use the `panic` target at the error level.
2025-01-08 15:51:14 +00:00
Benjamin Bouvier dc2775e194 chore!(ffi): rename thumbnail_url to thumbnail_path
This is a breaking change because uniffi may use foreign-language named
parameters based on the Rust parameter name.
2025-01-08 14:20:06 +01:00
Benjamin Bouvier 45c3752cae refactor!(ffi): common out more code in send_attachment and distinguish early from late errors
Some errors can be handled immediately and don't need a request to be
spawned, e.g. invalid mimetype and so on. The returned task handle still
deals about "late" errors about the upload failing (for sync uploads) or
the send queue failing to push the media upload (for async uploads).
2025-01-08 14:20:06 +01:00
Benjamin Bouvier ed178602d7 chore!(ffi): group parameters to upload in UploadParameters
Note: `Box<dyn ProgressWatcher>` couldn't be put in a `Record`, so
doesn't belong in `UploadParameters` as a result.
2025-01-08 14:20:06 +01:00
Benjamin Bouvier 35a03278c3 chore(ffi): rename url to filename in the FFI methods for sending attachments 2025-01-08 14:20:06 +01:00
Valere 9e69b631ee Merge pull request #4450 from matrix-org/valere/serialize_known_sender_data_b64
fix(crypto): Serialize sender data msk in base64 instead of numbers
2025-01-08 14:06:13 +01:00
Valere 5ff556f6c3 fix(crypto): Serialize sender data msk in base64 instead of numbers 2025-01-08 13:22:37 +01:00
Ivan Enderlin d64960679f refactor(sdk): Rename RoomEvents::filter_duplicated_events.
This patch renames `RoomEvents::filter_duplicated_events` to
`collect_valid_and_duplicated_events` as I believe it improves the
understanding of the code. The variables named `unique_events` are
renamed `events` as all (valid) events are returned, not only the unique
ones.
2025-01-08 13:01:59 +01:00
Ivan Enderlin 7ff1170681 chore: Re-indent. 2025-01-08 11:47:24 +01:00
Ivan Enderlin 55e25a3717 feat(sdk,ui): Add EventsOrigin::Pagination.
This patch adds the `Pagination` variant to the `EventsOrigin` enum.
Not something really mandatory and that is likely to fix a bug, but it's
now correct.
2025-01-08 11:43:03 +01:00
Joe Groocock 3f977b79fa feat(timeline): allow sending mentions along with media
Since 8205da898e it has been possible to
attach (intentional) mentions to _edited_ media captions, but the
send_$mediatype() timeline APIs provided no way to send them with the
initial event. This fixes that.

Signed-off-by: Joe Groocock <me@frebib.net>
2025-01-08 10:43:43 +01:00
Benjamin Bouvier aca8c8b8ee chore: remove some allow(dead_code) annotations and associated dead code (#4472)
We have quite a few `allow(dead_code)` annotations. While it's OK to use
in situations where the Cargo-feature combination explodes and makes it
hard to reason about when something is actually used or not, in other
situations it can be avoided, and show actual, dead code.
2025-01-08 10:37:18 +01:00
Kévin Commaille 47c24b9a17 fix(sdk): Fix test now that Ruma is fixed
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-01-08 10:35:28 +01:00
Kévin Commaille 47445b10f1 chore: Upgrade Ruma
This is using the ruma-0.12 branch where non-breaking changes are backported.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-01-08 10:35:28 +01:00
Jonas Platte c5a9a1e215 Clean up some imports
With experimental-sliding-sync enabled and e2e-encryption disabled,
there were a bunch of warnings about unused imports. This fixes them
(but a few warnings about other unused items remain).
2025-01-08 09:18:56 +01:00
Benjamin Bouvier 2ef14ded41 refactor(event cache): a few AllEventsCache refactorings (#4471)
I was investigating a potential deadlock with the event cache storage,
and only found a few places where to make the code a bit more idiomatic
and more readable.
2025-01-07 17:25:52 +01:00
Benjamin Bouvier 8205da898e feat(send queue): allow setting intentional mentions in media captions edits
Fixes #4302.
2025-01-07 16:52:53 +01:00
Benjamin Bouvier 618e47250d feat!(base): reintroduce Room::display_name
`compute_display_name` is made private again, and used only within the
base crate. A new public counterpart `Room::display_name` is introduced,
which returns a cached value for, or computes (and fills in cache) the
display name. This is simpler to use, and likely what most users expect
anyways.
2025-01-07 15:25:32 +01:00
Benjamin Bouvier 5110aa64aa doc(base): update lying doc comment of compute_display_name
It claimed that it would immediately return when the cached display name
value was computed, but that's absolutely not the case.

Spotted while reviewing a PR updating `iamb` to the latest version of
the SDK.
2025-01-07 14:39:07 +01:00
Benjamin Bouvier bcad0a3059 test(timeline): rewrite a test to use the MatrixMockServer instead 2025-01-07 11:58:34 +01:00
Benjamin Bouvier b7b88f58d2 feat!(send queue): make unrecoverable errors stop the sending queue
Instead of keeping on handling unwedged events from the sending queue,
it's now required to re-enable the send queue manually for the room that
encountered the sending error, all the time. This is more consistent,
and avoids weird behavior when a user would 1. send an event for which
sending fails, in an unrecoverable manner, 2. send an event that's
actually sendable.
2025-01-07 11:58:34 +01:00
Damir Jelić 412fcab4dc test: Await the device creation in the notification client redecryption test 2025-01-07 11:06:28 +01:00
Jonas Platte 8e75a940f7 Use Instant from web-time in more places (via ruma re-export)
web-time's Instant type is already used elsewhere in the project. It is
an alias for std's Instant type on most targets, but tries to call into
JavaScript on wasm32-unknown-unknown (assuming that the wasm blob is
used in from a browser context). Its Duration type is a plain re-export
of std's Duration, even on wasm32-unknown-unknown.
2025-01-07 09:35:52 +01:00
Kévin Commaille 70fb7899e6 feat!(timeline): Allow to send attachments from bytes (#4451)
Sometimes we can get the bytes directly, e.g. in Fractal we can get an
image from the clipboard. It avoids to have to write the data to a
temporary file only to have the data loaded back in memory by the SDK
right after.

The first commit to accept any type that implements `Into<String>` for
the filename is grouped here because it simplifies slightly the second
commit.

Note that we could also use `AttachmentSource` in the other
`send_attachment` APIs, on `Room` and `RoomSendQueue`, for consistency.

---------

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-01-06 15:44:29 +01:00
Benjamin Bouvier 1480fada6e refactor(event cache): make it clearer that vecdiff updates must be handled with with_events_mut
Every caller to `with_events_mut` must propagate the vector diff
updates, otherwise updates would be missing to the room event cache's
observers. This slightly tweaks the signature to make this a bit
clearer, and adjusts the code comment as well.
2025-01-06 13:14:31 +01:00
Kévin Commaille c50358366f refactor!(sdk): Set thumbnail in AttachmentConfig with builder method instead of constructor
`AttachmentConfig::with_thumbnail()` is replaced by
`AttachmentConfig::new().thumbnail()`.

Simplifies the use of `AttachmentConfig`, by avoiding code like:

```rust
let config = if let Some(thumbnail) = thumbnail {
  AttachmentConfig::with_thumbnail(thumbnail)
} else {
  AttachmentConfig::new()
};
```

---------

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2024-12-22 17:45:04 +00:00
Valere adb4428a69 test(crypto): Add some basic snapshot testing in crypto crate 2024-12-20 19:52:37 +01:00
Damir Jelić 667a8e684c chore: Fix a typo in the changelog 2024-12-20 13:59:54 +01:00
Ivan Enderlin f4b50db972 test: Increase timeout for codecoverage. 2024-12-20 13:57:45 +01:00
Ivan Enderlin 1abb2efc51 refactor(sdk): Rename two variables. 2024-12-20 13:57:45 +01:00
Ivan Enderlin c4132252d3 feat(ui): Enable TimelineSettings::vectordiffs_as_inputs if event cache storage is enabled.
This patch automatically enables
`TimelineSettings::vectordiffs_as_inputs` if and only if the event cache
storage is enabled.
2024-12-20 13:57:45 +01:00
Ivan Enderlin 51c76a15ad chore(ui): Make Clippy happy. 2024-12-20 13:57:45 +01:00
Ivan Enderlin f1842ba5d0 refactor(ui): Timeline receives pagination events as VectorDiffs!
This patch allows the paginated events of a `Timeline` to be received
via `RoomEventCacheUpdate::UpdateTimelineEvents` as `VectorDiff`s.
2024-12-20 13:57:45 +01:00
Ivan Enderlin d8dd72fd9c refactor(ui): Deduplicate timeline items conditionnally.
A previous patch deduplicates the remote events conditionnally. This
patch does the same but for timeline items.

The `Timeline` has its own deduplication algorithm (for remote
events, and for timeline items). The `Timeline` is about to receive
its updates via the `EventCache` which has its own deduplication
mechanism (`matrix_sdk::event_cache::Deduplicator`). To avoid conflicts
between the two, we conditionnally deduplicate timeline items based on
`TimelineSettings::vectordiffs_as_inputs`.

This patch takes the liberty to refactor the deduplication mechanism of
the timeline items to make it explicit with its own methods, so
that it can be re-used for `TimelineItemPosition::At`. A specific
short-circuit was present before, which is no more possible with the
rewrite to a generic mechanism. Consequently, when a local timeline
item becomes a remote timeline item, it was previously updated (via
`ObservableItems::replace`), but now the local timeline item is removed
(via `ObservableItems::remove`), and then the remote timeline item is
inserted (via `ObservableItems::insert`). Depending of whether a virtual
timeline item like a date divider is around, the position of the removal
and the insertion might not be the same (!), which is perfectly fine as
the date divider will be re-computed anyway. The result is exactly the
same, but the `VectorDiff` updates emitted by the `Timeline` are a bit
different (different paths, same result).

This is why this patch needs to update a couple of tests.
2024-12-20 13:57:45 +01:00
Ivan Enderlin 054f5e28f6 fix(common): Use a trick to avoid hitting the recursion_limit too quickly.
This patch adds a trick around `SyncTimelineEvent` to avoid reaching the
`recursion_limit` too quickly. Read the documentation in this patch to
learn more.
2024-12-20 13:57:45 +01:00
Ivan Enderlin 38e35b99d0 test(ui): Increase the recursion_limit.
Since we have added a new variant to `RoomEventCacheUpdate`, a macro
hits the recursion limit. It needs to be updated in order for tests to
run again.
2024-12-20 13:57:45 +01:00
Ivan Enderlin 39afb531ef task(ui): DayDivider has been renamed DateDivider.
This patch updates this branch to `main` where `DayDivider` has been
renamed `DateDivider`.
2024-12-20 13:57:45 +01:00
Ivan Enderlin c1ff5ff49f refactor(ui): Deduplicate remote events conditionnally.
The `Timeline` has its own remote event deduplication mechanism. But
we are transitioning to receive updates from the `EventCache` via
`VectorDiff`, which are emitted via `RoomEvents`, which already runs its
own deduplication mechanism (`matrix_sdk::event_cache::Deduplicator`).
Deduplication from the `EventCache` will generate `VectorDiff::Remove`
for example. It can create a conflict with the `Timeline` deduplication
mechanism.

This patch updates the deduplication mechanism from the `Timeline`
when adding or updating remote events to be conditionnal: when
`TimelineSettings::vectordiffs_as_inputs` is enabled, the deduplication
mechanism of the `Timeline` is silent, it does nothing, otherwise it
runs.
2024-12-20 13:57:45 +01:00
Ivan Enderlin 2358e4c32f task(ui): Support VectorDiff::Remove, in TimelineStateTransaction::handle_remote_events_with_diffs.
This patch updates
`TimelineStateTransaction::handle_remote_events_with_diffs` to support
`VectorDiff::Remove,`.
2024-12-20 13:57:45 +01:00
Ivan Enderlin 409fccb709 task(ui): Support VectorDiff::Insert in TimelineStateTransaction::handle_remote_events_with_diffs.
This patch updates
`TimelineStateTransaction::handle_remote_events_with_diffs` to support
`VectorDiff::Insert`.
2024-12-20 13:57:45 +01:00
Ivan Enderlin b25fd830ec task(ui): Add AllRemoteEvents::range.
This patch adds the `AllRemoteEvents::range` method. This
is going to be useful to support `VectorDiff::Insert` inside
`TimelineStateTransaction::handle_remote_events_with_diffs`.
2024-12-20 13:57:45 +01:00
Ivan Enderlin 02ab57870a task(ui): Add ObservableItems::insert_remote_event.
This patch adds the `ObservavbleItems::insert_remote_event` method.
This is going to be useful to implement `VectorDiff::Insert` inside
`TimelineStateTransaction::handle_remote_events_with_diffs`.
2024-12-20 13:57:45 +01:00
Ivan Enderlin eca3749b28 task(ui): Support VectorDiff::Clear in TimelineStateTransaction::handle_remote_events_with_diffs.
This patch updates
`TimelineStateTransaction::handle_remote_events_with_diffs` to support
`VectorDiff::Clear`.
2024-12-20 13:57:45 +01:00
Ivan Enderlin 3f17325bac task(ui): Support VectorDiff::PushBack in TimelineStateTransaction::handle_remote_events_with_diffs.
This patch updates
`TimelineStateTransaction::handle_remote_events_with_diffs` to support
`VectorDiff::PushBack`.
2024-12-20 13:57:45 +01:00
Ivan Enderlin 23c09b2c9d task(ui): Support VectorDiff::PushFront in TimelineStateTransaction::handle_remote_events_with_diffs.
This patch updates
`TimelineStateTransaction::handle_remote_events_with_diffs` to support
`VectorDiff::PushFront`.
2024-12-20 13:57:45 +01:00
Ivan Enderlin c1f8232450 task(ui): Support VectorDiff::Append in TimelineStateTransaction::handle_remote_events_with_diffs.
This patch updates
`TimelineStateTransaction::handle_remote_events_with_diffs` to support
`VectorDiff::Append`.
2024-12-20 13:57:45 +01:00
Ivan Enderlin e28073361d feat(ui): Add blank handle_remote_events_with_diffs. 2024-12-20 13:57:45 +01:00
Ivan Enderlin 1c2fb1ab72 refactor(sdk): Add RoomEventCacheUpdate::UpdateTimelineEvents.
This patch adds a new variant to `RoomEventCacheUpdate`, namely
`UpdateTimelineEvents. It's going to replace `AddTimelineEvents` soon
once it's stable enough. This is a transition. They are read by the
`Timeline` if and only if `TimelineSettings::vectordiffs_as_inputs` is
turned on.
2024-12-20 13:57:45 +01:00
Ivan Enderlin be89e3aacb feat(ui): Add TimelineBuilder::with_vectordiffs_as_inputs.
This patch adds `with_vectordiffs_as_inputs` on `TimelineBuilder` and
`vectordiffs_as_inputs` on `TimelineSettings`. This new flag allows to
transition from one system to another for the `Timeline`, when enabled,
the `Timeline` will accept `VectorDiff<SyncTimelineEvent>` for the
inputs instead of `Vec<SyncTimelineEvent>`.
2024-12-20 13:57:45 +01:00
Damir Jelić 36427b0e12 fix(ui): Consider banned rooms as rooms we left in the non-left rooms matcher
Recently we started to differentiate between rooms we've been banned
from from rooms we have left on our own.

Sadly the non-left rooms matcher only checked if the room state is not
equal to the Left state. This then accidentally moved all the banned
rooms to be considered as non-left.

We replace the single if expression with a match and list all the
states, this way we're going to be notified by the compiler that we need
to consider any new states we add in the future.
2024-12-20 12:35:34 +01:00
Daniel Salinas f8a9d12c88 Use a type alias to allow bindings to take advantage of custom types 2024-12-20 10:46:13 +01:00
Benjamin Bouvier 5f5e979e16 refactor!: Put the RequestConfig argument of Client::send() into a builder method
Instead of `Client::send(request, request_config)`, consumers can now do
`Client::send(request).with_request_config(request_config)`.
2024-12-20 10:35:18 +01:00
Valere 519f281844 Merge pull request #4428 from matrix-org/valere/insta_rs_snapshot_testing
test(snapshot): Use snapshot testing in sdk-common
2024-12-19 18:30:49 +01:00
Valere 3b31bbec0c test(snapshot): Use snapshot testing in sdk-common 2024-12-19 18:11:55 +01:00
Benjamin Bouvier f2942db316 refactor: avoid use of async_trait for RoomIdentityProvider
This is an 8 seconds (out of 22) decrease of the matrix-sdk compile
times.
2024-12-19 17:38:59 +01:00
Andy Balaam e4712be946 task(crypto): Support receiving stable identifier for MSC4147 2024-12-19 15:27:34 +00:00
Benjamin Bouvier bc8c4f5e58 fix(event cache): don't touch the linked chunk if an operation wouldn't cause meaningful changes
See comment on top of `deduplicated_all_new_events`.
2024-12-19 14:19:55 +01:00
Benjamin Bouvier fe9354a886 test: make test_room_keys_received_on_notification_client_trigger_redecryption more stable
When starting to back-paginate, in this test, we:

- either have a previous-batch token, that points to the first event
*before* the message was sent,
- or have no previous-batch token, because we stopped sync before
receiving the first sync result.

Because of the behavior introduced in 944a9220, we don't restart
back-paginating from the end, if we've reached the start. Now, if we are
in the case described by the first bullet item, then we may backpaginate
until the start of the room, and stop then, because we've back-paginated
all events. And so we'll never see the message sent by Alice after we
stopped sync'ing.

One solution to get to the desired state is to clear the internal state
of the room event cache, thus deleting the previous-batch token, thus
causing the situation described in the second bullet item. This achieves
what we want, that is, back-paginating from the end of the timeline.
2024-12-19 14:19:55 +01:00
Benjamin Bouvier d00ff8fa1f refactor(event cache): remove duplicated method RoomEventCacheState::clear() 2024-12-19 14:19:55 +01:00
Benjamin Bouvier 60f521cc23 feat(event cache): don't add a previous gap if all events were deduplicated, after back-pagination 2024-12-19 14:19:55 +01:00
Benjamin Bouvier bcb9a86a00 feat(event cache): don't add a previous gap if all events were deduplicated, after sync 2024-12-19 14:19:55 +01:00
Benjamin Bouvier 3f0712010f refactor(event cache): add a way to know if we deduplicated all events (at least one) 2024-12-19 14:19:55 +01:00
Benjamin Bouvier d89194f071 refactor: display source pagination error in PaginatorError::SdkError 2024-12-19 14:19:55 +01:00
Benjamin Bouvier a20ad728b5 feat(event cache): don't restart back-pagination from the end if we had no prev-batch token 2024-12-19 14:19:55 +01:00
Benjamin Bouvier 0d546dce5f refactor(event cache): move PaginationToken from the pagination to room/mod file 2024-12-19 14:19:55 +01:00
Jorge Martín 38cc9fb7c8 test(room): Improve Room::room_member_updates_sender tests 2024-12-19 14:14:05 +01:00
Jorge Martín 616c193a30 feat(room): create a cleanup task in Room::subscribe_to_knock_requests
This cleanup task will run while the knock request subscription runs and will use the `Room::room_member_updates_sender` notification to call `Room::remove_outdated_seen_knock_requests_ids` and remove outdated seen knock request ids automatically.
2024-12-19 14:14:05 +01:00
Jorge Martín 4a88e7cfee feat(room): add BaseRoom::remove_outdated_seen_knock_requests_ids fn
This will check the current seen knock request ids against the room members related to them and will remove those seen ids for members which are no longer in knock state or come from an outdated knock member event.
2024-12-19 14:14:05 +01:00
Jorge Martín 5d0fed5e53 feat(room): add helper methods to BaseRoom to get and write the current seen knock request ids while keeping them thread-safe with a lock around them 2024-12-19 14:14:05 +01:00
Jorge Martín 9975365a1e feat(room): add Room::room_member_updates_sender
This sender will notify receivers when new room members are received: this can happen either when reloading the full room member list from the HS or when new member events arrive during a sync.

The sender will emit a `RoomMembersUpdate`, which can be either a full reload or a partial one, including the user ids of the members that changed.
2024-12-19 14:14:05 +01:00
Integral f18e0b18a1 Replace PathBuf/Utf8PathBuf with Path/Utf8Path when ownership not needed 2024-12-19 13:29:09 +01:00
Jorge Martín b18100228e test(room): add test to verify how Room::observe_events will behave when several events are received in a short while 2024-12-19 12:16:49 +01:00
Benjamin Bouvier de568837fb fix(linked chunk): fix order handling of initial chunks in UpdateToVectorDiff::new()
The code would use a chunk iterator that moves forward, but call
`push_front()` repetitively on each chunk, semantically storing the
lengths in *reverse* order.

This could result in subsequent panics, when a new chunk was added,
because the links would not match what's expected (e.g. the last chunk
must have no successor, etc.).
2024-12-18 19:50:25 +01:00
Ivan Enderlin bc582ae101 doc(common): Update documentation of AsVector.
This patch updates the documentation of `AsVector`.
2024-12-18 19:47:25 +01:00
777 changed files with 131008 additions and 51455 deletions
+9
View File
@@ -2,3 +2,12 @@
retries = { backoff = "exponential", count = 3, delay = "1s", jitter = true }
# kill the slow tests if they still aren't up after 180s
slow-timeout = { period = "60s", terminate-after = 3 }
[profile.ci]
retries = { backoff = "exponential", count = 4, delay = "1s", jitter = true }
# kill the slow tests if they still aren't up after 180s
slow-timeout = { period = "60s", terminate-after = 3 }
[profile.ci.junit]
path = "junit.xml"
store-success-output = true
+7 -15
View File
@@ -9,8 +9,7 @@ exclude = [
[advisories]
version = 2
ignore = [
{ id = "RUSTSEC-2023-0071", reason = "We are not using RSA directly, nor do we depend on the RSA crate directly" },
{ id = "RUSTSEC-2024-0384", reason = "Unmaintained backoff crate, not critical. We'll migrate soon." },
{ id = "RUSTSEC-2024-0436", reason = "Unmaintained paste crate, not critical." },
]
[licenses]
@@ -18,6 +17,7 @@ version = 2
allow = [
"Apache-2.0",
"Apache-2.0 WITH LLVM-exception",
"CDLA-Permissive-2.0",
"BSD-2-Clause",
"BSD-3-Clause",
"BSL-1.0",
@@ -29,15 +29,6 @@ allow = [
]
exceptions = [
{ allow = ["Unicode-DFS-2016"], crate = "unicode-ident" },
{ allow = ["CDDL-1.0"], crate = "inferno" },
{ allow = ["LicenseRef-ring"], crate = "ring" },
]
[[licenses.clarify]]
name = "ring"
expression = "LicenseRef-ring"
license-files = [
{ path = "LICENSE", hash = 0xbd0eed23 },
]
[bans]
@@ -52,13 +43,14 @@ unknown-git = "deny"
allow-git = [
# A patch override for the bindings fixing a bug for Android before upstream
# releases a new version.
"https://github.com/element-hq/tracing.git",
# Sam as for the tracing dependency.
"https://github.com/element-hq/paranoid-android.git",
"https://github.com/tokio-rs/tracing.git",
# Well, it's Ruma.
"https://github.com/ruma/ruma",
# A patch override for the bindings: https://github.com/rodrimati1992/const_panic/pull/10
"https://github.com/jplatte/const_panic",
# A patch override for the bindings: https://github.com/smol-rs/async-compat/pull/22
"https://github.com/jplatte/async-compat",
"https://github.com/element-hq/async-compat",
# We can release vodozemac whenever we need but let's not block development
# on releases.
"https://github.com/matrix-org/vodozemac",
]
+26 -40
View File
@@ -1,54 +1,40 @@
name: Benchmarks
on:
push:
branches:
- "main"
pull_request:
workflow_dispatch:
jobs:
benchmarks:
name: Run Benchmarks
runs-on: ubuntu-latest
environment: matrix-rust-bot
if: github.event_name == 'push'
strategy:
matrix:
benchmark:
- crypto_bench
- event_cache
- linked_chunk
- store_bench
- timeline
steps:
- name: Checkout the repo
uses: actions/checkout@v4
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2024-11-26
components: rustfmt
- name: Setup rust toolchain, cache and cargo-codspeed binary
uses: moonrepo/setup-rust@ede6de059f8046a5e236c94046823e2af11ca670
with:
channel: stable
cache-target: release
bins: cargo-codspeed
- name: Run Benchmarks
run: cargo bench | tee benchmark-output.txt
- name: Build the benchmark target(s)
run: cargo codspeed build -p benchmarks ${{ matrix.benchmark }} --features codspeed
- name: Check benchmark result for PR
if: github.event_name == 'pull_request'
uses: benchmark-action/github-action-benchmark@v1
with:
name: Rust Benchmark
tool: 'cargo'
output-file-path: benchmark-output.txt
auto-push: false
# comment to alert the user this has gone bad
github-token: ${{ secrets.MRB_ACCESS_TOKEN }}
alert-threshold: '120%'
comment-on-alert: true
fail-threshold: '150%'
fail-on-alert: true
- name: Store benchmark result
if: github.event_name != 'pull_request'
uses: benchmark-action/github-action-benchmark@v1
with:
name: Rust Benchmark
tool: 'cargo'
output-file-path: benchmark-output.txt
github-token: ${{ secrets.GITHUB_TOKEN }}
auto-push: true
# Show alert with commit comment on detecting possible performance regression
alert-threshold: '150%'
comment-on-alert: true
fail-on-alert: true
alert-comment-cc-users: '@gnunicornBen,@jplatte,@poljar'
- name: Run the benchmarks
uses: CodSpeedHQ/action@76578c2a7ddd928664caa737f0e962e3085d4e7c
with:
run: cargo codspeed run
token: ${{ secrets.CODSPEED_TOKEN }}
+8 -8
View File
@@ -31,7 +31,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Install protoc
uses: taiki-e/install-action@v2
@@ -69,23 +69,23 @@ jobs:
steps:
- name: Checkout Rust SDK
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Checkout Kotlin Rust Components project
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
repository: matrix-org/matrix-rust-components-kotlin
path: rust-components-kotlin
ref: main
- name: Use JDK 17
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '17'
- name: Install android sdk
uses: malinskiy/action-android/install-sdk@release/0.1.4
uses: malinskiy/action-android/install-sdk@release/0.1.7
- name: Install android ndk
uses: nttld/setup-ndk@v1
@@ -136,7 +136,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
# install protoc in case we end up rebuilding opentelemetry-proto
- name: Install protoc
@@ -175,7 +175,7 @@ jobs:
run: swift test
- name: Build Framework
run: target/debug/xtask swift build-framework --target=aarch64-apple-ios --profile=reldbg
run: target/debug/xtask swift build-framework --target=aarch64-apple-ios --profile=dev --ios-deployment-target=18.0
complement-crypto:
name: "Run Complement Crypto tests"
@@ -191,7 +191,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
# install protoc in case we end up rebuilding opentelemetry-proto
- name: Install protoc
+64 -65
View File
@@ -6,11 +6,6 @@ on:
branches: [main]
pull_request:
branches: [main]
types:
- opened
- reopened
- synchronize
- ready_for_review
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -18,6 +13,9 @@ concurrency:
env:
CARGO_TERM_COLOR: always
# Insta.rs is run directly via cargo test. We don't want insta.rs to create new snapshots files.
# Just want it to run the tests (option `no` instead of `auto`).
INSTA_UPDATE: no
jobs:
xtask:
@@ -36,14 +34,16 @@ jobs:
- no-sqlite
- no-encryption-and-sqlite
- sqlite-cryptostore
- experimental-encrypted-state-events
- rustls-tls
- markdown
- socks
- sso-login
- search
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
@@ -85,7 +85,7 @@ jobs:
steps:
- name: Checkout the repo
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
@@ -116,7 +116,7 @@ jobs:
steps:
- name: Checkout the repo
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Install libsqlite
run: |
@@ -167,7 +167,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Install protoc
uses: taiki-e/install-action@v2
@@ -221,12 +221,16 @@ jobs:
- name: '[m]-common'
cmd: matrix-sdk-common
- name: '[m], no-default'
cmd: matrix-sdk-no-default
- name: '[m]-ui'
cmd: matrix-sdk-ui
check_only: true
- name: '[m]-indexeddb'
cmd: indexeddb
- name: '[m], no-default, wasm-flags'
cmd: matrix-sdk-no-default
- name: '[m], indexeddb stores'
cmd: matrix-sdk-indexeddb-stores
@@ -235,7 +239,7 @@ jobs:
steps:
- name: Checkout the repo
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
@@ -244,9 +248,10 @@ jobs:
components: clippy
- name: Install wasm-pack
uses: qmaru/wasm-pack-action@v0.5.0
uses: qmaru/wasm-pack-action@v0.5.1
if: '!matrix.check_only'
with:
version: v0.10.3
version: v0.13.1
- name: Load cache
uses: Swatinem/rust-cache@v2
@@ -274,46 +279,29 @@ jobs:
target/debug/xtask ci wasm ${{ matrix.cmd }}
- name: Wasm-Pack test
if: '!matrix.check_only'
run: |
target/debug/xtask ci wasm-pack ${{ matrix.cmd }}
formatting:
name: Check Formatting
runs-on: ubuntu-latest
steps:
- name: Checkout the repo
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2024-11-26
components: rustfmt
- name: Cargo fmt
run: |
cargo fmt -- --check
typos:
name: Spell Check with Typos
runs-on: ubuntu-latest
steps:
- name: Checkout Actions Repository
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Check the spelling of the files in our repo
uses: crate-ci/typos@v1.28.3
uses: crate-ci/typos@v1.35.7
clippy:
name: Run clippy
lint:
name: Lint
needs: xtask
runs-on: ubuntu-latest
steps:
- name: Checkout the repo
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Install protoc
uses: taiki-e/install-action@v2
@@ -323,8 +311,8 @@ jobs:
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2024-11-26
components: clippy
toolchain: nightly-2025-08-08
components: clippy, rustfmt
- name: Load cache
uses: Swatinem/rust-cache@v2
@@ -338,36 +326,21 @@ jobs:
key: "${{ needs.xtask.outputs.cachekey-linux }}"
fail-on-cache-miss: true
- name: Check Formatting
run: |
target/debug/xtask ci style
- name: Clippy
run: |
target/debug/xtask ci clippy
integration-tests:
name: Integration test
name: 'Integration test (features: ${{ matrix.feature }})'
runs-on: ubuntu-latest
# run several docker containers with the same networking stack so the hostname 'postgres'
# maps to the postgres container, etc.
# run several docker containers with the same networking stack so the hostname 'synapse'
# maps to the synapse container, etc.
services:
# synapse needs a postgres container
postgres:
# Docker Hub image
image: postgres
# Provide the password for postgres
env:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
POSTGRES_DB: syncv3
# Set health checks to wait until postgres has started
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
# Maps tcp port 5432 on service container to the host
- 5432:5432
# tests need a synapse: this is a service and not michaelkaye/setup-matrix-synapse@main as the
# latter does not provide networking for services to communicate with it.
synapse:
@@ -378,9 +351,16 @@ jobs:
ports:
- 8008:8008
strategy:
fail-fast: true
matrix:
feature:
- "default"
- "experimental-encrypted-state-events"
steps:
- name: Checkout the repo
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Install libsqlite
run: |
@@ -403,6 +383,25 @@ jobs:
RUST_LOG: "info,matrix_sdk=trace"
HOMESERVER_URL: "http://localhost:8008"
HOMESERVER_DOMAIN: "synapse"
SLIDING_SYNC_PROXY_URL: "http://localhost:8118"
run: |
cargo nextest run -p matrix-sdk-integration-testing
cargo nextest run -p matrix-sdk-integration-testing --features "${{ matrix.feature }}"
compile-bench:
name: 🚄 Compile benchmarks
runs-on: ubuntu-latest
steps:
- name: Checkout the repo
uses: actions/checkout@v5
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Load cache
uses: Swatinem/rust-cache@v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Compile benchmarks (no run)
run: |
cargo bench --profile dev --no-run
+91 -39
View File
@@ -5,11 +5,6 @@ on:
branches: [main]
pull_request:
branches: [main]
types:
- opened
- reopened
- synchronize
- ready_for_review
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -22,30 +17,17 @@ env:
RUST_LOG: info,matrix_sdk=trace
jobs:
xtask:
uses: ./.github/workflows/xtask.yml
code_coverage:
name: Code Coverage
needs: xtask
runs-on: "ubuntu-latest"
# run several docker containers with the same networking stack so the hostname 'postgres'
# maps to the postgres container, etc.
# run several docker containers with the same networking stack so the hostname 'synapse'
# maps to the synapse container, etc.
services:
postgres:
# Docker Hub image
image: postgres
# Provide the password for postgres
env:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
POSTGRES_DB: syncv3
# Set health checks to wait until postgres has started
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
# Maps tcp port 5432 on service container to the host
- 5432:5432
# tests need a synapse: this is a service and not michaelkaye/setup-matrix-synapse@main as the
# latter does not provide networking for services to communicate with it.
synapse:
@@ -57,8 +39,65 @@ jobs:
- 8008:8008
steps:
# This CI workflow can run into space issue, so we're cleaning up some
# space here.
- name: Create some more space
run: |
echo "Disk space before cleanup"
df -h
cd /opt
find . -maxdepth 1 -mindepth 1 '!' -path ./containerd '!' -path ./actionarchivecache '!' -path ./runner '!' -path ./runner-cache -exec rm -rf '{}' ';'
rm -rf /opt/hostedtoolcache
# Get rid of binaries and libs we're not interested in.
sudo rm -rf \
/usr/local/julia* \
/usr/local/aws*
sudo rm -rf \
/usr/local/bin/minikube \
/usr/local/bin/node \
/usr/local/bin/stack \
/usr/local/bin/bicep \
/usr/local/bin/pulumi* \
/usr/local/bin/helm \
/usr/local/bin/azcopy \
/usr/local/bin/packer \
/usr/local/bin/cmake-gui \
/usr/local/bin/cpack
sudo rm -rf \
/usr/local/share/powershell \
/usr/local/share/chromium
sudo rm -rf /usr/local/lib/android
echo "::group::/usr/local/bin/*"
du -hsc /usr/local/bin/* | sort -h
echo "::endgroup::"
echo "::group::/usr/local/share/*"
du -hsc /usr/local/share/* | sort -h
echo "::endgroup::"
echo "::group::/usr/local/*"
du -hsc /usr/local/* | sort -h
echo "::endgroup::"
echo "::group::/usr/local/lib/*"
du -hsc /usr/local/lib/* | sort -h
echo "::endgroup::"
echo "::group::/opt/*"
du -hsc /opt/* | sort -h
echo "::endgroup::"
echo "Disk space after cleanup"
df -h
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
ref: ${{ github.event.pull_request.head.sha }}
@@ -66,6 +105,8 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install libsqlite3-dev
sudo apt-get clean
sudo rm -rf /var/lib/apt/lists/*
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
@@ -78,29 +119,34 @@ jobs:
- name: Load cache
uses: Swatinem/rust-cache@v2
with:
prefix-key: "coverage"
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install tarpaulin
uses: taiki-e/install-action@v2
with:
tool: cargo-tarpaulin
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
# set up backend for integration tests
- uses: actions/setup-python@v5
with:
python-version: 3.8
- name: Install nextest
uses: taiki-e/install-action@nextest
- name: Run tarpaulin
- name: Get xtask
uses: actions/cache/restore@v4
with:
path: target/debug/xtask
key: "${{ needs.xtask.outputs.cachekey-linux }}"
fail-on-cache-miss: true
- name: Check total disk space before running
run: |
rustup run stable cargo tarpaulin \
--skip-clean --profile cov --out xml \
--features experimental-widgets,testing
df -h
- name: Create the coverage report
run: |
target/debug/xtask ci coverage -o codecov
env:
CARGO_PROFILE_COV_INHERITS: 'dev'
CARGO_PROFILE_COV_DEBUG: 1
HOMESERVER_URL: "http://localhost:8008"
HOMESERVER_DOMAIN: "synapse"
SLIDING_SYNC_PROXY_URL: "http://localhost:8118"
# Copied with minimal adjustments, source:
# https://github.com/google/mdbook-i18n-helpers/blob/2168b9cea1f4f76b55426591a9bcc308a620194f/.github/workflows/test.yml
@@ -112,6 +158,11 @@ jobs:
echo "Storing commit SHA ${{ github.event.pull_request.head.sha }}"
echo "${{ github.event.pull_request.head.sha }}" > commit_sha.txt
- name: Move the JUnit file into the root directory
shell: bash
run: |
mv target/nextest/ci/junit.xml ./junit.xml
# This stores the coverage report and metadata in artifacts.
# The actual upload to Codecov is executed by a different workflow `upload_coverage.yml`.
# The reason for this split is because `on.pull_request` workflows don't have access to secrets.
@@ -120,7 +171,8 @@ jobs:
with:
name: codecov_report
path: |
cobertura.xml
coverage.xml
junit.xml
pr_number.txt
commit_sha.txt
if-no-files-found: error
+1 -1
View File
@@ -10,5 +10,5 @@ jobs:
cargo-deny:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: EmbarkStudios/cargo-deny-action@v2
+35
View File
@@ -0,0 +1,35 @@
# Check if the path of changed file is longer than 260 characters
# that windows filesystem allows
name: Detect long path among changed files
on:
workflow_dispatch:
pull_request: # focus on the changed files in current PR
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
long-path:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Check for changed files
id: changed-files
uses: tj-actions/changed-files@v46.0.5
- name: Detect long path
env:
ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} # ignore the deleted files
MAX_LENGTH: 120 # set max length to 120, considering the base path of app project that uses matrix-sdk
run: |
for file in ${ALL_CHANGED_FILES}; do
if [ ${#file} -gt $MAX_LENGTH ]; then
echo "File path is too long. Length: ${#file}, Path: $file"
exit 1
fi
done
exit 0
@@ -7,6 +7,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Machete
uses: bnjbvr/cargo-machete@main
uses: bnjbvr/cargo-machete@v0.9.1
+3 -8
View File
@@ -4,11 +4,6 @@ on:
push:
branches: [main]
pull_request:
types:
- opened
- reopened
- synchronize
- ready_for_review
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -26,7 +21,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Install protoc
uses: taiki-e/install-action@v2
@@ -36,7 +31,7 @@ jobs:
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2024-11-26
toolchain: nightly-2025-08-08
- name: Install Node.js
uses: actions/setup-node@v4
@@ -57,7 +52,7 @@ jobs:
- name: Upload artifact
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: actions/upload-pages-artifact@v3
uses: actions/upload-pages-artifact@v4
with:
path: './target/doc/'
+1 -1
View File
@@ -7,6 +7,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Block Fixup Commit Merge
uses: 13rac1/block-fixup-merge-action@v2.0.0
+1 -6
View File
@@ -6,16 +6,11 @@ on:
branches: [main]
pull_request:
branches: [main]
types:
- opened
- reopened
- synchronize
- ready_for_review
jobs:
msrv:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: taiki-e/install-action@cargo-hack
- run: cargo hack check --rust-version --workspace --all-targets --ignore-private
+18 -1
View File
@@ -58,7 +58,7 @@ jobs:
echo "override_commit=$(<commit_sha.txt)" >> "$GITHUB_OUTPUT"
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
ref: ${{ steps.parse_previous_artifacts.outputs.override_commit || '' }}
path: repo_root
@@ -77,3 +77,20 @@ jobs:
working-directory: ${{ github.workspace }}/repo_root
# Location where coverage report files are searched for
directory: ${{ github.workspace }}
- name: Upload test results to Codecov
uses: codecov/test-results-action@v1
with:
token: ${{ secrets.CODECOV_UPLOAD_TOKEN }}
fail_ci_if_error: true
# Manual overrides for these parameters are needed because automatic detection
# in codecov-action does not work for non-`pull_request` workflows.
# In `main` branch push, these default to empty strings since we want to run
# the analysis on HEAD.
override_commit: ${{ steps.parse_previous_artifacts.outputs.override_commit || '' }}
override_pr: ${{ steps.parse_previous_artifacts.outputs.override_pr || '' }}
working-directory: ${{ github.workspace }}/repo_root
# Location where coverage report files are searched for
directory: ${{ github.workspace }}
+1 -1
View File
@@ -43,7 +43,7 @@ jobs:
steps:
- name: Checkout repo
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Calculate cache key
id: cachekey
+1
View File
@@ -4,6 +4,7 @@ master.zip
emsdk-*
.idea/
.env
.envrc
.build
.swiftpm
/Package.swift
+44
View File
@@ -30,6 +30,50 @@ integration tests that need a running synapse instance. These tests reside in
synapse for testing purposes.
### Snapshot Testing
You can add/review snapshot tests using [insta.rs](https://insta.rs)
Every new struct/enum that derives `Serialize` `Deserialise` should have a snapshot test for it.
Any code change that breaks serialisation will then break a test, the author will then have to decide
how to handle migration and test it if needed.
And for an improved review experience it's recommended (but not necessary) to install the cargo-insta tool:
Unix:
```
curl -LsSf https://insta.rs/install.sh | sh
```
Windows:
```
powershell -c "irm https://insta.rs/install.ps1 | iex"
```
Usual flow is to first run the test, then review them.
```
cargo insta test
cargo insta review
```
### Intermittent failure policy
While we strive to add test coverage for as many features as we can, it sometimes happens that the
tests will be intermittently failing in CI (such tests are sometimes called "flaky"). This can be
caused by race conditions of all sorts, either in the test code itself, but sometimes in the
underlying feature being tested too, and as such, it requires some investigation, usually from the
original author of the test.
Whenever such an intermittent failure happens, we try to open an issue to track the failures,
adding the
[`intermittent-failure`](https://github.com/matrix-org/matrix-rust-sdk/issues?q=is%3Aissue%20state%3Aopen%20label%3Aintermittent-failure)
label to it, and commenting with links to CI runs where the failure happened.
If a test has been intermittently failing for **two weeks** or more, and no one is actively working
on fixing it, then we might decide to mark the test as `ignored` until it is fixed, to not cause
unrelated failures in other contributors' pull requests and pushes.
## Pull requests
Ideally, a PR should have a *proper title*, with *atomic logical commits*, and
Generated
+1782 -1357
View File
File diff suppressed because it is too large Load Diff
+120 -90
View File
@@ -4,147 +4,129 @@ members = [
"bindings/matrix-sdk-crypto-ffi",
"bindings/matrix-sdk-ffi",
"crates/*",
"testing/*",
"examples/*",
"labs/*",
"testing/*",
"uniffi-bindgen",
"xtask",
]
exclude = [
"testing/data",
]
exclude = ["testing/data"]
# xtask, testing and the bindings should only be built when invoked explicitly.
default-members = ["benchmarks", "crates/*", "labs/*"]
resolver = "2"
[workspace.package]
rust-version = "1.82"
rust-version = "1.88"
[workspace.dependencies]
anyhow = "1.0.93"
anyhow = "1.0.99"
aquamarine = "0.6.0"
as_variant = "1.3.0"
assert-json-diff = "2.0.2"
assert_matches = "1.5.0"
assert_matches2 = "0.1.2"
async-compat = "0.2.5"
async-rx = "0.1.3"
# Bumping this to 0.3.6 produces a test failure because the semantic between the
# versions changed subtly.
async-stream = "0.3.5"
async-trait = "0.1.83"
as_variant = "1.2.0"
async-trait = "0.1.89"
base64 = "0.22.1"
bitflags = "2.9.3"
byteorder = "1.5.0"
chrono = "0.4.38"
cfg-if = "1.0.3"
clap = "4.5.46"
chrono = "0.4.41"
dirs = "6.0.0"
eyeball = { version = "0.8.8", features = ["tracing"] }
eyeball-im = { version = "0.5.1", features = ["tracing"] }
eyeball-im-util = "0.7.0"
eyeball-im = { version = "0.7.0", features = ["tracing"] }
eyeball-im-util = "0.9.0"
futures-core = "0.3.31"
futures-executor = "0.3.21"
futures-executor = "0.3.31"
futures-util = "0.3.31"
getrandom = { version = "0.2.15", default-features = false }
gloo-timers = "0.3.0"
growable-bloom-filter = "2.1.1"
hkdf = "0.12.4"
hmac = "0.12.1"
http = "1.1.0"
imbl = "3.0.0"
indexmap = "2.6.0"
itertools = "0.13.0"
js-sys = "0.3.69"
http = "1.3.1"
imbl = "5.0.0"
indexmap = "2.11.0"
insta = { version = "1.43.1", features = ["json", "redactions"] }
itertools = "0.14.0"
js-sys = "0.3.77"
mime = "0.3.17"
once_cell = "1.20.2"
oauth2 = { version = "5.0.0", default-features = false, features = ["reqwest", "timing-resistant-secret-traits"] }
once_cell = "1.21.3"
pbkdf2 = { version = "0.12.2" }
pin-project-lite = "0.2.15"
proptest = { version = "1.5.0", default-features = false, features = ["std"] }
pin-project-lite = "0.2.16"
proptest = { version = "1.6.0", default-features = false, features = ["std"] }
rand = "0.8.5"
reqwest = { version = "0.12.4", default-features = false }
reqwest = { version = "0.12.23", default-features = false }
rmp-serde = "1.3.0"
ruma = { version = "0.12.0", features = [
ruma = { version = "0.13.0", features = [
"client-api-c",
"compat-upload-signatures",
"compat-user-id",
"compat-arbitrary-length-ids",
"compat-tag-info",
"compat-encrypted-stickers",
"compat-lax-room-create-deser",
"compat-lax-room-topic-deser",
"unstable-msc3401",
"unstable-msc3266",
"unstable-msc3488",
"unstable-msc3489",
"unstable-msc4075",
"unstable-msc4140",
"unstable-msc4143",
"unstable-msc4171",
"unstable-msc4222",
"unstable-msc4278",
"unstable-msc4286",
"unstable-msc4306",
"unstable-msc4308"
] }
ruma-common = "0.15.0"
serde = "1.0.151"
serde_html_form = "0.2.0"
serde_json = "1.0.91"
sha2 = "0.10.8"
similar-asserts = "1.6.0"
sentry = { version = "0.42.0", default-features = false }
sentry-tracing = "0.42.0"
serde = { version = "1.0.219", features = ["rc"] }
serde_html_form = "0.2.7"
serde_json = "1.0.143"
sha2 = "0.10.9"
similar-asserts = "1.7.0"
stream_assert = "0.1.1"
tempfile = "3.9.0"
thiserror = "2.0.3"
tokio = { version = "1.41.1", default-features = false, features = ["sync"] }
tokio-stream = "0.1.14"
tracing = { version = "0.1.40", default-features = false, features = ["std"] }
tracing-core = "0.1.32"
tracing-subscriber = "0.3.18"
tempfile = "3.21.0"
thiserror = "2.0.16"
tokio = { version = "1.47.1", default-features = false, features = ["sync"] }
tokio-stream = "0.1.17"
tracing = { version = "0.1.41", default-features = false, features = ["std"] }
tracing-appender = "0.2.3"
tracing-core = "0.1.34"
tracing-subscriber = "0.3.20"
unicode-normalization = "0.1.24"
uniffi = { version = "0.28.0" }
uniffi_bindgen = { version = "0.28.0" }
url = "2.5.4"
uuid = "1.11.0"
vodozemac = { version = "0.8.1", features = ["insecure-pk-encryption"] }
url = "2.5.7"
uuid = "1.18.0"
vergen-gitcl = "1.0.8"
vodozemac = { version = "0.9.0", features = ["insecure-pk-encryption"] }
wasm-bindgen = "0.2.84"
wasm-bindgen-test = "0.3.33"
wasm-bindgen-test = "0.3.50"
web-sys = "0.3.69"
wiremock = "0.6.2"
wiremock = "0.6.5"
zeroize = "1.8.1"
matrix-sdk = { path = "crates/matrix-sdk", version = "0.9.0", default-features = false }
matrix-sdk-base = { path = "crates/matrix-sdk-base", version = "0.9.0" }
matrix-sdk-common = { path = "crates/matrix-sdk-common", version = "0.9.0" }
matrix-sdk-crypto = { path = "crates/matrix-sdk-crypto", version = "0.9.0" }
matrix-sdk = { path = "crates/matrix-sdk", version = "0.14.0", default-features = false }
matrix-sdk-base = { path = "crates/matrix-sdk-base", version = "0.14.0" }
matrix-sdk-common = { path = "crates/matrix-sdk-common", version = "0.14.0" }
matrix-sdk-crypto = { path = "crates/matrix-sdk-crypto", version = "0.14.0" }
matrix-sdk-ffi-macros = { path = "bindings/matrix-sdk-ffi-macros", version = "0.7.0" }
matrix-sdk-indexeddb = { path = "crates/matrix-sdk-indexeddb", version = "0.9.0", default-features = false }
matrix-sdk-qrcode = { path = "crates/matrix-sdk-qrcode", version = "0.9.0" }
matrix-sdk-sqlite = { path = "crates/matrix-sdk-sqlite", version = "0.9.0", default-features = false }
matrix-sdk-store-encryption = { path = "crates/matrix-sdk-store-encryption", version = "0.9.0" }
matrix-sdk-test = { path = "testing/matrix-sdk-test", version = "0.7.0" }
matrix-sdk-ui = { path = "crates/matrix-sdk-ui", version = "0.9.0", default-features = false }
# Default release profile, select with `--release`
[profile.release]
lto = true
# Default development profile; default for most Cargo commands, otherwise
# selected with `--debug`
[profile.dev]
# Saves a lot of disk space. If symbols are needed, use the dbg profile.
debug = 0
[profile.dev.package]
# Optimize quote even in debug mode. Speeds up proc-macros enough to account
# for the extra time of optimizing it for a clean build of matrix-sdk-ffi.
quote = { opt-level = 2 }
sha2 = { opt-level = 2 }
# Custom profile with full debugging info, use `--profile dbg` to select
[profile.dbg]
inherits = "dev"
debug = 2
# Custom profile for use in (debug) builds of the binding crates, use
# `--profile reldbg` to select
[profile.reldbg]
inherits = "dbg"
opt-level = 3
[patch.crates-io]
async-compat = { git = "https://github.com/jplatte/async-compat", rev = "16dc8597ec09a6102d58d4e7b67714a35dd0ecb8" }
const_panic = { git = "https://github.com/jplatte/const_panic", rev = "9024a4cb3eac45c1d2d980f17aaee287b17be498" }
# Needed to fix rotation log issue on Android (https://github.com/tokio-rs/tracing/issues/2937)
tracing = { git = "https://github.com/element-hq/tracing.git", rev = "ca9431f74d37c9d3b5e6a9f35b2c706711dab7dd" }
tracing-core = { git = "https://github.com/element-hq/tracing.git", rev = "ca9431f74d37c9d3b5e6a9f35b2c706711dab7dd" }
tracing-subscriber = { git = "https://github.com/element-hq/tracing.git", rev = "ca9431f74d37c9d3b5e6a9f35b2c706711dab7dd" }
tracing-appender = { git = "https://github.com/element-hq/tracing.git", rev = "ca9431f74d37c9d3b5e6a9f35b2c706711dab7dd" }
paranoid-android = { git = "https://github.com/element-hq/paranoid-android.git", rev = "69388ac5b4afeed7be4401c70ce17f6d9a2cf19b" }
matrix-sdk-indexeddb = { path = "crates/matrix-sdk-indexeddb", version = "0.14.0", default-features = false }
matrix-sdk-qrcode = { path = "crates/matrix-sdk-qrcode", version = "0.14.0" }
matrix-sdk-sqlite = { path = "crates/matrix-sdk-sqlite", version = "0.14.0", default-features = false }
matrix-sdk-store-encryption = { path = "crates/matrix-sdk-store-encryption", version = "0.14.0" }
matrix-sdk-test = { path = "testing/matrix-sdk-test", version = "0.14.0" }
matrix-sdk-test-utils = { path = "testing/matrix-sdk-test-utils", version = "0.14.0" }
matrix-sdk-ui = { path = "crates/matrix-sdk-ui", version = "0.14.0", default-features = false }
matrix-sdk-search = { path = "crates/matrix-sdk-search", version = "0.14.0" }
[workspace.lints.rust]
rust_2018_idioms = "warn"
@@ -166,10 +148,58 @@ cloned_instead_of_copied = "warn"
dbg_macro = "warn"
inefficient_to_string = "warn"
macro_use_imports = "warn"
manual_let_else = "warn"
mut_mut = "warn"
needless_borrow = "warn"
nonstandard_macro_braces = "warn"
redundant_clone = "warn"
str_to_string = "warn"
todo = "warn"
unnecessary_semicolon = "warn"
unused_async = "warn"
redundant_clone = "warn"
# Default development profile; default for most Cargo commands, otherwise
# selected with `--debug`
[profile.dev]
# Saves a lot of disk space. If symbols are needed, use the dbg profile.
debug = 0
[profile.dev.package]
# Optimize quote even in debug mode. Speeds up proc-macros enough to account
# for the extra time of optimizing it for a clean build of matrix-sdk-ffi.
quote = { opt-level = 2 }
sha2 = { opt-level = 2 }
# faster runs for insta.rs snapshot testing
insta.opt-level = 3
similar.opt-level = 3
# Custom profile with full debugging info, use `--profile dbg` to select
[profile.dbg]
inherits = "dev"
debug = 2
# Custom profile for use in (debug) builds of the binding crates, use
# `--profile reldbg` to select
[profile.reldbg]
inherits = "dbg"
opt-level = 3
[profile.profiling]
inherits = "release"
# LTO is too slow to compile.
lto = false
# Get symbol names for profiling purposes.
debug = true
[profile.bench]
inherits = "release"
lto = false
[patch.crates-io]
async-compat = { git = "https://github.com/element-hq/async-compat", rev = "5a27c8b290f1f1dcfc0c4ec22c464e38528aa591" }
const_panic = { git = "https://github.com/jplatte/const_panic", rev = "9024a4cb3eac45c1d2d980f17aaee287b17be498" }
# Needed to fix rotation log issue on Android (https://github.com/tokio-rs/tracing/issues/2937)
tracing = { git = "https://github.com/tokio-rs/tracing.git", rev = "20f5b3d8ba057ca9c4ae00ad30dda3dce8a71c05" }
tracing-core = { git = "https://github.com/tokio-rs/tracing.git", rev = "20f5b3d8ba057ca9c4ae00ad30dda3dce8a71c05" }
tracing-subscriber = { git = "https://github.com/tokio-rs/tracing.git", rev = "20f5b3d8ba057ca9c4ae00ad30dda3dce8a71c05" }
tracing-appender = { git = "https://github.com/tokio-rs/tracing.git", rev = "20f5b3d8ba057ca9c4ae00ad30dda3dce8a71c05" }
+73 -26
View File
@@ -1,43 +1,90 @@
![Build Status](https://img.shields.io/github/actions/workflow/status/matrix-org/matrix-rust-sdk/ci.yml?style=flat-square)
[![codecov](https://img.shields.io/codecov/c/github/matrix-org/matrix-rust-sdk/main.svg?style=flat-square)](https://codecov.io/gh/matrix-org/matrix-rust-sdk)
[![License](https://img.shields.io/badge/License-Apache%202.0-yellowgreen.svg?style=flat-square)](https://opensource.org/licenses/Apache-2.0)
[![#matrix-rust-sdk](https://img.shields.io/badge/matrix-%23matrix--rust--sdk-blue?style=flat-square)](https://matrix.to/#/#matrix-rust-sdk:matrix.org)
[![Docs - Main](https://img.shields.io/badge/docs-main-blue.svg?style=flat-square)](https://matrix-org.github.io/matrix-rust-sdk/matrix_sdk/)
[![Docs - Stable](https://img.shields.io/crates/v/matrix-sdk?color=blue&label=docs&style=flat-square)](https://docs.rs/matrix-sdk)
<h1 align="center">Matrix Rust SDK</h1>
# matrix-rust-sdk
<div align="center">
<em>Your all-in-one toolkit for creating Matrix clients with Rust, from simple bots to full-featured apps.</em>
<br />
<img src="contrib/logo.svg">
<hr />
<a href="https://github.com/matrix-org/matrix-rust-sdk/releases">
<img src="https://img.shields.io/github/v/release/matrix-org/matrix-rust-sdk?style=flat&labelColor=1C2E27&color=66845F&logo=GitHub&logoColor=white"></a>
<a href="https://crates.io/crates/matrix-sdk/">
<img src="https://img.shields.io/crates/v/matrix-sdk?style=flat&labelColor=1C2E27&color=66845F&logo=Rust&logoColor=white"></a>
<a href="https://codecov.io/gh/matrix-org/matrix-rust-sdk">
<img src="https://img.shields.io/codecov/c/gh/matrix-org/matrix-rust-sdk?style=flat&labelColor=1C2E27&color=66845F&logo=Codecov&logoColor=white"></a>
<br />
<a href="https://docs.rs/matrix-sdk/">
<img src="https://img.shields.io/docsrs/matrix-sdk?style=flat&labelColor=1C2E27&color=66845F&logo=Rust&logoColor=white"></a>
<a href="https://github.com/matrix-org/matrix-rust-sdk/actions/workflows/ci.yml">
<img src="https://img.shields.io/github/actions/workflow/status/matrix-org/matrix-rust-sdk/ci.yml?style=flat&labelColor=1C2E27&color=66845F&logo=GitHub%20Actions&logoColor=white"></a>
</div>
**matrix-rust-sdk** is an implementation of a [Matrix][] client-server library in [Rust][].
<div align="center">
[Matrix]: https://matrix.org/
[Rust]: https://www.rust-lang.org/
The Matrix Rust SDK is a collection of libraries that make it easier to build [Matrix] clients in [Rust].
<br />
<br />
<picture>
<source srcset="contrib/element-logo-light.png" media="(prefers-color-scheme: dark)">
<source srcset="contrib/element-logo-dark.png" media="(prefers-color-scheme: light)">
<img src="contrib/element-logo-fallback.png" alt="Element logo">
</picture>
<br />
<br />
Development of the SDK is proudly sponsored and maintained by [Element](https://element.io). Element uses the SDK in their next-generation mobile apps Element X on [iOS](https://github.com/element-hq/element-x-ios) and [Android](https://github.com/element-hq/element-x-android) and has plans to introduce it to the web and desktop clients as well.
The SDK is also the basis for multiple Matrix projects and we welcome contributions from all.
</div>
## Purpose
The SDK takes care of the low-level details like encryption,
syncing, and room state, so you can focus on your app's logic and UI. Whether
you're writing a small bot, a desktop client, or something in between, the SDK
is designed to be flexible, async-friendly, and ready to use out of the box.
## Project structure
The rust-sdk consists of multiple crates that can be picked at your convenience:
The Matrix Rust SDK is made up of several crates that build on top of each
other. The following crates are expected to be usable as direct dependencies:
- **matrix-sdk** - High level client library, with batteries included, you're most likely
interested in this.
- **matrix-sdk-base** - No (network) IO client state machine that can be used to embed a
Matrix client in your project or build a full fledged network enabled client
lib on top of it.
- **matrix-sdk-crypto** - No (network) IO encryption state machine that can be
used to add Matrix E2EE support to your client or client library.
- [matrix-sdk-ui](https://docs.rs/matrix-sdk-ui/latest/matrix_sdk_ui/) A high-level client library that makes it easy to build
full-featured UI clients with minimal setup. Check out our reference client,
[multiverse](https://github.com/matrix-org/matrix-rust-sdk/tree/main/labs/multiverse), for an example.
- [matrix-sdk](https://docs.rs/matrix-sdk/latest/matrix_sdk/) A mid-level client library, ideal for building bots, custom
clients, or higher-level abstractions. You can find example usage in the
[examples directory](https://github.com/matrix-org/matrix-rust-sdk/tree/main/examples).
- [matrix-sdk-crypto](https://docs.rs/matrix-sdk-crypto/latest/matrix_sdk_crypto/) A standalone encryption state machine with no network I/O,
providing end-to-end encryption support for Matrix clients and libraries.
See the [crypto tutorial](https://docs.rs/matrix-sdk-crypto/latest/matrix_sdk_crypto/tutorial/index.html)
for a step-by-step introduction.
All other crates are effectively internal-only and only structured as crates
for organizational purposes and to improve compilation times. Direct usage of them is discouraged.
## Status
The library is in an alpha state, things that are implemented generally work but
the API will change in breaking ways.
If you are interested in using the matrix-sdk now is the time to try it out and
provide feedback.
The library is considered production ready and backs multiple client
implementations such as Element X
[[1]](https://github.com/element-hq/element-x-ios)
[[2]](https://github.com/element-hq/element-x-android),
[Fractal](https://gitlab.gnome.org/World/fractal) and [iamb](https://github.com/ulyssa/iamb). Client developers should feel
confident to build upon it.
## Bindings
Some crates of the **matrix-rust-sdk** can be embedded inside other
environments, like Swift, Kotlin, JavaScript, Node.js etc. Please,
explore the [`bindings/`](./bindings/) directory to learn more.
The higher-level crates of the Matrix Rust SDK can be embedded in other
environments such as Swift, Kotlin, JavaScript, and Node.js. Check out the
[bindings/](./bindings/) directory to learn more about how to integrate the SDK
into your language of choice.
## License
[Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0)
[Matrix]: https://matrix.org/
[Rust]: https://www.rust-lang.org/
+19 -18
View File
@@ -13,35 +13,36 @@ The procedure is as follows:
1. Switch to a release branch:
```bash
git switch -c release-x.y.z
  ```
```bash
git switch -c release-x.y.z
```
2. Prepare the release. This will update the `README.md`, set the versions in
the `CHANGELOG.md` file, and bump the version in the `Cargo.toml` file.
```bash
cargo xtask release prepare --execute minor|patch|rc
```
```bash
cargo xtask release prepare --execute minor|patch|rc
```
3. Double-check and edit the `CHANGELOG.md` and `README.md` if necessary. Once you are
satisfied, push the branch and open a PR.
```bash
git push --set-upstream origin/release-x.y.z
```
```bash
git push --set-upstream origin/release-x.y.z
```
4. Pass the review and merge the branch as you would with any other branch.
5. Create tags for your new release, publish the release on crates.io and push
the tags:
```bash
# Switch to main first.
git switch main
# Pull in the now-merged release commit(s).
git pull
# Create tags, publish the release on crates.io, and push the tags.
cargo xtask release publish --execute
```
For more information on cargo-release: https://github.com/crate-ci/cargo-release
```bash
# Switch to main first.
git switch main
# Pull in the now-merged release commit(s).
git pull
# Create tags, publish the release on crates.io, and push the tags.
cargo xtask release publish --execute
```
For more information on cargo-release: https://github.com/crate-ci/cargo-release
-121
View File
@@ -1,121 +0,0 @@
# Upgrades 0.5 ➜ 0.6
This is a rough migration guide to help you upgrade your code using matrix-sdk 0.5 to the newly released matrix-sdk 0.6 . While it won't cover all edge cases and problems, we are trying to get the most common issues covered. If you experience any other difficulties in upgrade or need support with using the matrix-sdk in general, please approach us in our [matrix-sdk channel on matrix.org][matrix-channel].
## Minimum Supported Rust Version Update: `1.60`
We have updated the minimal rust version you need in order to build `matrix-sdk`, as we require some new dependency resolving features from it:
> These crates are built with the Rust language version 2021 and require a minimum compiler version of 1.60
## Dependencies
Many dependencies have been upgraded. Most notably, we are using `ruma` at version `0.7.0` now. It has seen some renamings and restructurings since our last release, so you might find that some Types have new names now.
## Repo Structure Updates
If you are looking at the repository itself, you will find we've rearranged the code quite a bit: we have split out any bindings-specific and testing related crates (and other things) into respective folders, and we've moved all `examples` into its own top-level-folder with each example as their own crate (rendering them easier to find and copy as starting points), all in all slimming down the `crates` folder to the core aspects.
## Architecture Changes / API overall
### Builder Pattern
We are moving to the [builder pattern][] (familiar from e.g. `std::io:process:Command`) as the main configurable path for many aspects of the API, including to construct Matrix-Requests and workflows. This has been and is an on-going effort, and this release sees a lot of APIs transitioning to this pattern, you should already be familiar with from the `matrix_sdk::Client::builder()` in `0.5`. This pattern been extended onto:
- the [login configuration][login builder] and [login with sso][ssologin builder],
- [`SledStore` configuratiion][sled-store builder]
- [`Indexeddb` configuration][indexeddb builder]
Most have fallback (though maybe with deprecation warning) support for an existing code path, but these are likely to be removed in upcoming releases.
### Splitting of concerns: Media
In an effort to declutter the `Client` API dedicated types have been created dealing with specific concerns in one place. In `0.5` we introduced `client.account()`, and `client.encryption()`, we are doing the same with `client.media()` to manage media and attachments in one place with the [`media::Media` type][media typ] now.
The signatures of media uploads, have also changed slightly: rather than expecting a reader `R: Read + Seek`, it now is a simple `&[u8]`. Which also means no more unnecessary `seek(0)` to reset the cursor, as we are just taking an immutable reference now.
### Event Handling & sync updaes
If you are using the `client.register_event_handler` function to receive updates on incoming sync events, you'll find yourself with a deprecation warning now. That is because we've refactored and redesigned the event handler logic to allowing `removing` of event handlers on the fly, too. For that the new `add_event_handler()` (and `add_room_event_handler`) will hand you an `EventHandlerHandle` (pardon the pun), which you can pass to `remove_event_handler`, or by using the convenient `client.event_handler_drop_guard` to create a `DropGuard` that will remove the handler when the guard is dropped. While the code still works, we recommend you switch to the new one, as we will be removing the `register_event_handler` and `register_event_handler_context` in a coming release.
Secondly, you will find a new [`sync_with_result_callback` sync function][sync with result]. Other than the previous sync functions, this will pass the entire `Result` to your callback, allowing you to handle errors or even raise some yourself to stop the loop. Further more, it will propagate any unhandled errors (it still handles retries as before) to the outer caller, allowing the higher level to decide how to handle that (e.g. in case of a network failure). This result-returning-behavior also punshes through the existing `sync` and `sync_with_callback`-API, allowing you to handle them on a higher level now (rather than the futures just resolving). If you find that warning, just adding a `?` to the `.await` of the call is probably the quickest way to move forward.
### Refresh Tokens
This release now [supports `refresh_token`s][refresh tokens PR] as part of the [`Session`][session]. It is implemented with a default-flag in serde so deserializing a previously serialized Session (e.g. in a store) will work as before. As part of `refresh_token` support, you can now configure the client via `ClientBuilder.request_refresh_token()` to refresh the access token automagically on certain failures or do it manually by calling `client.refresh_access_token()` yourself. Auto-refresh is _off_ by default.
You can stay informed about updates on the access token by listening to `client.session_tokens_signal()`.
### Further changes
- [`MessageOptions`][message options] has been updated to Matrix 1.3 by making the `from` parameter optional (and function signatures have been updated, too). You can now request the server sends you messages from the first one you are allowed to have received.
- `client.user_id()` is not a `future` anymore. Remove any `.await` you had behind it.
- `verified()`, `blacklisted()` and `deleted()` on `matrix_sdk::encryption::identities::Device` have been renamed with a `is_` prefix.
- `verified()` on `matrix_sdk::encryption::identities::UserIdentity`, too has been prefixed with `is_` and thus is now called `is_verified()`.
- The top-level crypto and state-store types of Indexeddb and Sled have been renamed to unique types>
- `state_store` and `crypto_store` do not need to be boxed anymore when passed to the [`StoreConfig`][store config]
- Indexeddb's `SerializationError` is now `IndexedDBStoreError`
- Javascript specific features are now behind the `js` feature-gate
- The new experimental next generation of sync ("sliding sync"), with a totally revamped api, can be found behind the optional `sliding-sync`-feature-gate
## Quick Troubleshooting
You find yourself focused with any of these, here are the steps to follow to upgrade your code accordingly:
### warning: use of deprecated associated function `matrix_sdk::Client::register_event_handler`: Use [`Client::add_event_handler`](#method.add_event_handler) instead
As it says on the tin: we have deprecated this function in favor of the newer removable handler approach (see above). You can still continue to use this `fn` for now, but it will be removed in a future release.
### warning: use of deprecated associated function `matrix_sdk::Client::login`: Replaced by [`Client::login_username`](#method.login_username)
We have replaced the login facilities with a `LoginBuilder` and recommend you use that from now on. This isn't an error yet, but the function will be removed in a future release.
### expected slice `[u8]`, found struct ...
We've updated the `send_attachment` and `Media` signatures to use `&[u8]` rather than `reader: Read + Seek` as it is more convenient and common place for most architectures anyways. If you are using `File::open(path)?` to get that handler, you can just replace that with `std::fs::read(path)?`
### no method named `verified` found for struct `matrix_sdk::encryption::identities::Device` in the current scope
Boolean flags like `verified`, `deleted`, `blacklisted`, etc have been renamed with a `is_` prefix. So, just follow the cargo suggestion:
```
|
69 | device.verified()
| ^^^^^^^^ help: there is an associated function with a similar name: `is_verified`
```
### unresolved import `matrix_sdk::ruma::events::AnySyncRoomEvent`
Ruma has been updated to `0.7.0`, you will find some ruma Events names have changed, most notably, the `AnySyncRoomEvent` is now named `AnySyncTimelineEvent` (and not `AnySyncStateEvent`, which cargo wrongly suggests). Just rename the import and usage of it.
### `std::option::Option<&matrix_sdk::ruma::UserId>` is not a future
You are seeing something along the lines of:
```
19 | if room_member.state_key != client.user_id().await.unwrap() {
| ^^^^^^ `std::option::Option<&matrix_sdk::ruma::UserId>` is not a future
|
= help: the trait `Future` is not implemented for `std::option::Option<&matrix_sdk::ruma::UserId>`
= note: std::option::Option<&matrix_sdk::ruma::UserId> must be a future or must implement `IntoFuture` to be awaited
= note: required because of the requirements on the impl of `IntoFuture` for `std::option::Option<&matrix_sdk::ruma::UserId>`
help: remove the `.await`
|
19 - if room_member.state_key != client.user_id().await.unwrap() {
19 + if room_member.state_key != client.user_id().unwrap() {
```
You are using `client.user_id().await` but `user_id()` is no longer `async`. Just follow the cargo suggestion and remove the `.await`, it is not necessary any longer.
[matrix-channel]: https://matrix.to/#/#matrix-rust-sdk:matrix.org
[builder pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html
[login builder]: https://docs.rs/matrix-sdk/latest/matrix_sdk/struct.LoginBuilder.html
[ssologin builder]: https://docs.rs/matrix-sdk/latest/matrix_sdk/struct.SsoLoginBuilder.html
[sled-store builder]: https://docs.rs/matrix-sdk-sled/latest/matrix_sdk_sled/struct.SledStateStoreBuilder.html
[indexeddb builder]: https://docs.rs/matrix-sdk-indexeddb/latest/matrix_sdk_indexeddb/struct.IndexeddbStateStoreBuilder.html
[media type]: https://docs.rs/matrix-sdk/latest/matrix_sdk//media/struct.Media.html
[sync with result]: https://docs.rs/matrix-sdk/latest/matrix_sdk/struct.Client.html#method.sync_with_result_callback
[session]: https://docs.rs/matrix-sdk/latest/matrix_sdk/struct.Session.html
[refresh tokens PR]: https://github.com/matrix-org/matrix-rust-sdk/pull/892
[store config]: https://docs.rs/matrix-sdk-base/latest/matrix_sdk_base/store/struct.StoreConfig.html
[message options]: https://docs.rs/matrix-sdk/latest/matrix_sdk/room/struct.MessagesOptions.html
+32 -20
View File
@@ -1,34 +1,41 @@
[package]
name = "benchmarks"
description = "Matrix SDK benchmarks"
edition = "2021"
edition = "2024"
license = "Apache-2.0"
rust-version = { workspace = true }
rust-version.workspace = true
version = "1.0.0"
publish = false
[dependencies]
criterion = { version = "0.5.1", features = ["async", "async_tokio", "html_reports"] }
matrix-sdk-base = { workspace = true }
matrix-sdk-crypto = { workspace = true }
matrix-sdk-sqlite = { workspace = true, features = ["crypto-store"] }
matrix-sdk-test = { workspace = true }
matrix-sdk-ui = { workspace = true }
matrix-sdk = { workspace = true, features = ["native-tls", "e2e-encryption", "sqlite"] }
ruma = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
tempfile = "3.3.0"
tokio = { workspace = true, default-features = false, features = ["rt-multi-thread"] }
wiremock = { workspace = true }
[package.metadata.release]
release = false
[target.'cfg(target_os = "linux")'.dependencies]
pprof = { version = "0.14.0", features = ["flamegraph", "criterion"] }
[features]
codspeed = []
[dependencies]
criterion = { version = "3.0.5", features = ["async", "async_tokio", "html_reports"], package = "codspeed-criterion-compat" }
matrix-sdk = { workspace = true, features = ["native-tls", "e2e-encryption", "sqlite", "testing"] }
matrix-sdk-base.workspace = true
matrix-sdk-crypto.workspace = true
matrix-sdk-sqlite = { workspace = true, features = ["crypto-store"] }
matrix-sdk-test.workspace = true
matrix-sdk-ui.workspace = true
ruma.workspace = true
serde.workspace = true
serde_json.workspace = true
tempfile.workspace = true
tokio = { workspace = true, default-features = false, features = ["rt-multi-thread"] }
wiremock.workspace = true
[[bench]]
name = "crypto_bench"
harness = false
[[bench]]
name = "linked_chunk"
harness = false
[[bench]]
name = "store_bench"
harness = false
@@ -37,5 +44,10 @@ harness = false
name = "room_bench"
harness = false
[package.metadata.release]
release = false
[[bench]]
name = "timeline"
harness = false
[[bench]]
name = "event_cache"
harness = false
+10 -3
View File
@@ -8,7 +8,7 @@ can be found [here](https://bheisler.github.io/criterion.rs/book/criterion_rs.ht
## Running the benchmarks
The benchmark can be simply run by using the `bench` command of `cargo`:
The benchmark can be run by using the `bench` command of `cargo`:
```bash
$ cargo bench
@@ -16,6 +16,13 @@ $ cargo bench
This will work from the workspace directory of the rust-sdk.
To lower compile times, you might be interested in using the `profiling` profile, that's optimized
for a fair tradeoff between compile times and runtime performance:
```bash
$ cargo bench --profile profiling
```
If you want to pass options to the benchmark [you'll need to specify the name of
the benchmark](https://bheisler.github.io/criterion.rs/book/faq.html#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options):
@@ -23,7 +30,7 @@ the benchmark](https://bheisler.github.io/criterion.rs/book/faq.html#cargo-bench
$ cargo bench --bench crypto_bench -- # Your options go here
```
If you want to run only a specific benchmark, simply pass the name of the
If you want to run only a specific benchmark, pass the name of the
benchmark as an argument:
```bash
@@ -65,7 +72,7 @@ permisive value is `-1`:
$ echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid
```
To generate flame graphs feature simply enable the profiling mode using the
To generate flame graphs feature, enable the profiling mode using the
`--profile-time` command line flag:
```bash
+93 -69
View File
@@ -1,15 +1,16 @@
use std::{ops::Deref, sync::Arc};
use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion, Throughput};
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use matrix_sdk_crypto::{EncryptionSettings, OlmMachine};
use matrix_sdk_sqlite::SqliteCryptoStore;
use matrix_sdk_test::ruma_response_from_json;
use ruma::{
DeviceId, OwnedUserId, TransactionId, UserId,
api::client::{
keys::{claim_keys, get_keys},
to_device::send_event_to_device::v3::Response as ToDeviceResponse,
},
device_id, room_id, user_id, DeviceId, OwnedUserId, TransactionId, UserId,
device_id, room_id, user_id,
};
use serde_json::Value;
use tokio::runtime::Builder;
@@ -58,10 +59,14 @@ pub fn keys_query(c: &mut Criterion) {
// Benchmark memory store.
group.bench_with_input(BenchmarkId::new("memory store", &name), &response, |b, response| {
b.to_async(&runtime)
.iter(|| async { machine.mark_request_as_sent(&txn_id, response).await.unwrap() })
});
group.bench_with_input(
BenchmarkId::new("Device keys query [memory]", &name),
&response,
|b, response| {
b.to_async(&runtime)
.iter(|| async { machine.mark_request_as_sent(&txn_id, response).await.unwrap() })
},
);
// Benchmark sqlite store.
@@ -71,10 +76,14 @@ pub fn keys_query(c: &mut Criterion) {
.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store, None))
.unwrap();
group.bench_with_input(BenchmarkId::new("sqlite store", &name), &response, |b, response| {
b.to_async(&runtime)
.iter(|| async { machine.mark_request_as_sent(&txn_id, response).await.unwrap() })
});
group.bench_with_input(
BenchmarkId::new("Device keys query [SQLite]", &name),
&response,
|b, response| {
b.to_async(&runtime)
.iter(|| async { machine.mark_request_as_sent(&txn_id, response).await.unwrap() })
},
);
{
let _guard = runtime.enter();
@@ -84,6 +93,8 @@ pub fn keys_query(c: &mut Criterion) {
group.finish()
}
/// This test panics on the CI, not sure why so we're disabling it for now.
#[cfg(not(feature = "codspeed"))]
pub fn keys_claiming(c: &mut Criterion) {
let runtime = Builder::new_multi_thread().build().expect("Can't create runtime");
@@ -99,49 +110,65 @@ pub fn keys_claiming(c: &mut Criterion) {
let name = format!("{count} one-time keys");
group.bench_with_input(BenchmarkId::new("memory store", &name), &response, |b, response| {
b.iter_batched(
|| {
let machine = runtime.block_on(OlmMachine::new(alice_id(), alice_device_id()));
runtime
.block_on(machine.mark_request_as_sent(&txn_id, &keys_query_response))
.unwrap();
(machine, &runtime, &txn_id)
},
move |(machine, runtime, txn_id)| {
runtime.block_on(async {
machine.mark_request_as_sent(txn_id, response).await.unwrap();
group.bench_with_input(
BenchmarkId::new("One-time keys claiming [memory]", &name),
&response,
|b, response| {
b.iter_batched(
|| {
let machine = runtime.block_on(OlmMachine::new(alice_id(), alice_device_id()));
runtime
.block_on(machine.mark_request_as_sent(&txn_id, &keys_query_response))
.unwrap();
(machine, &runtime, &txn_id)
},
move |(machine, runtime, txn_id)| {
runtime.block_on(async {
machine.mark_request_as_sent(txn_id, response).await.unwrap();
drop(machine);
})
},
criterion::BatchSize::SmallInput,
)
},
);
group.bench_with_input(
BenchmarkId::new("One-time keys claiming [SQLite]", &name),
&response,
|b, response| {
b.iter_batched(
|| {
let dir = tempfile::tempdir().unwrap();
let store = Arc::new(
runtime.block_on(SqliteCryptoStore::open(dir.path(), None)).unwrap(),
);
let machine = runtime
.block_on(OlmMachine::with_store(
alice_id(),
alice_device_id(),
store,
None,
))
.unwrap();
runtime
.block_on(machine.mark_request_as_sent(&txn_id, &keys_query_response))
.unwrap();
(machine, &runtime, &txn_id)
},
move |(machine, runtime, txn_id)| {
runtime.block_on(async {
machine.mark_request_as_sent(txn_id, response).await.unwrap();
});
let _ = runtime.enter();
drop(machine);
})
},
BatchSize::SmallInput,
)
});
group.bench_with_input(BenchmarkId::new("sqlite store", &name), &response, |b, response| {
b.iter_batched(
|| {
let dir = tempfile::tempdir().unwrap();
let store =
Arc::new(runtime.block_on(SqliteCryptoStore::open(dir.path(), None)).unwrap());
let machine = runtime
.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store, None))
.unwrap();
runtime
.block_on(machine.mark_request_as_sent(&txn_id, &keys_query_response))
.unwrap();
(machine, &runtime, &txn_id)
},
move |(machine, runtime, txn_id)| {
runtime.block_on(async {
machine.mark_request_as_sent(txn_id, response).await.unwrap();
drop(machine)
})
},
BatchSize::SmallInput,
)
});
},
criterion::BatchSize::SmallInput,
)
},
);
group.finish()
}
@@ -169,7 +196,7 @@ pub fn room_key_sharing(c: &mut Criterion) {
// Benchmark memory store.
group.bench_function(BenchmarkId::new("memory store", &name), |b| {
group.bench_function(BenchmarkId::new("Room key sharing [memory]", &name), |b| {
b.to_async(&runtime).iter(|| async {
let requests = machine
.share_room_key(
@@ -201,7 +228,7 @@ pub fn room_key_sharing(c: &mut Criterion) {
runtime.block_on(machine.mark_request_as_sent(&txn_id, &keys_query_response)).unwrap();
runtime.block_on(machine.mark_request_as_sent(&txn_id, &response)).unwrap();
group.bench_function(BenchmarkId::new("sqlite store", &name), |b| {
group.bench_function(BenchmarkId::new("Room key sharing [SQLite]", &name), |b| {
b.to_async(&runtime).iter(|| async {
let requests = machine
.share_room_key(
@@ -249,7 +276,7 @@ pub fn devices_missing_sessions_collecting(c: &mut Criterion) {
// Benchmark memory store.
group.bench_function(BenchmarkId::new("memory store", &name), |b| {
group.bench_function(BenchmarkId::new("Devices collecting [memory]", &name), |b| {
b.to_async(&runtime).iter_with_large_drop(|| async {
machine.get_missing_sessions(users.iter().map(Deref::deref)).await.unwrap()
})
@@ -266,7 +293,7 @@ pub fn devices_missing_sessions_collecting(c: &mut Criterion) {
runtime.block_on(machine.mark_request_as_sent(&txn_id, &response)).unwrap();
group.bench_function(BenchmarkId::new("sqlite store", &name), |b| {
group.bench_function(BenchmarkId::new("Devices collecting [SQLite]", &name), |b| {
b.to_async(&runtime).iter(|| async {
machine.get_missing_sessions(users.iter().map(Deref::deref)).await.unwrap()
})
@@ -280,21 +307,18 @@ pub fn devices_missing_sessions_collecting(c: &mut Criterion) {
group.finish()
}
fn criterion() -> Criterion {
#[cfg(target_os = "linux")]
let criterion = Criterion::default().with_profiler(pprof::criterion::PProfProfiler::new(
100,
pprof::criterion::Output::Flamegraph(None),
));
#[cfg(not(target_os = "linux"))]
let criterion = Criterion::default();
criterion
}
#[cfg(not(feature = "codspeed"))]
criterion_group! {
name = benches;
config = criterion();
config = Criterion::default();
targets = keys_query, keys_claiming, room_key_sharing, devices_missing_sessions_collecting,
}
#[cfg(feature = "codspeed")]
criterion_group! {
name = benches;
config = Criterion::default();
targets = keys_query, room_key_sharing, devices_missing_sessions_collecting,
}
criterion_main!(benches);
+353
View File
@@ -0,0 +1,353 @@
use std::{pin::Pin, sync::Arc};
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use matrix_sdk::{
RoomInfo, RoomState, SqliteEventCacheStore, StateStore,
store::StoreConfig,
sync::{JoinedRoomUpdate, RoomUpdates},
test_utils::client::MockClientBuilder,
};
use matrix_sdk_base::event_cache::store::{DynEventCacheStore, IntoEventCacheStore, MemoryStore};
use matrix_sdk_test::{ALICE, event_factory::EventFactory};
use ruma::{
EventId, RoomId, event_id,
events::{relation::RelationType, room::message::RoomMessageEventContentWithoutRelation},
room_id,
};
use tempfile::tempdir;
use tokio::runtime::Builder;
type StoreBuilder = Box<dyn Fn() -> Pin<Box<dyn Future<Output = Arc<DynEventCacheStore>>>>>;
fn handle_room_updates(c: &mut Criterion) {
// Create a new asynchronous runtime.
let runtime = Builder::new_multi_thread()
.enable_time()
.enable_io()
.build()
.expect("Failed to create an asynchronous runtime");
let mut group = c.benchmark_group("Event cache room updates");
group.sample_size(10);
const NUM_EVENTS: usize = 1000;
for num_rooms in [1, 10, 100] {
// Add some joined rooms, each with NUM_EVENTS in it, to the sync response.
let mut room_updates = RoomUpdates::default();
let mut changes = matrix_sdk::StateChanges::default();
for i in 0..num_rooms {
let room_id = RoomId::parse(format!("!room{i}:example.com")).unwrap();
let event_factory = EventFactory::new().room(&room_id).sender(&ALICE);
let mut joined_room_update = JoinedRoomUpdate::default();
for j in 0..NUM_EVENTS {
let event_id = EventId::parse(format!("$ev{i}_{j}")).unwrap();
let event =
event_factory.text_msg(format!("Message {j}")).event_id(&event_id).into();
joined_room_update.timeline.events.push(event);
}
room_updates.joined.insert(room_id.clone(), joined_room_update);
changes.add_room(RoomInfo::new(&room_id, RoomState::Joined));
}
// Declare new stores for this set of events.
let temp_dir = Arc::new(tempdir().unwrap());
let store_builders: Vec<(_, StoreBuilder)> = vec![
(
"memory",
Box::new(|| Box::pin(async { MemoryStore::default().into_event_cache_store() })),
),
(
"SQLite",
Box::new(move || {
let temp_dir = temp_dir.clone();
Box::pin(async move {
// Remove all the files in the temp_dir, to reset the event cache state.
for entry in temp_dir.path().read_dir().unwrap() {
let entry = entry.unwrap();
let path = entry.path();
if path.is_dir() {
// If it's a directory, remove it recursively.
std::fs::remove_dir_all(path).unwrap();
} else {
std::fs::remove_file(path).unwrap();
}
}
// Recreate a new store.
SqliteEventCacheStore::open(temp_dir.path().join("bench"), None)
.await
.unwrap()
.into_event_cache_store()
})
}),
),
];
let state_store = runtime.block_on(async {
let state_store = matrix_sdk::MemoryStore::new();
state_store.save_changes(&changes).await.unwrap();
Arc::new(state_store)
});
for (store_name, store_builder) in &store_builders {
let client = runtime.block_on(async {
let event_cache_store = store_builder().await;
let client = MockClientBuilder::new(None)
.on_builder(|builder| {
builder.store_config(
StoreConfig::new("cross-process-store-locks-holder-name".to_owned())
.state_store(state_store.clone())
.event_cache_store(event_cache_store.clone()),
)
})
.build()
.await;
client.event_cache().subscribe().unwrap();
client
});
// Define a state store with all rooms known in it.
// Define the throughput.
group.throughput(Throughput::Elements(num_rooms));
// Bench the handling of room updates.
group.bench_function(
BenchmarkId::new(
format!("Event cache room updates[{store_name}]"),
format!("room count: {num_rooms}"),
),
|bencher| {
bencher.to_async(&runtime).iter(
// The routine itself.
|| {
let room_updates = room_updates.clone();
let client = client.clone();
async move {
client.event_cache().clear_all_rooms().await.unwrap();
client
.event_cache()
.handle_room_updates(room_updates.clone())
.await
.unwrap();
}
},
)
},
);
}
}
group.finish()
}
fn find_event_relations(c: &mut Criterion) {
// Number of other events to saturate the DB, but that will not be affected by
// the benchmark. A small multiple of this number will be added.
// When running locally, run with more events than in Codespeed CI.
#[cfg(feature = "codspeed")]
const NUM_OTHER_EVENTS: usize = 100;
#[cfg(not(feature = "codspeed"))]
const NUM_OTHER_EVENTS: usize = 1000;
// Create a new asynchronous runtime.
let runtime = Builder::new_multi_thread()
.enable_time()
.enable_io()
.build()
.expect("Failed to create an asynchronous runtime");
let mut group = c.benchmark_group("Event cache room updates");
group.sample_size(10);
let room_id = room_id!("!room:ben.ch");
let other_room_id = room_id!("!other-room:ben.ch");
// Make the state store aware of the room, so that `client.get_room()` works
// with it.
let mut changes = matrix_sdk::StateChanges::default();
changes.add_room(RoomInfo::new(room_id, RoomState::Joined));
changes.add_room(RoomInfo::new(other_room_id, RoomState::Joined));
let state_store = runtime.block_on(async {
let state_store = matrix_sdk::MemoryStore::new();
state_store.save_changes(&changes).await.unwrap();
Arc::new(state_store)
});
for num_related_events in [10, 100, 1000] {
// Prefill the event cache store with one event and N related events.
let mut room_updates = RoomUpdates::default();
let event_factory = EventFactory::new().room(room_id).sender(&ALICE);
let mut joined_room_update = JoinedRoomUpdate::default();
// Add the target event.
let target_event_id = event_id!("$target");
let target_event =
event_factory.text_msg("hello world").event_id(target_event_id).into_event();
joined_room_update.timeline.events.push(target_event);
// Add the numerous edits.
for i in 0..num_related_events {
let event_id = EventId::parse(format!("$edit{i}")).unwrap();
let event = event_factory
.text_msg(format!("* edit {i}"))
.edit(
target_event_id,
RoomMessageEventContentWithoutRelation::text_plain(format!("edit {i}")),
)
.event_id(&event_id)
.into();
joined_room_update.timeline.events.push(event);
}
// Add other events, in the same room, without a relation.
for i in 0..NUM_OTHER_EVENTS {
let event_id = EventId::parse(format!("$msg{i}")).unwrap();
let event =
event_factory.text_msg(format!("unrelated message {i}")).event_id(&event_id).into();
joined_room_update.timeline.events.push(event);
}
// Add other events, in the same room, related to other events.
let other_target_event_id = event_id!("$other_target");
let other_target_event =
event_factory.text_msg("hello world").event_id(other_target_event_id).into_event();
joined_room_update.timeline.events.push(other_target_event);
for i in 0..NUM_OTHER_EVENTS {
let event_id = EventId::parse(format!("$unrelated{i}")).unwrap();
let event =
event_factory.reaction(other_target_event_id, "👍").event_id(&event_id).into();
joined_room_update.timeline.events.push(event);
}
room_updates.joined.insert(room_id.to_owned(), joined_room_update);
// Add other events, in another room.
let mut other_joined_room_update = JoinedRoomUpdate::default();
let event_factory = event_factory.room(other_room_id);
for i in 0..NUM_OTHER_EVENTS {
let event_id = EventId::parse(format!("$other_room{i}")).unwrap();
let event = event_factory.text_msg(format!("hi {i}")).event_id(&event_id).into();
other_joined_room_update.timeline.events.push(event);
}
room_updates.joined.insert(other_room_id.to_owned(), other_joined_room_update);
changes.add_room(RoomInfo::new(room_id, RoomState::Joined));
// Declare new stores for this set of events.
let temp_dir = Arc::new(tempdir().unwrap());
let stores = vec![
("memory", MemoryStore::default().into_event_cache_store()),
(
"SQLite",
runtime.block_on(async {
SqliteEventCacheStore::open(temp_dir.path().join("bench"), None)
.await
.unwrap()
.into_event_cache_store()
}),
),
];
for (store_name, event_cache_store) in stores {
let (client, room_event_cache, _drop_handles) = runtime.block_on(async {
let client = MockClientBuilder::new(None)
.on_builder(|builder| {
builder.store_config(
StoreConfig::new("cross-process-store-locks-holder-name".to_owned())
.state_store(state_store.clone())
.event_cache_store(event_cache_store),
)
})
.build()
.await;
client.event_cache().subscribe().unwrap();
// Sync the updates before starting the benchmark.
let mut update_recv = client.event_cache().subscribe_to_room_generic_updates();
client.event_cache().handle_room_updates(room_updates.clone()).await.unwrap();
// Wait for the event cache to notify us of the room updates.
let update = update_recv.recv().await.unwrap();
assert!(update.room_id == room_id || update.room_id == other_room_id);
let update = update_recv.recv().await.unwrap();
assert!(update.room_id == room_id || update.room_id == other_room_id);
let room = client.get_room(room_id).unwrap();
let room_event_cache = room.event_cache().await.unwrap();
(client, room_event_cache.0, room_event_cache.1)
});
// Define the throughput.
group.throughput(Throughput::Elements(num_related_events));
for filter in [None, Some(vec![RelationType::Replacement])] {
group.bench_function(
BenchmarkId::new(
format!("Event cache find_event_relations[{store_name}]"),
format!(
"{num_related_events} events, {} filter",
if filter.is_some() { "edits" } else { "#no" },
),
),
|bencher| {
bencher.to_async(&runtime).iter_batched(
// The setup.
|| (room_event_cache.clone(), filter.clone()),
// The routine itself.
|(room_event_cache, filter)| async move {
let (target, relations) = room_event_cache
.find_event_with_relations(target_event_id, filter)
.await
.unwrap();
assert_eq!(target.event_id().as_deref().unwrap(), target_event_id);
assert_eq!(relations.len(), num_related_events as usize);
},
criterion::BatchSize::PerIteration,
)
},
);
}
{
let _guard = runtime.enter();
drop(room_event_cache);
drop(client);
drop(_drop_handles);
}
}
}
{
let _guard = runtime.enter();
drop(state_store);
}
group.finish()
}
criterion_group! {
name = event_cache;
config = Criterion::default();
targets = handle_room_updates, find_event_relations,
}
criterion_main!(event_cache);
+274
View File
@@ -0,0 +1,274 @@
use std::{sync::Arc, time::Duration};
use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use matrix_sdk::{
SqliteEventCacheStore,
linked_chunk::{LinkedChunk, LinkedChunkId, Update, lazy_loader},
};
use matrix_sdk_base::event_cache::{
Event, Gap,
store::{DEFAULT_CHUNK_CAPACITY, DynEventCacheStore, IntoEventCacheStore, MemoryStore},
};
use matrix_sdk_test::{ALICE, event_factory::EventFactory};
use ruma::{EventId, room_id};
use tempfile::tempdir;
use tokio::runtime::Builder;
#[derive(Clone, Debug)]
enum Operation {
PushItemsBack(Vec<Event>),
PushGapBack(Gap),
}
#[cfg(not(feature = "codspeed"))]
const NUMBER_OF_EVENTS: &[u64] = &[10, 100, 1000, 10_000, 100_000];
#[cfg(feature = "codspeed")]
const NUMBER_OF_EVENTS: &[u64] = &[10, 100, 1000];
fn writing(c: &mut Criterion) {
// Create a new asynchronous runtime.
let runtime = Builder::new_multi_thread()
.enable_time()
.enable_io()
.build()
.expect("Failed to create an asynchronous runtime");
let room_id = room_id!("!foo:bar.baz");
let linked_chunk_id = LinkedChunkId::Room(room_id);
let event_factory = EventFactory::new().room(room_id).sender(&ALICE);
let mut group = c.benchmark_group("Linked chunk writing");
group.sample_size(10).measurement_time(Duration::from_secs(30));
for &number_of_events in NUMBER_OF_EVENTS {
let sqlite_temp_dir = tempdir().unwrap();
// Declare new stores for this set of events.
let stores: [(&str, Option<Arc<DynEventCacheStore>>); 3] = [
("none", None),
("memory store", Some(MemoryStore::default().into_event_cache_store())),
(
"sqlite store",
runtime.block_on(async {
Some(
SqliteEventCacheStore::open(sqlite_temp_dir.path().join("bench"), None)
.await
.unwrap()
.into_event_cache_store(),
)
}),
),
];
for (store_name, store) in stores {
// Create the operations we want to bench.
let mut operations = Vec::new();
{
let mut events = (0..number_of_events)
.map(|nth| {
event_factory
.text_msg("foo")
.event_id(&EventId::parse(format!("$ev{nth}")).unwrap())
.into_event()
})
.peekable();
let mut gap_nth = 0;
while events.peek().is_some() {
{
let events_to_push_back = events.by_ref().take(80).collect::<Vec<_>>();
if events_to_push_back.is_empty() {
break;
}
operations.push(Operation::PushItemsBack(events_to_push_back));
}
{
operations.push(Operation::PushGapBack(Gap {
prev_token: format!("gap{gap_nth}"),
}));
gap_nth += 1;
}
}
}
// Define the throughput.
group.throughput(Throughput::Elements(number_of_events));
// Get a bencher.
group.bench_with_input(
BenchmarkId::new(format!("Linked chunk writing [{store_name}]"), number_of_events),
&operations,
|bencher, operations| {
// Bench the routine.
bencher.to_async(&runtime).iter_batched(
|| operations.clone(),
|operations| async {
// The routine to bench!
let mut linked_chunk = LinkedChunk::<DEFAULT_CHUNK_CAPACITY, Event, Gap>::new_with_update_history();
for operation in operations {
match operation {
Operation::PushItemsBack(events) => linked_chunk.push_items_back(events),
Operation::PushGapBack(gap) => linked_chunk.push_gap_back(gap),
}
}
if let Some(store) = &store {
let updates = linked_chunk.updates().unwrap().take();
store.handle_linked_chunk_updates(linked_chunk_id, updates).await.unwrap();
// Empty the store.
store.handle_linked_chunk_updates(linked_chunk_id, vec![Update::Clear]).await.unwrap();
}
},
BatchSize::SmallInput
)
},
);
{
let _guard = runtime.enter();
drop(store);
}
}
}
group.finish()
}
fn reading(c: &mut Criterion) {
// Create a new asynchronous runtime.
let runtime = Builder::new_multi_thread()
.enable_time()
.enable_io()
.build()
.expect("Failed to create an asynchronous runtime");
let room_id = room_id!("!foo:bar.baz");
let linked_chunk_id = LinkedChunkId::Room(room_id);
let event_factory = EventFactory::new().room(room_id).sender(&ALICE);
let mut group = c.benchmark_group("Linked chunk reading");
group.sample_size(10);
for &num_events in NUMBER_OF_EVENTS {
let sqlite_temp_dir = tempdir().unwrap();
// Declare new stores for this set of events.
let stores: [(&str, Arc<DynEventCacheStore>); 2] = [
("memory store", MemoryStore::default().into_event_cache_store()),
(
"sqlite store",
runtime.block_on(async {
SqliteEventCacheStore::open(sqlite_temp_dir.path().join("bench"), None)
.await
.unwrap()
.into_event_cache_store()
}),
),
];
for (store_name, store) in stores {
// Store some events and gap chunks in the store.
{
let mut events = (0..num_events)
.map(|nth| {
event_factory
.text_msg("foo")
.event_id(&EventId::parse(format!("$ev{nth}")).unwrap())
.into_event()
})
.peekable();
let mut lc =
LinkedChunk::<DEFAULT_CHUNK_CAPACITY, Event, Gap>::new_with_update_history();
let mut num_gaps = 0;
while events.peek().is_some() {
let events_chunk = events.by_ref().take(80).collect::<Vec<_>>();
if events_chunk.is_empty() {
break;
}
lc.push_items_back(events_chunk);
lc.push_gap_back(Gap { prev_token: format!("gap{num_gaps}") });
num_gaps += 1;
}
// Now persist the updates to recreate this full linked chunk.
let updates = lc.updates().unwrap().take();
runtime
.block_on(store.handle_linked_chunk_updates(linked_chunk_id, updates))
.unwrap();
}
// Define the throughput.
group.throughput(Throughput::Elements(num_events));
// Bench the lazy loader.
group.bench_function(
BenchmarkId::new(format!("Linked chunk lazy loader[{store_name}]"), num_events),
|bencher| {
// Bench the routine.
bencher.to_async(&runtime).iter(|| async {
// Load the last chunk first,
let (last_chunk, chunk_id_gen) =
store.load_last_chunk(linked_chunk_id).await.unwrap();
let mut lc =
lazy_loader::from_last_chunk::<128, _, _>(last_chunk, chunk_id_gen)
.expect("no error when reconstructing the linked chunk")
.expect("there is a linked chunk in the store");
// Then load until the start of the linked chunk.
let mut cur_chunk_id = lc.chunks().next().unwrap().identifier();
while let Some(prev) =
store.load_previous_chunk(linked_chunk_id, cur_chunk_id).await.unwrap()
{
cur_chunk_id = prev.identifier;
lazy_loader::insert_new_first_chunk(&mut lc, prev)
.expect("no error when linking the previous lazy-loaded chunk");
}
})
},
);
// Bench the metadata loader.
group.bench_function(
BenchmarkId::new(format!("Linked chunk metadata loader[{store_name}]"), num_events),
|bencher| {
// Bench the routine.
bencher.to_async(&runtime).iter(|| async {
let _metadata = store
.load_all_chunks_metadata(linked_chunk_id)
.await
.expect("metadata must load");
})
},
);
{
let _guard = runtime.enter();
drop(store);
}
}
}
group.finish()
}
criterion_group! {
name = event_cache;
config = Criterion::default();
targets = writing, reading,
}
criterion_main!(event_cache);
+52 -100
View File
@@ -1,33 +1,26 @@
use std::{sync::Arc, time::Duration};
use std::time::Duration;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use matrix_sdk::{
config::SyncSettings, test_utils::logged_in_client_with_server, utils::IntoRawStateEventContent,
};
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use matrix_sdk::{store::RoomLoadSettings, test_utils::mocks::MatrixMockServer};
use matrix_sdk_base::{
store::StoreConfig, BaseClient, RoomInfo, RoomState, SessionMeta, StateChanges, StateStore,
BaseClient, RoomInfo, RoomState, SessionMeta, StateChanges, StateStore, ThreadingSupport,
store::StoreConfig,
};
use matrix_sdk_sqlite::SqliteStateStore;
use matrix_sdk_test::{
event_factory::EventFactory, EventBuilder, JoinedRoomBuilder, StateTestEvent,
SyncResponseBuilder,
};
use matrix_sdk_ui::{timeline::TimelineFocus, Timeline};
use matrix_sdk_test::{JoinedRoomBuilder, StateTestEvent, event_factory::EventFactory};
use matrix_sdk_ui::timeline::{TimelineBuilder, TimelineFocus};
use ruma::{
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId,
api::client::membership::get_member_events,
device_id,
events::room::member::{RoomMemberEvent, RoomMemberEventContent},
owned_room_id, owned_user_id,
events::room::member::{MembershipState, RoomMemberEvent},
mxc_uri, owned_room_id, owned_user_id,
serde::Raw,
user_id, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId,
user_id,
};
use serde::Serialize;
use serde_json::json;
use tokio::runtime::Builder;
use wiremock::{
matchers::{header, method, path, path_regex, query_param, query_param_is_missing},
Mock, MockServer, Request, ResponseTemplate,
};
use wiremock::{Request, ResponseTemplate};
pub fn receive_all_members_benchmark(c: &mut Criterion) {
const MEMBERS_IN_ROOM: usize = 100000;
@@ -35,28 +28,17 @@ pub fn receive_all_members_benchmark(c: &mut Criterion) {
let runtime = Builder::new_multi_thread().build().expect("Can't create runtime");
let room_id = owned_room_id!("!room:example.com");
let ev_builder = EventBuilder::new();
let f = EventFactory::new().room(&room_id);
let mut member_events: Vec<Raw<RoomMemberEvent>> = Vec::with_capacity(MEMBERS_IN_ROOM);
let member_content_json = json!({
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Alice Margatroid",
"membership": "join",
"reason": "Looking for support",
});
let member_content: Raw<RoomMemberEventContent> =
member_content_json.into_raw_state_event_content().cast();
for i in 0..MEMBERS_IN_ROOM {
let user_id = OwnedUserId::try_from(format!("@user_{}:matrix.org", i)).unwrap();
let state_key = user_id.to_string();
let event: Raw<RoomMemberEvent> = ev_builder
.make_state_event(
&user_id,
&room_id,
&state_key,
member_content.deserialize().unwrap(),
None,
)
.cast();
let user_id = OwnedUserId::try_from(format!("@user_{i}:matrix.org")).unwrap();
let event = f
.member(&user_id)
.membership(MembershipState::Join)
.avatar_url(mxc_uri!("mxc://example.org/SEsfnsuifSDFSSEF"))
.display_name("Alice Margatroid")
.reason("Looking for support")
.into_raw();
member_events.push(event);
}
@@ -75,17 +57,19 @@ pub fn receive_all_members_benchmark(c: &mut Criterion) {
.block_on(sqlite_store.save_changes(&changes))
.expect("initial filling of sqlite failed");
let base_client = BaseClient::with_store_config(
let base_client = BaseClient::new(
StoreConfig::new("cross-process-store-locks-holder-name".to_owned())
.state_store(sqlite_store),
ThreadingSupport::Disabled,
);
runtime
.block_on(base_client.set_session_meta(
.block_on(base_client.activate(
SessionMeta {
user_id: user_id!("@somebody:example.com").to_owned(),
device_id: device_id!("DEVICE_ID").to_owned(),
},
RoomLoadSettings::default(),
None,
))
.expect("Could not set session meta");
@@ -101,7 +85,7 @@ pub fn receive_all_members_benchmark(c: &mut Criterion) {
group.throughput(Throughput::Elements(count as u64));
group.sample_size(50);
group.bench_function(BenchmarkId::new("receive_members", name), |b| {
group.bench_function(BenchmarkId::new("Handle /members request [SQLite]", name), |b| {
b.to_async(&runtime).iter(|| async {
base_client.receive_all_members(&room_id, &request, &response).await.unwrap();
});
@@ -123,9 +107,7 @@ pub fn load_pinned_events_benchmark(c: &mut Criterion) {
let sender_id = owned_user_id!("@sender:example.com");
let f = EventFactory::new().room(&room_id).sender(&sender_id);
let (client, server) = runtime.block_on(logged_in_client_with_server());
let mut sync_response_builder = SyncResponseBuilder::new();
let mut joined_room_builder =
JoinedRoomBuilder::new(&room_id).add_state_event(StateTestEvent::Encryption);
@@ -147,17 +129,15 @@ pub fn load_pinned_events_benchmark(c: &mut Criterion) {
}
}
)));
let response_json =
sync_response_builder.add_joined_room(joined_room_builder).build_json_sync_response();
runtime.block_on(mock_sync(&server, response_json, None));
let sync_settings = SyncSettings::default();
runtime.block_on(client.sync_once(sync_settings)).expect("Could not sync");
runtime.block_on(server.reset());
let (server, client, room) = runtime.block_on(async move {
let server = MatrixMockServer::new().await;
let client = server.client_builder().build().await;
runtime.block_on(
Mock::given(method("GET"))
.and(path_regex(r"/_matrix/client/r0/rooms/.*/event/.*"))
let room = server.sync_room(&client, joined_room_builder).await;
server
.mock_room_event()
.respond_with(move |r: &Request| {
let segments: Vec<&str> = r.url.path_segments().expect("Invalid path").collect();
let event_id_str = segments[6];
@@ -171,39 +151,41 @@ pub fn load_pinned_events_benchmark(c: &mut Criterion) {
.set_delay(Duration::from_millis(50))
.set_body_json(event.json())
})
.mount(&server),
);
.mount()
.await;
client.event_cache().subscribe().unwrap();
(server, client, room)
});
let room = client.get_room(&room_id).expect("Room not found");
let pinned_event_ids = room.pinned_event_ids().unwrap_or_default();
assert!(!pinned_event_ids.is_empty());
assert_eq!(pinned_event_ids.len(), PINNED_EVENTS_COUNT);
let count = PINNED_EVENTS_COUNT;
let name = format!("{count} pinned events");
let mut group = c.benchmark_group("Test");
let mut group = c.benchmark_group("Load pinned events");
group.throughput(Throughput::Elements(count as u64));
group.sample_size(10);
let client = Arc::new(client);
{
let client = client.clone();
runtime.spawn_blocking(move || {
client.event_cache().subscribe().unwrap();
});
}
group.bench_function(BenchmarkId::new("load_pinned_events", name), |b| {
group.bench_function(BenchmarkId::new("Load pinned events [memory]", name), |b| {
b.to_async(&runtime).iter(|| async {
let pinned_event_ids = room.pinned_event_ids().unwrap_or_default();
assert!(!pinned_event_ids.is_empty());
assert_eq!(pinned_event_ids.len(), PINNED_EVENTS_COUNT);
// Reset cache so it always loads the events from the mocked endpoint
client.event_cache().empty_immutable_cache().await;
client
.event_cache_store()
.lock()
.await
.unwrap()
.clear_all_linked_chunks()
.await
.unwrap();
let timeline = Timeline::builder(&room)
let timeline = TimelineBuilder::new(&room)
.with_focus(TimelineFocus::PinnedEvents {
max_events_to_load: 100,
max_concurrent_requests: 10,
@@ -220,45 +202,15 @@ pub fn load_pinned_events_benchmark(c: &mut Criterion) {
{
let _guard = runtime.enter();
runtime.block_on(server.reset());
drop(server);
}
group.finish();
}
async fn mock_sync(server: &MockServer, response_body: impl Serialize, since: Option<String>) {
let mut mock_builder = Mock::given(method("GET"))
.and(path("/_matrix/client/r0/sync"))
.and(header("authorization", "Bearer 1234"));
if let Some(since) = since {
mock_builder = mock_builder.and(query_param("since", since));
} else {
mock_builder = mock_builder.and(query_param_is_missing("since"));
}
mock_builder
.respond_with(ResponseTemplate::new(200).set_body_json(response_body))
.mount(server)
.await;
}
fn criterion() -> Criterion {
#[cfg(target_os = "linux")]
let criterion = Criterion::default().with_profiler(pprof::criterion::PProfProfiler::new(
100,
pprof::criterion::Output::Flamegraph(None),
));
#[cfg(not(target_os = "linux"))]
let criterion = Criterion::default();
criterion
}
criterion_group! {
name = room;
config = criterion();
config = Criterion::default();
targets = receive_all_members_benchmark, load_pinned_events_benchmark,
}
criterion_main!(room);
+10 -26
View File
@@ -1,29 +1,15 @@
use std::sync::Arc;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use matrix_sdk::{
config::StoreConfig,
matrix_auth::{MatrixSession, MatrixSessionTokens},
Client, RoomInfo, RoomState, StateChanges,
Client, RoomInfo, RoomState, SessionTokens, StateChanges,
authentication::matrix::MatrixSession, config::StoreConfig,
};
use matrix_sdk_base::{store::MemoryStore, SessionMeta, StateStore as _};
use matrix_sdk_base::{SessionMeta, StateStore as _, store::MemoryStore};
use matrix_sdk_sqlite::SqliteStateStore;
use ruma::{device_id, user_id, RoomId};
use ruma::{RoomId, device_id, user_id};
use tokio::runtime::Builder;
fn criterion() -> Criterion {
#[cfg(target_os = "linux")]
let criterion = Criterion::default().with_profiler(pprof::criterion::PProfProfiler::new(
100,
pprof::criterion::Output::Flamegraph(None),
));
#[cfg(not(target_os = "linux"))]
let criterion = Criterion::default();
criterion
}
/// Number of joined rooms in the benchmark.
const NUM_JOINED_ROOMS: usize = 10000;
@@ -31,7 +17,7 @@ const NUM_JOINED_ROOMS: usize = 10000;
const NUM_STRIPPED_JOINED_ROOMS: usize = 10000;
pub fn restore_session(c: &mut Criterion) {
let runtime = Builder::new_multi_thread().build().expect("Can't create runtime");
let runtime = Builder::new_multi_thread().enable_time().build().expect("Can't create runtime");
// Create a fake list of changes, and a session to recover from.
let mut changes = StateChanges::default();
@@ -51,7 +37,7 @@ pub fn restore_session(c: &mut Criterion) {
user_id: user_id!("@somebody:example.com").to_owned(),
device_id: device_id!("DEVICE_ID").to_owned(),
},
tokens: MatrixSessionTokens { access_token: "OHEY".to_owned(), refresh_token: None },
tokens: SessionTokens { access_token: "OHEY".to_owned(), refresh_token: None },
};
// Start the benchmark.
@@ -59,13 +45,11 @@ pub fn restore_session(c: &mut Criterion) {
let mut group = c.benchmark_group("Client reload");
group.throughput(Throughput::Elements(100));
const NAME: &str = "restore a session";
// Memory
let mem_store = Arc::new(MemoryStore::new());
runtime.block_on(mem_store.save_changes(&changes)).expect("initial filling of mem failed");
group.bench_with_input(BenchmarkId::new("memory store", NAME), &mem_store, |b, store| {
group.bench_with_input("Restore session [memory store]", &mem_store, |b, store| {
b.to_async(&runtime).iter(|| async {
let client = Client::builder()
.homeserver_url("https://matrix.example.com")
@@ -93,7 +77,7 @@ pub fn restore_session(c: &mut Criterion) {
.expect("initial filling of sqlite failed");
group.bench_with_input(
BenchmarkId::new(format!("sqlite store {encrypted_suffix}"), NAME),
BenchmarkId::new("Restore session [SQLite]", encrypted_suffix),
&sqlite_store,
|b, store| {
b.to_async(&runtime).iter(|| async {
@@ -125,7 +109,7 @@ pub fn restore_session(c: &mut Criterion) {
criterion_group! {
name = benches;
config = criterion();
config = Criterion::default();
targets = restore_session
}
criterion_main!(benches);
+125
View File
@@ -0,0 +1,125 @@
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use matrix_sdk::test_utils::mocks::MatrixMockServer;
use matrix_sdk_test::{JoinedRoomBuilder, StateTestEvent, event_factory::EventFactory};
use matrix_sdk_ui::timeline::TimelineBuilder;
use ruma::{
EventId, events::room::message::RoomMessageEventContentWithoutRelation, owned_room_id,
owned_user_id,
};
use tokio::runtime::Builder;
/// Benchmark the time it takes to create a timeline (with read receipt
/// support), when there are many initial events at rest in the event cache.
///
/// `NUM_EVENTS` is the number of events that will be stored initially in the
/// event cache. It will be a mix of messages, reactions, edits and redactions,
/// so there are some aggregations to take into account by the timeline as well.
pub fn create_timeline_with_initial_events(c: &mut Criterion) {
const NUM_EVENTS: usize = 10000;
let runtime = Builder::new_multi_thread().enable_all().build().expect("Can't create runtime");
let room_id = owned_room_id!("!room:example.com");
let sender_id = owned_user_id!("@sender:example.com");
let other_sender_id = owned_user_id!("@other_sender:example.com");
let another_sender_id = owned_user_id!("@another_sender:example.com");
let f = EventFactory::new().room(&room_id);
let mut events = Vec::new();
for i in 0..NUM_EVENTS {
let sender = match i % 3 {
0 => &sender_id,
1 => &other_sender_id,
2 => &another_sender_id,
_ => unreachable!("math genius over here"),
};
let event_id = EventId::parse(format!("$event{i}")).unwrap();
let j = i % 10;
if j < 6 {
// Messages.
events.push(
f.text_msg(format!("Message {i}"))
.sender(sender)
.event_id(&event_id)
.into_raw_sync(),
);
} else if j < 8 {
// Reactions.
let prev_event = EventId::parse(format!("$event{}", i - 2)).unwrap();
events.push(
f.reaction(&prev_event, "👍").sender(sender).event_id(&event_id).into_raw_sync(),
);
} else if j == 8 {
// Edit.
// Note: (i-3)%3 is the same as i%3 -> same sender!
let prev_event = EventId::parse(format!("$event{}", i - 3)).unwrap();
events.push(
f.text_msg(format!("* Message {}v2", i - 3))
.edit(
&prev_event,
RoomMessageEventContentWithoutRelation::text_plain(format!(
"Message {}v2",
i - 3
)),
)
.sender(sender)
.event_id(&event_id)
.into_raw_sync(),
);
} else if j == 9 {
// Redaction.
// Note: (i-6)%3 is the same as i%6 -> same sender!
let prev_event = EventId::parse(format!("$event{}", i - 6)).unwrap();
events
.push(f.redaction(&prev_event).sender(sender).event_id(&event_id).into_raw_sync());
}
}
let builder = JoinedRoomBuilder::new(&room_id)
.add_state_event(StateTestEvent::Encryption)
.add_timeline_bulk(events);
let room = runtime.block_on(async move {
let server = MatrixMockServer::new().await;
let client = server.client_builder().build().await;
client.event_cache().subscribe().unwrap();
let room = server.sync_room(&client, builder).await;
drop(server);
room
});
let mut group = c.benchmark_group("Create a timeline");
group.throughput(Throughput::Elements(NUM_EVENTS as _));
group.sample_size(10);
group.bench_function(
BenchmarkId::new("Create a timeline with initial events", format!("{NUM_EVENTS} events")),
|b| {
b.to_async(&runtime).iter(|| async {
let timeline = TimelineBuilder::new(&room)
.track_read_marker_and_receipts()
.build()
.await
.expect("Could not create timeline");
let (items, _) = timeline.subscribe().await;
assert_eq!(items.len(), 20);
});
},
);
group.finish();
}
criterion_group! {
name = room;
config = Criterion::default();
targets = create_timeline_with_initial_events
}
criterion_main!(room);
+27 -21
View File
@@ -3,12 +3,15 @@ name = "matrix-sdk-crypto-ffi"
version = "0.1.0"
authors = ["Damir Jelić <poljar@termina.org.uk>"]
edition = "2021"
rust-version = { workspace = true }
rust-version.workspace = true
description = "Uniffi based bindings for the Rust SDK crypto crate"
repository = "https://github.com/matrix-org/matrix-rust-sdk"
license = "Apache-2.0"
publish = false
[package.metadata.release]
release = false
[lib]
crate-type = ["cdylib", "staticlib"]
@@ -20,24 +23,30 @@ path = "uniffi-bindgen.rs"
default = ["bundled-sqlite"]
bundled-sqlite = ["matrix-sdk-sqlite/bundled"]
# Enable experimental support for encrypting state events; see
# https://github.com/matrix-org/matrix-rust-sdk/issues/5397.
experimental-encrypted-state-events = [
"matrix-sdk-crypto/experimental-encrypted-state-events",
]
[dependencies]
anyhow = { workspace = true }
futures-util = { workspace = true }
hmac = "0.12.1"
http = { workspace = true }
anyhow.workspace = true
futures-util.workspace = true
hmac.workspace = true
http.workspace = true
matrix-sdk-common = { workspace = true, features = ["uniffi"] }
matrix-sdk-ffi-macros = { workspace = true }
pbkdf2 = "0.12.2"
rand = { workspace = true }
ruma = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
sha2 = { workspace = true }
thiserror = { workspace = true }
matrix-sdk-ffi-macros.workspace = true
pbkdf2.workspace = true
rand.workspace = true
ruma.workspace = true
serde.workspace = true
serde_json.workspace = true
sha2.workspace = true
thiserror.workspace = true
tracing-subscriber = { workspace = true, features = ["env-filter"] }
# keep in sync with uniffi dependency in matrix-sdk-ffi, and uniffi_bindgen in ffi CI job
uniffi = { workspace = true, features = ["cli"] }
vodozemac = { workspace = true }
vodozemac.workspace = true
zeroize = { workspace = true, features = ["zeroize_derive"] }
[dependencies.js_int]
@@ -53,20 +62,17 @@ workspace = true
features = ["crypto-store"]
[dependencies.tokio]
version = "1.33.0"
workspace = true
default-features = false
features = ["rt-multi-thread"]
[build-dependencies]
vergen = { version = "8.2.5", features = ["build", "git", "gitcl"] }
uniffi = { workspace = true, features = ["build"] }
vergen-gitcl = { workspace = true, features = ["build"] }
[dev-dependencies]
tempfile = "3.8.0"
assert_matches2 = { workspace = true }
assert_matches2.workspace = true
tempfile.workspace = true
[lints]
workspace = true
[package.metadata.release]
release = false
+10 -4
View File
@@ -1,6 +1,11 @@
use std::{env, error::Error, path::PathBuf, process::Command};
use std::{
env,
error::Error,
path::{Path, PathBuf},
process::Command,
};
use vergen::EmitBuilder;
use vergen_gitcl::{Emitter, GitclBuilder};
/// Adds a temporary workaround for an issue with the Rust compiler and Android
/// in x86_64 devices: https://github.com/rust-lang/rust/issues/109717.
@@ -39,7 +44,7 @@ fn setup_x86_64_android_workaround() {
}
/// Run the clang binary at `clang_path`, and return its major version number
fn get_clang_major_version(clang_path: &PathBuf) -> String {
fn get_clang_major_version(clang_path: &Path) -> String {
let clang_output =
Command::new(clang_path).arg("-dumpversion").output().expect("failed to start clang");
@@ -54,7 +59,8 @@ fn get_clang_major_version(clang_path: &PathBuf) -> String {
fn main() -> Result<(), Box<dyn Error>> {
setup_x86_64_android_workaround();
EmitBuilder::builder().git_sha(true).git_describe(true, false, None).emit()?;
let git_config = GitclBuilder::default().sha(true).describe(true, false, None).build()?;
Emitter::default().add_instructions(&git_config)?.emit()?;
Ok(())
}
@@ -3,7 +3,7 @@ use std::{collections::HashMap, iter, ops::DerefMut, sync::Arc};
use hmac::Hmac;
use matrix_sdk_crypto::{
backups::DecryptionError,
store::{BackupDecryptionKey, CryptoStoreError as InnerStoreError},
store::{types::BackupDecryptionKey, CryptoStoreError as InnerStoreError},
};
use pbkdf2::pbkdf2;
use rand::{distributions::Alphanumeric, thread_rng, Rng};
@@ -1,15 +1,16 @@
use std::{mem::ManuallyDrop, sync::Arc};
use matrix_sdk_common::executor::Handle;
use matrix_sdk_crypto::{
dehydrated_devices::{
DehydratedDevice as InnerDehydratedDevice, DehydratedDevices as InnerDehydratedDevices,
RehydratedDevice as InnerRehydratedDevice,
},
store::DehydratedDeviceKey as InnerDehydratedDeviceKey,
store::types::DehydratedDeviceKey as InnerDehydratedDeviceKey,
DecryptionSettings,
};
use ruma::{api::client::dehydrated_device, events::AnyToDeviceEvent, serde::Raw, OwnedDeviceId};
use serde_json::json;
use tokio::runtime::Handle;
use crate::{CryptoStoreError, DehydratedDeviceKey};
@@ -17,7 +18,9 @@ use crate::{CryptoStoreError, DehydratedDeviceKey};
#[uniffi(flat_error)]
pub enum DehydrationError {
#[error(transparent)]
Pickle(#[from] matrix_sdk_crypto::vodozemac::LibolmPickleError),
Pickle(#[from] matrix_sdk_crypto::vodozemac::DehydratedDeviceError),
#[error(transparent)]
LegacyPickle(#[from] matrix_sdk_crypto::vodozemac::LibolmPickleError),
#[error(transparent)]
MissingSigningKey(#[from] matrix_sdk_crypto::SignatureError),
#[error(transparent)]
@@ -35,6 +38,9 @@ impl From<matrix_sdk_crypto::dehydrated_devices::DehydrationError> for Dehydrati
match value {
matrix_sdk_crypto::dehydrated_devices::DehydrationError::Json(e) => Self::Json(e),
matrix_sdk_crypto::dehydrated_devices::DehydrationError::Pickle(e) => Self::Pickle(e),
matrix_sdk_crypto::dehydrated_devices::DehydrationError::LegacyPickle(e) => {
Self::LegacyPickle(e)
}
matrix_sdk_crypto::dehydrated_devices::DehydrationError::MissingSigningKey(e) => {
Self::MissingSigningKey(e)
}
@@ -149,9 +155,13 @@ impl Drop for RehydratedDevice {
#[matrix_sdk_ffi_macros::export]
impl RehydratedDevice {
pub fn receive_events(&self, events: String) -> Result<(), crate::CryptoStoreError> {
pub fn receive_events(
&self,
events: String,
decryption_settings: &DecryptionSettings,
) -> Result<(), crate::CryptoStoreError> {
let events: Vec<Raw<AnyToDeviceEvent>> = serde_json::from_str(&events)?;
self.runtime.block_on(self.inner.receive_events(events))?;
self.runtime.block_on(self.inner.receive_events(events, decryption_settings))?;
Ok(())
}
+3 -3
View File
@@ -78,10 +78,10 @@ pub enum DecryptionError {
impl From<MegolmError> for DecryptionError {
fn from(value: MegolmError) -> Self {
match value {
match &value {
MegolmError::MissingRoomKey(withheld_code) => Self::MissingRoomKey {
error: "Withheld Inbound group session".to_owned(),
withheld_code: withheld_code.map(|w| w.as_str().to_owned()),
error: value.to_string(),
withheld_code: withheld_code.as_ref().map(|w| w.as_str().to_owned()),
},
_ => Self::Megolm { error: value.to_string() },
}
+39 -12
View File
@@ -37,8 +37,11 @@ use matrix_sdk_common::deserialized_responses::{ShieldState as RustShieldState,
use matrix_sdk_crypto::{
olm::{IdentityKeys, InboundGroupSession, SenderData, Session},
store::{
Changes, CryptoStore, DehydratedDeviceKey as InnerDehydratedDeviceKey, PendingChanges,
RoomSettings as RustRoomSettings,
types::{
Changes, DehydratedDeviceKey as InnerDehydratedDeviceKey, PendingChanges,
RoomSettings as RustRoomSettings,
},
CryptoStore,
},
types::{
DeviceKey, DeviceKeys, EventEncryptionAlgorithm as RustEventEncryptionAlgorithm, SigningKey,
@@ -221,7 +224,7 @@ async fn migrate_data(
passphrase: Option<String>,
progress_listener: Box<dyn ProgressListener>,
) -> anyhow::Result<()> {
use matrix_sdk_crypto::{olm::PrivateCrossSigningIdentity, store::BackupDecryptionKey};
use matrix_sdk_crypto::{olm::PrivateCrossSigningIdentity, store::types::BackupDecryptionKey};
use vodozemac::olm::Account;
use zeroize::Zeroize;
@@ -507,6 +510,7 @@ fn collect_sessions(
imported: session.imported,
backed_up: session.backed_up,
history_visibility: None,
shared_history: false,
algorithm: RustEventEncryptionAlgorithm::MegolmV1AesSha2,
};
@@ -661,6 +665,9 @@ impl From<HistoryVisibility> for RustHistoryVisibility {
pub struct EncryptionSettings {
/// The encryption algorithm that should be used in the room.
pub algorithm: EventEncryptionAlgorithm,
/// Whether state event encryption is enabled.
#[cfg(feature = "experimental-encrypted-state-events")]
pub encrypt_state_events: bool,
/// How long can the room key be used before it should be rotated. Time in
/// seconds.
pub rotation_period: u64,
@@ -680,15 +687,22 @@ pub struct EncryptionSettings {
impl From<EncryptionSettings> for RustEncryptionSettings {
fn from(v: EncryptionSettings) -> Self {
let sharing_strategy = if v.only_allow_trusted_devices {
CollectStrategy::OnlyTrustedDevices
} else if v.error_on_verified_user_problem {
CollectStrategy::ErrorOnVerifiedUserProblem
} else {
CollectStrategy::AllDevices
};
RustEncryptionSettings {
algorithm: v.algorithm.into(),
#[cfg(feature = "experimental-encrypted-state-events")]
encrypt_state_events: false,
rotation_period: Duration::from_secs(v.rotation_period),
rotation_period_msgs: v.rotation_period_msgs,
history_visibility: v.history_visibility.into(),
sharing_strategy: CollectStrategy::DeviceBasedStrategy {
only_allow_trusted_devices: v.only_allow_trusted_devices,
error_on_verified_user_problem: v.error_on_verified_user_problem,
},
sharing_strategy,
}
}
}
@@ -812,10 +826,10 @@ impl BackupKeys {
}
}
impl TryFrom<matrix_sdk_crypto::store::BackupKeys> for BackupKeys {
impl TryFrom<matrix_sdk_crypto::store::types::BackupKeys> for BackupKeys {
type Error = ();
fn try_from(keys: matrix_sdk_crypto::store::BackupKeys) -> Result<Self, Self::Error> {
fn try_from(keys: matrix_sdk_crypto::store::types::BackupKeys) -> Result<Self, Self::Error> {
Ok(Self {
recovery_key: BackupRecoveryKey {
inner: keys.decryption_key.ok_or(())?,
@@ -860,8 +874,8 @@ impl From<InnerDehydratedDeviceKey> for DehydratedDeviceKey {
}
}
impl From<matrix_sdk_crypto::store::RoomKeyCounts> for RoomKeyCounts {
fn from(count: matrix_sdk_crypto::store::RoomKeyCounts) -> Self {
impl From<matrix_sdk_crypto::store::types::RoomKeyCounts> for RoomKeyCounts {
fn from(count: matrix_sdk_crypto::store::types::RoomKeyCounts) -> Self {
Self { total: count.total as i64, backed_up: count.backed_up as i64 }
}
}
@@ -901,6 +915,10 @@ impl From<matrix_sdk_crypto::CrossSigningStatus> for CrossSigningStatus {
pub struct RoomSettings {
/// The encryption algorithm that should be used in the room.
pub algorithm: EventEncryptionAlgorithm,
/// Whether state event encryption is enabled.
#[cfg(feature = "experimental-encrypted-state-events")]
#[serde(default)]
pub encrypt_state_events: bool,
/// Should untrusted devices receive the room key, or should they be
/// excluded from the conversation.
pub only_allow_trusted_devices: bool,
@@ -911,7 +929,12 @@ impl TryFrom<RustRoomSettings> for RoomSettings {
fn try_from(value: RustRoomSettings) -> Result<Self, Self::Error> {
let algorithm = value.algorithm.try_into()?;
Ok(Self { algorithm, only_allow_trusted_devices: value.only_allow_trusted_devices })
Ok(Self {
algorithm,
#[cfg(feature = "experimental-encrypted-state-events")]
encrypt_state_events: value.encrypt_state_events,
only_allow_trusted_devices: value.only_allow_trusted_devices,
})
}
}
@@ -1164,6 +1187,8 @@ mod tests {
assert_eq!(
Some(RoomSettings {
algorithm: EventEncryptionAlgorithm::OlmV1Curve25519AesSha2,
#[cfg(feature = "experimental-encrypted-state-events")]
encrypt_state_events: false,
only_allow_trusted_devices: true
}),
settings1
@@ -1173,6 +1198,8 @@ mod tests {
assert_eq!(
Some(RoomSettings {
algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2,
#[cfg(feature = "experimental-encrypted-state-events")]
encrypt_state_events: false,
only_allow_trusted_devices: false
}),
settings2
+51 -35
View File
@@ -16,9 +16,10 @@ use matrix_sdk_crypto::{
},
decrypt_room_key_export, encrypt_room_key_export,
olm::ExportedRoomKey,
store::{BackupDecryptionKey, Changes},
store::types::{BackupDecryptionKey, Changes},
types::requests::ToDeviceRequest,
DecryptionSettings, LocalTrust, OlmMachine as InnerMachine, UserIdentity as SdkUserIdentity,
CollectStrategy, DecryptionSettings, LocalTrust, OlmMachine as InnerMachine,
UserIdentity as SdkUserIdentity,
};
use ruma::{
api::{
@@ -38,7 +39,7 @@ use ruma::{
},
events::{
key::verification::VerificationMethod, room::message::MessageType, AnyMessageLikeEvent,
AnySyncMessageLikeEvent, MessageLikeEvent,
AnySyncMessageLikeEvent, AnyTimelineEvent, MessageLikeEvent,
},
serde::Raw,
to_device::DeviceIdOrAllDevices,
@@ -96,8 +97,8 @@ pub struct RoomKeyInfo {
pub session_id: String,
}
impl From<matrix_sdk_crypto::store::RoomKeyInfo> for RoomKeyInfo {
fn from(value: matrix_sdk_crypto::store::RoomKeyInfo) -> Self {
impl From<matrix_sdk_crypto::store::types::RoomKeyInfo> for RoomKeyInfo {
fn from(value: matrix_sdk_crypto::store::types::RoomKeyInfo) -> Self {
Self {
algorithm: value.algorithm.to_string(),
room_id: value.room_id.to_string(),
@@ -354,7 +355,7 @@ impl OlmMachine {
.map(|d| d.into()))
}
/// Manually the device of the given user with the given device ID.
/// Manually verify the device of the given user with the given device ID.
///
/// This method will attempt to sign the device using our private cross
/// signing key.
@@ -526,6 +527,7 @@ impl OlmMachine {
key_counts: HashMap<String, i32>,
unused_fallback_keys: Option<Vec<String>>,
next_batch_token: String,
decryption_settings: &DecryptionSettings,
) -> Result<SyncChangesResult, CryptoStoreError> {
let to_device: ToDevice = serde_json::from_str(&events)?;
let device_changes: RumaDeviceLists = device_changes.into();
@@ -544,18 +546,22 @@ impl OlmMachine {
let unused_fallback_keys: Option<Vec<OneTimeKeyAlgorithm>> =
unused_fallback_keys.map(|u| u.into_iter().map(OneTimeKeyAlgorithm::from).collect());
let (to_device_events, room_key_infos) = self.runtime.block_on(
self.inner.receive_sync_changes(matrix_sdk_crypto::EncryptionSyncChanges {
to_device_events: to_device.events,
changed_devices: &device_changes,
one_time_keys_counts: &key_counts,
unused_fallback_keys: unused_fallback_keys.as_deref(),
next_batch_token: Some(next_batch_token),
}),
)?;
let (to_device_events, room_key_infos) =
self.runtime.block_on(self.inner.receive_sync_changes(
matrix_sdk_crypto::EncryptionSyncChanges {
to_device_events: to_device.events,
changed_devices: &device_changes,
one_time_keys_counts: &key_counts,
unused_fallback_keys: unused_fallback_keys.as_deref(),
next_batch_token: Some(next_batch_token),
},
decryption_settings,
))?;
let to_device_events =
to_device_events.into_iter().map(|event| event.json().get().to_owned()).collect();
let to_device_events = to_device_events
.into_iter()
.map(|event| event.to_raw().json().get().to_owned())
.collect();
let room_key_infos = room_key_infos.into_iter().map(|info| info.into()).collect();
Ok(SyncChangesResult { to_device_events, room_key_infos })
@@ -827,6 +833,7 @@ impl OlmMachine {
device_id: String,
event_type: String,
content: String,
share_strategy: CollectStrategy,
) -> Result<Option<Request>, CryptoStoreError> {
let user_id = parse_user_id(&user_id)?;
let device_id = device_id.as_str().into();
@@ -835,8 +842,11 @@ impl OlmMachine {
let device = self.runtime.block_on(self.inner.get_device(&user_id, device_id, None))?;
if let Some(device) = device {
let encrypted_content =
self.runtime.block_on(device.encrypt_event_raw(&event_type, &content))?;
let encrypted_content = self.runtime.block_on(device.encrypt_event_raw(
&event_type,
&content,
share_strategy,
))?;
let request = ToDeviceRequest::new(
user_id.as_ref(),
@@ -892,7 +902,7 @@ impl OlmMachine {
))?;
if handle_verification_events {
if let Ok(e) = decrypted.event.deserialize() {
if let Ok(AnyTimelineEvent::MessageLike(e)) = decrypted.event.deserialize() {
match &e {
AnyMessageLikeEvent::RoomMessage(MessageLikeEvent::Original(
original_event,
@@ -915,20 +925,25 @@ impl OlmMachine {
let event_json: Event<'_> = serde_json::from_str(decrypted.event.json().get())?;
Ok(match &encryption_info.algorithm_info {
AlgorithmInfo::MegolmV1AesSha2 { curve25519_key, sender_claimed_keys } => {
DecryptedEvent {
clear_event: serde_json::to_string(&event_json)?,
sender_curve25519_key: curve25519_key.to_owned(),
claimed_ed25519_key: sender_claimed_keys
.get(&DeviceKeyAlgorithm::Ed25519)
.cloned(),
forwarding_curve25519_chain: vec![],
shield_state: if strict_shields {
encryption_info.verification_state.to_shield_state_strict().into()
} else {
encryption_info.verification_state.to_shield_state_lax().into()
},
}
AlgorithmInfo::MegolmV1AesSha2 {
curve25519_key,
sender_claimed_keys,
session_id: _,
} => DecryptedEvent {
clear_event: serde_json::to_string(&event_json)?,
sender_curve25519_key: curve25519_key.to_owned(),
claimed_ed25519_key: sender_claimed_keys.get(&DeviceKeyAlgorithm::Ed25519).cloned(),
forwarding_curve25519_chain: vec![],
shield_state: if strict_shields {
encryption_info.verification_state.to_shield_state_strict().into()
} else {
encryption_info.verification_state.to_shield_state_lax().into()
},
},
AlgorithmInfo::OlmV1Curve25519AesSha2 { .. } => {
// cannot happen because `decrypt_room_event` would have fail to decrypt olm for
// a room (EventError::UnsupportedAlgorithm)
panic!("Unsupported olm algorithm in room")
}
})
}
@@ -1335,7 +1350,8 @@ impl OlmMachine {
let (sas, request) = self.runtime.block_on(device.start_verification())?;
Some(StartSasResult {
sas: Sas { inner: sas, runtime: self.runtime.handle().to_owned() }.into(),
sas: Sas { inner: Box::new(sas), runtime: self.runtime.handle().to_owned() }
.into(),
request: request.into(),
})
} else {
@@ -27,7 +27,7 @@ use ruma::{
to_device::send_event_to_device::v3::Response as ToDeviceResponse,
},
assign,
events::EventContent,
events::MessageLikeEventContent,
OwnedTransactionId, UserId,
};
use serde_json::json;
@@ -224,8 +224,8 @@ impl From<&ToDeviceRequest> for Request {
}
}
impl From<&RoomMessageRequest> for Request {
fn from(r: &RoomMessageRequest) -> Self {
impl From<&Box<RoomMessageRequest>> for Request {
fn from(r: &Box<RoomMessageRequest>) -> Self {
Self::RoomMessage {
request_id: r.txn_id.to_string(),
room_id: r.room_id.to_string(),
@@ -1,6 +1,7 @@
use std::sync::Arc;
use futures_util::{Stream, StreamExt};
use matrix_sdk_common::executor::Handle;
use matrix_sdk_crypto::{
matrix_sdk_qrcode::QrVerificationData, CancelInfo as RustCancelInfo, QrVerification as InnerQr,
QrVerificationState, Sas as InnerSas, SasState as RustSasState,
@@ -8,7 +9,6 @@ use matrix_sdk_crypto::{
VerificationRequestState as RustVerificationRequestState,
};
use ruma::events::key::verification::VerificationMethod;
use tokio::runtime::Handle;
use vodozemac::{base64_decode, base64_encode};
use crate::{CryptoStoreError, OutgoingVerificationRequest, SignatureUploadRequest};
@@ -88,7 +88,7 @@ impl Verification {
/// returns `None` if the verification is not a `Sas` verification.
pub fn as_sas(&self) -> Option<Arc<Sas>> {
if let InnerVerification::SasV1(sas) = &self.inner {
Some(Sas { inner: sas.to_owned(), runtime: self.runtime.to_owned() }.into())
Some(Sas { inner: sas.clone(), runtime: self.runtime.to_owned() }.into())
} else {
None
}
@@ -98,7 +98,7 @@ impl Verification {
/// returns `None` if the verification is not a `QrCode` verification.
pub fn as_qr(&self) -> Option<Arc<QrCode>> {
if let InnerVerification::QrV1(qr) = &self.inner {
Some(QrCode { inner: qr.to_owned(), runtime: self.runtime.to_owned() }.into())
Some(QrCode { inner: qr.clone(), runtime: self.runtime.to_owned() }.into())
} else {
None
}
@@ -108,7 +108,7 @@ impl Verification {
/// The `m.sas.v1` verification flow.
#[derive(uniffi::Object)]
pub struct Sas {
pub(crate) inner: InnerSas,
pub(crate) inner: Box<InnerSas>,
pub(crate) runtime: Handle,
}
@@ -324,7 +324,7 @@ impl From<QrVerificationState> for QrCodeState {
/// verification flow.
#[derive(uniffi::Object)]
pub struct QrCode {
pub(crate) inner: InnerQr,
pub(crate) inner: Box<InnerQr>,
pub(crate) runtime: Handle,
}
@@ -669,7 +669,7 @@ impl VerificationRequest {
/// verification flow.
pub fn start_sas_verification(&self) -> Result<Option<StartSasResult>, CryptoStoreError> {
Ok(self.runtime.block_on(self.inner.start_sas())?.map(|(sas, r)| StartSasResult {
sas: Arc::new(Sas { inner: sas, runtime: self.runtime.clone() }),
sas: Arc::new(Sas { inner: Box::new(sas), runtime: self.runtime.clone() }),
request: r.into(),
}))
}
@@ -690,7 +690,7 @@ impl VerificationRequest {
Ok(self
.runtime
.block_on(self.inner.generate_qr_code())?
.map(|qr| QrCode { inner: qr, runtime: self.runtime.clone() }.into()))
.map(|qr| QrCode { inner: Box::new(qr), runtime: self.runtime.clone() }.into()))
}
/// Pass data from a scanned QR code to an active verification request and
@@ -717,7 +717,7 @@ impl VerificationRequest {
let request = qr.reciprocate()?;
Some(ScanResult {
qr: QrCode { inner: qr, runtime: self.runtime.clone() }.into(),
qr: QrCode { inner: Box::new(qr), runtime: self.runtime.clone() }.into(),
request: request.into(),
})
} else {
@@ -791,8 +791,7 @@ impl VerificationRequest {
// task.
let should_break = matches!(
state,
RustVerificationRequestState::Done { .. }
| RustVerificationRequestState::Cancelled { .. }
RustVerificationRequestState::Done | RustVerificationRequestState::Cancelled { .. }
);
let state = Self::convert_verification_request(&request, state);
+5 -1
View File
@@ -7,8 +7,9 @@ license = "Apache-2.0"
name = "matrix-sdk-ffi-macros"
readme = "README.md"
repository = "https://github.com/matrix-org/matrix-rust-sdk"
rust-version = { workspace = true }
rust-version.workspace = true
version = "0.7.0"
publish = false
[lib]
proc-macro = true
@@ -22,3 +23,6 @@ syn = { version = "2.0.43", features = ["full", "extra-traits"] }
[lints]
workspace = true
[package.metadata.release]
release = false
+6 -1
View File
@@ -51,7 +51,12 @@ pub fn export(attr: TokenStream, item: TokenStream) -> TokenStream {
let res = match syn::parse(item) {
Ok(item) => match has_async_fn(item) {
true => quote! { #[uniffi::export(async_runtime = "tokio", #attr2)] },
true => {
quote! {
#[cfg_attr(target_family = "wasm", uniffi::export(#attr2))]
#[cfg_attr(not(target_family = "wasm"), uniffi::export(async_runtime = "tokio", #attr2))]
}
}
false => quote! { #[uniffi::export(#attr2)] },
},
Err(e) => e.into_compile_error(),
+239 -1
View File
@@ -1,7 +1,194 @@
# unreleased
# Changelog
All notable changes to this project will be documented in this file.
<!-- next-header -->
## [Unreleased] - ReleaseDate
## [0.14.0] - 2025-09-04
### Features:
- Add `LowPriority` and `NonLowPriority` variants to `RoomListEntriesDynamicFilterKind` for filtering
rooms based on their low priority status. These filters allow clients to show only low priority rooms
or exclude low priority rooms from the room list.
([#5508](https://github.com/matrix-org/matrix-rust-sdk/pull/5508))
- Add `room_version` and `privileged_creators_role` to `RoomInfo` ([#5449](https://github.com/matrix-org/matrix-rust-sdk/pull/5449)).
- The [`unstable-hydra`] feature has been enabled, which enables room v12 changes in the SDK.
([#5450](https://github.com/matrix-org/matrix-rust-sdk/pull/5450)).
- Add experimental support for
[MSC4306](https://github.com/matrix-org/matrix-spec-proposals/pull/4306), with the
`Room::fetch_thread_subscription()` and `Room::set_thread_subscription()` methods.
([#5442](https://github.com/matrix-org/matrix-rust-sdk/pull/5442))
- [**breaking**] [`GalleryUploadParameters::reply`] and [`UploadParameters::reply`] have been both
replaced with a new optional `in_reply_to` field, that's a string which will be parsed into an
`OwnedEventId` when sending the event. The thread relationship will be automatically filled in,
based on the timeline focus.
([5427](https://github.com/matrix-org/matrix-rust-sdk/pull/5427))
- [**breaking**] [`Timeline::send_reply()`] now automatically fills in the thread relationship,
based on the timeline focus. As a result, it only takes an `OwnedEventId` parameter, instead of
the `Reply` type. The proper way to start a thread is now thus to create a threaded-focused
timeline, and then use `Timeline::send()`.
([5427](https://github.com/matrix-org/matrix-rust-sdk/pull/5427))
- Add `HomeserverLoginDetails::supports_sso_login` for legacy SSO support information.
This is primarily for Element X to give a dedicated error message in case
it connects a homeserver with only this method available.
([#5222](https://github.com/matrix-org/matrix-rust-sdk/pull/5222))
### Breaking changes:
- The timeline will now always use the send queue to upload medias, so the
`UploadParameters::use_send_queue` bool has been removed. Make sure to listen to the send queue's
error updates, and to handle send queue restarts.
([#5525](https://github.com/matrix-org/matrix-rust-sdk/pull/5525))
- Support for the legacy media upload progress has been disabled. Media upload progress is
available through the send queue, and can be enabled thanks to
`Client::enable_send_queue_upload_progress()`.
([#5525](https://github.com/matrix-org/matrix-rust-sdk/pull/5525))
- `TimelineDiff` is now exported as a true `uniffi::Enum` instead of the weird `uniffi::Object` hybrid. This matches
both `RoomDirectorySearchEntryUpdate` and `RoomListEntriesUpdate` and can be used in the same way.
([#5474](https://github.com/matrix-org/matrix-rust-sdk/pull/5474))
- The `creator` field of `RoomInfo` has been renamed to `creators` and can now contain a list of
user IDs, to reflect that a room can now have several creators, as introduced in room version 12.
([#5436](https://github.com/matrix-org/matrix-rust-sdk/pull/5436))
- The `PowerLevel` type was introduced to represent power levels instead of `i64` to differentiate
the infinite power level of creators, as introduced in room version 12. It is used in
`suggested_role_for_power_level`, `suggested_power_level_for_role` and `RoomMember`.
([#5436](https://github.com/matrix-org/matrix-rust-sdk/pull/5436))
- `Client::get_url` now returns a `Vec<u8>` instead of a `String`. It also throws an error when the
response isn't status code 200 OK, instead of providing the error in the response body.
([#5438](https://github.com/matrix-org/matrix-rust-sdk/pull/5438))
- `RoomPreview::info()` doesn't return a result anymore. All unknown join rules are handled in the
`JoinRule::Custom` variant.
([#5337](https://github.com/matrix-org/matrix-rust-sdk/pull/5337))
- The `reason` argument of `Room::report_room` is now required, do to a clarification in the spec.
([#5337](https://github.com/matrix-org/matrix-rust-sdk/pull/5337))
- `PublicRoomJoinRule` has more variants, supporting all the known values from the spec.
([#5337](https://github.com/matrix-org/matrix-rust-sdk/pull/5337))
- The fields of `MediaPreviewConfig` are both optional, allowing to use the type for room account
data as well as global account data.
([#5337](https://github.com/matrix-org/matrix-rust-sdk/pull/5337))
- The `event_id` field of `PredecessorRoom` was removed, due to its removal in the Matrix
specification with MSC4291.
([#5419](https://github.com/matrix-org/matrix-rust-sdk/pull/5419))
- `Client::url_for_oidc` now allows requesting additional scopes for the OAuth2 authorization code grant.
([#5395](https://github.com/matrix-org/matrix-rust-sdk/pull/5395))
- `Client::url_for_oidc` now allows passing an optional existing device id from a previous login call.
([#5394](https://github.com/matrix-org/matrix-rust-sdk/pull/5394))
- `ClientBuilder::build_with_qr_code` has been removed. Instead, the Client should be built by passing
`QrCodeData::server_name` to `ClientBuilder::server_name_or_homeserver_url`, after which QR login can be performed by
calling `Client::login_with_qr_code`. ([#5388](https://github.com/matrix-org/matrix-rust-sdk/pull/5388))
- The MSRV has been bumped to Rust 1.88.
([#5431](https://github.com/matrix-org/matrix-rust-sdk/pull/5431))
- `Room::send_call_notification` and `Room::send_call_notification_if_needed` have been removed, since the event type they send is outdated, and `Client` is not actually supposed to be able to join MatrixRTC sessions (yet). In practice, users of these methods probably already rely on another MatrixRTC implementation to participate in sessions, and such an implementation should be capable of sending notifications itself.
- The `GalleryItemInfo` variants now take an `UploadSource` rather than a `String` path to enable uploading
from bytes directly.
([#5529](https://github.com/matrix-org/matrix-rust-sdk/pull/5529))
- Media and gallery uploads now use `UploadSource` to specify the thumbnail.
([#5530](https://github.com/matrix-org/matrix-rust-sdk/pull/5530))
## [0.13.0] - 2025-07-10
### Features
- Add `NotificationRoomInfo::topic` to the `NotificationRoomInfo` struct, which
contains the topic of the room. This is useful for displaying the room topic
in notifications. ([#5300](https://github.com/matrix-org/matrix-rust-sdk/pull/5300))
- Add `EmbeddedEventDetails::timestamp` and `EmbeddedEventDetails::event_or_transaction_id`
which are already available in regular timeline items.
([#5331](https://github.com/matrix-org/matrix-rust-sdk/pull/5331))
- `RoomListService::subscribe_to_rooms` becomes `async` and automatically calls
`matrix_sdk::latest_events::LatestEvents::listen_to_room`
([#5369](https://github.com/matrix-org/matrix-rust-sdk/pull/5369))
### Refactor
- Adjust features in the `matrix-sdk-ffi` crate to expose more platform-specific knobs.
Previously the `matrix-sdk-ffi` was configured primarily by target configs, choosing
between the tls flavor (`rustls-tls` or `native-tls`) and features like `sentry` based
purely on the target. As we work to add an additional Wasm target to this crate,
the cross product of target specific features has become somewhat chaotic, and we
have shifted to externalize these choices as feature flags.
To maintain existing compatibility on the major platforms, these features should be used:
Android: `"bundled-sqlite,unstable-msc4274,rustls-tls,sentry"`
iOS: `"bundled-sqlite,unstable-msc4274,native-tls,sentry"`
Javascript/Wasm: `"unstable-msc4274,native-tls"`
In the future additional choices (such as session storage, `sqlite` and `indexeddb`)
will likely be added as well.
Breaking changes:
- `Client::reset_server_capabilities` has been renamed to `Client::reset_server_info`.
([#5167](https://github.com/matrix-org/matrix-rust-sdk/pull/5167))
- `RoomPreview::join_rule`, `NotificationItem::join_rule`, `RoomInfo::is_public`, and
`Room::is_public()` return values are now optional. They will be set to `None` if the join rule
state event is missing for a given room. `NotificationRoomInfo::is_public` has been removed;
callers can inspect the value of `NotificationItem::join_rule` to determine if the room is public
(i.e. if the join rule is `Public`).
([#5278](https://github.com/matrix-org/matrix-rust-sdk/pull/5278))
## [0.12.0] - 2025-06-10
Breaking changes:
- `Client::send_call_notification_if_needed` now returns `Result<bool>` instead of `Result<()>` so we can check if
the event was sent.
- `Client::upload_avatar` and `Timeline::send_attachment` now may fail if a file too large for the homeserver media
config is uploaded.
- `UploadParameters` replaces field `filename: String` with `source: UploadSource`.
`UploadSource` is an enum which may take a filename or a filename and bytes, which
allows a foreign language to read file contents natively and then pass those contents to
the foreign function when uploading a file through the `Timeline`.
([#4948](https://github.com/matrix-org/matrix-rust-sdk/pull/4948))
- `RoomInfo` replaces its field `is_tombstoned: bool` with `tombstone: Option<RoomTombstoneInfo>`,
containing the data needed to implement the room migration UI, a message and the replacement room id.
([#5027](https://github.com/matrix-org/matrix-rust-sdk/pull/5027))
Additions:
- `Client::subscribe_to_room_info` allows clients to subscribe to room info updates in rooms which may not be known yet.
This is useful when displaying a room preview for an unknown room, so when we receive any membership change for it,
we can automatically update the UI.
- `Client::get_max_media_upload_size` to get the max size of a request sent to the homeserver so we can tweak our media
uploads by compressing/transcoding the media.
- Add `ClientBuilder::enable_share_history_on_invite` to enable experimental support for sharing encrypted room history
on invite, per [MSC4268](https://github.com/matrix-org/matrix-spec-proposals/pull/4268).
([#5141](https://github.com/matrix-org/matrix-rust-sdk/pull/5141))
- Support for adding a Sentry layer to the FFI bindings has been added. Only `tracing` statements with
the field `sentry=true` will be forwarded to Sentry, in addition to default Sentry filters.
- Add room topic string to `StateEventContent`
- Add `UploadSource` for representing upload data - this is analogous to `matrix_sdk_ui::timeline::AttachmentSource`
- Add `Client::observe_account_data_event` and `Client::observe_room_account_data_event` to
subscribe to global and room account data changes.
([#4994](https://github.com/matrix-org/matrix-rust-sdk/pull/4994))
- Add `Timeline::send_gallery` to send MSC4274-style galleries.
([#5163](https://github.com/matrix-org/matrix-rust-sdk/pull/5163))
- Add `reply_params` to `GalleryUploadParameters` to allow sending galleries as (threaded) replies.
([#5173](https://github.com/matrix-org/matrix-rust-sdk/pull/5173))
Breaking changes:
- `contacts` has been removed from `OidcConfiguration` (it was unused since the switch to OAuth).
## [0.11.0] - 2025-04-11
Breaking changes:
- `TracingConfiguration` now includes a new field `trace_log_packs`, which gives a convenient way
to set the TRACE log level for multiple targets related to a given feature.
([#4824](https://github.com/matrix-org/matrix-rust-sdk/pull/4824))
- `setup_tracing` has been renamed `init_platform`; in addition to the `TracingConfiguration`
parameter it also now takes a boolean indicating whether to spawn a minimal tokio runtime for the
application; in general for main app processes this can be set to `false`, and memory-constrained
programs can set it to `true`.
- Matrix client API errors coming from API responses will now be mapped to `ClientError::MatrixApi`, containing both the
original message and the associated error code and kind.
- `EventSendState` now has two additional variants: `CrossSigningNotSetup` and
`SendingFromUnverifiedDevice`. These indicate that your own device is not
properly cross-signed, which is a requirement when using the identity-based
@@ -26,11 +213,62 @@ Breaking changes:
- There is a new `abortOidcLogin` method that should be called if the webview is dismissed without a callback (
or fails to present).
- The rest of `AuthenticationError` is now found in the OidcError type.
- `OidcAuthenticationData` is now called `OidcAuthorizationData`.
- The `get_element_call_required_permissions` function now requires the device_id.
- Some `OidcPrompt` cases have been removed (`None`, `SelectAccount`).
- `Room::is_encrypted` is replaced by `Room::latest_encryption_state`
which returns a value of the new `EncryptionState` enum; another
`Room::encryption_state` non-async and infallible method is added to get the
`EncryptionState` without running a network request.
([#4777](https://github.com/matrix-org/matrix-rust-sdk/pull/4777)). One can
safely replace:
```rust
room.is_encrypted().await?
```
by
```rust
room.latest_encryption_state().await?.is_encrypted()
```
- `ClientBuilder::passphrase` is renamed `session_passphrase`
([#4870](https://github.com/matrix-org/matrix-rust-sdk/pull/4870/))
- Merge `Timeline::send_thread_reply` into `Timeline::send_reply`. This
changes the parameters of `send_reply` which now requires passing the
event ID (and thread reply behaviour) inside a `ReplyParameters` struct.
([#4880](https://github.com/matrix-org/matrix-rust-sdk/pull/4880/))
- The `dynamic_registrations_file` field of `OidcConfiguration` was removed.
Clients are supposed to re-register with the homeserver for every login.
- `RoomPreview::own_membership_details` is now `RoomPreview::member_with_sender_info`, takes any user id and returns an
`Option<RoomMemberWithSenderInfo>`.
Additions:
- Add `Encryption::get_user_identity` which returns `UserIdentity`
- Add `ClientBuilder::room_key_recipient_strategy`
- Add `Room::send_raw`
- Add `NotificationSettings::set_custom_push_rule`
- Expose `withdraw_verification` to `UserIdentity`
- Expose `report_room` to `Room`
- Add `RoomInfo::encryption_state`
([#4788](https://github.com/matrix-org/matrix-rust-sdk/pull/4788))
- Add `Timeline::send_thread_reply` for clients that need to start threads
themselves.
([4819](https://github.com/matrix-org/matrix-rust-sdk/pull/4819))
- Add `ClientBuilder::session_pool_max_size`, `::session_cache_size` and `::session_journal_size_limit` to control the
stores configuration, especially their memory consumption
([#4870](https://github.com/matrix-org/matrix-rust-sdk/pull/4870/))
- Add `ClientBuilder::system_is_memory_constrained` to indicate that the system
has less memory available than the current standard
([#4894](https://github.com/matrix-org/matrix-rust-sdk/pull/4894))
- Add `Room::member_with_sender_info` to get both a room member's info and for the user who sent the `m.room.member`
event the `RoomMember` is based on.
+73 -57
View File
@@ -1,87 +1,103 @@
[package]
name = "matrix-sdk-ffi"
version = "0.2.0"
version = "0.14.0"
edition = "2021"
homepage = "https://github.com/matrix-org/matrix-rust-sdk"
keywords = ["matrix", "chat", "messaging", "ffi"]
license = "Apache-2.0"
readme = "README.md"
rust-version = { workspace = true }
rust-version.workspace = true
repository = "https://github.com/matrix-org/matrix-rust-sdk"
publish = false
[package.metadata.release]
release = true
[lib]
crate-type = ["cdylib", "staticlib"]
crate-type = [
# Needed by uniffi for Android bindings
"cdylib",
# Needed by uniffi for iOS bindings
"staticlib",
# Needed by uniffi for JS/Wasm bindings, which use rust as an intermediate language
"lib"
]
[features]
default = ["bundled-sqlite"]
default = ["bundled-sqlite", "unstable-msc4274"]
bundled-sqlite = ["matrix-sdk/bundled-sqlite"]
[build-dependencies]
uniffi = { workspace = true, features = ["build"] }
vergen = { version = "8.1.3", features = ["build", "git", "gitcl"] }
unstable-msc4274 = ["matrix-sdk-ui/unstable-msc4274"]
# Required when targeting a Javascript environment, like Wasm in a browser.
js = ["matrix-sdk-ui/js"]
# Use the TLS implementation provided by the host system, necessary on iOS and Wasm platforms.
native-tls = ["matrix-sdk/native-tls", "sentry?/native-tls"]
# Use Rustls as the TLS implementation, necessary on Android platforms.
rustls-tls = ["matrix-sdk/rustls-tls", "sentry?/rustls"]
# Enable sentry error monitoring, not compatible with Wasm platforms.
sentry = ["dep:sentry", "dep:sentry-tracing"]
[dependencies]
anyhow = { workspace = true }
as_variant = { workspace = true }
async-compat = "0.2.1"
eyeball-im = { workspace = true }
anyhow.workspace = true
extension-trait = "1.0.1"
futures-util = { workspace = true }
eyeball-im.workspace = true
futures-util.workspace = true
language-tags = "0.3.2"
log-panics = { version = "2", features = ["with-backtrace"] }
matrix-sdk-ffi-macros = { workspace = true }
matrix-sdk = { workspace = true, features = [
"anyhow",
"e2e-encryption",
"experimental-widgets",
"markdown",
"socks",
"sqlite",
"uniffi",
] }
matrix-sdk-common.workspace = true
matrix-sdk-ffi-macros.workspace = true
matrix-sdk-ui = { workspace = true, features = ["uniffi"] }
mime = "0.3.16"
once_cell = { workspace = true }
ruma = { workspace = true, features = ["html", "unstable-unspecified", "unstable-msc3488", "compat-unset-avatar", "unstable-msc3245-v1-compat"] }
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
tracing-core = { workspace = true }
once_cell.workspace = true
ruma = { workspace = true, features = ["html", "unstable-msc3488", "compat-unset-avatar", "unstable-msc3245-v1-compat", "unstable-msc4278", "unstable-hydra"] }
serde.workspace = true
serde_json.workspace = true
sentry = { workspace = true, optional = true, default-features = false, features = [
# Most default features enabled otherwise.
"backtrace",
"contexts",
"panic",
"reqwest",
"sentry-debug-images",
] }
sentry-tracing = { workspace = true, optional = true }
thiserror.workspace = true
tracing.workspace = true
tracing-appender.workspace = true
tracing-core.workspace = true
tracing-subscriber = { workspace = true, features = ["env-filter"] }
tracing-appender = { version = "0.2.2" }
url.workspace = true
uuid = { version = "1.4.1", features = ["v4"] }
zeroize.workspace = true
oauth2.workspace = true
[target.'cfg(target_family = "wasm")'.dependencies]
console_error_panic_hook = "0.1.7"
tokio = { workspace = true, features = ["sync", "macros"] }
uniffi.workspace = true
[target.'cfg(not(target_family = "wasm"))'.dependencies]
async-compat.workspace = true
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
uniffi = { workspace = true, features = ["tokio"] }
url = { workspace = true }
zeroize = { workspace = true }
uuid = { version = "1.4.1", features = ["v4"] }
language-tags = "0.3.2"
[target.'cfg(target_os = "android")'.dependencies]
paranoid-android = "0.2.1"
[target.'cfg(target_os = "android")'.dependencies.matrix-sdk]
workspace = true
features = [
"anyhow",
"e2e-encryption",
"experimental-oidc",
"experimental-sliding-sync",
"experimental-widgets",
"markdown",
"rustls-tls", # note: differ from block below
"socks",
"sqlite",
"uniffi",
]
[dev-dependencies]
similar-asserts.workspace = true
[target.'cfg(not(target_os = "android"))'.dependencies.matrix-sdk]
workspace = true
features = [
"anyhow",
"e2e-encryption",
"experimental-oidc",
"experimental-sliding-sync",
"experimental-widgets",
"markdown",
"native-tls", # note: differ from block above
"socks",
"sqlite",
"uniffi",
]
[build-dependencies]
uniffi = { workspace = true, features = ["build"] }
vergen-gitcl = { workspace = true, features = ["build"] }
[lints]
workspace = true
[package.metadata.release]
release = false
+20
View File
@@ -2,8 +2,28 @@
This uses [`uniffi`](https://mozilla.github.io/uniffi-rs/Overview.html) to build the matrix bindings for native support and wasm-bindgen for web-browser assembly support. Please refer to the specific section to figure out how to build and use the bindings for your platform.
## Features
Given the number of platforms targeted, we have broken out a number of features
### Platform specific
- `rustls-tls`: Use Rustls as the TLS implementation, necessary on Android platforms.
- `native-tls`: Use the TLS implementation provided by the host system, necessary on iOS and Wasm platforms.
### Functionality
- `sentry`: Enable error monitoring using Sentry, not supports on Wasm platforms.
- `bundled-sqlite`: Use an embedded version of sqlite instead of the system provided one.
### Unstable specs
- `unstable-msc4274`: Adds support for gallery message types, which contain multiple media elements.
## Platforms
Each supported target should use features to select the relevant TLS system. Here are some suggested feature flags for the major platforms:
- Android: `"bundled-sqlite,unstable-msc4274,rustls-tls,sentry"`
- iOS: `"bundled-sqlite,unstable-msc4274,native-tls,sentry"`
- Javascript/Wasm: `"unstable-msc4274,native-tls"`
### Swift/iOS sync
+12 -4
View File
@@ -1,6 +1,11 @@
use std::{env, error::Error, path::PathBuf, process::Command};
use std::{
env,
error::Error,
path::{Path, PathBuf},
process::Command,
};
use vergen::EmitBuilder;
use vergen_gitcl::{Emitter, GitclBuilder};
/// Adds a temporary workaround for an issue with the Rust compiler and Android
/// in x86_64 devices: https://github.com/rust-lang/rust/issues/109717.
@@ -39,7 +44,7 @@ fn setup_x86_64_android_workaround() {
}
/// Run the clang binary at `clang_path`, and return its major version number
fn get_clang_major_version(clang_path: &PathBuf) -> String {
fn get_clang_major_version(clang_path: &Path) -> String {
let clang_output =
Command::new(clang_path).arg("-dumpversion").output().expect("failed to start clang");
@@ -54,6 +59,9 @@ fn get_clang_major_version(clang_path: &PathBuf) -> String {
fn main() -> Result<(), Box<dyn Error>> {
setup_x86_64_android_workaround();
uniffi::generate_scaffolding("./src/api.udl").expect("Building the UDL file failed");
EmitBuilder::builder().git_sha(true).emit()?;
let git_config = GitclBuilder::default().sha(true).build()?;
Emitter::default().add_instructions(&git_config)?.emit()?;
Ok(())
}
-5
View File
@@ -8,8 +8,3 @@ dictionary Mentions {
interface RoomMessageEventContentWithoutRelation {
RoomMessageEventContentWithoutRelation with_mentions(Mentions mentions);
};
[Error]
interface ClientError {
Generic(string msg);
};
+83 -65
View File
@@ -5,18 +5,14 @@ use std::{
};
use matrix_sdk::{
oidc::{
registrations::OidcRegistrationsError,
types::{
iana::oauth::OAuthClientAuthenticationMethod,
oidc::ApplicationType,
registration::{ClientMetadata, Localized, VerifiedClientMetadata},
requests::GrantType,
},
OidcError as SdkOidcError,
authentication::oauth::{
error::OAuthAuthorizationCodeError,
registration::{ApplicationType, ClientMetadata, Localized, OAuthGrantType},
ClientId, ClientRegistrationData, OAuthError as SdkOAuthError,
},
Error,
};
use ruma::serde::Raw;
use url::Url;
use crate::client::{Client, OidcPrompt, SlidingSyncVersion};
@@ -27,6 +23,7 @@ pub struct HomeserverLoginDetails {
pub(crate) sliding_sync_version: SlidingSyncVersion,
pub(crate) supports_oidc_login: bool,
pub(crate) supported_oidc_prompts: Vec<OidcPrompt>,
pub(crate) supports_sso_login: bool,
pub(crate) supports_password_login: bool,
}
@@ -47,6 +44,11 @@ impl HomeserverLoginDetails {
self.supports_oidc_login
}
/// Whether the current homeserver supports login using legacy SSO.
pub fn supports_sso_login(&self) -> bool {
self.supports_sso_login
}
/// The prompts advertised by the authentication issuer for use in the login
/// URL.
pub fn supported_oidc_prompts(&self) -> Vec<OidcPrompt> {
@@ -116,60 +118,76 @@ pub struct OidcConfiguration {
/// successful.
pub redirect_uri: String,
/// A URI that contains information about the client.
pub client_uri: Option<String>,
pub client_uri: String,
/// A URI that contains the client's logo.
pub logo_uri: Option<String>,
/// A URI that contains the client's terms of service.
pub tos_uri: Option<String>,
/// A URI that contains the client's privacy policy.
pub policy_uri: Option<String>,
/// An array of e-mail addresses of people responsible for this client.
pub contacts: Option<Vec<String>>,
/// Pre-configured registrations for use with issuers that don't support
/// Pre-configured registrations for use with homeservers that don't support
/// dynamic client registration.
pub static_registrations: HashMap<String, String>,
/// A file path where any dynamic registrations should be stored.
///
/// Suggested value: `{base_path}/oidc/registrations.json`
pub dynamic_registrations_file: String,
/// The keys of the map should be the URLs of the homeservers, but keys
/// using `issuer` URLs are also supported.
pub static_registrations: HashMap<String, String>,
}
impl TryInto<VerifiedClientMetadata> for &OidcConfiguration {
type Error = OidcError;
impl OidcConfiguration {
pub(crate) fn redirect_uri(&self) -> Result<Url, OidcError> {
Url::parse(&self.redirect_uri).map_err(|_| OidcError::CallbackUrlInvalid)
}
fn try_into(self) -> Result<VerifiedClientMetadata, Self::Error> {
let redirect_uri =
Url::parse(&self.redirect_uri).map_err(|_| OidcError::CallbackUrlInvalid)?;
pub(crate) fn client_metadata(&self) -> Result<Raw<ClientMetadata>, OidcError> {
let redirect_uri = self.redirect_uri()?;
let client_name = self.client_name.as_ref().map(|n| Localized::new(n.to_owned(), []));
let client_uri = self.client_uri.localized_url()?;
let logo_uri = self.logo_uri.localized_url()?;
let policy_uri = self.policy_uri.localized_url()?;
let tos_uri = self.tos_uri.localized_url()?;
let contacts = self.contacts.clone();
ClientMetadata {
application_type: Some(ApplicationType::Native),
redirect_uris: Some(vec![redirect_uri]),
grant_types: Some(vec![
GrantType::RefreshToken,
GrantType::AuthorizationCode,
GrantType::DeviceCode,
]),
// A native client shouldn't use authentication as the credentials could be intercepted.
token_endpoint_auth_method: Some(OAuthClientAuthenticationMethod::None),
let metadata = ClientMetadata {
// The server should display the following fields when getting the user's consent.
client_name,
contacts,
client_uri,
logo_uri,
policy_uri,
tos_uri,
..Default::default()
..ClientMetadata::new(
ApplicationType::Native,
vec![
OAuthGrantType::AuthorizationCode { redirect_uris: vec![redirect_uri] },
OAuthGrantType::DeviceCode,
],
client_uri,
)
};
Raw::new(&metadata).map_err(|_| OidcError::MetadataInvalid)
}
pub(crate) fn registration_data(&self) -> Result<ClientRegistrationData, OidcError> {
let client_metadata = self.client_metadata()?;
let mut registration_data = ClientRegistrationData::new(client_metadata);
if !self.static_registrations.is_empty() {
let static_registrations = self
.static_registrations
.iter()
.filter_map(|(issuer, client_id)| {
let Ok(issuer) = Url::parse(issuer) else {
tracing::error!("Failed to parse {issuer:?}");
return None;
};
Some((issuer, ClientId::new(client_id.clone())))
})
.collect();
registration_data.static_registrations = Some(static_registrations);
}
.validate()
.map_err(|_| OidcError::MetadataInvalid)
Ok(registration_data)
}
}
@@ -182,8 +200,6 @@ pub enum OidcError {
NotSupported,
#[error("Unable to use OIDC as the supplied client metadata is invalid.")]
MetadataInvalid,
#[error("Failed to use the supplied registrations file path.")]
RegistrationsPathInvalid,
#[error("The supplied callback URL used to complete OIDC is invalid.")]
CallbackUrlInvalid,
#[error("The OIDC login was cancelled by the user.")]
@@ -193,23 +209,17 @@ pub enum OidcError {
Generic { message: String },
}
impl From<SdkOidcError> for OidcError {
fn from(e: SdkOidcError) -> OidcError {
impl From<SdkOAuthError> for OidcError {
fn from(e: SdkOAuthError) -> OidcError {
match e {
SdkOidcError::MissingAuthenticationIssuer => OidcError::NotSupported,
SdkOidcError::MissingRedirectUri => OidcError::MetadataInvalid,
SdkOidcError::InvalidCallbackUrl => OidcError::CallbackUrlInvalid,
SdkOidcError::InvalidState => OidcError::CallbackUrlInvalid,
SdkOidcError::CancelledAuthorization => OidcError::Cancelled,
_ => OidcError::Generic { message: e.to_string() },
}
}
}
impl From<OidcRegistrationsError> for OidcError {
fn from(e: OidcRegistrationsError) -> OidcError {
match e {
OidcRegistrationsError::InvalidFilePath => OidcError::RegistrationsPathInvalid,
SdkOAuthError::Discovery(error) if error.is_not_supported() => OidcError::NotSupported,
SdkOAuthError::AuthorizationCode(OAuthAuthorizationCodeError::RedirectUri(_))
| SdkOAuthError::AuthorizationCode(OAuthAuthorizationCodeError::InvalidState) => {
OidcError::CallbackUrlInvalid
}
SdkOAuthError::AuthorizationCode(OAuthAuthorizationCodeError::Cancelled) => {
OidcError::Cancelled
}
_ => OidcError::Generic { message: e.to_string() },
}
}
@@ -218,7 +228,7 @@ impl From<OidcRegistrationsError> for OidcError {
impl From<Error> for OidcError {
fn from(e: Error) -> OidcError {
match e {
Error::Oidc(e) => e.into(),
Error::OAuth(e) => (*e).into(),
_ => OidcError::Generic { message: e.to_string() },
}
}
@@ -227,17 +237,25 @@ impl From<Error> for OidcError {
/* Helpers */
trait OptionExt {
/// Convenience method to convert a string to a URL and returns it as a
/// Localized URL. No localization is actually performed.
/// Convenience method to convert an `Option<String>` to a URL and returns
/// it as a Localized URL. No localization is actually performed.
fn localized_url(&self) -> Result<Option<Localized<Url>>, OidcError>;
}
impl OptionExt for Option<String> {
fn localized_url(&self) -> Result<Option<Localized<Url>>, OidcError> {
self.as_deref()
.map(|uri| -> Result<Localized<Url>, OidcError> {
Ok(Localized::new(Url::parse(uri).map_err(|_| OidcError::MetadataInvalid)?, []))
})
.transpose()
self.as_deref().map(StrExt::localized_url).transpose()
}
}
trait StrExt {
/// Convenience method to convert a string to a URL and returns it as a
/// Localized URL. No localization is actually performed.
fn localized_url(&self) -> Result<Localized<Url>, OidcError>;
}
impl StrExt for str {
fn localized_url(&self) -> Result<Localized<Url>, OidcError> {
Ok(Localized::new(Url::parse(self).map_err(|_| OidcError::MetadataInvalid)?, []))
}
}
File diff suppressed because it is too large Load Diff
+282 -364
View File
@@ -1,33 +1,25 @@
use std::{fs, num::NonZeroUsize, path::PathBuf, sync::Arc, time::Duration};
use std::{fs, num::NonZeroUsize, path::Path, sync::Arc, time::Duration};
use futures_util::StreamExt;
#[cfg(not(target_family = "wasm"))]
use matrix_sdk::reqwest::Certificate;
use matrix_sdk::{
authentication::qrcode::{self, DeviceCodeErrorResponseType, LoginFailureReason},
crypto::{
types::qr_login::{LoginQrCodeDecodeError, QrCodeModeData},
CollectStrategy, TrustRequirement,
},
crypto::{CollectStrategy, DecryptionSettings, TrustRequirement},
encryption::{BackupDownloadStrategy, EncryptionSettings},
event_cache::EventCacheError,
reqwest::Certificate,
ruma::{ServerName, UserId},
sliding_sync::{
Error as MatrixSlidingSyncError, VersionBuilder as MatrixSlidingSyncVersionBuilder,
VersionBuilderError,
},
Client as MatrixClient, ClientBuildError as MatrixClientBuildError, HttpError, IdParseError,
RumaApiError,
RumaApiError, SqliteStoreConfig, ThreadingSupport,
};
use ruma::api::error::{DeserializationError, FromHttpResponseError};
use tracing::{debug, error};
use url::Url;
use zeroize::Zeroizing;
use super::{client::Client, RUNTIME};
use crate::{
authentication::OidcConfiguration, client::ClientSessionDelegate, error::ClientError,
helpers::unwrap_or_clone_arc, task_handle::TaskHandle,
};
use super::client::Client;
use crate::{client::ClientSessionDelegate, error::ClientError, helpers::unwrap_or_clone_arc};
/// A list of bytes containing a certificate in DER or PEM form.
pub type CertificateBytes = Vec<u8>;
@@ -39,152 +31,6 @@ enum HomeserverConfig {
ServerNameOrUrl(String),
}
/// Data for the QR code login mechanism.
///
/// The [`QrCodeData`] can be serialized and encoded as a QR code or it can be
/// decoded from a QR code.
#[derive(Debug, uniffi::Object)]
pub struct QrCodeData {
inner: qrcode::QrCodeData,
}
#[matrix_sdk_ffi_macros::export]
impl QrCodeData {
/// Attempt to decode a slice of bytes into a [`QrCodeData`] object.
///
/// The slice of bytes would generally be returned by a QR code decoder.
#[uniffi::constructor]
pub fn from_bytes(bytes: Vec<u8>) -> Result<Arc<Self>, QrCodeDecodeError> {
Ok(Self { inner: qrcode::QrCodeData::from_bytes(&bytes)? }.into())
}
}
/// Error type for the decoding of the [`QrCodeData`].
#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi(flat_error)]
pub enum QrCodeDecodeError {
#[error("Error decoding QR code: {error:?}")]
Crypto {
#[from]
error: LoginQrCodeDecodeError,
},
}
#[derive(Debug, thiserror::Error, uniffi::Error)]
pub enum HumanQrLoginError {
#[error("Linking with this device is not supported.")]
LinkingNotSupported,
#[error("The sign in was cancelled.")]
Cancelled,
#[error("The sign in was not completed in the required time.")]
Expired,
#[error("A secure connection could not have been established between the two devices.")]
ConnectionInsecure,
#[error("The sign in was declined.")]
Declined,
#[error("An unknown error has happened.")]
Unknown,
#[error("The homeserver doesn't provide sliding sync in its configuration.")]
SlidingSyncNotAvailable,
#[error("Unable to use OIDC as the supplied client metadata is invalid.")]
OidcMetadataInvalid,
#[error("The other device is not signed in and as such can't sign in other devices.")]
OtherDeviceNotSignedIn,
}
impl From<qrcode::QRCodeLoginError> for HumanQrLoginError {
fn from(value: qrcode::QRCodeLoginError) -> Self {
use qrcode::{QRCodeLoginError, SecureChannelError};
match value {
QRCodeLoginError::LoginFailure { reason, .. } => match reason {
LoginFailureReason::UnsupportedProtocol => HumanQrLoginError::LinkingNotSupported,
LoginFailureReason::AuthorizationExpired => HumanQrLoginError::Expired,
LoginFailureReason::UserCancelled => HumanQrLoginError::Cancelled,
_ => HumanQrLoginError::Unknown,
},
QRCodeLoginError::Oidc(e) => {
if let Some(e) = e.as_request_token_error() {
match e {
DeviceCodeErrorResponseType::AccessDenied => HumanQrLoginError::Declined,
DeviceCodeErrorResponseType::ExpiredToken => HumanQrLoginError::Expired,
_ => HumanQrLoginError::Unknown,
}
} else {
HumanQrLoginError::Unknown
}
}
QRCodeLoginError::SecureChannel(e) => match e {
SecureChannelError::Utf8(_)
| SecureChannelError::MessageDecode(_)
| SecureChannelError::Json(_)
| SecureChannelError::RendezvousChannel(_) => HumanQrLoginError::Unknown,
SecureChannelError::SecureChannelMessage { .. }
| SecureChannelError::Ecies(_)
| SecureChannelError::InvalidCheckCode => HumanQrLoginError::ConnectionInsecure,
SecureChannelError::InvalidIntent => HumanQrLoginError::OtherDeviceNotSignedIn,
},
QRCodeLoginError::UnexpectedMessage { .. }
| QRCodeLoginError::CrossProcessRefreshLock(_)
| QRCodeLoginError::DeviceKeyUpload(_)
| QRCodeLoginError::SessionTokens(_)
| QRCodeLoginError::UserIdDiscovery(_)
| QRCodeLoginError::SecretImport(_) => HumanQrLoginError::Unknown,
}
}
}
/// Enum describing the progress of the QR-code login.
#[derive(Debug, Default, Clone, uniffi::Enum)]
pub enum QrLoginProgress {
/// The login process is starting.
#[default]
Starting,
/// We established a secure channel with the other device.
EstablishingSecureChannel {
/// The check code that the device should display so the other device
/// can confirm that the channel is secure as well.
check_code: u8,
/// The string representation of the check code, will be guaranteed to
/// be 2 characters long, preserving the leading zero if the
/// first digit is a zero.
check_code_string: String,
},
/// We are waiting for the login and for the OIDC provider to give us an
/// access token.
WaitingForToken { user_code: String },
/// The login has successfully finished.
Done,
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait QrLoginProgressListener: Sync + Send {
fn on_update(&self, state: QrLoginProgress);
}
impl From<qrcode::LoginProgress> for QrLoginProgress {
fn from(value: qrcode::LoginProgress) -> Self {
use qrcode::LoginProgress;
match value {
LoginProgress::Starting => Self::Starting,
LoginProgress::EstablishingSecureChannel { check_code } => {
let check_code = check_code.to_digit();
Self::EstablishingSecureChannel {
check_code,
check_code_string: format!("{check_code:02}"),
}
}
LoginProgress::WaitingForToken { user_code } => Self::WaitingForToken { user_code },
LoginProgress::Done => Self::Done,
}
}
}
#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi(flat_error)]
pub enum ClientBuildError {
@@ -255,38 +101,56 @@ impl From<ClientError> for ClientBuildError {
#[derive(Clone, uniffi::Object)]
pub struct ClientBuilder {
session_paths: Option<SessionPaths>,
session_passphrase: Zeroizing<Option<String>>,
session_pool_max_size: Option<usize>,
session_cache_size: Option<u32>,
session_journal_size_limit: Option<u32>,
system_is_memory_constrained: bool,
username: Option<String>,
homeserver_cfg: Option<HomeserverConfig>,
passphrase: Zeroizing<Option<String>>,
user_agent: Option<String>,
sliding_sync_version_builder: SlidingSyncVersionBuilder,
proxy: Option<String>,
disable_ssl_verification: bool,
disable_automatic_token_refresh: bool,
cross_process_store_locks_holder_name: Option<String>,
enable_oidc_refresh_lock: bool,
session_delegate: Option<Arc<dyn ClientSessionDelegate>>,
additional_root_certificates: Vec<Vec<u8>>,
disable_built_in_root_certificates: bool,
encryption_settings: EncryptionSettings,
room_key_recipient_strategy: CollectStrategy,
decryption_trust_requirement: TrustRequirement,
decryption_settings: DecryptionSettings,
enable_share_history_on_invite: bool,
request_config: Option<RequestConfig>,
/// Whether to enable use of the event cache store, for reloading events
/// when building timelines et al.
use_event_cache_persistent_storage: bool,
#[cfg(not(target_family = "wasm"))]
user_agent: Option<String>,
#[cfg(not(target_family = "wasm"))]
proxy: Option<String>,
#[cfg(not(target_family = "wasm"))]
disable_ssl_verification: bool,
#[cfg(not(target_family = "wasm"))]
disable_built_in_root_certificates: bool,
#[cfg(not(target_family = "wasm"))]
additional_root_certificates: Vec<Vec<u8>>,
threading_support: ThreadingSupport,
}
/// The timeout applies to each read operation, and resets after a successful
/// read. This is more appropriate for detecting stalled connections when the
/// size isnt known beforehand.
const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(60);
#[matrix_sdk_ffi_macros::export]
impl ClientBuilder {
#[uniffi::constructor]
pub fn new() -> Arc<Self> {
Arc::new(Self {
session_paths: None,
session_passphrase: Zeroizing::new(None),
session_pool_max_size: None,
session_cache_size: None,
session_journal_size_limit: None,
system_is_memory_constrained: false,
username: None,
homeserver_cfg: None,
passphrase: Zeroizing::new(None),
user_agent: None,
sliding_sync_version_builder: SlidingSyncVersionBuilder::None,
proxy: None,
@@ -304,29 +168,15 @@ impl ClientBuilder {
auto_enable_backups: false,
},
room_key_recipient_strategy: Default::default(),
decryption_trust_requirement: TrustRequirement::Untrusted,
decryption_settings: DecryptionSettings {
sender_device_trust_requirement: TrustRequirement::Untrusted,
},
enable_share_history_on_invite: false,
request_config: Default::default(),
use_event_cache_persistent_storage: false,
threading_support: ThreadingSupport::Disabled,
})
}
/// Whether to use the event cache persistent storage or not.
///
/// This is a temporary feature flag, for testing the event cache's
/// persistent storage. Follow new developments in https://github.com/matrix-org/matrix-rust-sdk/issues/3280.
///
/// This is disabled by default. When disabled, a one-time cleanup is
/// performed when creating the client, and it will clear all the events
/// previously stored in the event cache.
///
/// When enabled, it will attempt to store events in the event cache as
/// they're received, and reuse them when reconstructing timelines.
pub fn use_event_cache_persistent_storage(self: Arc<Self>, value: bool) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.use_event_cache_persistent_storage = value;
Arc::new(builder)
}
pub fn cross_process_store_locks_holder_name(
self: Arc<Self>,
holder_name: String,
@@ -363,6 +213,74 @@ impl ClientBuilder {
Arc::new(builder)
}
/// Set the passphrase for the stores given to
/// [`ClientBuilder::session_paths`].
pub fn session_passphrase(self: Arc<Self>, passphrase: Option<String>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.session_passphrase = Zeroizing::new(passphrase);
Arc::new(builder)
}
/// Set the pool max size for the SQLite stores given to
/// [`ClientBuilder::session_paths`].
///
/// Each store exposes an async pool of connections. This method controls
/// the size of the pool. The larger the pool is, the more memory is
/// consumed, but also the more the app is reactive because it doesn't need
/// to wait on a pool to be available to run queries.
///
/// See [`SqliteStoreConfig::pool_max_size`] to learn more.
pub fn session_pool_max_size(self: Arc<Self>, pool_max_size: Option<u32>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.session_pool_max_size = pool_max_size
.map(|size| size.try_into().expect("`pool_max_size` is too large to fit in `usize`"));
Arc::new(builder)
}
/// Set the cache size for the SQLite stores given to
/// [`ClientBuilder::session_paths`].
///
/// Each store exposes a SQLite connection. This method controls the cache
/// size, in **bytes (!)**.
///
/// The cache represents data SQLite holds in memory at once per open
/// database file. The default cache implementation does not allocate the
/// full amount of cache memory all at once. Cache memory is allocated
/// in smaller chunks on an as-needed basis.
///
/// See [`SqliteStoreConfig::cache_size`] to learn more.
pub fn session_cache_size(self: Arc<Self>, cache_size: Option<u32>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.session_cache_size = cache_size;
Arc::new(builder)
}
/// Set the size limit for the SQLite WAL files of stores given to
/// [`ClientBuilder::session_paths`].
///
/// Each store uses the WAL journal mode. This method controls the size
/// limit of the WAL files, in **bytes (!)**.
///
/// See [`SqliteStoreConfig::journal_size_limit`] to learn more.
pub fn session_journal_size_limit(self: Arc<Self>, limit: Option<u32>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.session_journal_size_limit = limit;
Arc::new(builder)
}
/// Tell the client that the system is memory constrained, like in a push
/// notification process for example.
///
/// So far, at the time of writing (2025-04-07), it changes the defaults of
/// [`SqliteStoreConfig`], so one might not need to call
/// [`ClientBuilder::session_cache_size`] and siblings for example. Please
/// check [`SqliteStoreConfig::with_low_memory_config`].
pub fn system_is_memory_constrained(self: Arc<Self>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.system_is_memory_constrained = true;
Arc::new(builder)
}
pub fn username(self: Arc<Self>, username: String) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.username = Some(username);
@@ -387,18 +305,6 @@ impl ClientBuilder {
Arc::new(builder)
}
pub fn passphrase(self: Arc<Self>, passphrase: Option<String>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.passphrase = Zeroizing::new(passphrase);
Arc::new(builder)
}
pub fn user_agent(self: Arc<Self>, user_agent: String) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.user_agent = Some(user_agent);
Arc::new(builder)
}
pub fn sliding_sync_version_builder(
self: Arc<Self>,
version_builder: SlidingSyncVersionBuilder,
@@ -408,43 +314,12 @@ impl ClientBuilder {
Arc::new(builder)
}
pub fn proxy(self: Arc<Self>, url: String) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.proxy = Some(url);
Arc::new(builder)
}
pub fn disable_ssl_verification(self: Arc<Self>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.disable_ssl_verification = true;
Arc::new(builder)
}
pub fn disable_automatic_token_refresh(self: Arc<Self>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.disable_automatic_token_refresh = true;
Arc::new(builder)
}
pub fn add_root_certificates(
self: Arc<Self>,
certificates: Vec<CertificateBytes>,
) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.additional_root_certificates = certificates;
Arc::new(builder)
}
/// Don't trust any system root certificates, only trust the certificates
/// provided through
/// [`add_root_certificates`][ClientBuilder::add_root_certificates].
pub fn disable_built_in_root_certificates(self: Arc<Self>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.disable_built_in_root_certificates = true;
Arc::new(builder)
}
pub fn auto_enable_cross_signing(
self: Arc<Self>,
auto_enable_cross_signing: bool,
@@ -483,12 +358,25 @@ impl ClientBuilder {
}
/// Set the trust requirement to be used when decrypting events.
pub fn room_decryption_trust_requirement(
pub fn decryption_settings(
self: Arc<Self>,
trust_requirement: TrustRequirement,
decryption_settings: DecryptionSettings,
) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.decryption_trust_requirement = trust_requirement;
builder.decryption_settings = decryption_settings;
Arc::new(builder)
}
/// Set whether to enable the experimental support for sending and receiving
/// encrypted room history on invite, per [MSC4268].
///
/// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
pub fn enable_share_history_on_invite(
self: Arc<Self>,
enable_share_history_on_invite: bool,
) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.enable_share_history_on_invite = enable_share_history_on_invite;
Arc::new(builder)
}
@@ -499,6 +387,23 @@ impl ClientBuilder {
Arc::new(builder)
}
/// Whether the client should support threads client-side or not, and enable
/// experimental support for MSC4306 (threads subscriptions) or not.
pub fn threads_enabled(
self: Arc<Self>,
enabled: bool,
thread_subscriptions: bool,
) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
let support = if enabled {
ThreadingSupport::Enabled { with_subscriptions: thread_subscriptions }
} else {
ThreadingSupport::Disabled
};
builder.threading_support = support;
Arc::new(builder)
}
pub async fn build(self: Arc<Self>) -> Result<Arc<Client>, ClientBuildError> {
let builder = unwrap_or_clone_arc(self);
let mut inner_builder = MatrixClient::builder();
@@ -508,27 +413,50 @@ impl ClientBuilder {
inner_builder.cross_process_store_locks_holder_name(holder_name.clone());
}
if let Some(session_paths) = &builder.session_paths {
let data_path = PathBuf::from(&session_paths.data_path);
let cache_path = PathBuf::from(&session_paths.cache_path);
let store_path = if let Some(session_paths) = &builder.session_paths {
// This is the path where both the state store and the crypto store will live.
let data_path = Path::new(&session_paths.data_path);
// This is the path where the event cache store will live.
let cache_path = Path::new(&session_paths.cache_path);
debug!(
data_path = %data_path.to_string_lossy(),
cache_path = %cache_path.to_string_lossy(),
"Creating directories for data and cache stores.",
event_cache_path = %cache_path.to_string_lossy(),
"Creating directories for data (state and crypto) and cache stores.",
);
fs::create_dir_all(&data_path)?;
fs::create_dir_all(&cache_path)?;
fs::create_dir_all(data_path)?;
fs::create_dir_all(cache_path)?;
inner_builder = inner_builder.sqlite_store_with_cache_path(
&data_path,
&cache_path,
builder.passphrase.as_deref(),
);
let mut sqlite_store_config = if builder.system_is_memory_constrained {
SqliteStoreConfig::with_low_memory_config(data_path)
} else {
SqliteStoreConfig::new(data_path)
};
sqlite_store_config =
sqlite_store_config.passphrase(builder.session_passphrase.as_deref());
if let Some(size) = builder.session_pool_max_size {
sqlite_store_config = sqlite_store_config.pool_max_size(size);
}
if let Some(size) = builder.session_cache_size {
sqlite_store_config = sqlite_store_config.cache_size(size);
}
if let Some(limit) = builder.session_journal_size_limit {
sqlite_store_config = sqlite_store_config.journal_size_limit(limit);
}
inner_builder = inner_builder
.sqlite_store_with_config_and_cache_path(sqlite_store_config, Some(cache_path));
Some(data_path.to_owned())
} else {
debug!("Not using a store path.");
}
None
};
// Determine server either from URL, server name or user ID.
inner_builder = match builder.homeserver_cfg {
@@ -552,74 +480,66 @@ impl ClientBuilder {
}
};
let mut certificates = Vec::new();
#[cfg(not(target_family = "wasm"))]
{
let mut certificates = Vec::new();
for certificate in builder.additional_root_certificates {
// We don't really know what type of certificate we may get here, so let's try
// first one type, then the other.
match Certificate::from_der(&certificate) {
Ok(cert) => {
certificates.push(cert);
}
Err(der_error) => {
let cert = Certificate::from_pem(&certificate).map_err(|pem_error| {
ClientBuildError::Generic {
message: format!("Failed to add a root certificate as DER ({der_error:?}) or PEM ({pem_error:?})"),
}
})?;
certificates.push(cert);
for certificate in builder.additional_root_certificates {
// We don't really know what type of certificate we may get here, so let's try
// first one type, then the other.
match Certificate::from_der(&certificate) {
Ok(cert) => {
certificates.push(cert);
}
Err(der_error) => {
let cert = Certificate::from_pem(&certificate).map_err(|pem_error| {
ClientBuildError::Generic {
message: format!("Failed to add a root certificate as DER ({der_error:?}) or PEM ({pem_error:?})"),
}
})?;
certificates.push(cert);
}
}
}
}
inner_builder = inner_builder.add_root_certificates(certificates);
inner_builder = inner_builder.add_root_certificates(certificates);
if builder.disable_built_in_root_certificates {
inner_builder = inner_builder.disable_built_in_root_certificates();
}
if builder.disable_built_in_root_certificates {
inner_builder = inner_builder.disable_built_in_root_certificates();
}
if let Some(proxy) = builder.proxy {
inner_builder = inner_builder.proxy(proxy);
}
if let Some(proxy) = builder.proxy {
inner_builder = inner_builder.proxy(proxy);
}
if builder.disable_ssl_verification {
inner_builder = inner_builder.disable_ssl_verification();
if builder.disable_ssl_verification {
inner_builder = inner_builder.disable_ssl_verification();
}
if let Some(user_agent) = builder.user_agent {
inner_builder = inner_builder.user_agent(user_agent);
}
}
if !builder.disable_automatic_token_refresh {
inner_builder = inner_builder.handle_refresh_tokens();
}
if let Some(user_agent) = builder.user_agent {
inner_builder = inner_builder.user_agent(user_agent);
}
inner_builder = inner_builder
.with_encryption_settings(builder.encryption_settings)
.with_room_key_recipient_strategy(builder.room_key_recipient_strategy)
.with_decryption_trust_requirement(builder.decryption_trust_requirement);
.with_decryption_settings(builder.decryption_settings)
.with_enable_share_history_on_invite(builder.enable_share_history_on_invite);
match builder.sliding_sync_version_builder {
SlidingSyncVersionBuilder::None => {
inner_builder = inner_builder
.sliding_sync_version_builder(MatrixSlidingSyncVersionBuilder::None)
}
SlidingSyncVersionBuilder::Proxy { url } => {
inner_builder = inner_builder.sliding_sync_version_builder(
MatrixSlidingSyncVersionBuilder::Proxy {
url: Url::parse(&url)
.map_err(|e| ClientBuildError::Generic { message: e.to_string() })?,
},
)
}
SlidingSyncVersionBuilder::Native => {
inner_builder = inner_builder
.sliding_sync_version_builder(MatrixSlidingSyncVersionBuilder::Native)
}
SlidingSyncVersionBuilder::DiscoverProxy => {
inner_builder = inner_builder
.sliding_sync_version_builder(MatrixSlidingSyncVersionBuilder::DiscoverProxy)
}
SlidingSyncVersionBuilder::DiscoverNative => {
inner_builder = inner_builder
.sliding_sync_version_builder(MatrixSlidingSyncVersionBuilder::DiscoverNative)
@@ -629,11 +549,13 @@ impl ClientBuilder {
if let Some(config) = builder.request_config {
let mut updated_config = matrix_sdk::config::RequestConfig::default();
if let Some(retry_limit) = config.retry_limit {
updated_config = updated_config.retry_limit(retry_limit);
updated_config =
updated_config.retry_limit(retry_limit.try_into().unwrap_or(usize::MAX));
}
if let Some(timeout) = config.timeout {
updated_config = updated_config.timeout(Duration::from_millis(timeout));
}
updated_config = updated_config.read_timeout(DEFAULT_READ_TIMEOUT);
if let Some(max_concurrent_requests) = config.max_concurrent_requests {
if max_concurrent_requests > 0 {
updated_config = updated_config.max_concurrent_requests(NonZeroUsize::new(
@@ -641,89 +563,87 @@ impl ClientBuilder {
));
}
}
if let Some(retry_timeout) = config.retry_timeout {
updated_config = updated_config.retry_timeout(Duration::from_millis(retry_timeout));
if let Some(max_retry_time) = config.max_retry_time {
updated_config =
updated_config.max_retry_time(Duration::from_millis(max_retry_time));
}
inner_builder = inner_builder.request_config(updated_config);
}
inner_builder = inner_builder.with_threading_support(builder.threading_support);
let sdk_client = inner_builder.build().await?;
if builder.use_event_cache_persistent_storage {
// Enable the persistent storage \o/
sdk_client.event_cache().enable_storage()?;
// Disable retries for this request to prevent it from being retried
// indefinitely
let config = sdk_client.request_config().disable_retry();
// Log server version information at info level.
if let Ok(server_info) = sdk_client.server_vendor_info(Some(config)).await {
tracing::info!(
server_name = %server_info.server_name,
version = %server_info.version,
"Connected to Matrix server"
);
} else {
// Get rid of all the previous events, if any.
let store = sdk_client
.event_cache_store()
.lock()
.await
.map_err(EventCacheError::LockingStorage)?;
store.clear_all_rooms_chunks().await.map_err(EventCacheError::Storage)?;
tracing::warn!("Could not retrieve server version information");
}
Ok(Arc::new(
Client::new(sdk_client, builder.enable_oidc_refresh_lock, builder.session_delegate)
.await?,
Client::new(
sdk_client,
builder.enable_oidc_refresh_lock,
builder.session_delegate,
store_path,
)
.await?,
))
}
/// Finish the building of the client and attempt to log in using the
/// provided [`QrCodeData`].
///
/// This method will build the client and immediately attempt to log the
/// client in using the provided [`QrCodeData`] using the login
/// mechanism described in [MSC4108]. As such this methods requires OIDC
/// support as well as sliding sync support.
///
/// The usage of the progress_listener is required to transfer the
/// [`CheckCode`] to the existing client.
///
/// [MSC4108]: https://github.com/matrix-org/matrix-spec-proposals/pull/4108
pub async fn build_with_qr_code(
self: Arc<Self>,
qr_code_data: &QrCodeData,
oidc_configuration: &OidcConfiguration,
progress_listener: Box<dyn QrLoginProgressListener>,
) -> Result<Arc<Client>, HumanQrLoginError> {
let QrCodeModeData::Reciprocate { server_name } = &qr_code_data.inner.mode_data else {
return Err(HumanQrLoginError::OtherDeviceNotSignedIn);
};
let builder = self.server_name_or_homeserver_url(server_name.to_owned());
let client = builder.build().await.map_err(|e| match e {
ClientBuildError::SlidingSync(_) => HumanQrLoginError::SlidingSyncNotAvailable,
_ => {
error!("Couldn't build the client {e:?}");
HumanQrLoginError::Unknown
}
})?;
let client_metadata =
oidc_configuration.try_into().map_err(|_| HumanQrLoginError::OidcMetadataInvalid)?;
let oidc = client.inner.oidc();
let login = oidc.login_with_qr_code(&qr_code_data.inner, client_metadata);
let mut progress = login.subscribe_to_progress();
// We create this task, which will get cancelled once it's dropped, just in case
// the progress stream doesn't end.
let _progress_task = TaskHandle::new(RUNTIME.spawn(async move {
while let Some(state) = progress.next().await {
progress_listener.on_update(state.into());
}
}));
login.await?;
Ok(client)
}
}
#[derive(Clone)]
#[cfg(not(target_family = "wasm"))]
#[matrix_sdk_ffi_macros::export]
impl ClientBuilder {
pub fn proxy(self: Arc<Self>, url: String) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.proxy = Some(url);
Arc::new(builder)
}
pub fn disable_ssl_verification(self: Arc<Self>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.disable_ssl_verification = true;
Arc::new(builder)
}
pub fn add_root_certificates(
self: Arc<Self>,
certificates: Vec<CertificateBytes>,
) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.additional_root_certificates = certificates;
Arc::new(builder)
}
/// Don't trust any system root certificates, only trust the certificates
/// provided through
/// [`add_root_certificates`][ClientBuilder::add_root_certificates].
pub fn disable_built_in_root_certificates(self: Arc<Self>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.disable_built_in_root_certificates = true;
Arc::new(builder)
}
pub fn user_agent(self: Arc<Self>, user_agent: String) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.user_agent = Some(user_agent);
Arc::new(builder)
}
}
/// The store paths the client will use when built.
#[derive(Clone)]
struct SessionPaths {
/// The path that the client will use to store its data.
data_path: String,
@@ -742,14 +662,12 @@ pub struct RequestConfig {
/// Max number of concurrent requests. No value means no limits.
max_concurrent_requests: Option<u64>,
/// Base delay between retries.
retry_timeout: Option<u64>,
max_retry_time: Option<u64>,
}
#[derive(Clone, uniffi::Enum)]
pub enum SlidingSyncVersionBuilder {
None,
Proxy { url: String },
Native,
DiscoverProxy,
DiscoverNative,
}
-22
View File
@@ -1,22 +0,0 @@
use serde::Deserialize;
use crate::ClientError;
/// Well-known settings specific to ElementCall
#[derive(Deserialize, uniffi::Record)]
pub struct ElementCallWellKnown {
widget_url: String,
}
/// Element specific well-known settings
#[derive(Deserialize, uniffi::Record)]
pub struct ElementWellKnown {
call: Option<ElementCallWellKnown>,
registration_helper_url: Option<String>,
}
/// Helper function to parse a string into a ElementWellKnown struct
#[matrix_sdk_ffi_macros::export]
pub fn make_element_well_known(string: String) -> Result<ElementWellKnown, ClientError> {
serde_json::from_str(&string).map_err(ClientError::new)
}
+47 -29
View File
@@ -5,12 +5,15 @@ use matrix_sdk::{
encryption,
encryption::{backups, recovery},
};
use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
use thiserror::Error;
use tracing::{error, info};
use zeroize::Zeroize;
use super::RUNTIME;
use crate::{client::Client, error::ClientError, ruma::AuthData, task_handle::TaskHandle};
use crate::{
client::Client, error::ClientError, ruma::AuthData, runtime::get_runtime_handle,
task_handle::TaskHandle,
};
#[derive(uniffi::Object)]
pub struct Encryption {
@@ -25,22 +28,22 @@ pub struct Encryption {
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait BackupStateListener: Sync + Send {
pub trait BackupStateListener: SyncOutsideWasm + SendOutsideWasm {
fn on_update(&self, status: BackupState);
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait BackupSteadyStateListener: Sync + Send {
pub trait BackupSteadyStateListener: SyncOutsideWasm + SendOutsideWasm {
fn on_update(&self, status: BackupUploadState);
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait RecoveryStateListener: Sync + Send {
pub trait RecoveryStateListener: SyncOutsideWasm + SendOutsideWasm {
fn on_update(&self, status: RecoveryState);
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait VerificationStateListener: Sync + Send {
pub trait VerificationStateListener: SyncOutsideWasm + SendOutsideWasm {
fn on_update(&self, status: VerificationState);
}
@@ -164,7 +167,7 @@ impl From<recovery::RecoveryState> for RecoveryState {
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait EnableRecoveryProgressListener: Sync + Send {
pub trait EnableRecoveryProgressListener: SyncOutsideWasm + SendOutsideWasm {
fn on_update(&self, status: EnableRecoveryProgress);
}
@@ -230,7 +233,7 @@ impl Encryption {
pub fn backup_state_listener(&self, listener: Box<dyn BackupStateListener>) -> Arc<TaskHandle> {
let mut stream = self.inner.backups().state_stream();
let stream_task = TaskHandle::new(RUNTIME.spawn(async move {
let stream_task = TaskHandle::new(get_runtime_handle().spawn(async move {
while let Some(state) = stream.next().await {
let Ok(state) = state else { continue };
listener.on_update(state.into());
@@ -267,7 +270,7 @@ impl Encryption {
) -> Arc<TaskHandle> {
let mut stream = self.inner.recovery().state_stream();
let stream_task = TaskHandle::new(RUNTIME.spawn(async move {
let stream_task = TaskHandle::new(get_runtime_handle().spawn(async move {
while let Some(state) = stream.next().await {
listener.on_update(state.into());
}
@@ -281,7 +284,7 @@ impl Encryption {
}
pub async fn is_last_device(&self) -> Result<bool> {
Ok(self.inner.recovery().are_we_the_last_man_standing().await?)
Ok(self.inner.recovery().is_last_device().await?)
}
pub async fn wait_for_backup_upload_steady_state(
@@ -294,7 +297,7 @@ impl Encryption {
let task = if let Some(listener) = progress_listener {
let mut progress_stream = wait_for_steady_state.subscribe_to_progress();
Some(RUNTIME.spawn(async move {
Some(get_runtime_handle().spawn(async move {
while let Some(progress) = progress_stream.next().await {
let Ok(progress) = progress else { continue };
listener.on_update(progress.into());
@@ -335,7 +338,7 @@ impl Encryption {
let mut progress_stream = enable.subscribe_to_progress();
let task = RUNTIME.spawn(async move {
let task = get_runtime_handle().spawn(async move {
while let Some(progress) = progress_stream.next().await {
let Ok(progress) = progress else { continue };
progress_listener.on_update(progress.into());
@@ -369,12 +372,8 @@ impl Encryption {
/// Completely reset the current user's crypto identity: reset the cross
/// signing keys, delete the existing backup and recovery key.
pub async fn reset_identity(&self) -> Result<Option<Arc<IdentityResetHandle>>, ClientError> {
if let Some(reset_handle) = self
.inner
.recovery()
.reset_identity()
.await
.map_err(|e| ClientError::Generic { msg: e.to_string() })?
if let Some(reset_handle) =
self.inner.recovery().reset_identity().await.map_err(ClientError::from_err)?
{
return Ok(Some(Arc::new(IdentityResetHandle { inner: reset_handle })));
}
@@ -400,7 +399,7 @@ impl Encryption {
) -> Arc<TaskHandle> {
let mut subscriber = self.inner.verification_state();
Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
while let Some(verification_state) = subscriber.next().await {
listener.on_update(verification_state.into());
}
@@ -441,9 +440,9 @@ impl Encryption {
info!("No identity found in the store.");
}
Err(error) => {
error!("Failed fetching identity from the store: {}", error);
error!("Failed fetching identity from the store: {error}");
}
};
}
info!("Requesting identity from the server.");
@@ -495,6 +494,28 @@ impl UserIdentity {
pub fn is_verified(&self) -> bool {
self.inner.is_verified()
}
/// True if we verified this identity at some point in the past.
///
/// To reset this latch back to `false`, one must call
/// [`UserIdentity::withdraw_verification()`].
pub fn was_previously_verified(&self) -> bool {
self.inner.was_previously_verified()
}
/// Remove the requirement for this identity to be verified.
///
/// If an identity was previously verified and is not anymore it will be
/// reported to the user. In order to remove this notice users have to
/// verify again or to withdraw the verification requirement.
pub(crate) async fn withdraw_verification(&self) -> Result<(), ClientError> {
Ok(self.inner.withdraw_verification().await?)
}
/// Was this identity previously verified, and is no longer?
pub fn has_verification_violation(&self) -> bool {
self.inner.has_verification_violation()
}
}
#[derive(uniffi::Object)]
@@ -519,12 +540,9 @@ impl IdentityResetHandle {
/// 4. Finally, re-enable key backups only if they were enabled before
pub async fn reset(&self, auth: Option<AuthData>) -> Result<(), ClientError> {
if let Some(auth) = auth {
self.inner
.reset(Some(auth.into()))
.await
.map_err(|e| ClientError::Generic { msg: e.to_string() })
self.inner.reset(Some(auth.into())).await.map_err(ClientError::from_err)
} else {
self.inner.reset(None).await.map_err(|e| ClientError::Generic { msg: e.to_string() })
self.inner.reset(None).await.map_err(ClientError::from_err)
}
}
@@ -548,7 +566,7 @@ impl From<&matrix_sdk::encryption::CrossSigningResetAuthType> for CrossSigningRe
fn from(value: &matrix_sdk::encryption::CrossSigningResetAuthType) -> Self {
match value {
encryption::CrossSigningResetAuthType::Uiaa(_) => Self::Uiaa,
encryption::CrossSigningResetAuthType::Oidc(info) => Self::Oidc { info: info.into() },
encryption::CrossSigningResetAuthType::OAuth(info) => Self::Oidc { info: info.into() },
}
}
}
@@ -559,8 +577,8 @@ pub struct OidcCrossSigningResetInfo {
pub approval_url: String,
}
impl From<&matrix_sdk::encryption::OidcCrossSigningResetInfo> for OidcCrossSigningResetInfo {
fn from(value: &matrix_sdk::encryption::OidcCrossSigningResetInfo) -> Self {
impl From<&matrix_sdk::encryption::OAuthCrossSigningResetInfo> for OidcCrossSigningResetInfo {
fn from(value: &matrix_sdk::encryption::OAuthCrossSigningResetInfo) -> Self {
Self { approval_url: value.approval_url.to_string() }
}
}
+529 -33
View File
@@ -1,157 +1,213 @@
use std::{collections::HashMap, fmt, fmt::Display};
use std::{collections::HashMap, error::Error, fmt, fmt::Display};
use matrix_sdk::{
encryption::CryptoStoreError, event_cache::EventCacheError, oidc::OidcError, reqwest,
room::edit::EditError, send_queue::RoomSendQueueError, HttpError, IdParseError,
NotificationSettingsError as SdkNotificationSettingsError,
authentication::oauth::OAuthError,
encryption::{identities::RequestVerificationError, CryptoStoreError},
event_cache::EventCacheError,
reqwest,
room::edit::EditError,
send_queue::RoomSendQueueError,
HttpError, IdParseError, NotificationSettingsError as SdkNotificationSettingsError,
QueueWedgeError as SdkQueueWedgeError, StoreError,
};
use matrix_sdk_ui::{encryption_sync_service, notification_client, sync_service, timeline};
use ruma::{
api::client::error::{ErrorBody, ErrorKind as RumaApiErrorKind, RetryAfter},
MilliSecondsSinceUnixEpoch,
};
use tracing::warn;
use uniffi::UnexpectedUniFFICallbackError;
use crate::room_list::RoomListError;
use crate::{room_list::RoomListError, timeline::FocusEventError};
#[derive(Debug, thiserror::Error)]
#[derive(Debug, thiserror::Error, uniffi::Error)]
pub enum ClientError {
#[error("client error: {msg}")]
Generic { msg: String },
Generic { msg: String, details: Option<String> },
#[error("api error {code}: {msg}")]
MatrixApi { kind: ErrorKind, code: String, msg: String, details: Option<String> },
}
impl ClientError {
pub(crate) fn new<E: Display>(error: E) -> Self {
Self::Generic { msg: error.to_string() }
pub(crate) fn from_str<E: Display>(error: E, details: Option<String>) -> Self {
warn!("Error: {error}");
Self::Generic { msg: error.to_string(), details }
}
pub(crate) fn from_err<E: Error>(e: E) -> Self {
let details = Some(format!("{e:?}"));
Self::from_str(e, details)
}
}
impl From<anyhow::Error> for ClientError {
fn from(e: anyhow::Error) -> ClientError {
ClientError::Generic { msg: format!("{e:#}") }
let details = format!("{e:?}");
ClientError::Generic { msg: format!("{e:#}"), details: Some(details) }
}
}
impl From<reqwest::Error> for ClientError {
fn from(e: reqwest::Error) -> Self {
Self::new(e)
Self::from_err(e)
}
}
impl From<UnexpectedUniFFICallbackError> for ClientError {
fn from(e: UnexpectedUniFFICallbackError) -> Self {
Self::new(e)
Self::from_err(e)
}
}
impl From<matrix_sdk::Error> for ClientError {
fn from(e: matrix_sdk::Error) -> Self {
Self::new(e)
match e {
matrix_sdk::Error::Http(http_error) => {
if let Some(api_error) = http_error.as_client_api_error() {
if let ErrorBody::Standard { kind, message } = &api_error.body {
let code = kind.errcode().to_string();
let Ok(kind) = kind.to_owned().try_into() else {
// We couldn't parse the API error, so we return a generic one instead
return (*http_error).into();
};
return Self::MatrixApi {
kind,
code,
msg: message.to_owned(),
details: Some(format!("{api_error:?}")),
};
}
}
(*http_error).into()
}
_ => Self::from_err(e),
}
}
}
impl From<StoreError> for ClientError {
fn from(e: StoreError) -> Self {
Self::new(e)
Self::from_err(e)
}
}
impl From<CryptoStoreError> for ClientError {
fn from(e: CryptoStoreError) -> Self {
Self::new(e)
Self::from_err(e)
}
}
impl From<HttpError> for ClientError {
fn from(e: HttpError) -> Self {
Self::new(e)
Self::from_err(e)
}
}
impl From<IdParseError> for ClientError {
fn from(e: IdParseError) -> Self {
Self::new(e)
Self::from_err(e)
}
}
impl From<serde_json::Error> for ClientError {
fn from(e: serde_json::Error) -> Self {
Self::new(e)
Self::from_err(e)
}
}
impl From<url::ParseError> for ClientError {
fn from(e: url::ParseError) -> Self {
Self::new(e)
Self::from_err(e)
}
}
impl From<mime::FromStrError> for ClientError {
fn from(e: mime::FromStrError) -> Self {
Self::new(e)
Self::from_err(e)
}
}
impl From<encryption_sync_service::Error> for ClientError {
fn from(e: encryption_sync_service::Error) -> Self {
Self::new(e)
Self::from_err(e)
}
}
impl From<timeline::Error> for ClientError {
fn from(e: timeline::Error) -> Self {
Self::new(e)
Self::from_err(e)
}
}
impl From<timeline::UnsupportedEditItem> for ClientError {
fn from(e: timeline::UnsupportedEditItem) -> Self {
Self::new(e)
Self::from_err(e)
}
}
impl From<notification_client::Error> for ClientError {
fn from(e: notification_client::Error) -> Self {
Self::new(e)
Self::from_err(e)
}
}
impl From<sync_service::Error> for ClientError {
fn from(e: sync_service::Error) -> Self {
Self::new(e)
Self::from_err(e)
}
}
impl From<OidcError> for ClientError {
fn from(e: OidcError) -> Self {
Self::new(e)
impl From<OAuthError> for ClientError {
fn from(e: OAuthError) -> Self {
Self::from_err(e)
}
}
impl From<RoomError> for ClientError {
fn from(e: RoomError) -> Self {
Self::new(e)
Self::from_err(e)
}
}
impl From<RoomListError> for ClientError {
fn from(e: RoomListError) -> Self {
Self::new(e)
Self::from_err(e)
}
}
impl From<EventCacheError> for ClientError {
fn from(e: EventCacheError) -> Self {
Self::new(e)
Self::from_err(e)
}
}
impl From<EditError> for ClientError {
fn from(e: EditError) -> Self {
Self::new(e)
Self::from_err(e)
}
}
impl From<RoomSendQueueError> for ClientError {
fn from(e: RoomSendQueueError) -> Self {
Self::new(e)
Self::from_err(e)
}
}
impl From<NotYetImplemented> for ClientError {
fn from(_: NotYetImplemented) -> Self {
Self::from_str("This functionality is not implemented yet.", None)
}
}
impl From<FocusEventError> for ClientError {
fn from(e: FocusEventError) -> Self {
Self::from_err(e)
}
}
impl From<RequestVerificationError> for ClientError {
fn from(e: RequestVerificationError) -> Self {
Self::from_err(e)
}
}
@@ -258,6 +314,8 @@ pub enum RoomError {
TimelineUnavailable,
#[error("Invalid thumbnail data")]
InvalidThumbnailData,
#[error("Invalid replied to event ID")]
InvalidRepliedToEventId,
#[error("Failed sending attachment")]
FailedSendingAttachment,
}
@@ -321,3 +379,441 @@ impl From<matrix_sdk::Error> for NotificationSettingsError {
#[derive(thiserror::Error, Debug)]
#[error("not implemented yet")]
pub struct NotYetImplemented;
#[derive(Clone, Debug, PartialEq, Eq, uniffi::Enum)]
// Please keep the variants sorted alphabetically.
pub enum ErrorKind {
/// `M_BAD_ALIAS`
///
/// One or more [room aliases] within the `m.room.canonical_alias` event do
/// not point to the room ID for which the state event is to be sent to.
///
/// [room aliases]: https://spec.matrix.org/latest/client-server-api/#room-aliases
BadAlias,
/// `M_BAD_JSON`
///
/// The request contained valid JSON, but it was malformed in some way, e.g.
/// missing required keys, invalid values for keys.
BadJson,
/// `M_BAD_STATE`
///
/// The state change requested cannot be performed, such as attempting to
/// unban a user who is not banned.
BadState,
/// `M_BAD_STATUS`
///
/// The application service returned a bad status.
BadStatus {
/// The HTTP status code of the response.
status: Option<u16>,
/// The body of the response.
body: Option<String>,
},
/// `M_CANNOT_LEAVE_SERVER_NOTICE_ROOM`
///
/// The user is unable to reject an invite to join the [server notices]
/// room.
///
/// [server notices]: https://spec.matrix.org/latest/client-server-api/#server-notices
CannotLeaveServerNoticeRoom,
/// `M_CANNOT_OVERWRITE_MEDIA`
///
/// The [`create_content_async`] endpoint was called with a media ID that
/// already has content.
///
/// [`create_content_async`]: crate::media::create_content_async
CannotOverwriteMedia,
/// `M_CAPTCHA_INVALID`
///
/// The Captcha provided did not match what was expected.
CaptchaInvalid,
/// `M_CAPTCHA_NEEDED`
///
/// A Captcha is required to complete the request.
CaptchaNeeded,
/// `M_CONNECTION_FAILED`
///
/// The connection to the application service failed.
ConnectionFailed,
/// `M_CONNECTION_TIMEOUT`
///
/// The connection to the application service timed out.
ConnectionTimeout,
/// `M_DUPLICATE_ANNOTATION`
///
/// The request is an attempt to send a [duplicate annotation].
///
/// [duplicate annotation]: https://spec.matrix.org/latest/client-server-api/#avoiding-duplicate-annotations
DuplicateAnnotation,
/// `M_EXCLUSIVE`
///
/// The resource being requested is reserved by an application service, or
/// the application service making the request has not created the
/// resource.
Exclusive,
/// `M_FORBIDDEN`
///
/// Forbidden access, e.g. joining a room without permission, failed login.
Forbidden,
/// `M_GUEST_ACCESS_FORBIDDEN`
///
/// The room or resource does not permit [guests] to access it.
///
/// [guests]: https://spec.matrix.org/latest/client-server-api/#guest-access
GuestAccessForbidden,
/// `M_INCOMPATIBLE_ROOM_VERSION`
///
/// The client attempted to join a room that has a version the server does
/// not support.
IncompatibleRoomVersion {
/// The room's version.
room_version: String,
},
/// `M_INVALID_PARAM`
///
/// A parameter that was specified has the wrong value. For example, the
/// server expected an integer and instead received a string.
InvalidParam,
/// `M_INVALID_ROOM_STATE`
///
/// The initial state implied by the parameters to the [`create_room`]
/// request is invalid, e.g. the user's `power_level` is set below that
/// necessary to set the room name.
///
/// [`create_room`]: crate::room::create_room
InvalidRoomState,
/// `M_INVALID_USERNAME`
///
/// The desired user name is not valid.
InvalidUsername,
/// `M_LIMIT_EXCEEDED`
///
/// The request has been refused due to [rate limiting]: too many requests
/// have been sent in a short period of time.
///
/// [rate limiting]: https://spec.matrix.org/latest/client-server-api/#rate-limiting
LimitExceeded {
/// How long a client should wait before they can try again.
retry_after_ms: Option<u64>,
},
/// `M_MISSING_PARAM`
///
/// A required parameter was missing from the request.
MissingParam,
/// `M_MISSING_TOKEN`
///
/// No [access token] was specified for the request, but one is required.
///
/// [access token]: https://spec.matrix.org/latest/client-server-api/#client-authentication
MissingToken,
/// `M_NOT_FOUND`
///
/// No resource was found for this request.
NotFound,
/// `M_NOT_JSON`
///
/// The request did not contain valid JSON.
NotJson,
/// `M_NOT_YET_UPLOADED`
///
/// An `mxc:` URI generated with the [`create_mxc_uri`] endpoint was used
/// and the content is not yet available.
///
/// [`create_mxc_uri`]: crate::media::create_mxc_uri
NotYetUploaded,
/// `M_RESOURCE_LIMIT_EXCEEDED`
///
/// The request cannot be completed because the homeserver has reached a
/// resource limit imposed on it. For example, a homeserver held in a
/// shared hosting environment may reach a resource limit if it starts
/// using too much memory or disk space.
ResourceLimitExceeded {
/// A URI giving a contact method for the server administrator.
admin_contact: String,
},
/// `M_ROOM_IN_USE`
///
/// The [room alias] specified in the [`create_room`] request is already
/// taken.
///
/// [`create_room`]: crate::room::create_room
/// [room alias]: https://spec.matrix.org/latest/client-server-api/#room-aliases
RoomInUse,
/// `M_SERVER_NOT_TRUSTED`
///
/// The client's request used a third-party server, e.g. identity server,
/// that this server does not trust.
ServerNotTrusted,
/// `M_THREEPID_AUTH_FAILED`
///
/// Authentication could not be performed on the [third-party identifier].
///
/// [third-party identifier]: https://spec.matrix.org/latest/client-server-api/#adding-account-administrative-contact-information
ThreepidAuthFailed,
/// `M_THREEPID_DENIED`
///
/// The server does not permit this [third-party identifier]. This may
/// happen if the server only permits, for example, email addresses from
/// a particular domain.
///
/// [third-party identifier]: https://spec.matrix.org/latest/client-server-api/#adding-account-administrative-contact-information
ThreepidDenied,
/// `M_THREEPID_IN_USE`
///
/// The [third-party identifier] is already in use by another user.
///
/// [third-party identifier]: https://spec.matrix.org/latest/client-server-api/#adding-account-administrative-contact-information
ThreepidInUse,
/// `M_THREEPID_MEDIUM_NOT_SUPPORTED`
///
/// The homeserver does not support adding a [third-party identifier] of the
/// given medium.
///
/// [third-party identifier]: https://spec.matrix.org/latest/client-server-api/#adding-account-administrative-contact-information
ThreepidMediumNotSupported,
/// `M_THREEPID_NOT_FOUND`
///
/// No account matching the given [third-party identifier] could be found.
///
/// [third-party identifier]: https://spec.matrix.org/latest/client-server-api/#adding-account-administrative-contact-information
ThreepidNotFound,
/// `M_TOO_LARGE`
///
/// The request or entity was too large.
TooLarge,
/// `M_UNABLE_TO_AUTHORISE_JOIN`
///
/// The room is [restricted] and none of the conditions can be validated by
/// the homeserver. This can happen if the homeserver does not know
/// about any of the rooms listed as conditions, for example.
///
/// [restricted]: https://spec.matrix.org/latest/client-server-api/#restricted-rooms
UnableToAuthorizeJoin,
/// `M_UNABLE_TO_GRANT_JOIN`
///
/// A different server should be attempted for the join. This is typically
/// because the resident server can see that the joining user satisfies
/// one or more conditions, such as in the case of [restricted rooms],
/// but the resident server would be unable to meet the authorization
/// rules.
///
/// [restricted rooms]: https://spec.matrix.org/latest/client-server-api/#restricted-rooms
UnableToGrantJoin,
/// `M_UNAUTHORIZED`
///
/// The request was not correctly authorized. Usually due to login failures.
Unauthorized,
/// `M_UNKNOWN`
///
/// An unknown error has occurred.
Unknown,
/// `M_UNKNOWN_TOKEN`
///
/// The [access or refresh token] specified was not recognized.
///
/// [access or refresh token]: https://spec.matrix.org/latest/client-server-api/#client-authentication
UnknownToken {
/// If this is `true`, the client is in a "[soft logout]" state, i.e.
/// the server requires re-authentication but the session is not
/// invalidated. The client can acquire a new access token by
/// specifying the device ID it is already using to the login API.
///
/// [soft logout]: https://spec.matrix.org/latest/client-server-api/#soft-logout
soft_logout: bool,
},
/// `M_UNRECOGNIZED`
///
/// The server did not understand the request.
///
/// This is expected to be returned with a 404 HTTP status code if the
/// endpoint is not implemented or a 405 HTTP status code if the
/// endpoint is implemented, but the incorrect HTTP method is used.
Unrecognized,
/// `M_UNSUPPORTED_ROOM_VERSION`
///
/// The request to [`create_room`] used a room version that the server does
/// not support.
///
/// [`create_room`]: crate::room::create_room
UnsupportedRoomVersion,
/// `M_URL_NOT_SET`
///
/// The application service doesn't have a URL configured.
UrlNotSet,
/// `M_USER_DEACTIVATED`
///
/// The user ID associated with the request has been deactivated.
UserDeactivated,
/// `M_USER_IN_USE`
///
/// The desired user ID is already taken.
UserInUse,
/// `M_USER_LOCKED`
///
/// The account has been [locked] and cannot be used at this time.
///
/// [locked]: https://spec.matrix.org/latest/client-server-api/#account-locking
UserLocked,
/// `M_USER_SUSPENDED`
///
/// The account has been [suspended] and can only be used for limited
/// actions at this time.
///
/// [suspended]: https://spec.matrix.org/latest/client-server-api/#account-suspension
UserSuspended,
/// `M_WEAK_PASSWORD`
///
/// The password was [rejected] by the server for being too weak.
///
/// [rejected]: https://spec.matrix.org/latest/client-server-api/#notes-on-password-management
WeakPassword,
/// `M_WRONG_ROOM_KEYS_VERSION`
///
/// The version of the [room keys backup] provided in the request does not
/// match the current backup version.
///
/// [room keys backup]: https://spec.matrix.org/latest/client-server-api/#server-side-key-backups
WrongRoomKeysVersion {
/// The currently active backup version.
current_version: Option<String>,
},
/// A custom API error.
Custom { errcode: String },
}
impl TryFrom<RumaApiErrorKind> for ErrorKind {
type Error = NotYetImplemented;
fn try_from(value: RumaApiErrorKind) -> Result<Self, Self::Error> {
match &value {
RumaApiErrorKind::BadAlias => Ok(ErrorKind::BadAlias),
RumaApiErrorKind::BadJson => Ok(ErrorKind::BadJson),
RumaApiErrorKind::BadState => Ok(ErrorKind::BadState),
RumaApiErrorKind::BadStatus { status, body } => Ok(ErrorKind::BadStatus {
status: status.map(|code| code.clone().as_u16()),
body: body.clone(),
}),
RumaApiErrorKind::CannotLeaveServerNoticeRoom => {
Ok(ErrorKind::CannotLeaveServerNoticeRoom)
}
RumaApiErrorKind::CannotOverwriteMedia => Ok(ErrorKind::CannotOverwriteMedia),
RumaApiErrorKind::CaptchaInvalid => Ok(ErrorKind::CaptchaInvalid),
RumaApiErrorKind::CaptchaNeeded => Ok(ErrorKind::CaptchaNeeded),
RumaApiErrorKind::ConnectionFailed => Ok(ErrorKind::ConnectionFailed),
RumaApiErrorKind::ConnectionTimeout => Ok(ErrorKind::ConnectionTimeout),
RumaApiErrorKind::DuplicateAnnotation => Ok(ErrorKind::DuplicateAnnotation),
RumaApiErrorKind::Exclusive => Ok(ErrorKind::Exclusive),
RumaApiErrorKind::Forbidden { .. } => Ok(ErrorKind::Forbidden),
RumaApiErrorKind::GuestAccessForbidden => Ok(ErrorKind::GuestAccessForbidden),
RumaApiErrorKind::IncompatibleRoomVersion { room_version } => {
Ok(ErrorKind::IncompatibleRoomVersion { room_version: room_version.to_string() })
}
RumaApiErrorKind::InvalidParam => Ok(ErrorKind::InvalidParam),
RumaApiErrorKind::InvalidRoomState => Ok(ErrorKind::InvalidRoomState),
RumaApiErrorKind::InvalidUsername => Ok(ErrorKind::InvalidUsername),
RumaApiErrorKind::LimitExceeded { retry_after } => {
let retry_after_ms = match retry_after {
Some(RetryAfter::Delay(duration)) => Some(duration.as_millis() as u64),
Some(RetryAfter::DateTime(system_time)) => {
let duration = MilliSecondsSinceUnixEpoch::now()
.to_system_time()
.and_then(|now| system_time.duration_since(now).ok());
duration.map(|duration| duration.as_millis() as u64)
}
None => None,
};
Ok(ErrorKind::LimitExceeded { retry_after_ms })
}
RumaApiErrorKind::MissingParam => Ok(ErrorKind::MissingParam),
RumaApiErrorKind::MissingToken => Ok(ErrorKind::MissingToken),
RumaApiErrorKind::NotFound => Ok(ErrorKind::NotFound),
RumaApiErrorKind::NotJson => Ok(ErrorKind::NotJson),
RumaApiErrorKind::NotYetUploaded => Ok(ErrorKind::NotYetUploaded),
RumaApiErrorKind::ResourceLimitExceeded { admin_contact } => {
Ok(ErrorKind::ResourceLimitExceeded { admin_contact: admin_contact.to_owned() })
}
RumaApiErrorKind::RoomInUse => Ok(ErrorKind::RoomInUse),
RumaApiErrorKind::ServerNotTrusted => Ok(ErrorKind::ServerNotTrusted),
RumaApiErrorKind::ThreepidAuthFailed => Ok(ErrorKind::ThreepidAuthFailed),
RumaApiErrorKind::ThreepidDenied => Ok(ErrorKind::ThreepidDenied),
RumaApiErrorKind::ThreepidInUse => Ok(ErrorKind::ThreepidInUse),
RumaApiErrorKind::ThreepidMediumNotSupported => {
Ok(ErrorKind::ThreepidMediumNotSupported)
}
RumaApiErrorKind::ThreepidNotFound => Ok(ErrorKind::ThreepidNotFound),
RumaApiErrorKind::TooLarge => Ok(ErrorKind::TooLarge),
RumaApiErrorKind::UnableToAuthorizeJoin => Ok(ErrorKind::UnableToAuthorizeJoin),
RumaApiErrorKind::UnableToGrantJoin => Ok(ErrorKind::UnableToGrantJoin),
RumaApiErrorKind::Unauthorized => Ok(ErrorKind::Unauthorized),
RumaApiErrorKind::Unknown => Ok(ErrorKind::Unknown),
RumaApiErrorKind::UnknownToken { soft_logout } => {
Ok(ErrorKind::UnknownToken { soft_logout: soft_logout.to_owned() })
}
RumaApiErrorKind::Unrecognized => Ok(ErrorKind::Unrecognized),
RumaApiErrorKind::UnsupportedRoomVersion => Ok(ErrorKind::UnsupportedRoomVersion),
RumaApiErrorKind::UrlNotSet => Ok(ErrorKind::UrlNotSet),
RumaApiErrorKind::UserDeactivated => Ok(ErrorKind::UserDeactivated),
RumaApiErrorKind::UserInUse => Ok(ErrorKind::UserInUse),
RumaApiErrorKind::UserLocked => Ok(ErrorKind::UserLocked),
RumaApiErrorKind::UserSuspended => Ok(ErrorKind::UserSuspended),
RumaApiErrorKind::WeakPassword => Ok(ErrorKind::WeakPassword),
RumaApiErrorKind::WrongRoomKeysVersion { current_version } => {
Ok(ErrorKind::WrongRoomKeysVersion { current_version: current_version.to_owned() })
}
RumaApiErrorKind::_Custom { .. } => {
// There is no way to map the extra values since they're private, so we omit
// them
Ok(ErrorKind::Custom { errcode: value.errcode().to_string() })
}
// In any other case, return it as the mapping not being yet implemented
_ => Err(NotYetImplemented),
}
}
}
+36 -9
View File
@@ -1,3 +1,5 @@
use std::ops::Deref;
use anyhow::{bail, Context};
use matrix_sdk::IdParseError;
use matrix_sdk_ui::timeline::TimelineEventItemId;
@@ -17,11 +19,12 @@ use ruma::{
use crate::{
room_member::MembershipState,
ruma::{MessageType, NotifyType},
utils::Timestamp,
ClientError,
};
#[derive(uniffi::Object)]
pub struct TimelineEvent(pub(crate) AnySyncTimelineEvent);
pub struct TimelineEvent(pub(crate) Box<AnySyncTimelineEvent>);
#[matrix_sdk_ffi_macros::export]
impl TimelineEvent {
@@ -33,12 +36,12 @@ impl TimelineEvent {
self.0.sender().to_string()
}
pub fn timestamp(&self) -> u64 {
self.0.origin_server_ts().0.into()
pub fn timestamp(&self) -> Timestamp {
self.0.origin_server_ts().into()
}
pub fn event_type(&self) -> Result<TimelineEventType, ClientError> {
let event_type = match &self.0 {
let event_type = match self.0.deref() {
AnySyncTimelineEvent::MessageLike(event) => {
TimelineEventType::MessageLike { content: event.clone().try_into()? }
}
@@ -52,11 +55,19 @@ impl TimelineEvent {
impl From<AnyTimelineEvent> for TimelineEvent {
fn from(event: AnyTimelineEvent) -> Self {
Self(event.into())
Self(Box::new(event.into()))
}
}
#[derive(uniffi::Enum)]
// A note about this `allow(clippy::large_enum_variant)`.
// In order to reduce the size of `TimelineEventType`, we would need to
// put some parts in a `Box`, or an `Arc`. Sadly, it doesn't play well with
// UniFFI. We would need to change the `uniffi::Record` of the subtypes into
// `uniffi::Object`, which is a radical change. It would simplify the memory
// usage, but it would slow down the performance around the FFI border. Thus,
// let's consider this is a false-positive lint in this particular case.
#[allow(clippy::large_enum_variant)]
pub enum TimelineEventType {
MessageLike { content: MessageLikeEventContent },
State { content: StateEventContent },
@@ -82,7 +93,7 @@ pub enum StateEventContent {
RoomServerAcl,
RoomThirdPartyInvite,
RoomTombstone,
RoomTopic,
RoomTopic { topic: String },
SpaceChild,
SpaceParent,
}
@@ -117,16 +128,28 @@ impl TryFrom<AnySyncStateEvent> for StateEventContent {
AnySyncStateEvent::RoomServerAcl(_) => StateEventContent::RoomServerAcl,
AnySyncStateEvent::RoomThirdPartyInvite(_) => StateEventContent::RoomThirdPartyInvite,
AnySyncStateEvent::RoomTombstone(_) => StateEventContent::RoomTombstone,
AnySyncStateEvent::RoomTopic(_) => StateEventContent::RoomTopic,
AnySyncStateEvent::RoomTopic(content) => {
let content = get_state_event_original_content(content)?;
StateEventContent::RoomTopic { topic: content.topic }
}
AnySyncStateEvent::SpaceChild(_) => StateEventContent::SpaceChild,
AnySyncStateEvent::SpaceParent(_) => StateEventContent::SpaceParent,
_ => bail!("Unsupported state event"),
_ => bail!("Unsupported state event: {:?}", value.event_type()),
};
Ok(event)
}
}
#[derive(uniffi::Enum)]
// A note about this `allow(clippy::large_enum_variant)`.
// In order to reduce the size of `MessageLineEventContent`, we would need to
// put some parts in a `Box`, or an `Arc`. Sadly, it doesn't play well with
// UniFFI. We would need to change the `uniffi::Record` of the subtypes into
// `uniffi::Object`, which is a radical change. It would simplify the memory
// usage, but it would slow down the performance around the FFI border. Thus,
// let's consider this is a false-positive lint in this particular case.
#[allow(clippy::large_enum_variant)]
pub enum MessageLikeEventContent {
CallAnswer,
CallInvite,
@@ -221,7 +244,7 @@ impl TryFrom<AnySyncMessageLikeEvent> for MessageLikeEventContent {
MessageLikeEventContent::RoomRedaction { redacted_event_id, reason }
}
AnySyncMessageLikeEvent::Sticker(_) => MessageLikeEventContent::Sticker,
_ => bail!("Unsupported Event Type"),
_ => bail!("Unsupported Event Type: {:?}", value.event_type()),
};
Ok(content)
}
@@ -364,6 +387,8 @@ pub enum RoomMessageEventMessageType {
Audio,
Emote,
File,
#[cfg(feature = "unstable-msc4274")]
Gallery,
Image,
Location,
Notice,
@@ -380,6 +405,8 @@ impl From<RumaMessageType> for RoomMessageEventMessageType {
RumaMessageType::Audio { .. } => Self::Audio,
RumaMessageType::Emote { .. } => Self::Emote,
RumaMessageType::File { .. } => Self::File,
#[cfg(feature = "unstable-msc4274")]
RumaMessageType::Gallery { .. } => Self::Gallery,
RumaMessageType::Image { .. } => Self::Image,
RumaMessageType::Location { .. } => Self::Location,
RumaMessageType::Notice { .. } => Self::Notice,
+7 -8
View File
@@ -1,40 +1,39 @@
// TODO: target-os conditional would be good.
#![allow(unused_qualifications, clippy::new_without_default)]
#![allow(clippy::empty_line_after_doc_comments)] // Needed because uniffi macros contain empty
// lines after docs.
// Needed because uniffi macros contain empty lines after docs.
#![allow(clippy::empty_line_after_doc_comments)]
mod authentication;
mod chunk_iterator;
mod client;
mod client_builder;
mod element;
mod encryption;
mod error;
mod event;
mod helpers;
mod identity_status_change;
mod live_location_share;
mod notification;
mod notification_settings;
mod platform;
mod qr_code;
mod room;
mod room_alias;
mod room_directory_search;
mod room_info;
mod room_list;
mod room_member;
mod room_preview;
mod ruma;
mod runtime;
mod session_verification;
mod spaces;
mod sync_service;
mod task_handle;
mod timeline;
mod timeline_event_filter;
mod tracing;
mod utd;
mod utils;
mod widget;
use async_compat::TOKIO1 as RUNTIME;
use matrix_sdk::ruma::events::room::message::RoomMessageEventContentWithoutRelation;
use self::{
@@ -0,0 +1,32 @@
// Copyright 2024 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::ruma::LocationContent;
#[derive(uniffi::Record)]
pub struct LastLocation {
/// The most recent location content of the user.
pub location: LocationContent,
/// A timestamp in milliseconds since Unix Epoch on that day in local
/// time.
pub ts: u64,
}
/// Details of a users live location share.
#[derive(uniffi::Record)]
pub struct LiveLocationShare {
/// The user's last known location.
pub last_location: LastLocation,
/// The live status of the live location share.
pub(crate) is_live: bool,
/// The user ID of the person sharing their live location.
pub user_id: String,
}
+138 -17
View File
@@ -1,11 +1,17 @@
use std::sync::Arc;
use std::{collections::HashMap, sync::Arc};
use matrix_sdk_ui::notification_client::{
NotificationClient as MatrixNotificationClient, NotificationItem as MatrixNotificationItem,
NotificationClient as SdkNotificationClient, NotificationEvent as SdkNotificationEvent,
NotificationItem as SdkNotificationItem, NotificationStatus as SdkNotificationStatus,
};
use ruma::{EventId, RoomId};
use ruma::{EventId, OwnedEventId, OwnedRoomId, RoomId};
use crate::{client::Client, error::ClientError, event::TimelineEvent};
use crate::{
client::{Client, JoinRule},
error::ClientError,
event::TimelineEvent,
room::Room,
};
#[derive(uniffi::Enum)]
pub enum NotificationEvent {
@@ -25,6 +31,8 @@ pub struct NotificationRoomInfo {
pub display_name: String,
pub avatar_url: Option<String>,
pub canonical_alias: Option<String>,
pub topic: Option<String>,
pub join_rule: Option<JoinRule>,
pub joined_members_count: u64,
pub is_encrypted: Option<bool>,
pub is_direct: bool,
@@ -42,19 +50,19 @@ pub struct NotificationItem {
/// information to create a push context.
pub is_noisy: Option<bool>,
pub has_mention: Option<bool>,
pub thread_id: Option<String>,
}
impl NotificationItem {
fn from_inner(item: MatrixNotificationItem) -> Self {
fn from_inner(item: SdkNotificationItem) -> Self {
let event = match item.event {
matrix_sdk_ui::notification_client::NotificationEvent::Timeline(event) => {
SdkNotificationEvent::Timeline(event) => {
NotificationEvent::Timeline { event: Arc::new(TimelineEvent(event)) }
}
matrix_sdk_ui::notification_client::NotificationEvent::Invite(event) => {
SdkNotificationEvent::Invite(event) => {
NotificationEvent::Invite { sender: event.sender.to_string() }
}
};
Self {
event,
sender_info: NotificationSenderInfo {
@@ -66,47 +74,160 @@ impl NotificationItem {
display_name: item.room_computed_display_name,
avatar_url: item.room_avatar_url,
canonical_alias: item.room_canonical_alias,
topic: item.room_topic,
join_rule: item.room_join_rule.map(TryInto::try_into).transpose().ok().flatten(),
joined_members_count: item.joined_members_count,
is_encrypted: item.is_room_encrypted,
is_direct: item.is_direct_message_room,
},
is_noisy: item.is_noisy,
has_mention: item.has_mention,
thread_id: item.thread_id.map(|t| t.to_string()),
}
}
}
#[allow(clippy::large_enum_variant)]
#[derive(uniffi::Enum)]
pub enum NotificationStatus {
/// The event has been found and was not filtered out.
Event { item: NotificationItem },
/// The event couldn't be found in the network queries used to find it.
EventNotFound,
/// The event has been filtered out, either because of the user's push
/// rules, or because the user which triggered it is ignored by the
/// current user.
EventFilteredOut,
}
impl From<SdkNotificationStatus> for NotificationStatus {
fn from(item: SdkNotificationStatus) -> Self {
match item {
SdkNotificationStatus::Event(item) => {
NotificationStatus::Event { item: NotificationItem::from_inner(*item) }
}
SdkNotificationStatus::EventNotFound => NotificationStatus::EventNotFound,
SdkNotificationStatus::EventFilteredOut => NotificationStatus::EventFilteredOut,
}
}
}
#[allow(clippy::large_enum_variant)]
#[derive(uniffi::Enum)]
pub enum BatchNotificationResult {
/// We have more detailed information about the notification.
Ok { status: NotificationStatus },
/// An error occurred while trying to fetch the notification.
Error {
/// The error message observed while handling a specific notification.
message: String,
},
}
#[derive(uniffi::Object)]
pub struct NotificationClient {
pub(crate) inner: MatrixNotificationClient,
pub(crate) inner: SdkNotificationClient,
/// A reference to the FFI client.
///
/// Note: we do this to make it so that the FFI `NotificationClient` keeps
/// the FFI `Client` and thus the SDK `Client` alive. Otherwise, we
/// would need to repeat the hack done in the FFI `Client::drop` method.
pub(crate) _client: Arc<Client>,
pub(crate) client: Arc<Client>,
}
#[matrix_sdk_ffi_macros::export]
impl NotificationClient {
/// See also documentation of
/// `MatrixNotificationClient::get_notification`.
/// Fetches a room by its ID using the in-memory state store backed client.
///
/// Useful to retrieve room information after running the limited
/// notification client sliding sync loop.
pub fn get_room(&self, room_id: String) -> Result<Option<Arc<Room>>, ClientError> {
let room_id = RoomId::parse(room_id)?;
let sdk_room = self.inner.get_room(&room_id);
let room = sdk_room
.map(|room| Arc::new(Room::new(room, self.client.utd_hook_manager.get().cloned())));
Ok(room)
}
/// Fetches the content of a notification.
///
/// This will first try to get the notification using a short-lived sliding
/// sync, and if the sliding-sync can't find the event, then it'll use a
/// `/context` query to find the event with associated member information.
///
/// An error result means that we couldn't resolve the notification; in that
/// case, a dummy notification may be displayed instead.
pub async fn get_notification(
&self,
room_id: String,
event_id: String,
) -> Result<Option<NotificationItem>, ClientError> {
) -> Result<NotificationStatus, ClientError> {
let room_id = RoomId::parse(room_id)?;
let event_id = EventId::parse(event_id)?;
let item =
self.inner.get_notification(&room_id, &event_id).await.map_err(ClientError::from)?;
if let Some(item) = item {
Ok(Some(NotificationItem::from_inner(item)))
} else {
Ok(None)
Ok(item.into())
}
/// Get several notification items in a single batch.
///
/// Returns an error if the flow failed when preparing to fetch the
/// notifications, and a [`HashMap`] containing either a
/// [`BatchNotificationResult`], that indicates if the notification was
/// successfully fetched (in which case, it's a [`NotificationStatus`]), or
/// an error message if it couldn't be fetched.
pub async fn get_notifications(
&self,
requests: Vec<NotificationItemsRequest>,
) -> Result<HashMap<String, BatchNotificationResult>, ClientError> {
let requests =
requests.into_iter().map(TryInto::try_into).collect::<Result<Vec<_>, _>>()?;
let items = self.inner.get_notifications(&requests).await?;
let mut batch_result = HashMap::new();
for (key, value) in items.into_iter() {
let result = match value {
Ok(status) => BatchNotificationResult::Ok { status: status.into() },
Err(error) => BatchNotificationResult::Error { message: error.to_string() },
};
batch_result.insert(key.to_string(), result);
}
Ok(batch_result)
}
}
/// A request for notification items grouped by their room.
#[derive(uniffi::Record)]
pub struct NotificationItemsRequest {
room_id: String,
event_ids: Vec<String>,
}
impl NotificationItemsRequest {
/// The parsed [`OwnedRoomId`] to use with the SDK crates.
pub fn room_id(&self) -> Result<OwnedRoomId, ClientError> {
RoomId::parse(&self.room_id).map_err(ClientError::from)
}
/// The parsed [`OwnedEventId`] list to use with the SDK crates.
pub fn event_ids(&self) -> Result<Vec<OwnedEventId>, ClientError> {
self.event_ids
.iter()
.map(|id| EventId::parse(id).map_err(ClientError::from))
.collect::<Result<Vec<_>, _>>()
}
}
impl TryFrom<NotificationItemsRequest>
for matrix_sdk_ui::notification_client::NotificationItemsRequest
{
type Error = ClientError;
fn try_from(value: NotificationItemsRequest) -> Result<Self, Self::Error> {
Ok(Self { room_id: value.room_id()?, event_ids: value.event_ids()? })
}
}
@@ -9,14 +9,352 @@ use matrix_sdk::{
ruma::events::push_rules::PushRulesEvent,
Client as MatrixClient,
};
use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
use ruma::{
push::{PredefinedOverrideRuleId, PredefinedUnderrideRuleId, RuleKind},
RoomId,
push::{
Action as SdkAction, ComparisonOperator as SdkComparisonOperator, PredefinedOverrideRuleId,
PredefinedUnderrideRuleId, PushCondition as SdkPushCondition, RoomMemberCountIs,
RuleKind as SdkRuleKind, ScalarJsonValue as SdkJsonValue, Tweak as SdkTweak,
},
Int, RoomId, UInt,
};
use tokio::sync::RwLock as AsyncRwLock;
use crate::error::NotificationSettingsError;
#[derive(Clone, Default, uniffi::Enum)]
pub enum ComparisonOperator {
/// Equals
#[default]
Eq,
/// Less than
Lt,
/// Greater than
Gt,
/// Greater or equal
Ge,
/// Less or equal
Le,
}
impl From<SdkComparisonOperator> for ComparisonOperator {
fn from(value: SdkComparisonOperator) -> Self {
match value {
SdkComparisonOperator::Eq => Self::Eq,
SdkComparisonOperator::Lt => Self::Lt,
SdkComparisonOperator::Gt => Self::Gt,
SdkComparisonOperator::Ge => Self::Ge,
SdkComparisonOperator::Le => Self::Le,
}
}
}
impl From<ComparisonOperator> for SdkComparisonOperator {
fn from(value: ComparisonOperator) -> Self {
match value {
ComparisonOperator::Eq => Self::Eq,
ComparisonOperator::Lt => Self::Lt,
ComparisonOperator::Gt => Self::Gt,
ComparisonOperator::Ge => Self::Ge,
ComparisonOperator::Le => Self::Le,
}
}
}
#[derive(Debug, Clone, Default, uniffi::Enum)]
pub enum JsonValue {
/// Represents a `null` value.
#[default]
Null,
/// Represents a boolean.
Bool { value: bool },
/// Represents an integer.
Integer { value: i64 },
/// Represents a string.
String { value: String },
}
impl From<SdkJsonValue> for JsonValue {
fn from(value: SdkJsonValue) -> Self {
match value {
SdkJsonValue::Null => Self::Null,
SdkJsonValue::Bool(b) => Self::Bool { value: b },
SdkJsonValue::Integer(i) => Self::Integer { value: i.into() },
SdkJsonValue::String(s) => Self::String { value: s },
}
}
}
impl From<JsonValue> for SdkJsonValue {
fn from(value: JsonValue) -> Self {
match value {
JsonValue::Null => Self::Null,
JsonValue::Bool { value } => Self::Bool(value),
JsonValue::Integer { value } => Self::Integer(Int::new(value).unwrap_or_default()),
JsonValue::String { value } => Self::String(value),
}
}
}
#[derive(Clone, uniffi::Enum)]
pub enum PushCondition {
/// A glob pattern match on a field of the event.
EventMatch {
/// The [dot-separated path] of the property of the event to match.
///
/// [dot-separated path]: https://spec.matrix.org/latest/appendices/#dot-separated-property-paths
key: String,
/// The glob-style pattern to match against.
///
/// Patterns with no special glob characters should be treated as having
/// asterisks prepended and appended when testing the condition.
pattern: String,
},
/// Matches unencrypted messages where `content.body` contains the owner's
/// display name in that room.
ContainsDisplayName,
/// Matches the current number of members in the room.
RoomMemberCount { prefix: ComparisonOperator, count: u64 },
/// Takes into account the current power levels in the room, ensuring the
/// sender of the event has high enough power to trigger the
/// notification.
SenderNotificationPermission {
/// The field in the power level event the user needs a minimum power
/// level for.
///
/// Fields must be specified under the `notifications` property in the
/// power level event's `content`.
key: String,
},
/// Exact value match on a property of the event.
EventPropertyIs {
/// The [dot-separated path] of the property of the event to match.
///
/// [dot-separated path]: https://spec.matrix.org/latest/appendices/#dot-separated-property-paths
key: String,
/// The value to match against.
value: JsonValue,
},
/// Exact value match on a value in an array property of the event.
EventPropertyContains {
/// The [dot-separated path] of the property of the event to match.
///
/// [dot-separated path]: https://spec.matrix.org/latest/appendices/#dot-separated-property-paths
key: String,
/// The value to match against.
value: JsonValue,
},
}
impl TryFrom<SdkPushCondition> for PushCondition {
type Error = String;
fn try_from(value: SdkPushCondition) -> Result<Self, Self::Error> {
Ok(match value {
SdkPushCondition::EventMatch { key, pattern } => Self::EventMatch { key, pattern },
SdkPushCondition::ContainsDisplayName => Self::ContainsDisplayName,
SdkPushCondition::RoomMemberCount { is } => {
Self::RoomMemberCount { prefix: is.prefix.into(), count: is.count.into() }
}
SdkPushCondition::SenderNotificationPermission { key } => {
Self::SenderNotificationPermission { key: key.to_string() }
}
SdkPushCondition::EventPropertyIs { key, value } => {
Self::EventPropertyIs { key, value: value.into() }
}
SdkPushCondition::EventPropertyContains { key, value } => {
Self::EventPropertyContains { key, value: value.into() }
}
_ => return Err("Unsupported condition type".to_owned()),
})
}
}
impl From<PushCondition> for SdkPushCondition {
fn from(value: PushCondition) -> Self {
match value {
PushCondition::EventMatch { key, pattern } => Self::EventMatch { key, pattern },
PushCondition::ContainsDisplayName => Self::ContainsDisplayName,
PushCondition::RoomMemberCount { prefix, count } => Self::RoomMemberCount {
is: RoomMemberCountIs {
prefix: prefix.into(),
count: UInt::new(count).unwrap_or_default(),
},
},
PushCondition::SenderNotificationPermission { key } => {
Self::SenderNotificationPermission { key: key.into() }
}
PushCondition::EventPropertyIs { key, value } => {
Self::EventPropertyIs { key, value: value.into() }
}
PushCondition::EventPropertyContains { key, value } => {
Self::EventPropertyContains { key, value: value.into() }
}
}
}
}
#[derive(Clone, uniffi::Enum)]
pub enum RuleKind {
/// User-configured rules that override all other kinds.
Override,
/// Lowest priority user-defined rules.
Underride,
/// Sender-specific rules.
Sender,
/// Room-specific rules.
Room,
/// Content-specific rules.
Content,
Custom {
value: String,
},
}
impl From<SdkRuleKind> for RuleKind {
fn from(value: SdkRuleKind) -> Self {
match value {
SdkRuleKind::Override => Self::Override,
SdkRuleKind::Underride => Self::Underride,
SdkRuleKind::Sender => Self::Sender,
SdkRuleKind::Room => Self::Room,
SdkRuleKind::Content => Self::Content,
SdkRuleKind::_Custom(_) => Self::Custom { value: value.as_str().to_owned() },
_ => Self::Custom { value: value.to_string() },
}
}
}
impl From<RuleKind> for SdkRuleKind {
fn from(value: RuleKind) -> Self {
match value {
RuleKind::Override => Self::Override,
RuleKind::Underride => Self::Underride,
RuleKind::Sender => Self::Sender,
RuleKind::Room => Self::Room,
RuleKind::Content => Self::Content,
RuleKind::Custom { value } => SdkRuleKind::from(value),
}
}
}
#[derive(Clone, uniffi::Enum)]
/// Enum representing the push notification tweaks for a rule.
pub enum Tweak {
/// A string representing the sound to be played when this notification
/// arrives.
///
/// A value of "default" means to play a default sound. A device may choose
/// to alert the user by some other means if appropriate, eg. vibration.
Sound { value: String },
/// A boolean representing whether or not this message should be highlighted
/// in the UI.
Highlight { value: bool },
/// A custom tweak
Custom {
/// The name of the custom tweak (`set_tweak` field)
name: String,
/// The value of the custom tweak as an encoded JSON string
value: String,
},
}
impl TryFrom<SdkTweak> for Tweak {
type Error = String;
fn try_from(value: SdkTweak) -> Result<Self, Self::Error> {
Ok(match value {
SdkTweak::Sound(sound) => Self::Sound { value: sound },
SdkTweak::Highlight(highlight) => Self::Highlight { value: highlight },
SdkTweak::Custom { name, value } => {
let json_string = serde_json::to_string(&value)
.map_err(|e| format!("Failed to serialize custom tweak value: {e}"))?;
Self::Custom { name, value: json_string }
}
_ => return Err("Unsupported tweak type".to_owned()),
})
}
}
impl TryFrom<Tweak> for SdkTweak {
type Error = String;
fn try_from(value: Tweak) -> Result<Self, Self::Error> {
Ok(match value {
Tweak::Sound { value } => Self::Sound(value),
Tweak::Highlight { value } => Self::Highlight(value),
Tweak::Custom { name, value } => {
let json_value: serde_json::Value = serde_json::from_str(&value)
.map_err(|e| format!("Failed to deserialize custom tweak value: {e}"))?;
let value = serde_json::from_value(json_value)
.map_err(|e| format!("Failed to convert JSON value: {e}"))?;
Self::Custom { name, value }
}
})
}
}
#[derive(Clone, uniffi::Enum)]
/// Enum representing the push notification actions for a rule.
pub enum Action {
/// Causes matching events to generate a notification.
Notify,
/// Sets an entry in the 'tweaks' dictionary sent to the push gateway.
SetTweak { value: Tweak },
}
impl TryFrom<SdkAction> for Action {
type Error = String;
fn try_from(value: SdkAction) -> Result<Self, Self::Error> {
Ok(match value {
SdkAction::Notify => Self::Notify,
SdkAction::SetTweak(tweak) => Self::SetTweak {
value: tweak.try_into().map_err(|e| format!("Failed to convert tweak: {e}"))?,
},
_ => return Err("Unsupported action type".to_owned()),
})
}
}
impl TryFrom<Action> for SdkAction {
type Error = String;
fn try_from(value: Action) -> Result<Self, Self::Error> {
Ok(match value {
Action::Notify => Self::Notify,
Action::SetTweak { value } => Self::SetTweak(
value.try_into().map_err(|e| format!("Failed to convert tweak: {e}"))?,
),
})
}
}
/// Enum representing the push notification modes for a room.
#[derive(Clone, uniffi::Enum)]
pub enum RoomNotificationMode {
@@ -50,7 +388,7 @@ impl From<RoomNotificationMode> for SdkRoomNotificationMode {
/// Delegate to notify of changes in push rules
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait NotificationSettingsDelegate: Sync + Send {
pub trait NotificationSettingsDelegate: SyncOutsideWasm + SendOutsideWasm {
fn settings_did_change(&self);
}
@@ -267,7 +605,7 @@ impl NotificationSettings {
pub async fn is_room_mention_enabled(&self) -> Result<bool, NotificationSettingsError> {
let notification_settings = self.sdk_notification_settings.read().await;
let enabled = notification_settings
.is_push_rule_enabled(RuleKind::Override, PredefinedOverrideRuleId::IsRoomMention)
.is_push_rule_enabled(SdkRuleKind::Override, PredefinedOverrideRuleId::IsRoomMention)
.await?;
Ok(enabled)
}
@@ -280,7 +618,7 @@ impl NotificationSettings {
let notification_settings = self.sdk_notification_settings.read().await;
notification_settings
.set_push_rule_enabled(
RuleKind::Override,
SdkRuleKind::Override,
PredefinedOverrideRuleId::IsRoomMention,
enabled,
)
@@ -292,7 +630,7 @@ impl NotificationSettings {
pub async fn is_user_mention_enabled(&self) -> Result<bool, NotificationSettingsError> {
let notification_settings = self.sdk_notification_settings.read().await;
let enabled = notification_settings
.is_push_rule_enabled(RuleKind::Override, PredefinedOverrideRuleId::IsUserMention)
.is_push_rule_enabled(SdkRuleKind::Override, PredefinedOverrideRuleId::IsUserMention)
.await?;
Ok(enabled)
}
@@ -304,14 +642,14 @@ impl NotificationSettings {
let notification_settings = self.sdk_notification_settings.read().await;
// Check stable identifier
if let Ok(enabled) = notification_settings
.is_push_rule_enabled(RuleKind::Override, ".m.rule.encrypted_event")
.is_push_rule_enabled(SdkRuleKind::Override, ".m.rule.encrypted_event")
.await
{
enabled
} else {
// Check unstable identifier
notification_settings
.is_push_rule_enabled(RuleKind::Override, ".org.matrix.msc4028.encrypted_event")
.is_push_rule_enabled(SdkRuleKind::Override, ".org.matrix.msc4028.encrypted_event")
.await
.unwrap_or(false)
}
@@ -332,7 +670,7 @@ impl NotificationSettings {
let notification_settings = self.sdk_notification_settings.read().await;
notification_settings
.set_push_rule_enabled(
RuleKind::Override,
SdkRuleKind::Override,
PredefinedOverrideRuleId::IsUserMention,
enabled,
)
@@ -344,7 +682,7 @@ impl NotificationSettings {
pub async fn is_call_enabled(&self) -> Result<bool, NotificationSettingsError> {
let notification_settings = self.sdk_notification_settings.read().await;
let enabled = notification_settings
.is_push_rule_enabled(RuleKind::Underride, PredefinedUnderrideRuleId::Call)
.is_push_rule_enabled(SdkRuleKind::Underride, PredefinedUnderrideRuleId::Call)
.await?;
Ok(enabled)
}
@@ -353,7 +691,7 @@ impl NotificationSettings {
pub async fn set_call_enabled(&self, enabled: bool) -> Result<(), NotificationSettingsError> {
let notification_settings = self.sdk_notification_settings.read().await;
notification_settings
.set_push_rule_enabled(RuleKind::Underride, PredefinedUnderrideRuleId::Call, enabled)
.set_push_rule_enabled(SdkRuleKind::Underride, PredefinedUnderrideRuleId::Call, enabled)
.await?;
Ok(())
}
@@ -363,7 +701,7 @@ impl NotificationSettings {
let notification_settings = self.sdk_notification_settings.read().await;
let enabled = notification_settings
.is_push_rule_enabled(
RuleKind::Override,
SdkRuleKind::Override,
PredefinedOverrideRuleId::InviteForMe.as_str(),
)
.await?;
@@ -378,7 +716,7 @@ impl NotificationSettings {
let notification_settings = self.sdk_notification_settings.read().await;
notification_settings
.set_push_rule_enabled(
RuleKind::Override,
SdkRuleKind::Override,
PredefinedOverrideRuleId::InviteForMe.as_str(),
enabled,
)
@@ -386,6 +724,30 @@ impl NotificationSettings {
Ok(())
}
/// Sets a custom push rule with the given actions and conditions.
pub async fn set_custom_push_rule(
&self,
rule_id: String,
rule_kind: RuleKind,
actions: Vec<Action>,
conditions: Vec<PushCondition>,
) -> Result<(), NotificationSettingsError> {
let notification_settings = self.sdk_notification_settings.read().await;
let actions: Result<Vec<_>, _> =
actions.into_iter().map(|action| action.try_into()).collect();
let actions = actions.map_err(|e| NotificationSettingsError::Generic { msg: e })?;
notification_settings
.create_custom_conditional_push_rule(
rule_id,
rule_kind.into(),
actions,
conditions.into_iter().map(|condition| condition.into()).collect(),
)
.await?;
Ok(())
}
/// Unmute a room.
///
/// # Arguments
+710 -137
View File
@@ -1,3 +1,9 @@
use std::sync::OnceLock;
#[cfg(feature = "sentry")]
use std::sync::{atomic::AtomicBool, Arc};
#[cfg(feature = "sentry")]
use tracing::warn;
use tracing_appender::rolling::{RollingFileAppender, Rotation};
use tracing_core::Subscriber;
use tracing_subscriber::{
@@ -8,167 +14,158 @@ use tracing_subscriber::{
time::FormatTime,
FormatEvent, FormatFields, FormattedFields,
},
layer::SubscriberExt,
layer::{Layered, SubscriberExt as _},
registry::LookupSpan,
util::SubscriberInitExt,
EnvFilter, Layer,
reload::{self, Handle},
util::SubscriberInitExt as _,
EnvFilter, Layer, Registry,
};
pub fn log_panics() {
std::env::set_var("RUST_BACKTRACE", "1");
log_panics::init();
use crate::{error::ClientError, tracing::LogLevel};
// Adjusted version of tracing_subscriber::fmt::Format
struct EventFormatter {
display_timestamp: bool,
display_level: bool,
}
fn text_layers<S>(config: TracingConfiguration) -> impl Layer<S>
impl EventFormatter {
fn new() -> Self {
Self { display_timestamp: true, display_level: true }
}
#[cfg(target_os = "android")]
fn for_logcat() -> Self {
// Level and time are already captured by logcat separately
Self { display_timestamp: false, display_level: false }
}
fn format_timestamp(&self, writer: &mut fmt::format::Writer<'_>) -> std::fmt::Result {
if fmt::time::SystemTime.format_time(writer).is_err() {
writer.write_str("<unknown time>")?;
}
Ok(())
}
fn write_filename(
&self,
writer: &mut fmt::format::Writer<'_>,
filename: &str,
) -> std::fmt::Result {
const CRATES_IO_PATH_MATCHER: &str = ".cargo/registry/src/index.crates.io";
let crates_io_filename = filename
.split_once(CRATES_IO_PATH_MATCHER)
.and_then(|(_, rest)| rest.split_once('/').map(|(_, rest)| rest));
if let Some(filename) = crates_io_filename {
writer.write_str("<crates.io>/")?;
writer.write_str(filename)
} else {
writer.write_str(filename)
}
}
}
impl<S, N> FormatEvent<S, N> for EventFormatter
where
S: Subscriber + for<'a> LookupSpan<'a>,
N: for<'a> FormatFields<'a> + 'static,
{
// Adjusted version of tracing_subscriber::fmt::Format
struct EventFormatter {
display_timestamp: bool,
display_level: bool,
}
fn format_event(
&self,
ctx: &fmt::FmtContext<'_, S, N>,
mut writer: fmt::format::Writer<'_>,
event: &tracing_core::Event<'_>,
) -> std::fmt::Result {
let meta = event.metadata();
impl EventFormatter {
fn new() -> Self {
Self { display_timestamp: true, display_level: true }
if self.display_timestamp {
self.format_timestamp(&mut writer)?;
writer.write_char(' ')?;
}
#[cfg(target_os = "android")]
fn for_logcat() -> Self {
// Level and time are already captured by logcat separately
Self { display_timestamp: false, display_level: false }
if self.display_level {
// For info and warn, add a padding space to the left
write!(writer, "{:>5} ", meta.level())?;
}
fn format_timestamp(&self, writer: &mut fmt::format::Writer<'_>) -> std::fmt::Result {
if fmt::time::SystemTime.format_time(writer).is_err() {
writer.write_str("<unknown time>")?;
}
Ok(())
}
write!(writer, "{}: ", meta.target())?;
fn write_filename(
&self,
writer: &mut fmt::format::Writer<'_>,
filename: &str,
) -> std::fmt::Result {
const CRATES_IO_PATH_MATCHER: &str = ".cargo/registry/src/index.crates.io";
let crates_io_filename = filename
.split_once(CRATES_IO_PATH_MATCHER)
.and_then(|(_, rest)| rest.split_once('/').map(|(_, rest)| rest));
ctx.format_fields(writer.by_ref(), event)?;
if let Some(filename) = crates_io_filename {
writer.write_str("<crates.io>/")?;
writer.write_str(filename)
} else {
writer.write_str(filename)
if let Some(filename) = meta.file() {
writer.write_str(" | ")?;
self.write_filename(&mut writer, filename)?;
if let Some(line_number) = meta.line() {
write!(writer, ":{line_number}")?;
}
}
}
impl<S, N> FormatEvent<S, N> for EventFormatter
where
S: Subscriber + for<'a> LookupSpan<'a>,
N: for<'a> FormatFields<'a> + 'static,
{
fn format_event(
&self,
ctx: &fmt::FmtContext<'_, S, N>,
mut writer: fmt::format::Writer<'_>,
event: &tracing_core::Event<'_>,
) -> std::fmt::Result {
let meta = event.metadata();
if let Some(scope) = ctx.event_scope() {
writer.write_str(" | spans: ")?;
if self.display_timestamp {
self.format_timestamp(&mut writer)?;
writer.write_char(' ')?;
}
let mut first = true;
if self.display_level {
// For info and warn, add a padding space to the left
write!(writer, "{:>5} ", meta.level())?;
}
write!(writer, "{}: ", meta.target())?;
ctx.format_fields(writer.by_ref(), event)?;
if let Some(filename) = meta.file() {
writer.write_str(" | ")?;
self.write_filename(&mut writer, filename)?;
if let Some(line_number) = meta.line() {
write!(writer, ":{line_number}")?;
for span in scope.from_root() {
if !first {
writer.write_str(" > ")?;
}
}
if let Some(scope) = ctx.event_scope() {
writer.write_str(" | spans: ")?;
first = false;
let mut first = true;
write!(writer, "{}", span.name())?;
for span in scope.from_root() {
if !first {
writer.write_str(" > ")?;
}
first = false;
write!(writer, "{}", span.name())?;
if let Some(fields) = &span.extensions().get::<FormattedFields<N>>() {
if !fields.is_empty() {
write!(writer, "{{{fields}}}")?;
}
if let Some(fields) = &span.extensions().get::<FormattedFields<N>>() {
if !fields.is_empty() {
write!(writer, "{{{fields}}}")?;
}
}
}
writeln!(writer)
}
writeln!(writer)
}
}
let file_layer = config.write_to_files.map(|c| {
let mut builder = RollingFileAppender::builder()
.rotation(Rotation::HOURLY)
.filename_prefix(&c.file_prefix);
// Another fields formatter is necessary because of this bug
// https://github.com/tokio-rs/tracing/issues/1372. Using a new
// formatter for the fields forces to record them in different span
// extensions, and thus remove the duplicated fields in the span.
#[derive(Default)]
struct FieldsFormatterForFiles(DefaultFields);
if let Some(max_files) = c.max_files {
builder = builder.max_log_files(max_files as usize)
};
if let Some(file_suffix) = c.file_suffix {
builder = builder.filename_suffix(file_suffix)
}
impl<'writer> FormatFields<'writer> for FieldsFormatterForFiles {
fn format_fields<R: RecordFields>(
&self,
writer: Writer<'writer>,
fields: R,
) -> std::fmt::Result {
self.0.format_fields(writer, fields)
}
}
let writer = builder.build(&c.path).expect("Failed to create a rolling file appender.");
type ReloadHandle = Handle<
tracing_subscriber::fmt::Layer<
Layered<EnvFilter, Registry>,
FieldsFormatterForFiles,
EventFormatter,
RollingFileAppender,
>,
Layered<EnvFilter, Registry>,
>;
// Another fields formatter is necessary because of this bug
// https://github.com/tokio-rs/tracing/issues/1372. Using a new
// formatter for the fields forces to record them in different span
// extensions, and thus remove the duplicated fields in the span.
#[derive(Default)]
struct FieldsFormatterForFiles(DefaultFields);
fn text_layers(
config: TracingConfiguration,
) -> (impl Layer<Layered<EnvFilter, Registry>>, Option<ReloadHandle>) {
let (file_layer, reload_handle) = config
.write_to_files
.map(|c| {
let layer = make_file_layer(c);
reload::Layer::new(layer)
})
.unzip();
impl<'writer> FormatFields<'writer> for FieldsFormatterForFiles {
fn format_fields<R: RecordFields>(
&self,
writer: Writer<'writer>,
fields: R,
) -> std::fmt::Result {
self.0.format_fields(writer, fields)
}
}
fmt::layer()
.fmt_fields(FieldsFormatterForFiles::default())
.event_format(EventFormatter::new())
// EventFormatter doesn't support ANSI colors anyways, but the
// default field formatter does, which is unhelpful for iOS +
// Android logs, but enabled by default.
.with_ansi(false)
.with_writer(writer)
});
Layer::and_then(
let layers = Layer::and_then(
file_layer,
config.write_to_stdout_or_system.then(|| {
// Another fields formatter is necessary because of this bug
@@ -206,7 +203,41 @@ where
"org.matrix.rust.sdk".to_owned(),
));
}),
)
);
(layers, reload_handle)
}
fn make_file_layer(
file_configuration: TracingFileConfiguration,
) -> tracing_subscriber::fmt::Layer<
Layered<EnvFilter, Registry, Registry>,
FieldsFormatterForFiles,
EventFormatter,
RollingFileAppender,
> {
let mut builder = RollingFileAppender::builder()
.rotation(Rotation::HOURLY)
.filename_prefix(&file_configuration.file_prefix);
if let Some(max_files) = file_configuration.max_files {
builder = builder.max_log_files(max_files as usize)
}
if let Some(file_suffix) = file_configuration.file_suffix {
builder = builder.filename_suffix(file_suffix)
}
let writer =
builder.build(&file_configuration.path).expect("Failed to create a rolling file appender.");
fmt::layer()
.fmt_fields(FieldsFormatterForFiles::default())
.event_format(EventFormatter::new())
// EventFormatter doesn't support ANSI colors anyways, but the
// default field formatter does, which is unhelpful for iOS +
// Android logs, but enabled by default.
.with_ansi(false)
.with_writer(writer)
}
/// Configuration to save logs to (rotated) log-files.
@@ -228,26 +259,568 @@ pub struct TracingFileConfiguration {
max_files: Option<u64>,
}
#[derive(PartialEq, PartialOrd)]
enum LogTarget {
// External crates.
Hyper,
// FFI modules.
MatrixSdkFfi,
// SDK base modules.
MatrixSdkBaseEventCache,
MatrixSdkBaseSlidingSync,
MatrixSdkBaseStoreAmbiguityMap,
MatrixSdkBaseResponseProcessors,
// SDK common modules.
MatrixSdkCommonStoreLocks,
// SDK modules.
MatrixSdk,
MatrixSdkClient,
MatrixSdkCrypto,
MatrixSdkCryptoAccount,
MatrixSdkEventCache,
MatrixSdkEventCacheStore,
MatrixSdkHttpClient,
MatrixSdkOidc,
MatrixSdkSendQueue,
MatrixSdkSlidingSync,
// SDK UI modules.
MatrixSdkUiTimeline,
MatrixSdkUiNotificationClient,
}
impl LogTarget {
fn as_str(&self) -> &'static str {
match self {
LogTarget::Hyper => "hyper",
LogTarget::MatrixSdkFfi => "matrix_sdk_ffi",
LogTarget::MatrixSdkBaseEventCache => "matrix_sdk_base::event_cache",
LogTarget::MatrixSdkBaseSlidingSync => "matrix_sdk_base::sliding_sync",
LogTarget::MatrixSdkBaseStoreAmbiguityMap => "matrix_sdk_base::store::ambiguity_map",
LogTarget::MatrixSdkBaseResponseProcessors => "matrix_sdk_base::response_processors",
LogTarget::MatrixSdkCommonStoreLocks => "matrix_sdk_common::store_locks",
LogTarget::MatrixSdk => "matrix_sdk",
LogTarget::MatrixSdkClient => "matrix_sdk::client",
LogTarget::MatrixSdkCrypto => "matrix_sdk_crypto",
LogTarget::MatrixSdkCryptoAccount => "matrix_sdk_crypto::olm::account",
LogTarget::MatrixSdkOidc => "matrix_sdk::oidc",
LogTarget::MatrixSdkHttpClient => "matrix_sdk::http_client",
LogTarget::MatrixSdkSlidingSync => "matrix_sdk::sliding_sync",
LogTarget::MatrixSdkEventCache => "matrix_sdk::event_cache",
LogTarget::MatrixSdkSendQueue => "matrix_sdk::send_queue",
LogTarget::MatrixSdkEventCacheStore => "matrix_sdk_sqlite::event_cache_store",
LogTarget::MatrixSdkUiTimeline => "matrix_sdk_ui::timeline",
LogTarget::MatrixSdkUiNotificationClient => "matrix_sdk_ui::notification_client",
}
}
}
const DEFAULT_TARGET_LOG_LEVELS: &[(LogTarget, LogLevel)] = &[
(LogTarget::Hyper, LogLevel::Warn),
(LogTarget::MatrixSdkFfi, LogLevel::Info),
(LogTarget::MatrixSdk, LogLevel::Info),
(LogTarget::MatrixSdkClient, LogLevel::Trace),
(LogTarget::MatrixSdkCrypto, LogLevel::Debug),
(LogTarget::MatrixSdkCryptoAccount, LogLevel::Trace),
(LogTarget::MatrixSdkOidc, LogLevel::Trace),
(LogTarget::MatrixSdkHttpClient, LogLevel::Debug),
(LogTarget::MatrixSdkSlidingSync, LogLevel::Info),
(LogTarget::MatrixSdkBaseSlidingSync, LogLevel::Info),
(LogTarget::MatrixSdkUiTimeline, LogLevel::Info),
(LogTarget::MatrixSdkSendQueue, LogLevel::Info),
(LogTarget::MatrixSdkEventCache, LogLevel::Info),
(LogTarget::MatrixSdkBaseEventCache, LogLevel::Info),
(LogTarget::MatrixSdkEventCacheStore, LogLevel::Info),
(LogTarget::MatrixSdkCommonStoreLocks, LogLevel::Warn),
(LogTarget::MatrixSdkBaseStoreAmbiguityMap, LogLevel::Warn),
(LogTarget::MatrixSdkUiNotificationClient, LogLevel::Info),
(LogTarget::MatrixSdkBaseResponseProcessors, LogLevel::Debug),
];
const IMMUTABLE_LOG_TARGETS: &[LogTarget] = &[
LogTarget::Hyper, // Too verbose
LogTarget::MatrixSdk, // Too generic
LogTarget::MatrixSdkFfi, // Too verbose
LogTarget::MatrixSdkCommonStoreLocks, // Too verbose
LogTarget::MatrixSdkBaseStoreAmbiguityMap, // Too verbose
];
/// A log pack can be used to set the trace log level for a group of multiple
/// log targets at once, for debugging purposes.
#[derive(uniffi::Enum)]
pub enum TraceLogPacks {
/// Enables all the logs relevant to the event cache.
EventCache,
/// Enables all the logs relevant to the send queue.
SendQueue,
/// Enables all the logs relevant to the timeline.
Timeline,
/// Enables all the logs relevant to the notification client.
NotificationClient,
/// Enables all the logs relevant to sync profiling.
SyncProfiling,
}
impl TraceLogPacks {
// Note: all the log targets returned here must be part of
// `DEFAULT_TARGET_LOG_LEVELS`.
fn targets(&self) -> &[LogTarget] {
match self {
TraceLogPacks::EventCache => &[
LogTarget::MatrixSdkEventCache,
LogTarget::MatrixSdkBaseEventCache,
LogTarget::MatrixSdkEventCacheStore,
],
TraceLogPacks::SendQueue => &[LogTarget::MatrixSdkSendQueue],
TraceLogPacks::Timeline => &[LogTarget::MatrixSdkUiTimeline],
TraceLogPacks::NotificationClient => &[LogTarget::MatrixSdkUiNotificationClient],
TraceLogPacks::SyncProfiling => &[
LogTarget::MatrixSdkSlidingSync,
LogTarget::MatrixSdkBaseSlidingSync,
LogTarget::MatrixSdkBaseResponseProcessors,
LogTarget::MatrixSdkCrypto,
],
}
}
}
#[cfg(feature = "sentry")]
struct SentryLoggingCtx {
/// The Sentry client guard, which keeps the Sentry context alive.
_guard: sentry::ClientInitGuard,
/// Whether the Sentry layer is enabled or not, at a global level.
enabled: Arc<AtomicBool>,
}
struct LoggingCtx {
reload_handle: Option<ReloadHandle>,
#[cfg(feature = "sentry")]
sentry: Option<SentryLoggingCtx>,
}
static LOGGING: OnceLock<LoggingCtx> = OnceLock::new();
#[derive(uniffi::Record)]
pub struct TracingConfiguration {
/// A filter line following the [RUST_LOG format].
/// The desired log level.
log_level: LogLevel,
/// All the log packs, that will be set to `TRACE` when they're enabled.
trace_log_packs: Vec<TraceLogPacks>,
/// Additional targets that the FFI client would like to use.
///
/// [RUST_LOG format]: https://rust-lang-nursery.github.io/rust-cookbook/development_tools/debugging/config_log.html
filter: String,
/// This can include, for instance, the target names for created
/// [`crate::tracing::Span`]. These targets will use the global log level by
/// default.
extra_targets: Vec<String>,
/// Whether to log to stdout, or in the logcat on Android.
write_to_stdout_or_system: bool,
/// If set, configures rotated log files where to write additional logs.
write_to_files: Option<TracingFileConfiguration>,
/// If set, the Sentry DSN to use for error reporting.
#[cfg(feature = "sentry")]
sentry_dsn: Option<String>,
}
impl TracingConfiguration {
/// Sets up the tracing configuration and return a [`Logger`] instance
/// holding onto it.
#[cfg_attr(not(feature = "sentry"), allow(unused_mut))]
fn build(mut self) -> LoggingCtx {
// Show full backtraces, if we run into panics.
std::env::set_var("RUST_BACKTRACE", "1");
// Log panics.
log_panics::init();
let env_filter = build_tracing_filter(&self);
let logging_ctx;
#[cfg(feature = "sentry")]
{
// Prepare the Sentry layer, if a DSN is provided.
let (sentry_layer, sentry_logging_ctx) =
if let Some(sentry_dsn) = self.sentry_dsn.take() {
// Initialize the Sentry client with the given options.
let sentry_guard = sentry::init((
sentry_dsn,
sentry::ClientOptions {
traces_sample_rate: 0.0,
attach_stacktrace: true,
release: Some(env!("VERGEN_GIT_SHA").into()),
..sentry::ClientOptions::default()
},
));
let sentry_enabled = Arc::new(AtomicBool::new(true));
// Add a Sentry layer to the tracing subscriber.
//
// Pass custom event and span filters, which will ignore anything, if the Sentry
// support has been globally disabled, or if the statement doesn't include a
// `sentry` field set to `true`.
let sentry_layer = sentry_tracing::layer()
.event_filter({
let enabled = sentry_enabled.clone();
move |metadata| {
if enabled.load(std::sync::atomic::Ordering::SeqCst)
&& metadata.fields().field("sentry").is_some()
{
sentry_tracing::default_event_filter(metadata)
} else {
// Ignore the event.
sentry_tracing::EventFilter::Ignore
}
}
})
.span_filter({
let enabled = sentry_enabled.clone();
move |metadata| {
if enabled.load(std::sync::atomic::Ordering::SeqCst) {
sentry_tracing::default_span_filter(metadata)
} else {
// Ignore, if sentry is globally disabled.
false
}
}
});
(
Some(sentry_layer),
Some(SentryLoggingCtx { _guard: sentry_guard, enabled: sentry_enabled }),
)
} else {
(None, None)
};
let (text_layers, reload_handle) = crate::platform::text_layers(self);
tracing_subscriber::registry()
.with(tracing_subscriber::EnvFilter::new(&env_filter))
.with(text_layers)
.with(sentry_layer)
.init();
logging_ctx = LoggingCtx { reload_handle, sentry: sentry_logging_ctx };
}
#[cfg(not(feature = "sentry"))]
{
let (text_layers, reload_handle) = crate::platform::text_layers(self);
tracing_subscriber::registry()
.with(tracing_subscriber::EnvFilter::new(&env_filter))
.with(text_layers)
.init();
logging_ctx = LoggingCtx { reload_handle };
}
// Log the log levels 🧠.
tracing::info!(env_filter, "Logging has been set up");
logging_ctx
}
}
fn build_tracing_filter(config: &TracingConfiguration) -> String {
// We are intentionally not setting a global log level because we don't want to
// risk third party crates logging sensitive information.
// As such we need to make sure that panics will be properly logged.
// On 2025-01-08, `log_panics` uses the `panic` target, at the error log level.
let mut filters = vec!["panic=error".to_owned()];
let global_level = config.log_level;
DEFAULT_TARGET_LOG_LEVELS.iter().for_each(|(target, default_level)| {
let level = if IMMUTABLE_LOG_TARGETS.contains(target) {
// If the target is immutable, keep the log level.
*default_level
} else if config.trace_log_packs.iter().any(|pack| pack.targets().contains(target)) {
// If a log pack includes that target, set the associated log level to TRACE.
LogLevel::Trace
} else if *default_level > global_level {
// If the default level is more verbose than the global level, keep the default.
*default_level
} else {
// Otherwise, use the global level.
global_level
};
filters.push(format!("{}={}", target.as_str(), level.as_str()));
});
// Finally append the extra targets requested by the client.
for target in &config.extra_targets {
filters.push(format!("{}={}", target, config.log_level.as_str()));
}
filters.join(",")
}
/// Sets up logs and the tokio runtime for the current application.
///
/// If `use_lightweight_tokio_runtime` is set to true, this will set up a
/// lightweight tokio runtime, for processes that have memory limitations (like
/// the NSE process on iOS). Otherwise, this can remain false, in which case a
/// multithreaded tokio runtime will be set up.
#[matrix_sdk_ffi_macros::export]
pub fn setup_tracing(config: TracingConfiguration) {
log_panics();
pub fn init_platform(
config: TracingConfiguration,
use_lightweight_tokio_runtime: bool,
) -> Result<(), ClientError> {
#[cfg(all(feature = "js", target_family = "wasm"))]
{
console_error_panic_hook::set_once();
}
#[cfg(not(target_family = "wasm"))]
{
LOGGING.set(config.build()).map_err(|_| ClientError::Generic {
msg: "logger already initialized".to_owned(),
details: None,
})?;
tracing_subscriber::registry()
.with(EnvFilter::new(&config.filter))
.with(text_layers(config))
.init();
if use_lightweight_tokio_runtime {
setup_lightweight_tokio_runtime();
} else {
setup_multithreaded_tokio_runtime();
}
}
Ok(())
}
/// Set the global enablement level for the Sentry layer (after the logs have
/// been set up).
#[matrix_sdk_ffi_macros::export]
#[cfg(feature = "sentry")]
pub fn enable_sentry_logging(enabled: bool) {
if let Some(ctx) = LOGGING.get() {
if let Some(sentry_ctx) = &ctx.sentry {
sentry_ctx.enabled.store(enabled, std::sync::atomic::Ordering::SeqCst);
} else {
warn!("Sentry logging is not enabled");
}
} else {
// Can't use log statements here, since logging hasn't been enabled yet 🧠
eprintln!("Logging hasn't been enabled yet");
};
}
/// Updates the tracing subscriber with a new file writer based on the provided
/// configuration.
///
/// This method will throw if `init_platform` hasn't been called, or if it was
/// called with `write_to_files` set to `None`.
#[matrix_sdk_ffi_macros::export]
pub fn reload_tracing_file_writer(
configuration: TracingFileConfiguration,
) -> Result<(), ClientError> {
let Some(logging_context) = LOGGING.get() else {
return Err(ClientError::Generic {
msg: "Logging hasn't been initialized yet".to_owned(),
details: None,
});
};
let Some(reload_handle) = logging_context.reload_handle.as_ref() else {
return Err(ClientError::Generic {
msg: "Logging wasn't initialized with a file config".to_owned(),
details: None,
});
};
let layer = make_file_layer(configuration);
reload_handle.reload(layer).map_err(|error| ClientError::Generic {
msg: format!("Failed to reload file config: {error}"),
details: None,
})
}
#[cfg(not(target_family = "wasm"))]
fn setup_multithreaded_tokio_runtime() {
async_compat::set_runtime_builder(Box::new(|| {
eprintln!("spawning a multithreaded tokio runtime");
let mut builder = tokio::runtime::Builder::new_multi_thread();
builder.enable_all();
builder
}));
}
#[cfg(not(target_family = "wasm"))]
fn setup_lightweight_tokio_runtime() {
async_compat::set_runtime_builder(Box::new(|| {
eprintln!("spawning a lightweight tokio runtime");
// Get the number of available cores through the system, if possible.
let num_available_cores =
std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1);
// The number of worker threads will be either that or 4, whichever is smaller.
let num_worker_threads = num_available_cores.min(4);
// Chosen by a fair dice roll.
let num_blocking_threads = 2;
// 1 MiB of memory per worker thread. Should be enough for everyone™.
let max_memory_bytes = 1024 * 1024;
let mut builder = tokio::runtime::Builder::new_multi_thread();
builder
.enable_all()
.worker_threads(num_worker_threads)
.thread_stack_size(max_memory_bytes)
.max_blocking_threads(num_blocking_threads);
builder
}));
}
#[cfg(test)]
mod tests {
use similar_asserts::assert_eq;
use super::build_tracing_filter;
use crate::platform::TraceLogPacks;
#[test]
fn test_default_tracing_filter() {
let config = super::TracingConfiguration {
log_level: super::LogLevel::Error,
trace_log_packs: Vec::new(),
extra_targets: vec!["super_duper_app".to_owned()],
write_to_stdout_or_system: true,
write_to_files: None,
#[cfg(feature = "sentry")]
sentry_dsn: None,
};
let filter = build_tracing_filter(&config);
assert_eq!(
filter,
r#"panic=error,
hyper=warn,
matrix_sdk_ffi=info,
matrix_sdk=info,
matrix_sdk::client=trace,
matrix_sdk_crypto=debug,
matrix_sdk_crypto::olm::account=trace,
matrix_sdk::oidc=trace,
matrix_sdk::http_client=debug,
matrix_sdk::sliding_sync=info,
matrix_sdk_base::sliding_sync=info,
matrix_sdk_ui::timeline=info,
matrix_sdk::send_queue=info,
matrix_sdk::event_cache=info,
matrix_sdk_base::event_cache=info,
matrix_sdk_sqlite::event_cache_store=info,
matrix_sdk_common::store_locks=warn,
matrix_sdk_base::store::ambiguity_map=warn,
matrix_sdk_ui::notification_client=info,
matrix_sdk_base::response_processors=debug,
super_duper_app=error"#
.split('\n')
.map(|s| s.trim())
.collect::<Vec<_>>()
.join("")
);
}
#[test]
fn test_trace_tracing_filter() {
let config = super::TracingConfiguration {
log_level: super::LogLevel::Trace,
trace_log_packs: Vec::new(),
extra_targets: vec!["super_duper_app".to_owned(), "some_other_span".to_owned()],
write_to_stdout_or_system: true,
write_to_files: None,
#[cfg(feature = "sentry")]
sentry_dsn: None,
};
let filter = build_tracing_filter(&config);
assert_eq!(
filter,
r#"panic=error,
hyper=warn,
matrix_sdk_ffi=info,
matrix_sdk=info,
matrix_sdk::client=trace,
matrix_sdk_crypto=trace,
matrix_sdk_crypto::olm::account=trace,
matrix_sdk::oidc=trace,
matrix_sdk::http_client=trace,
matrix_sdk::sliding_sync=trace,
matrix_sdk_base::sliding_sync=trace,
matrix_sdk_ui::timeline=trace,
matrix_sdk::send_queue=trace,
matrix_sdk::event_cache=trace,
matrix_sdk_base::event_cache=trace,
matrix_sdk_sqlite::event_cache_store=trace,
matrix_sdk_common::store_locks=warn,
matrix_sdk_base::store::ambiguity_map=warn,
matrix_sdk_ui::notification_client=trace,
matrix_sdk_base::response_processors=trace,
super_duper_app=trace,
some_other_span=trace"#
.split('\n')
.map(|s| s.trim())
.collect::<Vec<_>>()
.join("")
);
}
#[test]
fn test_trace_log_packs() {
let config = super::TracingConfiguration {
log_level: super::LogLevel::Info,
trace_log_packs: vec![TraceLogPacks::EventCache, TraceLogPacks::SendQueue],
extra_targets: vec!["super_duper_app".to_owned()],
write_to_stdout_or_system: true,
write_to_files: None,
#[cfg(feature = "sentry")]
sentry_dsn: None,
};
let filter = build_tracing_filter(&config);
assert_eq!(
filter,
r#"panic=error,
hyper=warn,
matrix_sdk_ffi=info,
matrix_sdk=info,
matrix_sdk::client=trace,
matrix_sdk_crypto=debug,
matrix_sdk_crypto::olm::account=trace,
matrix_sdk::oidc=trace,
matrix_sdk::http_client=debug,
matrix_sdk::sliding_sync=info,
matrix_sdk_base::sliding_sync=info,
matrix_sdk_ui::timeline=info,
matrix_sdk::send_queue=trace,
matrix_sdk::event_cache=trace,
matrix_sdk_base::event_cache=trace,
matrix_sdk_sqlite::event_cache_store=trace,
matrix_sdk_common::store_locks=warn,
matrix_sdk_base::store::ambiguity_map=warn,
matrix_sdk_ui::notification_client=info,
matrix_sdk_base::response_processors=debug,
super_duper_app=info"#
.split('\n')
.map(|s| s.trim())
.collect::<Vec<_>>()
.join("")
);
}
}
+166
View File
@@ -0,0 +1,166 @@
use std::sync::Arc;
use matrix_sdk::{
authentication::oauth::qrcode::{self, DeviceCodeErrorResponseType, LoginFailureReason},
crypto::types::qr_login::{LoginQrCodeDecodeError, QrCodeModeData},
};
use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
use tracing::error;
/// Data for the QR code login mechanism.
///
/// The [`QrCodeData`] can be serialized and encoded as a QR code or it can be
/// decoded from a QR code.
#[derive(Debug, uniffi::Object)]
pub struct QrCodeData {
pub(crate) inner: qrcode::QrCodeData,
}
#[matrix_sdk_ffi_macros::export]
impl QrCodeData {
/// Attempt to decode a slice of bytes into a [`QrCodeData`] object.
///
/// The slice of bytes would generally be returned by a QR code decoder.
#[uniffi::constructor]
pub fn from_bytes(bytes: Vec<u8>) -> Result<Arc<Self>, QrCodeDecodeError> {
Ok(Self { inner: qrcode::QrCodeData::from_bytes(&bytes)? }.into())
}
/// The server name contained within the scanned QR code data.
///
/// Note: This value is only present when scanning a QR code the belongs to
/// a logged in client. The mode where the new client shows the QR code
/// will return `None`.
pub fn server_name(&self) -> Option<String> {
match &self.inner.mode_data {
QrCodeModeData::Reciprocate { server_name } => Some(server_name.to_owned()),
QrCodeModeData::Login => None,
}
}
}
/// Error type for the decoding of the [`QrCodeData`].
#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi(flat_error)]
pub enum QrCodeDecodeError {
#[error("Error decoding QR code: {error:?}")]
Crypto {
#[from]
error: LoginQrCodeDecodeError,
},
}
#[derive(Debug, thiserror::Error, uniffi::Error)]
pub enum HumanQrLoginError {
#[error("Linking with this device is not supported.")]
LinkingNotSupported,
#[error("The sign in was cancelled.")]
Cancelled,
#[error("The sign in was not completed in the required time.")]
Expired,
#[error("A secure connection could not have been established between the two devices.")]
ConnectionInsecure,
#[error("The sign in was declined.")]
Declined,
#[error("An unknown error has happened.")]
Unknown,
#[error("The homeserver doesn't provide sliding sync in its configuration.")]
SlidingSyncNotAvailable,
#[error("Unable to use OIDC as the supplied client metadata is invalid.")]
OidcMetadataInvalid,
#[error("The other device is not signed in and as such can't sign in other devices.")]
OtherDeviceNotSignedIn,
}
impl From<qrcode::QRCodeLoginError> for HumanQrLoginError {
fn from(value: qrcode::QRCodeLoginError) -> Self {
use qrcode::{QRCodeLoginError, SecureChannelError};
match value {
QRCodeLoginError::LoginFailure { reason, .. } => match reason {
LoginFailureReason::UnsupportedProtocol => HumanQrLoginError::LinkingNotSupported,
LoginFailureReason::AuthorizationExpired => HumanQrLoginError::Expired,
LoginFailureReason::UserCancelled => HumanQrLoginError::Cancelled,
_ => HumanQrLoginError::Unknown,
},
QRCodeLoginError::OAuth(e) => {
if let Some(e) = e.as_request_token_error() {
match e {
DeviceCodeErrorResponseType::AccessDenied => HumanQrLoginError::Declined,
DeviceCodeErrorResponseType::ExpiredToken => HumanQrLoginError::Expired,
_ => HumanQrLoginError::Unknown,
}
} else {
HumanQrLoginError::Unknown
}
}
QRCodeLoginError::SecureChannel(e) => match e {
SecureChannelError::Utf8(_)
| SecureChannelError::MessageDecode(_)
| SecureChannelError::Json(_)
| SecureChannelError::RendezvousChannel(_) => HumanQrLoginError::Unknown,
SecureChannelError::SecureChannelMessage { .. }
| SecureChannelError::Ecies(_)
| SecureChannelError::InvalidCheckCode => HumanQrLoginError::ConnectionInsecure,
SecureChannelError::InvalidIntent => HumanQrLoginError::OtherDeviceNotSignedIn,
},
QRCodeLoginError::UnexpectedMessage { .. }
| QRCodeLoginError::CrossProcessRefreshLock(_)
| QRCodeLoginError::DeviceKeyUpload(_)
| QRCodeLoginError::SessionTokens(_)
| QRCodeLoginError::UserIdDiscovery(_)
| QRCodeLoginError::SecretImport(_) => HumanQrLoginError::Unknown,
}
}
}
/// Enum describing the progress of the QR-code login.
#[derive(Debug, Default, Clone, uniffi::Enum)]
pub enum QrLoginProgress {
/// The login process is starting.
#[default]
Starting,
/// We established a secure channel with the other device.
EstablishingSecureChannel {
/// The check code that the device should display so the other device
/// can confirm that the channel is secure as well.
check_code: u8,
/// The string representation of the check code, will be guaranteed to
/// be 2 characters long, preserving the leading zero if the
/// first digit is a zero.
check_code_string: String,
},
/// We are waiting for the login and for the OAuth 2.0 authorization server
/// to give us an access token.
WaitingForToken { user_code: String },
/// The login has successfully finished.
Done,
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait QrLoginProgressListener: SyncOutsideWasm + SendOutsideWasm {
fn on_update(&self, state: QrLoginProgress);
}
impl From<qrcode::LoginProgress> for QrLoginProgress {
fn from(value: qrcode::LoginProgress) -> Self {
use qrcode::LoginProgress;
match value {
LoginProgress::Starting => Self::Starting,
LoginProgress::EstablishingSecureChannel { check_code } => {
let check_code = check_code.to_digit();
Self::EstablishingSecureChannel {
check_code,
check_code_string: format!("{check_code:02}"),
}
}
LoginProgress::WaitingForToken { user_code } => Self::WaitingForToken { user_code },
LoginProgress::Done => Self::Done,
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,233 @@
use std::collections::HashMap;
use anyhow::Result;
use ruma::{
events::{room::power_levels::RoomPowerLevels as RumaPowerLevels, TimelineEventType},
OwnedUserId, UserId,
};
use crate::{
error::ClientError,
event::{MessageLikeEventType, StateEventType},
};
#[derive(uniffi::Object)]
pub struct RoomPowerLevels {
inner: RumaPowerLevels,
own_user_id: OwnedUserId,
}
impl RoomPowerLevels {
pub fn new(value: RumaPowerLevels, own_user_id: OwnedUserId) -> Self {
Self { inner: value, own_user_id }
}
}
#[matrix_sdk_ffi_macros::export]
impl RoomPowerLevels {
fn values(&self) -> RoomPowerLevelsValues {
self.inner.clone().into()
}
/// Gets a map with the `UserId` of users with power levels other than `0`
/// and their power level.
pub fn user_power_levels(&self) -> HashMap<String, i64> {
let mut user_power_levels = HashMap::<String, i64>::new();
for (id, level) in self.inner.users.iter() {
user_power_levels.insert(id.to_string(), (*level).into());
}
user_power_levels
}
/// Returns true if the current user is able to ban in the room.
pub fn can_own_user_ban(&self) -> bool {
self.inner.user_can_ban(&self.own_user_id)
}
/// Returns true if the user with the given user_id is able to ban in the
/// room.
///
/// The call may fail if there is an error in getting the power levels.
pub fn can_user_ban(&self, user_id: String) -> Result<bool, ClientError> {
let user_id = UserId::parse(&user_id)?;
Ok(self.inner.user_can_ban(&user_id))
}
/// Returns true if the current user is able to redact their own messages in
/// the room.
pub fn can_own_user_redact_own(&self) -> bool {
self.inner.user_can_redact_own_event(&self.own_user_id)
}
/// Returns true if the user with the given user_id is able to redact
/// their own messages in the room.
///
/// The call may fail if there is an error in getting the power levels.
pub fn can_user_redact_own(&self, user_id: String) -> Result<bool, ClientError> {
let user_id = UserId::parse(&user_id)?;
Ok(self.inner.user_can_redact_own_event(&user_id))
}
/// Returns true if the current user user is able to redact messages of
/// other users in the room.
pub fn can_own_user_redact_other(&self) -> bool {
self.inner.user_can_redact_event_of_other(&self.own_user_id)
}
/// Returns true if the user with the given user_id is able to redact
/// messages of other users in the room.
///
/// The call may fail if there is an error in getting the power levels.
pub fn can_user_redact_other(&self, user_id: String) -> Result<bool, ClientError> {
let user_id = UserId::parse(&user_id)?;
Ok(self.inner.user_can_redact_event_of_other(&user_id))
}
/// Returns true if the current user is able to invite in the room.
pub fn can_own_user_invite(&self) -> bool {
self.inner.user_can_invite(&self.own_user_id)
}
/// Returns true if the user with the given user_id is able to invite in the
/// room.
///
/// The call may fail if there is an error in getting the power levels.
pub fn can_user_invite(&self, user_id: String) -> Result<bool, ClientError> {
let user_id = UserId::parse(&user_id)?;
Ok(self.inner.user_can_invite(&user_id))
}
/// Returns true if the current user is able to kick in the room.
pub fn can_own_user_kick(&self) -> bool {
self.inner.user_can_kick(&self.own_user_id)
}
/// Returns true if the user with the given user_id is able to kick in the
/// room.
///
/// The call may fail if there is an error in getting the power levels.
pub fn can_user_kick(&self, user_id: String) -> Result<bool, ClientError> {
let user_id = UserId::parse(&user_id)?;
Ok(self.inner.user_can_kick(&user_id))
}
/// Returns true if the current user is able to send a specific state event
/// type in the room.
pub fn can_own_user_send_state(&self, state_event: StateEventType) -> bool {
self.inner.user_can_send_state(&self.own_user_id, state_event.into())
}
/// Returns true if the user with the given user_id is able to send a
/// specific state event type in the room.
///
/// The call may fail if there is an error in getting the power levels.
pub fn can_user_send_state(
&self,
user_id: String,
state_event: StateEventType,
) -> Result<bool, ClientError> {
let user_id = UserId::parse(&user_id)?;
Ok(self.inner.user_can_send_state(&user_id, state_event.into()))
}
/// Returns true if the current user is able to send a specific message type
/// in the room.
pub fn can_own_user_send_message(&self, message: MessageLikeEventType) -> bool {
self.inner.user_can_send_message(&self.own_user_id, message.into())
}
/// Returns true if the user with the given user_id is able to send a
/// specific message type in the room.
///
/// The call may fail if there is an error in getting the power levels.
pub fn can_user_send_message(
&self,
user_id: String,
message: MessageLikeEventType,
) -> Result<bool, ClientError> {
let user_id = UserId::parse(&user_id)?;
Ok(self.inner.user_can_send_message(&user_id, message.into()))
}
/// Returns true if the current user is able to pin or unpin events in the
/// room.
pub fn can_own_user_pin_unpin(&self) -> bool {
self.inner.user_can_send_state(&self.own_user_id, StateEventType::RoomPinnedEvents.into())
}
/// Returns true if the user with the given user_id is able to pin or unpin
/// events in the room.
///
/// The call may fail if there is an error in getting the power levels.
pub fn can_user_pin_unpin(&self, user_id: String) -> Result<bool, ClientError> {
let user_id = UserId::parse(&user_id)?;
Ok(self.inner.user_can_send_state(&user_id, StateEventType::RoomPinnedEvents.into()))
}
/// Returns true if the current user is able to trigger a notification in
/// the room.
pub fn can_own_user_trigger_room_notification(&self) -> bool {
self.inner.user_can_trigger_room_notification(&self.own_user_id)
}
/// Returns true if the user with the given user_id is able to trigger a
/// notification in the room.
///
/// The call may fail if there is an error in getting the power levels.
pub fn can_user_trigger_room_notification(&self, user_id: String) -> Result<bool, ClientError> {
let user_id = UserId::parse(&user_id)?;
Ok(self.inner.user_can_trigger_room_notification(&user_id))
}
}
/// This intermediary struct is used to expose the power levels values through
/// FFI and work around it not exposing public exported object fields.
#[derive(uniffi::Record)]
pub struct RoomPowerLevelsValues {
/// The level required to ban a user.
pub ban: i64,
/// The level required to invite a user.
pub invite: i64,
/// The level required to kick a user.
pub kick: i64,
/// The level required to redact an event.
pub redact: i64,
/// The default level required to send message events.
pub events_default: i64,
/// The default level required to send state events.
pub state_default: i64,
/// The default power level for every user in the room.
pub users_default: i64,
/// The level required to change the room's name.
pub room_name: i64,
/// The level required to change the room's avatar.
pub room_avatar: i64,
/// The level required to change the room's topic.
pub room_topic: i64,
}
impl From<RumaPowerLevels> for RoomPowerLevelsValues {
fn from(value: RumaPowerLevels) -> Self {
fn state_event_level_for(
power_levels: &RumaPowerLevels,
event_type: &TimelineEventType,
) -> i64 {
let default_state: i64 = power_levels.state_default.into();
power_levels.events.get(event_type).map_or(default_state, |&level| level.into())
}
Self {
ban: value.ban.into(),
invite: value.invite.into(),
kick: value.kick.into(),
redact: value.redact.into(),
events_default: value.events_default.into(),
state_default: value.state_default.into(),
users_default: value.users_default.into(),
room_name: state_event_level_for(&value, &TimelineEventType::RoomName),
room_avatar: state_event_level_for(&value, &TimelineEventType::RoomAvatar),
room_topic: state_event_level_for(&value, &TimelineEventType::RoomTopic),
}
}
}
@@ -1,19 +1,23 @@
use std::collections::HashMap;
use std::sync::Arc;
use matrix_sdk::RoomState;
use matrix_sdk::{EncryptionState, RoomState};
use tracing::warn;
use crate::{
client::JoinRule,
error::ClientError,
notification_settings::RoomNotificationMode,
room::{Membership, RoomHero},
room::{
power_levels::RoomPowerLevels, Membership, RoomHero, RoomHistoryVisibility, SuccessorRoom,
},
room_member::RoomMember,
};
#[derive(uniffi::Record)]
pub struct RoomInfo {
id: String,
creator: Option<String>,
encryption_state: EncryptionState,
creators: Option<Vec<String>>,
/// The room's name from the room state event if received from sync, or one
/// that's been computed otherwise.
display_name: Option<String>,
@@ -22,9 +26,14 @@ pub struct RoomInfo {
topic: Option<String>,
avatar_url: Option<String>,
is_direct: bool,
is_public: bool,
/// Whether the room is public or not, based on the join rules.
///
/// Can be `None` if the join rules state event is not available for this
/// room.
is_public: Option<bool>,
is_space: bool,
is_tombstoned: bool,
/// If present, it means the room has been archived/upgraded.
successor_room: Option<SuccessorRoom>,
is_favourite: bool,
canonical_alias: Option<String>,
alternative_aliases: Vec<String>,
@@ -39,7 +48,6 @@ pub struct RoomInfo {
active_members_count: u64,
invited_members_count: u64,
joined_members_count: u64,
user_power_levels: HashMap<String, i64>,
highlight_count: u64,
notification_count: u64,
cached_user_defined_notification_mode: Option<RoomNotificationMode>,
@@ -60,28 +68,48 @@ pub struct RoomInfo {
pinned_event_ids: Vec<String>,
/// The join rule for this room, if known.
join_rule: Option<JoinRule>,
/// The history visibility for this room, if known.
history_visibility: RoomHistoryVisibility,
/// This room's current power levels.
///
/// Can be missing if the room power levels event is missing from the store.
power_levels: Option<Arc<RoomPowerLevels>>,
/// This room's version.
room_version: Option<String>,
/// Whether creators are privileged over every other user (have infinite
/// power level).
privileged_creators_role: bool,
}
impl RoomInfo {
pub(crate) async fn new(room: &matrix_sdk::Room) -> matrix_sdk::Result<Self> {
pub(crate) async fn new(room: &matrix_sdk::Room) -> Result<Self, ClientError> {
let unread_notification_counts = room.unread_notification_counts();
let power_levels_map = room.users_with_power_levels().await;
let mut user_power_levels = HashMap::<String, i64>::new();
for (id, level) in power_levels_map.iter() {
user_power_levels.insert(id.to_string(), *level);
}
let pinned_event_ids =
room.pinned_event_ids().unwrap_or_default().iter().map(|id| id.to_string()).collect();
let join_rule = room.join_rule().try_into();
if let Err(e) = &join_rule {
warn!("Failed to parse join rule: {:?}", e);
}
let join_rule = room
.join_rule()
.map(TryInto::try_into)
.transpose()
.inspect_err(|err| {
warn!("Failed to parse join rule: {err}");
})
.ok()
.flatten();
let power_levels = room
.power_levels()
.await
.ok()
.map(|p| RoomPowerLevels::new(p, room.own_user_id().to_owned()));
Ok(Self {
id: room.room_id().to_string(),
creator: room.creator().as_ref().map(ToString::to_string),
encryption_state: room.encryption_state(),
creators: room
.creators()
.map(|creators| creators.into_iter().map(Into::into).collect()),
display_name: room.cached_display_name().map(|name| name.to_string()),
raw_name: room.name(),
topic: room.topic(),
@@ -89,7 +117,7 @@ impl RoomInfo {
is_direct: room.is_direct().await?,
is_public: room.is_public(),
is_space: room.is_space(),
is_tombstoned: room.is_tombstoned(),
successor_room: room.successor_room().map(Into::into),
is_favourite: room.is_favourite(),
canonical_alias: room.canonical_alias().map(Into::into),
alternative_aliases: room.alt_aliases().into_iter().map(Into::into).collect(),
@@ -110,7 +138,6 @@ impl RoomInfo {
active_members_count: room.active_members_count(),
invited_members_count: room.invited_members_count(),
joined_members_count: room.joined_members_count(),
user_power_levels,
highlight_count: unread_notification_counts.highlight_count,
notification_count: unread_notification_counts.notification_count,
cached_user_defined_notification_mode: room
@@ -127,7 +154,15 @@ impl RoomInfo {
num_unread_notifications: room.num_unread_notifications(),
num_unread_mentions: room.num_unread_mentions(),
pinned_event_ids,
join_rule: join_rule.ok(),
join_rule,
history_visibility: room.history_visibility_or_default().try_into()?,
power_levels: power_levels.map(Arc::new),
room_version: room.version().map(|version| version.to_string()),
privileged_creators_role: room
.version()
.and_then(|version| version.rules())
.map(|rules| rules.authorization.explicitly_privilege_room_creators)
.unwrap_or_default(),
})
}
}
@@ -18,25 +18,31 @@ use std::{fmt::Debug, sync::Arc};
use eyeball_im::VectorDiff;
use futures_util::StreamExt;
use matrix_sdk::room_directory_search::RoomDirectorySearch as SdkRoomDirectorySearch;
use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
use ruma::ServerName;
use tokio::sync::RwLock;
use super::RUNTIME;
use crate::{error::ClientError, task_handle::TaskHandle};
use crate::{error::ClientError, runtime::get_runtime_handle, task_handle::TaskHandle};
#[derive(uniffi::Enum)]
pub enum PublicRoomJoinRule {
Public,
Knock,
Restricted,
KnockRestricted,
Invite,
}
impl TryFrom<ruma::directory::PublicRoomJoinRule> for PublicRoomJoinRule {
impl TryFrom<ruma::room::JoinRuleKind> for PublicRoomJoinRule {
type Error = String;
fn try_from(value: ruma::directory::PublicRoomJoinRule) -> Result<Self, Self::Error> {
fn try_from(value: ruma::room::JoinRuleKind) -> Result<Self, Self::Error> {
match value {
ruma::directory::PublicRoomJoinRule::Public => Ok(Self::Public),
ruma::directory::PublicRoomJoinRule::Knock => Ok(Self::Knock),
ruma::room::JoinRuleKind::Public => Ok(Self::Public),
ruma::room::JoinRuleKind::Knock => Ok(Self::Knock),
ruma::room::JoinRuleKind::Restricted => Ok(Self::Restricted),
ruma::room::JoinRuleKind::KnockRestricted => Ok(Self::KnockRestricted),
ruma::room::JoinRuleKind::Invite => Ok(Self::Invite),
rule => Err(format!("unsupported join rule: {rule:?}")),
}
}
@@ -137,11 +143,11 @@ impl RoomDirectorySearch {
) -> Arc<TaskHandle> {
let (initial_values, mut stream) = self.inner.read().await.results();
Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
listener.on_update(vec![RoomDirectorySearchEntryUpdate::Reset {
values: initial_values.into_iter().map(Into::into).collect(),
}]);
listener.on_update(vec![RoomDirectorySearchEntryUpdate::Reset {
values: initial_values.into_iter().map(Into::into).collect(),
}]);
Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
while let Some(diffs) = stream.next().await {
listener.on_update(diffs.into_iter().map(|diff| diff.into()).collect());
}
@@ -149,11 +155,6 @@ impl RoomDirectorySearch {
}
}
#[derive(uniffi::Record)]
pub struct RoomDirectorySearchEntriesResult {
pub entries_stream: Arc<TaskHandle>,
}
#[derive(uniffi::Enum)]
pub enum RoomDirectorySearchEntryUpdate {
Append { values: Vec<RoomDescription> },
@@ -198,6 +199,6 @@ impl From<VectorDiff<matrix_sdk::room_directory_search::RoomDescription>>
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait RoomDirectorySearchEntriesListener: Send + Sync + Debug {
pub trait RoomDirectorySearchEntriesListener: SendOutsideWasm + SyncOutsideWasm + Debug {
fn on_update(&self, room_entries_update: Vec<RoomDirectorySearchEntryUpdate>);
}
+60 -260
View File
@@ -3,33 +3,30 @@
use std::{fmt::Debug, mem::MaybeUninit, ptr::addr_of_mut, sync::Arc, time::Duration};
use eyeball_im::VectorDiff;
use futures_util::{pin_mut, StreamExt, TryFutureExt};
use matrix_sdk::ruma::{
api::client::sync::sync_events::UnreadNotificationsCount as RumaUnreadNotificationsCount,
RoomId,
use futures_util::{pin_mut, StreamExt};
use matrix_sdk::{
ruma::{
api::client::sync::sync_events::UnreadNotificationsCount as RumaUnreadNotificationsCount,
RoomId,
},
Room as SdkRoom,
};
use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
use matrix_sdk_ui::{
room_list_service::filters::{
new_filter_all, new_filter_any, new_filter_category, new_filter_favourite,
new_filter_fuzzy_match_room_name, new_filter_invite, new_filter_joined,
new_filter_non_left, new_filter_none, new_filter_normalized_match_room_name,
new_filter_unread, BoxedFilterFn, RoomCategory,
new_filter_all, new_filter_any, new_filter_category, new_filter_deduplicate_versions,
new_filter_favourite, new_filter_fuzzy_match_room_name, new_filter_invite,
new_filter_joined, new_filter_low_priority, new_filter_non_left, new_filter_none,
new_filter_normalized_match_room_name, new_filter_not, new_filter_space, new_filter_unread,
BoxedFilterFn, RoomCategory,
},
timeline::default_event_filter,
unable_to_decrypt_hook::UtdHookManager,
};
use ruma::{OwnedRoomOrAliasId, OwnedServerName, ServerName};
use tokio::sync::RwLock;
use crate::{
error::ClientError,
room::{Membership, Room},
room_info::RoomInfo,
room_preview::RoomPreview,
timeline::{EventTimelineItem, Timeline},
timeline_event_filter::TimelineEventTypeFilter,
utils::AsyncRuntimeDropped,
TaskHandle, RUNTIME,
runtime::get_runtime_handle,
TaskHandle,
};
#[derive(Debug, thiserror::Error, uniffi::Error)]
@@ -44,15 +41,12 @@ pub enum RoomListError {
RoomNotFound { room_name: String },
#[error("invalid room ID: {error}")]
InvalidRoomId { error: String },
#[error("A timeline instance already exists for room {room_name}")]
TimelineAlreadyExists { room_name: String },
#[error("A timeline instance hasn't been initialized for room {room_name}")]
TimelineNotInitialized { room_name: String },
#[error("Timeline couldn't be initialized: {error}")]
InitializingTimeline { error: String },
#[error("Event cache ran into an error: {error}")]
EventCache { error: String },
#[error("The requested room doesn't match the membership requirements {expected:?}, observed {actual:?}")]
#[error(
"The requested room doesn't match the membership requirements {expected:?}, \
observed {actual:?}"
)]
IncorrectRoomMembership { expected: Vec<Membership>, actual: Membership },
}
@@ -64,12 +58,6 @@ impl From<matrix_sdk_ui::room_list_service::Error> for RoomListError {
SlidingSync(error) => Self::SlidingSync { error: error.to_string() },
UnknownList(list_name) => Self::UnknownList { list_name },
RoomNotFound(room_id) => Self::RoomNotFound { room_name: room_id.to_string() },
TimelineAlreadyExists(room_id) => {
Self::TimelineAlreadyExists { room_name: room_id.to_string() }
}
InitializingTimeline(source) => {
Self::InitializingTimeline { error: source.to_string() }
}
EventCache(error) => Self::EventCache { error: error.to_string() },
}
}
@@ -92,7 +80,7 @@ impl RoomListService {
fn state(&self, listener: Box<dyn RoomListServiceStateListener>) -> Arc<TaskHandle> {
let state_stream = self.inner.state();
Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
pin_mut!(state_stream);
while let Some(state) = state_stream.next().await {
@@ -101,13 +89,10 @@ impl RoomListService {
})))
}
fn room(&self, room_id: String) -> Result<Arc<RoomListItem>, RoomListError> {
fn room(&self, room_id: String) -> Result<Arc<Room>, RoomListError> {
let room_id = <&RoomId>::try_from(room_id.as_str()).map_err(RoomListError::from)?;
Ok(Arc::new(RoomListItem {
inner: Arc::new(self.inner.room(room_id)?),
utd_hook: self.utd_hook.clone(),
}))
Ok(Arc::new(Room::new(self.inner.room(room_id)?, self.utd_hook.clone())))
}
async fn all_rooms(self: Arc<Self>) -> Result<Arc<RoomList>, RoomListError> {
@@ -128,7 +113,7 @@ impl RoomListService {
Duration::from_millis(delay_before_hiding_in_ms.into()),
);
Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
pin_mut!(sync_indicator_stream);
while let Some(sync_indicator) = sync_indicator_stream.next().await {
@@ -137,7 +122,7 @@ impl RoomListService {
})))
}
fn subscribe_to_rooms(&self, room_ids: Vec<String>) -> Result<(), RoomListError> {
async fn subscribe_to_rooms(&self, room_ids: Vec<String>) -> Result<(), RoomListError> {
let room_ids = room_ids
.into_iter()
.map(|room_id| {
@@ -145,7 +130,9 @@ impl RoomListService {
})
.collect::<Result<Vec<_>, _>>()?;
self.inner.subscribe_to_rooms(&room_ids.iter().map(AsRef::as_ref).collect::<Vec<_>>());
self.inner
.subscribe_to_rooms(&room_ids.iter().map(AsRef::as_ref).collect::<Vec<_>>())
.await;
Ok(())
}
@@ -167,7 +154,7 @@ impl RoomList {
Ok(RoomListLoadingStateResult {
state: loading_state.get().into(),
state_stream: Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
state_stream: Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
pin_mut!(loading_state);
while let Some(loading_state) = loading_state.next().await {
@@ -182,8 +169,7 @@ impl RoomList {
page_size: u32,
listener: Box<dyn RoomListEntriesListener>,
) -> Arc<RoomListEntriesWithDynamicAdaptersResult> {
let this = self.clone();
let utd_hook = self.room_list_service.utd_hook.clone();
let this = self;
// The following code deserves a bit of explanation.
// `matrix_sdk_ui::room_list_service::RoomList::entries_with_dynamic_adapters`
@@ -237,14 +223,15 @@ impl RoomList {
let dynamic_entries_controller =
Arc::new(RoomListDynamicEntriesController::new(dynamic_entries_controller));
let entries_stream = Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
let utd_hook = this.room_list_service.utd_hook.clone();
let entries_stream = Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
pin_mut!(entries_stream);
while let Some(diffs) = entries_stream.next().await {
listener.on_update(
diffs
.into_iter()
.map(|diff| RoomListEntriesUpdate::from(diff, utd_hook.clone()))
.map(|room| RoomListEntriesUpdate::from(utd_hook.clone(), room))
.collect(),
);
}
@@ -271,7 +258,7 @@ impl RoomList {
Arc::new(unsafe { result.assume_init() })
}
fn room(&self, room_id: String) -> Result<Arc<RoomListItem>, RoomListError> {
fn room(&self, room_id: String) -> Result<Arc<Room>, RoomListError> {
self.room_list_service.room(room_id)
}
}
@@ -362,63 +349,60 @@ impl From<matrix_sdk_ui::room_list_service::RoomListLoadingState> for RoomListLo
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait RoomListServiceStateListener: Send + Sync + Debug {
pub trait RoomListServiceStateListener: SendOutsideWasm + SyncOutsideWasm + Debug {
fn on_update(&self, state: RoomListServiceState);
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait RoomListLoadingStateListener: Send + Sync + Debug {
pub trait RoomListLoadingStateListener: SendOutsideWasm + SyncOutsideWasm + Debug {
fn on_update(&self, state: RoomListLoadingState);
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait RoomListServiceSyncIndicatorListener: Send + Sync + Debug {
pub trait RoomListServiceSyncIndicatorListener: SendOutsideWasm + SyncOutsideWasm + Debug {
fn on_update(&self, sync_indicator: RoomListServiceSyncIndicator);
}
#[derive(uniffi::Enum)]
pub enum RoomListEntriesUpdate {
Append { values: Vec<Arc<RoomListItem>> },
Append { values: Vec<Arc<Room>> },
Clear,
PushFront { value: Arc<RoomListItem> },
PushBack { value: Arc<RoomListItem> },
PushFront { value: Arc<Room> },
PushBack { value: Arc<Room> },
PopFront,
PopBack,
Insert { index: u32, value: Arc<RoomListItem> },
Set { index: u32, value: Arc<RoomListItem> },
Insert { index: u32, value: Arc<Room> },
Set { index: u32, value: Arc<Room> },
Remove { index: u32 },
Truncate { length: u32 },
Reset { values: Vec<Arc<RoomListItem>> },
Reset { values: Vec<Arc<Room>> },
}
impl RoomListEntriesUpdate {
fn from(
vector_diff: VectorDiff<matrix_sdk_ui::room_list_service::Room>,
utd_hook: Option<Arc<UtdHookManager>>,
) -> Self {
fn from(utd_hook: Option<Arc<UtdHookManager>>, vector_diff: VectorDiff<SdkRoom>) -> Self {
match vector_diff {
VectorDiff::Append { values } => Self::Append {
values: values
.into_iter()
.map(|value| Arc::new(RoomListItem::from(value, utd_hook.clone())))
.map(|value| Arc::new(Room::new(value, utd_hook.clone())))
.collect(),
},
VectorDiff::Clear => Self::Clear,
VectorDiff::PushFront { value } => {
Self::PushFront { value: Arc::new(RoomListItem::from(value, utd_hook)) }
Self::PushFront { value: Arc::new(Room::new(value, utd_hook)) }
}
VectorDiff::PushBack { value } => {
Self::PushBack { value: Arc::new(RoomListItem::from(value, utd_hook)) }
Self::PushBack { value: Arc::new(Room::new(value, utd_hook)) }
}
VectorDiff::PopFront => Self::PopFront,
VectorDiff::PopBack => Self::PopBack,
VectorDiff::Insert { index, value } => Self::Insert {
index: u32::try_from(index).unwrap(),
value: Arc::new(RoomListItem::from(value, utd_hook)),
value: Arc::new(Room::new(value, utd_hook)),
},
VectorDiff::Set { index, value } => Self::Set {
index: u32::try_from(index).unwrap(),
value: Arc::new(RoomListItem::from(value, utd_hook)),
value: Arc::new(Room::new(value, utd_hook)),
},
VectorDiff::Remove { index } => Self::Remove { index: u32::try_from(index).unwrap() },
VectorDiff::Truncate { length } => {
@@ -427,7 +411,7 @@ impl RoomListEntriesUpdate {
VectorDiff::Reset { values } => Self::Reset {
values: values
.into_iter()
.map(|value| Arc::new(RoomListItem::from(value, utd_hook.clone())))
.map(|value| Arc::new(Room::new(value, utd_hook.clone())))
.collect(),
},
}
@@ -435,7 +419,7 @@ impl RoomListEntriesUpdate {
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait RoomListEntriesListener: Send + Sync + Debug {
pub trait RoomListEntriesListener: SendOutsideWasm + SyncOutsideWasm + Debug {
fn on_update(&self, room_entries_update: Vec<RoomListEntriesUpdate>);
}
@@ -471,15 +455,21 @@ impl RoomListDynamicEntriesController {
pub enum RoomListEntriesDynamicFilterKind {
All { filters: Vec<RoomListEntriesDynamicFilterKind> },
Any { filters: Vec<RoomListEntriesDynamicFilterKind> },
NonSpace,
NonLeft,
// Not { filter: RoomListEntriesDynamicFilterKind } - requires recursive enum
// support in uniffi https://github.com/mozilla/uniffi-rs/issues/396
Joined,
Unread,
Favourite,
LowPriority,
NonLowPriority,
Invite,
Category { expect: RoomListFilterCategory },
None,
NormalizedMatchRoomName { pattern: String },
FuzzyMatchRoomName { pattern: String },
DeduplicateVersions,
}
#[derive(uniffi::Enum)]
@@ -509,9 +499,12 @@ impl From<RoomListEntriesDynamicFilterKind> for BoxedFilterFn {
filters.into_iter().map(|filter| BoxedFilterFn::from(filter)).collect(),
)),
Kind::NonLeft => Box::new(new_filter_non_left()),
Kind::NonSpace => Box::new(new_filter_not(Box::new(new_filter_space()))),
Kind::Joined => Box::new(new_filter_joined()),
Kind::Unread => Box::new(new_filter_unread()),
Kind::Favourite => Box::new(new_filter_favourite()),
Kind::LowPriority => Box::new(new_filter_low_priority()),
Kind::NonLowPriority => Box::new(new_filter_not(Box::new(new_filter_low_priority()))),
Kind::Invite => Box::new(new_filter_invite()),
Kind::Category { expect } => Box::new(new_filter_category(expect.into())),
Kind::None => Box::new(new_filter_none()),
@@ -521,204 +514,11 @@ impl From<RoomListEntriesDynamicFilterKind> for BoxedFilterFn {
Kind::FuzzyMatchRoomName { pattern } => {
Box::new(new_filter_fuzzy_match_room_name(&pattern))
}
Kind::DeduplicateVersions => Box::new(new_filter_deduplicate_versions()),
}
}
}
#[derive(uniffi::Object)]
pub struct RoomListItem {
inner: Arc<matrix_sdk_ui::room_list_service::Room>,
utd_hook: Option<Arc<UtdHookManager>>,
}
impl RoomListItem {
fn from(
value: matrix_sdk_ui::room_list_service::Room,
utd_hook: Option<Arc<UtdHookManager>>,
) -> Self {
Self { inner: Arc::new(value), utd_hook }
}
}
#[matrix_sdk_ffi_macros::export]
impl RoomListItem {
fn id(&self) -> String {
self.inner.id().to_string()
}
/// Returns the room's name from the state event if available, otherwise
/// compute a room name based on the room's nature (DM or not) and number of
/// members.
fn display_name(&self) -> Option<String> {
self.inner.cached_display_name()
}
fn avatar_url(&self) -> Option<String> {
self.inner.avatar_url().map(|uri| uri.to_string())
}
fn is_direct(&self) -> bool {
RUNTIME.block_on(self.inner.inner_room().is_direct()).unwrap_or(false)
}
fn canonical_alias(&self) -> Option<String> {
self.inner.inner_room().canonical_alias().map(|alias| alias.to_string())
}
async fn room_info(&self) -> Result<RoomInfo, ClientError> {
Ok(RoomInfo::new(self.inner.inner_room()).await?)
}
/// The room's current membership state.
fn membership(&self) -> Membership {
self.inner.inner_room().state().into()
}
/// Builds a `Room` FFI from an invited room without initializing its
/// internal timeline.
///
/// An error will be returned if the room is a state different than invited.
///
/// ⚠️ Holding on to this room instance after it has been joined is not
/// safe. Use `full_room` instead.
#[deprecated(note = "Please use `preview_room` instead.")]
fn invited_room(&self) -> Result<Arc<Room>, RoomListError> {
if !matches!(self.membership(), Membership::Invited) {
return Err(RoomListError::IncorrectRoomMembership {
expected: vec![Membership::Invited],
actual: self.membership(),
});
}
Ok(Arc::new(Room::new(self.inner.inner_room().clone())))
}
/// Builds a `RoomPreview` from a room list item. This is intended for
/// invited or knocked rooms.
///
/// An error will be returned if the room is in a state other than invited
/// or knocked.
async fn preview_room(&self, via: Vec<String>) -> Result<Arc<RoomPreview>, ClientError> {
// Validate parameters first.
let server_names: Vec<OwnedServerName> = via
.into_iter()
.map(|server| ServerName::parse(server).map_err(ClientError::from))
.collect::<Result<_, ClientError>>()?;
// Validate internal room state.
let membership = self.membership();
if !matches!(membership, Membership::Invited | Membership::Knocked) {
return Err(RoomListError::IncorrectRoomMembership {
expected: vec![Membership::Invited, Membership::Knocked],
actual: membership,
}
.into());
}
// Do the thing.
let client = self.inner.client();
let (room_or_alias_id, mut server_names) = if let Some(alias) = self.inner.canonical_alias()
{
let room_or_alias_id: OwnedRoomOrAliasId = alias.into();
(room_or_alias_id, Vec::new())
} else {
let room_or_alias_id: OwnedRoomOrAliasId = self.inner.id().to_owned().into();
(room_or_alias_id, server_names)
};
// If no server names are provided and the room's membership is invited,
// add the server name from the sender's user id as a fallback value
if server_names.is_empty() {
if let Ok(invite_details) = self.inner.invite_details().await {
if let Some(inviter) = invite_details.inviter {
server_names.push(inviter.user_id().server_name().to_owned());
}
}
}
let room_preview = client.get_room_preview(&room_or_alias_id, server_names).await?;
Ok(Arc::new(RoomPreview::new(AsyncRuntimeDropped::new(client), room_preview)))
}
/// Build a full `Room` FFI object, filling its associated timeline.
///
/// An error will be returned if the room is a state different than joined
/// or if its internal timeline hasn't been initialized.
fn full_room(&self) -> Result<Arc<Room>, RoomListError> {
if !matches!(self.membership(), Membership::Joined) {
return Err(RoomListError::IncorrectRoomMembership {
expected: vec![Membership::Joined],
actual: self.membership(),
});
}
if let Some(timeline) = self.inner.timeline() {
Ok(Arc::new(Room::with_timeline(
self.inner.inner_room().clone(),
Arc::new(RwLock::new(Some(Timeline::from_arc(timeline)))),
)))
} else {
Err(RoomListError::TimelineNotInitialized {
room_name: self.inner.inner_room().room_id().to_string(),
})
}
}
/// Checks whether the Room's timeline has been initialized before.
fn is_timeline_initialized(&self) -> bool {
self.inner.is_timeline_initialized()
}
/// Initializes the timeline for this room using the provided parameters.
///
/// * `event_type_filter` - An optional [`TimelineEventTypeFilter`] to be
/// used to filter timeline events besides the default timeline filter. If
/// `None` is passed, only the default timeline filter will be used.
/// * `internal_id_prefix` - An optional String that will be prepended to
/// all the timeline item's internal IDs, making it possible to
/// distinguish different timeline instances from each other.
async fn init_timeline(
&self,
event_type_filter: Option<Arc<TimelineEventTypeFilter>>,
internal_id_prefix: Option<String>,
) -> Result<(), RoomListError> {
let mut timeline_builder = self
.inner
.default_room_timeline_builder()
.await
.map_err(|err| RoomListError::InitializingTimeline { error: err.to_string() })?;
if let Some(event_type_filter) = event_type_filter {
timeline_builder = timeline_builder.event_filter(move |event, room_version_id| {
// Always perform the default filter first
default_event_filter(event, room_version_id) && event_type_filter.filter(event)
});
}
if let Some(internal_id_prefix) = internal_id_prefix {
timeline_builder = timeline_builder.with_internal_id_prefix(internal_id_prefix);
}
if let Some(utd_hook) = self.utd_hook.clone() {
timeline_builder = timeline_builder.with_unable_to_decrypt_hook(utd_hook);
}
self.inner.init_timeline_with_builder(timeline_builder).map_err(RoomListError::from).await
}
/// Checks whether the room is encrypted or not.
///
/// **Note**: this info may not be reliable if you don't set up
/// `m.room.encryption` as required state.
async fn is_encrypted(&self) -> bool {
self.inner.is_encrypted().await.unwrap_or(false)
}
async fn latest_event(&self) -> Option<EventTimelineItem> {
self.inner.latest_event().await.map(Into::into)
}
}
#[derive(uniffi::Object)]
pub struct UnreadNotificationsCount {
highlight_count: u32,
+82 -10
View File
@@ -1,5 +1,5 @@
use matrix_sdk::room::{RoomMember as SdkRoomMember, RoomMemberRole};
use ruma::UserId;
use ruma::{events::room::power_levels::UserPowerLevel, UserId};
use crate::error::{ClientError, NotYetImplemented};
@@ -57,16 +57,25 @@ impl TryFrom<matrix_sdk::ruma::events::room::member::MembershipState> for Member
}
}
/// Get the suggested role for the given power level.
///
/// Returns an error if the value of the power level is out of range for numbers
/// accepted in canonical JSON.
#[matrix_sdk_ffi_macros::export]
pub fn suggested_role_for_power_level(power_level: i64) -> RoomMemberRole {
pub fn suggested_role_for_power_level(
power_level: PowerLevel,
) -> Result<RoomMemberRole, ClientError> {
// It's not possible to expose the constructor on the Enum through Uniffi ☹️
RoomMemberRole::suggested_role_for_power_level(power_level)
Ok(RoomMemberRole::suggested_role_for_power_level(power_level.try_into()?))
}
/// Get the suggested power level for the given role.
///
/// Returns an error if the value of the power level is unsupported.
#[matrix_sdk_ffi_macros::export]
pub fn suggested_power_level_for_role(role: RoomMemberRole) -> i64 {
pub fn suggested_power_level_for_role(role: RoomMemberRole) -> Result<PowerLevel, ClientError> {
// It's not possible to expose methods on an Enum through Uniffi ☹️
role.suggested_power_level()
Ok(role.suggested_power_level().try_into()?)
}
/// Generates a `matrix.to` permalink to the given userID.
@@ -76,17 +85,18 @@ pub fn matrix_to_user_permalink(user_id: String) -> Result<String, ClientError>
Ok(user_id.matrix_to_uri().to_string())
}
#[derive(uniffi::Record)]
#[derive(Clone, uniffi::Record)]
pub struct RoomMember {
pub user_id: String,
pub display_name: Option<String>,
pub avatar_url: Option<String>,
pub membership: MembershipState,
pub is_name_ambiguous: bool,
pub power_level: i64,
pub normalized_power_level: i64,
pub power_level: PowerLevel,
pub normalized_power_level: PowerLevel,
pub is_ignored: bool,
pub suggested_role_for_power_level: RoomMemberRole,
pub membership_change_reason: Option<String>,
}
impl TryFrom<SdkRoomMember> for RoomMember {
@@ -99,10 +109,72 @@ impl TryFrom<SdkRoomMember> for RoomMember {
avatar_url: m.avatar_url().map(|a| a.to_string()),
membership: m.membership().clone().try_into()?,
is_name_ambiguous: m.name_ambiguous(),
power_level: m.power_level(),
normalized_power_level: m.normalized_power_level(),
power_level: m.power_level().try_into()?,
normalized_power_level: m.normalized_power_level().try_into()?,
is_ignored: m.is_ignored(),
suggested_role_for_power_level: m.suggested_role_for_power_level(),
membership_change_reason: m.event().reason().map(|s| s.to_owned()),
})
}
}
/// Contains the current user's room member info and the optional room member
/// info of the sender of the `m.room.member` event that this info represents.
#[derive(Clone, uniffi::Record)]
pub struct RoomMemberWithSenderInfo {
/// The room member.
room_member: RoomMember,
/// The info of the sender of the event `room_member` is based on, if
/// available.
sender_info: Option<RoomMember>,
}
impl TryFrom<matrix_sdk::room::RoomMemberWithSenderInfo> for RoomMemberWithSenderInfo {
type Error = ClientError;
fn try_from(value: matrix_sdk::room::RoomMemberWithSenderInfo) -> Result<Self, Self::Error> {
Ok(Self {
room_member: value.room_member.try_into()?,
sender_info: value.sender_info.map(|member| member.try_into()).transpose()?,
})
}
}
#[derive(Clone, uniffi::Enum)]
pub enum PowerLevel {
/// The user is a room creator and has infinite power level.
///
/// This power level was introduced in room version 12.
Infinite,
/// The user has the given power level.
Value { value: i64 },
}
impl TryFrom<UserPowerLevel> for PowerLevel {
type Error = NotYetImplemented;
fn try_from(value: UserPowerLevel) -> Result<Self, Self::Error> {
match value {
UserPowerLevel::Infinite => Ok(Self::Infinite),
UserPowerLevel::Int(value) => Ok(Self::Value { value: value.into() }),
_ => Err(NotYetImplemented),
}
}
}
impl TryFrom<PowerLevel> for UserPowerLevel {
type Error = ClientError;
fn try_from(value: PowerLevel) -> Result<Self, Self::Error> {
Ok(match value {
PowerLevel::Infinite => Self::Infinite,
PowerLevel::Value { value } => {
Self::Int(value.try_into().map_err(|err| ClientError::Generic {
msg: "Power level is out of range".to_owned(),
details: Some(format!("{err:?}")),
})?)
}
})
}
}
+53 -34
View File
@@ -1,13 +1,12 @@
use anyhow::Context as _;
use matrix_sdk::{room_preview::RoomPreview as SdkRoomPreview, Client};
use ruma::{room::RoomType as RumaRoomType, space::SpaceRoomJoinRule};
use tracing::warn;
use ruma::room::{JoinRuleSummary, RoomType as RumaRoomType};
use crate::{
client::JoinRule,
client::{AllowRule, JoinRule},
error::ClientError,
room::{Membership, RoomHero},
room_member::RoomMember,
room_member::{RoomMember, RoomMemberWithSenderInfo},
utils::AsyncRuntimeDropped,
};
@@ -22,9 +21,9 @@ pub struct RoomPreview {
#[matrix_sdk_ffi_macros::export]
impl RoomPreview {
/// Returns the room info the preview contains.
pub fn info(&self) -> Result<RoomPreviewInfo, ClientError> {
pub fn info(&self) -> RoomPreviewInfo {
let info = &self.inner;
Ok(RoomPreviewInfo {
RoomPreviewInfo {
room_id: info.room_id.to_string(),
canonical_alias: info.canonical_alias.as_ref().map(|alias| alias.to_string()),
name: info.name.clone(),
@@ -32,30 +31,30 @@ impl RoomPreview {
avatar_url: info.avatar_url.as_ref().map(|url| url.to_string()),
num_joined_members: info.num_joined_members,
num_active_members: info.num_active_members,
room_type: info.room_type.as_ref().into(),
room_type: info.room_type.clone().into(),
is_history_world_readable: info.is_world_readable,
membership: info.state.map(|state| state.into()),
join_rule: info
.join_rule
.clone()
.try_into()
.map_err(|_| anyhow::anyhow!("unhandled SpaceRoomJoinRule kind"))?,
join_rule: info.join_rule.clone().map(Into::into),
is_direct: info.is_direct,
heroes: info
.heroes
.as_ref()
.map(|heroes| heroes.iter().map(|h| h.to_owned().into()).collect()),
})
}
}
/// Leave the room if the room preview state is either joined, invited or
/// knocked.
///
/// If rejecting an invite then also forget it as an extra layer of
/// protection against spam attacks.
///
/// Will return an error otherwise.
pub async fn leave(&self) -> Result<(), ClientError> {
let room =
self.client.get_room(&self.inner.room_id).context("missing room for a room preview")?;
room.leave().await.map_err(Into::into)
Ok(room.leave().await?)
}
/// Get the user who created the invite, if any.
@@ -64,6 +63,20 @@ impl RoomPreview {
let invite_details = room.invite_details().await.ok()?;
invite_details.inviter.and_then(|m| m.try_into().ok())
}
/// Forget the room if we had access to it, and it was left or banned.
pub async fn forget(&self) -> Result<(), ClientError> {
let room =
self.client.get_room(&self.inner.room_id).context("missing room for a room preview")?;
room.forget().await?;
Ok(())
}
/// Get the membership details for the current user.
pub async fn own_membership_details(&self) -> Option<RoomMemberWithSenderInfo> {
let room = self.client.get_room(&self.inner.room_id)?;
room.member_with_sender_info(self.client.user_id()?).await.ok()?.try_into().ok()
}
}
impl RoomPreview {
@@ -96,30 +109,36 @@ pub struct RoomPreviewInfo {
/// The membership state for the current user, if known.
pub membership: Option<Membership>,
/// The join rule for this room (private, public, knock, etc.).
pub join_rule: JoinRule,
pub join_rule: Option<JoinRule>,
/// Whether the room is direct or not, if known.
pub is_direct: Option<bool>,
/// Room heroes.
pub heroes: Option<Vec<RoomHero>>,
}
impl TryFrom<SpaceRoomJoinRule> for JoinRule {
type Error = ();
fn try_from(join_rule: SpaceRoomJoinRule) -> Result<Self, ()> {
Ok(match join_rule {
SpaceRoomJoinRule::Invite => JoinRule::Invite,
SpaceRoomJoinRule::Knock => JoinRule::Knock,
SpaceRoomJoinRule::Private => JoinRule::Private,
SpaceRoomJoinRule::Restricted => JoinRule::Restricted { rules: Vec::new() },
SpaceRoomJoinRule::KnockRestricted => JoinRule::KnockRestricted { rules: Vec::new() },
SpaceRoomJoinRule::Public => JoinRule::Public,
SpaceRoomJoinRule::_Custom(_) => JoinRule::Custom { repr: join_rule.to_string() },
_ => {
warn!("unhandled SpaceRoomJoinRule: {join_rule}");
return Err(());
}
})
impl From<JoinRuleSummary> for JoinRule {
fn from(join_rule: JoinRuleSummary) -> Self {
match join_rule {
JoinRuleSummary::Invite => JoinRule::Invite,
JoinRuleSummary::Knock => JoinRule::Knock,
JoinRuleSummary::Private => JoinRule::Private,
JoinRuleSummary::Restricted(summary) => JoinRule::Restricted {
rules: summary
.allowed_room_ids
.iter()
.map(|room_id| AllowRule::RoomMembership { room_id: room_id.to_string() })
.collect(),
},
JoinRuleSummary::KnockRestricted(summary) => JoinRule::KnockRestricted {
rules: summary
.allowed_room_ids
.iter()
.map(|room_id| AllowRule::RoomMembership { room_id: room_id.to_string() })
.collect(),
},
JoinRuleSummary::Public => JoinRule::Public,
_ => JoinRule::Custom { repr: join_rule.as_str().to_owned() },
}
}
}
@@ -134,8 +153,8 @@ pub enum RoomType {
Custom { value: String },
}
impl From<Option<&RumaRoomType>> for RoomType {
fn from(value: Option<&RumaRoomType>) -> Self {
impl From<Option<RumaRoomType>> for RoomType {
fn from(value: Option<RumaRoomType>) -> Self {
match value {
Some(RumaRoomType::Space) => RoomType::Space,
Some(RumaRoomType::_Custom(_)) => RoomType::Custom {
File diff suppressed because it is too large Load Diff
+110
View File
@@ -0,0 +1,110 @@
// Copyright 2025 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Runtime abstractions for cross-platform async execution. This provides
//! a stand-in for tokio's `get_runtime_handle` method that will work on
//! both Wasm and non-Wasm platforms. It also provides corresponding types
//! that can be used in place of tokio's `Handle` and `Runtime` types.
#[cfg(not(target_family = "wasm"))]
mod sys {
pub use tokio::runtime::Handle;
/// Get a runtime handle appropriate for the current target platform.
///
/// This function returns a unified `Handle` type that works across both
/// Wasm and non-Wasm platforms, allowing code to be written that is
/// agnostic to the platform-specific runtime implementation.
///
/// Returns:
/// - A `tokio::runtime::Handle` on non-Wasm platforms
/// - A `WasmRuntimeHandle` on Wasm platforms
pub fn get_runtime_handle() -> Handle {
async_compat::get_runtime_handle()
}
}
#[cfg(target_family = "wasm")]
mod sys {
use std::future::Future;
use matrix_sdk_common::executor::{spawn, JoinHandle};
/// A dummy guard that does nothing when dropped.
/// This is used for the Wasm implementation to match
/// tokio::runtime::EnterGuard.
#[derive(Debug)]
pub struct RuntimeGuard;
/// A runtime handle implementation for WebAssembly targets.
///
/// This implements a minimal subset of the tokio::runtime::Handle API
/// that is needed for the matrix-rust-sdk to function on Wasm.
#[derive(Default, Debug)]
pub struct Handle;
pub type Runtime = Handle;
impl Handle {
/// Spawns a future in the wasm32 bindgen runtime.
#[track_caller]
pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
where
F: Future + 'static,
F::Output: 'static,
{
spawn(future)
}
/// Runs the provided function on an executor dedicated to blocking
/// operations.
#[track_caller]
pub fn spawn_blocking<F, R>(&self, func: F) -> JoinHandle<R>
where
F: FnOnce() -> R + 'static,
R: 'static,
{
spawn(async move { func() })
}
/// Runs a future to completion on the current thread.
pub fn block_on<F, T>(&self, future: F) -> T
where
F: Future<Output = T>,
{
futures_executor::block_on(future)
}
/// Enters the runtime context.
///
/// For WebAssembly, this is a no-op that returns a dummy guard.
pub fn enter(&self) -> RuntimeGuard {
RuntimeGuard
}
}
/// Get a runtime handle appropriate for the current target platform.
///
/// This function returns a unified `Handle` type that works across both
/// Wasm and non-Wasm platforms, allowing code to be written that is
/// agnostic to the platform-specific runtime implementation.
///
/// Returns:
/// - A `tokio::runtime::Handle` on non-Wasm platforms
/// - A `WasmRuntimeHandle` on Wasm platforms
pub fn get_runtime_handle() -> Handle {
Handle
}
}
pub use sys::*;
@@ -7,13 +7,16 @@ use matrix_sdk::{
verification::{SasState, SasVerification, VerificationRequest, VerificationRequestState},
Encryption,
},
ruma::events::{key::verification::VerificationMethod, AnyToDeviceEvent},
ruma::events::key::verification::VerificationMethod,
Account,
};
use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
use ruma::UserId;
use tracing::{error, info};
use tracing::{error, warn};
use super::RUNTIME;
use crate::error::ClientError;
use crate::{
client::UserProfile, error::ClientError, runtime::get_runtime_handle, utils::Timestamp,
};
#[derive(uniffi::Object)]
pub struct SessionVerificationEmoji {
@@ -39,18 +42,18 @@ pub enum SessionVerificationData {
}
/// Details about the incoming verification request
#[derive(Debug, uniffi::Record)]
#[derive(uniffi::Record)]
pub struct SessionVerificationRequestDetails {
sender_id: String,
sender_profile: UserProfile,
flow_id: String,
device_id: String,
display_name: Option<String>,
device_display_name: Option<String>,
/// First time this device was seen in milliseconds since epoch.
first_seen_timestamp: u64,
first_seen_timestamp: Timestamp,
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait SessionVerificationControllerDelegate: Sync + Send {
pub trait SessionVerificationControllerDelegate: SyncOutsideWasm + SendOutsideWasm {
fn did_receive_verification_request(&self, details: SessionVerificationRequestDetails);
fn did_accept_verification_request(&self);
fn did_start_sas_verification(&self);
@@ -66,6 +69,7 @@ pub type Delegate = Arc<RwLock<Option<Box<dyn SessionVerificationControllerDeleg
pub struct SessionVerificationController {
encryption: Encryption,
user_identity: UserIdentity,
account: Account,
delegate: Delegate,
verification_request: Arc<RwLock<Option<VerificationRequest>>>,
sas_verification: Arc<RwLock<Option<SasVerification>>>,
@@ -92,17 +96,9 @@ impl SessionVerificationController {
.encryption
.get_verification_request(&sender_id, flow_id)
.await
.ok_or(ClientError::new("Unknown session verification request"))?;
.ok_or(ClientError::from_str("Unknown session verification request", None))?;
*self.verification_request.write().unwrap() = Some(verification_request.clone());
RUNTIME.spawn(Self::listen_to_verification_request_changes(
verification_request,
self.sas_verification.clone(),
self.delegate.clone(),
));
Ok(())
self.set_ongoing_verification_request(verification_request)
}
/// Accept the previously acknowledged verification request
@@ -118,23 +114,33 @@ impl SessionVerificationController {
}
/// Request verification for the current device
pub async fn request_verification(&self) -> Result<(), ClientError> {
pub async fn request_device_verification(&self) -> Result<(), ClientError> {
let methods = vec![VerificationMethod::SasV1];
let verification_request = self
.user_identity
.request_verification_with_methods(methods)
.await
.map_err(anyhow::Error::from)?;
let verification_request =
self.user_identity.request_verification_with_methods(methods).await?;
*self.verification_request.write().unwrap() = Some(verification_request.clone());
self.set_ongoing_verification_request(verification_request)
}
RUNTIME.spawn(Self::listen_to_verification_request_changes(
verification_request,
self.sas_verification.clone(),
self.delegate.clone(),
));
/// Request verification for the given user
pub async fn request_user_verification(&self, user_id: String) -> Result<(), ClientError> {
let user_id = UserId::parse(user_id)?;
Ok(())
let user_identity = self
.encryption
.get_user_identity(&user_id)
.await?
.ok_or(ClientError::from_str("Unknown user identity", None))?;
if user_identity.is_verified() {
return Err(ClientError::from_str("User is already verified", None));
}
let methods = vec![VerificationMethod::SasV1];
let verification_request = user_identity.request_verification_with_methods(methods).await?;
self.set_ongoing_verification_request(verification_request)
}
/// Transition the current verification request into a SAS verification
@@ -143,7 +149,7 @@ impl SessionVerificationController {
let verification_request = self.verification_request.read().unwrap().clone();
let Some(verification_request) = verification_request else {
return Err(ClientError::new("Verification request missing."));
return Err(ClientError::from_str("Verification request missing.", None));
};
match verification_request.start_sas().await {
@@ -155,7 +161,8 @@ impl SessionVerificationController {
}
let delegate = self.delegate.clone();
RUNTIME.spawn(Self::listen_to_sas_verification_changes(verification, delegate));
get_runtime_handle()
.spawn(Self::listen_to_sas_verification_changes(verification, delegate));
}
_ => {
if let Some(delegate) = &*self.delegate.read().unwrap() {
@@ -172,7 +179,7 @@ impl SessionVerificationController {
let sas_verification = self.sas_verification.read().unwrap().clone();
let Some(sas_verification) = sas_verification else {
return Err(ClientError::new("SAS verification missing"));
return Err(ClientError::from_str("SAS verification missing", None));
};
Ok(sas_verification.confirm().await?)
@@ -183,7 +190,7 @@ impl SessionVerificationController {
let sas_verification = self.sas_verification.read().unwrap().clone();
let Some(sas_verification) = sas_verification else {
return Err(ClientError::new("SAS verification missing"));
return Err(ClientError::from_str("SAS verification missing", None));
};
Ok(sas_verification.mismatch().await?)
@@ -194,7 +201,7 @@ impl SessionVerificationController {
let verification_request = self.verification_request.read().unwrap().clone();
let Some(verification_request) = verification_request else {
return Err(ClientError::new("Verification request missing."));
return Err(ClientError::from_str("Verification request missing.", None));
};
Ok(verification_request.cancel().await?)
@@ -202,50 +209,93 @@ impl SessionVerificationController {
}
impl SessionVerificationController {
pub(crate) fn new(encryption: Encryption, user_identity: UserIdentity) -> Self {
pub(crate) fn new(
encryption: Encryption,
user_identity: UserIdentity,
account: Account,
) -> Self {
SessionVerificationController {
encryption,
user_identity,
account,
delegate: Arc::new(RwLock::new(None)),
verification_request: Arc::new(RwLock::new(None)),
sas_verification: Arc::new(RwLock::new(None)),
}
}
pub(crate) async fn process_to_device_message(&self, event: AnyToDeviceEvent) {
if let AnyToDeviceEvent::KeyVerificationRequest(event) = event {
info!("Received verification request: {:}", event.sender);
let Some(request) = self
.encryption
.get_verification_request(&event.sender, &event.content.transaction_id)
.await
else {
error!("Failed retrieving verification request");
return;
};
if !request.is_self_verification() {
info!("Received non-self verification request. Ignoring.");
return;
}
let VerificationRequestState::Requested { other_device_data, .. } = request.state()
else {
error!("Received key verification event but the request is in the wrong state.");
return;
};
if let Some(delegate) = &*self.delegate.read().unwrap() {
delegate.did_receive_verification_request(SessionVerificationRequestDetails {
sender_id: request.other_user_id().into(),
flow_id: request.flow_id().into(),
device_id: other_device_data.device_id().into(),
display_name: other_device_data.display_name().map(str::to_string),
first_seen_timestamp: other_device_data.first_time_seen_ts().get().into(),
});
/// Ask the controller to process an incoming request based on the sender
/// and flow identifier. It will fetch the request, verify that it's in the
/// correct state and then and notify the delegate.
pub(crate) async fn process_incoming_verification_request(
&self,
sender: &UserId,
flow_id: impl AsRef<str>,
) {
if sender != self.user_identity.user_id() {
if let Some(status) = self.encryption.cross_signing_status().await {
if !status.is_complete() {
warn!(
"Cannot verify other users until our own device's cross-signing status \
is complete: {status:?}"
);
return;
}
}
}
let Some(request) = self.encryption.get_verification_request(sender, flow_id).await else {
error!("Failed retrieving verification request");
return;
};
let VerificationRequestState::Requested { other_device_data, .. } = request.state() else {
error!("Received verification request event but the request is in the wrong state.");
return;
};
let Ok(sender_profile) = UserProfile::fetch(&self.account, sender).await else {
error!("Failed fetching user profile for verification request");
return;
};
if let Some(delegate) = &*self.delegate.read().unwrap() {
delegate.did_receive_verification_request(SessionVerificationRequestDetails {
sender_profile,
flow_id: request.flow_id().into(),
device_id: other_device_data.device_id().into(),
device_display_name: other_device_data.display_name().map(str::to_string),
first_seen_timestamp: other_device_data.first_time_seen_ts().into(),
});
}
}
fn set_ongoing_verification_request(
&self,
verification_request: VerificationRequest,
) -> Result<(), ClientError> {
if let Some(ongoing_verification_request) =
self.verification_request.read().unwrap().clone()
{
if !ongoing_verification_request.is_done()
&& !ongoing_verification_request.is_cancelled()
{
return Err(ClientError::from_str(
"There is another verification flow ongoing.",
None,
));
}
}
*self.verification_request.write().unwrap() = Some(verification_request.clone());
get_runtime_handle().spawn(Self::listen_to_verification_request_changes(
verification_request,
self.sas_verification.clone(),
self.delegate.clone(),
));
Ok(())
}
async fn listen_to_verification_request_changes(
@@ -271,7 +321,7 @@ impl SessionVerificationController {
}
let delegate = delegate.clone();
RUNTIME.spawn(Self::listen_to_sas_verification_changes(
get_runtime_handle().spawn(Self::listen_to_sas_verification_changes(
verification,
delegate,
));
+276
View File
@@ -0,0 +1,276 @@
// Copyright 2025 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::{fmt::Debug, sync::Arc};
use eyeball_im::VectorDiff;
use futures_util::{pin_mut, StreamExt};
use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
use matrix_sdk_ui::spaces::{
room_list::SpaceRoomListPaginationState, SpaceRoom as UISpaceRoom,
SpaceRoomList as UISpaceRoomList, SpaceService as UISpaceService,
};
use ruma::RoomId;
use crate::{
client::JoinRule,
error::ClientError,
room::{Membership, RoomHero},
room_preview::RoomType,
runtime::get_runtime_handle,
TaskHandle,
};
/// The main entry point into the Spaces facilities.
///
/// The spaces service is responsible for retrieving one's joined rooms,
/// building a graph out of their `m.space.parent` and `m.space.child` state
/// events, and providing access to the top-level spaces and their children.
#[derive(uniffi::Object)]
pub struct SpaceService {
inner: UISpaceService,
}
impl SpaceService {
/// Creates a new `SpaceService` instance.
pub(crate) fn new(inner: UISpaceService) -> Self {
Self { inner }
}
}
#[matrix_sdk_ffi_macros::export]
impl SpaceService {
/// Returns a list of all the top-level joined spaces. It will eagerly
/// compute the latest version and also notify subscribers if there were
/// any changes.
pub async fn joined_spaces(&self) -> Vec<SpaceRoom> {
self.inner.joined_spaces().await.into_iter().map(Into::into).collect()
}
/// Subscribes to updates on the joined spaces list. If space rooms are
/// joined or left, the stream will yield diffs that reflect the changes.
pub async fn subscribe_to_joined_spaces(
&self,
listener: Box<dyn SpaceServiceJoinedSpacesListener>,
) -> Arc<TaskHandle> {
let (initial_values, mut stream) = self.inner.subscribe_to_joined_spaces().await;
listener.on_update(vec![SpaceListUpdate::Reset {
values: initial_values.into_iter().map(Into::into).collect(),
}]);
Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
while let Some(diffs) = stream.next().await {
listener.on_update(diffs.into_iter().map(Into::into).collect());
}
})))
}
/// Returns a `SpaceRoomList` for the given space ID.
#[allow(clippy::unused_async)]
// This method doesn't need to be async but if its not the FFI layer panics
// with "there is no no reactor running, must be called from the context
// of a Tokio 1.x runtime" error because the underlying constructor spawns
// an async task.
pub async fn space_room_list(
&self,
space_id: String,
) -> Result<Arc<SpaceRoomList>, ClientError> {
let space_id = RoomId::parse(space_id)?;
Ok(Arc::new(SpaceRoomList::new(self.inner.space_room_list(space_id))))
}
}
/// The `SpaceRoomList`represents a paginated list of direct rooms
/// that belong to a particular space.
///
/// It can be used to paginate through the list (and have live updates on the
/// pagination state) as well as subscribe to changes as rooms are joined or
/// left.
///
/// The `SpaceRoomList` also automatically subscribes to client room changes
/// and updates the list accordingly as rooms are joined or left.
#[derive(uniffi::Object)]
pub struct SpaceRoomList {
inner: UISpaceRoomList,
}
impl SpaceRoomList {
/// Creates a new `SpaceRoomList` for the underlying UI crate room list.
fn new(inner: UISpaceRoomList) -> Self {
Self { inner }
}
}
#[matrix_sdk_ffi_macros::export]
impl SpaceRoomList {
/// Returns if the room list is currently paginating or not.
pub fn pagination_state(&self) -> SpaceRoomListPaginationState {
self.inner.pagination_state()
}
/// Subscribe to pagination updates.
pub fn subscribe_to_pagination_state_updates(
&self,
listener: Box<dyn SpaceRoomListPaginationStateListener>,
) -> Arc<TaskHandle> {
let pagination_state = self.inner.subscribe_to_pagination_state_updates();
Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
pin_mut!(pagination_state);
while let Some(state) = pagination_state.next().await {
listener.on_update(state);
}
})))
}
/// Return the current list of rooms.
pub fn rooms(&self) -> Vec<SpaceRoom> {
self.inner.rooms().into_iter().map(Into::into).collect()
}
/// Subscribes to room list updates.
pub fn subscribe_to_room_update(
&self,
listener: Box<dyn SpaceRoomListEntriesListener>,
) -> Arc<TaskHandle> {
let (initial_values, mut stream) = self.inner.subscribe_to_room_updates();
listener.on_update(vec![SpaceListUpdate::Reset {
values: initial_values.into_iter().map(Into::into).collect(),
}]);
Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
while let Some(diffs) = stream.next().await {
listener.on_update(diffs.into_iter().map(Into::into).collect());
}
})))
}
/// Ask the list to retrieve the next page if the end hasn't been reached
/// yet. Otherwise it no-ops.
pub async fn paginate(&self) -> Result<(), ClientError> {
self.inner.paginate().await.map_err(ClientError::from)
}
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait SpaceRoomListPaginationStateListener: SendOutsideWasm + SyncOutsideWasm + Debug {
fn on_update(&self, pagination_state: SpaceRoomListPaginationState);
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait SpaceRoomListEntriesListener: SendOutsideWasm + SyncOutsideWasm + Debug {
fn on_update(&self, rooms: Vec<SpaceListUpdate>);
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait SpaceServiceJoinedSpacesListener: SendOutsideWasm + SyncOutsideWasm + Debug {
fn on_update(&self, room_updates: Vec<SpaceListUpdate>);
}
/// Structure representing a room in a space and aggregated information
/// relevant to the UI layer.
#[derive(uniffi::Record)]
pub struct SpaceRoom {
/// The ID of the room.
pub room_id: String,
/// The canonical alias of the room, if any.
pub canonical_alias: Option<String>,
/// The name of the room, if any.
pub name: Option<String>,
/// The topic of the room, if any.
pub topic: Option<String>,
/// The URL for the room's avatar, if one is set.
pub avatar_url: Option<String>,
/// The type of room from `m.room.create`, if any.
pub room_type: RoomType,
/// The number of members joined to the room.
pub num_joined_members: u64,
/// The join rule of the room.
pub join_rule: Option<JoinRule>,
/// Whether the room may be viewed by users without joining.
pub world_readable: Option<bool>,
/// Whether guest users may join the room and participate in it.
pub guest_can_join: bool,
/// The number of children room this has, if a space.
pub children_count: u64,
/// Whether this room is joined, left etc.
pub state: Option<Membership>,
/// A list of room members considered to be heroes.
pub heroes: Option<Vec<RoomHero>>,
}
impl From<UISpaceRoom> for SpaceRoom {
fn from(room: UISpaceRoom) -> Self {
Self {
room_id: room.room_id.into(),
canonical_alias: room.canonical_alias.map(|alias| alias.into()),
name: room.name,
topic: room.topic,
avatar_url: room.avatar_url.map(|url| url.into()),
room_type: room.room_type.into(),
num_joined_members: room.num_joined_members,
join_rule: room.join_rule.map(Into::into),
world_readable: room.world_readable,
guest_can_join: room.guest_can_join,
children_count: room.children_count,
state: room.state.map(Into::into),
heroes: room.heroes.map(|heroes| heroes.into_iter().map(Into::into).collect()),
}
}
}
#[derive(uniffi::Enum)]
pub enum SpaceListUpdate {
Append { values: Vec<SpaceRoom> },
Clear,
PushFront { value: SpaceRoom },
PushBack { value: SpaceRoom },
PopFront,
PopBack,
Insert { index: u32, value: SpaceRoom },
Set { index: u32, value: SpaceRoom },
Remove { index: u32 },
Truncate { length: u32 },
Reset { values: Vec<SpaceRoom> },
}
impl From<VectorDiff<UISpaceRoom>> for SpaceListUpdate {
fn from(diff: VectorDiff<UISpaceRoom>) -> Self {
match diff {
VectorDiff::Append { values } => {
Self::Append { values: values.into_iter().map(|v| v.into()).collect() }
}
VectorDiff::Clear => Self::Clear,
VectorDiff::PushFront { value } => Self::PushFront { value: value.into() },
VectorDiff::PushBack { value } => Self::PushBack { value: value.into() },
VectorDiff::PopFront => Self::PopFront,
VectorDiff::PopBack => Self::PopBack,
VectorDiff::Insert { index, value } => {
Self::Insert { index: index as u32, value: value.into() }
}
VectorDiff::Set { index, value } => {
Self::Set { index: index as u32, value: value.into() }
}
VectorDiff::Remove { index } => Self::Remove { index: index as u32 },
VectorDiff::Truncate { length } => Self::Truncate { length: length as u32 },
VectorDiff::Reset { values } => {
Self::Reset { values: values.into_iter().map(|v| v.into()).collect() }
}
}
}
}
+34 -124
View File
@@ -12,24 +12,22 @@
// See the License for that specific language governing permissions and
// limitations under the License.
use std::{fmt::Debug, sync::Arc, time::Duration};
use std::{fmt::Debug, sync::Arc};
use futures_util::pin_mut;
use matrix_sdk::{crypto::types::events::UtdCause, Client};
use matrix_sdk::Client;
use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
use matrix_sdk_ui::{
sync_service::{
State as MatrixSyncServiceState, SyncService as MatrixSyncService,
SyncServiceBuilder as MatrixSyncServiceBuilder,
},
unable_to_decrypt_hook::{
UnableToDecryptHook, UnableToDecryptInfo as SdkUnableToDecryptInfo, UtdHookManager,
},
unable_to_decrypt_hook::UtdHookManager,
};
use tracing::error;
use crate::{
error::ClientError, helpers::unwrap_or_clone_arc, room_list::RoomListService, TaskHandle,
RUNTIME,
error::ClientError, helpers::unwrap_or_clone_arc, room_list::RoomListService,
runtime::get_runtime_handle, TaskHandle,
};
#[derive(uniffi::Enum)]
@@ -38,6 +36,7 @@ pub enum SyncServiceState {
Running,
Terminated,
Error,
Offline,
}
impl From<MatrixSyncServiceState> for SyncServiceState {
@@ -47,12 +46,13 @@ impl From<MatrixSyncServiceState> for SyncServiceState {
MatrixSyncServiceState::Running => Self::Running,
MatrixSyncServiceState::Terminated => Self::Terminated,
MatrixSyncServiceState::Error => Self::Error,
MatrixSyncServiceState::Offline => Self::Offline,
}
}
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait SyncServiceStateObserver: Send + Sync + Debug {
pub trait SyncServiceStateObserver: SendOutsideWasm + SyncOutsideWasm + Debug {
fn on_update(&self, state: SyncServiceState);
}
@@ -72,17 +72,17 @@ impl SyncService {
}
pub async fn start(&self) {
self.inner.start().await;
self.inner.start().await
}
pub async fn stop(&self) -> Result<(), ClientError> {
Ok(self.inner.stop().await?)
pub async fn stop(&self) {
self.inner.stop().await
}
pub fn state(&self, listener: Box<dyn SyncServiceStateObserver>) -> Arc<TaskHandle> {
let state_stream = self.inner.state();
Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
pin_mut!(state_stream);
while let Some(state) = state_stream.next().await {
@@ -90,23 +90,26 @@ impl SyncService {
}
})))
}
/// Force expiring both sliding sync sessions.
///
/// This ensures that the sync service is stopped before expiring both
/// sessions. It should be used sparingly, as it will cause a restart of
/// the sessions on the server as well.
pub async fn expire_sessions(&self) {
self.inner.expire_sessions().await;
}
}
#[derive(Clone, uniffi::Object)]
pub struct SyncServiceBuilder {
client: Client,
builder: MatrixSyncServiceBuilder,
utd_hook: Option<Arc<UtdHookManager>>,
}
impl SyncServiceBuilder {
pub(crate) fn new(client: Client) -> Arc<Self> {
Arc::new(Self {
client: client.clone(),
builder: MatrixSyncService::builder(client),
utd_hook: None,
})
pub(crate) fn new(client: Client, utd_hook: Option<Arc<UtdHookManager>>) -> Arc<Self> {
Arc::new(Self { builder: MatrixSyncService::builder(client), utd_hook })
}
}
@@ -115,33 +118,20 @@ impl SyncServiceBuilder {
pub fn with_cross_process_lock(self: Arc<Self>) -> Arc<Self> {
let this = unwrap_or_clone_arc(self);
let builder = this.builder.with_cross_process_lock();
Arc::new(Self { client: this.client, builder, utd_hook: this.utd_hook })
Arc::new(Self { builder, ..this })
}
pub async fn with_utd_hook(
self: Arc<Self>,
delegate: Box<dyn UnableToDecryptDelegate>,
) -> Arc<Self> {
// UTDs detected before this duration may be reclassified as "late decryption"
// events (or discarded, if they get decrypted fast enough).
const UTD_HOOK_GRACE_PERIOD: Duration = Duration::from_secs(60);
/// Enable the "offline" mode for the [`SyncService`].
pub fn with_offline_mode(self: Arc<Self>) -> Arc<Self> {
let this = unwrap_or_clone_arc(self);
let builder = this.builder.with_offline_mode();
Arc::new(Self { builder, ..this })
}
let mut utd_hook = UtdHookManager::new(Arc::new(UtdHook { delegate }), this.client.clone())
.with_max_delay(UTD_HOOK_GRACE_PERIOD);
if let Err(e) = utd_hook.reload_from_store().await {
error!("Unable to reload UTD hook data from data store: {}", e);
// Carry on with the setup anyway; we shouldn't fail setup just
// because the UTD hook failed to load its data.
}
Arc::new(Self {
client: this.client,
builder: this.builder,
utd_hook: Some(Arc::new(utd_hook)),
})
pub fn with_share_pos(self: Arc<Self>, enable: bool) -> Arc<Self> {
let this = unwrap_or_clone_arc(self);
let builder = this.builder.with_share_pos(enable);
Arc::new(Self { builder, ..this })
}
pub async fn finish(self: Arc<Self>) -> Result<Arc<SyncService>, ClientError> {
@@ -152,83 +142,3 @@ impl SyncServiceBuilder {
}))
}
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait UnableToDecryptDelegate: Sync + Send {
fn on_utd(&self, info: UnableToDecryptInfo);
}
struct UtdHook {
delegate: Box<dyn UnableToDecryptDelegate>,
}
impl std::fmt::Debug for UtdHook {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UtdHook").finish_non_exhaustive()
}
}
impl UnableToDecryptHook for UtdHook {
fn on_utd(&self, info: SdkUnableToDecryptInfo) {
const IGNORE_UTD_PERIOD: Duration = Duration::from_secs(4);
// UTDs that have been decrypted in the `IGNORE_UTD_PERIOD` are just ignored and
// not considered UTDs.
if let Some(duration) = &info.time_to_decrypt {
if *duration < IGNORE_UTD_PERIOD {
return;
}
}
// Report the UTD to the client.
self.delegate.on_utd(info.into());
}
}
#[derive(uniffi::Record)]
pub struct UnableToDecryptInfo {
/// The identifier of the event that couldn't get decrypted.
event_id: String,
/// If the event could be decrypted late (that is, the event was encrypted
/// at first, but could be decrypted later on), then this indicates the
/// time it took to decrypt the event. If it is not set, this is
/// considered a definite UTD.
///
/// If set, this is in milliseconds.
pub time_to_decrypt_ms: Option<u64>,
/// What we know about what caused this UTD. E.g. was this event sent when
/// we were not a member of this room?
pub cause: UtdCause,
/// The difference between the event creation time (`origin_server_ts`) and
/// the time our device was created. If negative, this event was sent
/// *before* our device was created.
pub event_local_age_millis: i64,
/// Whether the user had verified their own identity at the point they
/// received the UTD event.
pub user_trusts_own_identity: bool,
/// The homeserver of the user that sent the undecryptable event.
pub sender_homeserver: String,
/// Our local user's own homeserver, or `None` if the client is not logged
/// in.
pub own_homeserver: Option<String>,
}
impl From<SdkUnableToDecryptInfo> for UnableToDecryptInfo {
fn from(value: SdkUnableToDecryptInfo) -> Self {
Self {
event_id: value.event_id.to_string(),
time_to_decrypt_ms: value.time_to_decrypt.map(|ttd| ttd.as_millis() as u64),
cause: value.cause,
event_local_age_millis: value.event_local_age_millis,
user_trusts_own_identity: value.user_trusts_own_identity,
sender_homeserver: value.sender_homeserver.to_string(),
own_homeserver: value.own_homeserver.map(String::from),
}
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
use tokio::task::JoinHandle;
use matrix_sdk_common::executor::JoinHandle;
use tracing::debug;
/// A task handle is a way to keep the handle a task running by itself in
@@ -0,0 +1,185 @@
use std::sync::Arc;
use matrix_sdk_ui::timeline::event_type_filter::TimelineEventTypeFilter as InnerTimelineEventTypeFilter;
use ruma::{
events::{AnySyncTimelineEvent, TimelineEventType},
EventId,
};
use super::FocusEventError;
use crate::{
error::ClientError,
event::{MessageLikeEventType, RoomMessageEventMessageType, StateEventType},
};
#[derive(uniffi::Object)]
pub struct TimelineEventTypeFilter {
inner: InnerTimelineEventTypeFilter,
}
#[matrix_sdk_ffi_macros::export]
impl TimelineEventTypeFilter {
#[uniffi::constructor]
pub fn include(event_types: Vec<FilterTimelineEventType>) -> Arc<Self> {
let event_types: Vec<TimelineEventType> =
event_types.iter().map(|t| t.clone().into()).collect();
Arc::new(Self { inner: InnerTimelineEventTypeFilter::Include(event_types) })
}
#[uniffi::constructor]
pub fn exclude(event_types: Vec<FilterTimelineEventType>) -> Arc<Self> {
let event_types: Vec<TimelineEventType> =
event_types.iter().map(|t| t.clone().into()).collect();
Arc::new(Self { inner: InnerTimelineEventTypeFilter::Exclude(event_types) })
}
}
impl TimelineEventTypeFilter {
/// Filters an [`event`] to decide whether it should be part of the timeline
/// based on [`AnySyncTimelineEvent::event_type()`].
pub(crate) fn filter(&self, event: &AnySyncTimelineEvent) -> bool {
self.inner.filter(event)
}
}
#[derive(uniffi::Enum, Clone)]
pub enum FilterTimelineEventType {
MessageLike { event_type: MessageLikeEventType },
State { event_type: StateEventType },
}
impl From<FilterTimelineEventType> for TimelineEventType {
fn from(value: FilterTimelineEventType) -> TimelineEventType {
match value {
FilterTimelineEventType::MessageLike { event_type } => {
ruma::events::MessageLikeEventType::from(event_type).into()
}
FilterTimelineEventType::State { event_type } => {
ruma::events::StateEventType::from(event_type).into()
}
}
}
}
#[derive(uniffi::Enum)]
pub enum TimelineFocus {
Live {
/// Whether to hide in-thread replies from the live timeline.
hide_threaded_events: bool,
},
Event {
/// The initial event to focus on. This is usually the target of a
/// permalink.
event_id: String,
/// The number of context events to load around the focused event.
num_context_events: u16,
/// Whether to hide in-thread replies from the live timeline.
hide_threaded_events: bool,
},
Thread {
/// The thread root event ID to focus on.
root_event_id: String,
},
PinnedEvents {
max_events_to_load: u16,
max_concurrent_requests: u16,
},
}
impl TryFrom<TimelineFocus> for matrix_sdk_ui::timeline::TimelineFocus {
type Error = ClientError;
fn try_from(
value: TimelineFocus,
) -> Result<matrix_sdk_ui::timeline::TimelineFocus, Self::Error> {
match value {
TimelineFocus::Live { hide_threaded_events } => Ok(Self::Live { hide_threaded_events }),
TimelineFocus::Event { event_id, num_context_events, hide_threaded_events } => {
let parsed_event_id =
EventId::parse(&event_id).map_err(|err| FocusEventError::InvalidEventId {
event_id: event_id.clone(),
err: err.to_string(),
})?;
Ok(Self::Event {
target: parsed_event_id,
num_context_events,
hide_threaded_events,
})
}
TimelineFocus::Thread { root_event_id } => {
let parsed_root_event_id = EventId::parse(&root_event_id).map_err(|err| {
FocusEventError::InvalidEventId {
event_id: root_event_id.clone(),
err: err.to_string(),
}
})?;
Ok(Self::Thread { root_event_id: parsed_root_event_id })
}
TimelineFocus::PinnedEvents { max_events_to_load, max_concurrent_requests } => {
Ok(Self::PinnedEvents { max_events_to_load, max_concurrent_requests })
}
}
}
}
/// Changes how date dividers get inserted, either in between each day or in
/// between each month
#[derive(uniffi::Enum)]
pub enum DateDividerMode {
Daily,
Monthly,
}
impl From<DateDividerMode> for matrix_sdk_ui::timeline::DateDividerMode {
fn from(value: DateDividerMode) -> Self {
match value {
DateDividerMode::Daily => Self::Daily,
DateDividerMode::Monthly => Self::Monthly,
}
}
}
#[derive(uniffi::Enum)]
pub enum TimelineFilter {
/// Show all the events in the timeline, independent of their type.
All,
/// Show only `m.room.messages` of the given room message types.
OnlyMessage {
/// A list of [`RoomMessageEventMessageType`] that will be allowed to
/// appear in the timeline.
types: Vec<RoomMessageEventMessageType>,
},
/// Show only events which match this filter.
EventTypeFilter { filter: Arc<TimelineEventTypeFilter> },
}
/// Various options used to configure the timeline's behavior.
#[derive(uniffi::Record)]
pub struct TimelineConfiguration {
/// What should the timeline focus on?
pub focus: TimelineFocus,
/// How should we filter out events from the timeline?
pub filter: TimelineFilter,
/// An optional String that will be prepended to
/// all the timeline item's internal IDs, making it possible to
/// distinguish different timeline instances from each other.
pub internal_id_prefix: Option<String>,
/// How often to insert date dividers
pub date_divider_mode: DateDividerMode,
/// Should the read receipts and read markers be tracked for the timeline
/// items in this instance?
///
/// As this has a non negligible performance impact, make sure to enable it
/// only when you need it.
pub track_read_receipts: bool,
/// Whether this timeline instance should report UTDs through the client's
/// delegate.
pub report_utds: bool,
}
+23 -220
View File
@@ -12,72 +12,31 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::{collections::HashMap, sync::Arc};
use std::collections::HashMap;
use matrix_sdk::{crypto::types::events::UtdCause, room::power_levels::power_level_user_changes};
use matrix_sdk_ui::timeline::{PollResult, RoomPinnedEventsChange, TimelineDetails};
use ruma::events::{room::MediaSource as RumaMediaSource, EventContent, FullStateEventContent};
use matrix_sdk::room::power_levels::power_level_user_changes;
use matrix_sdk_ui::timeline::RoomPinnedEventsChange;
use ruma::events::FullStateEventContent;
use super::ProfileDetails;
use crate::{
error::ClientError,
ruma::{ImageInfo, MediaSource, MediaSourceExt, Mentions, MessageType, PollKind},
};
use crate::{timeline::msg_like::MsgLikeContent, utils::Timestamp};
impl From<matrix_sdk_ui::timeline::TimelineItemContent> for TimelineItemContent {
fn from(value: matrix_sdk_ui::timeline::TimelineItemContent) -> Self {
use matrix_sdk_ui::timeline::TimelineItemContent as Content;
match value {
Content::Message(message) => {
let msgtype = message.msgtype().msgtype().to_owned();
match TryInto::<MessageContent>::try_into(message) {
Ok(message) => TimelineItemContent::Message { content: message },
Err(error) => TimelineItemContent::FailedToParseMessageLike {
event_type: msgtype,
error: error.to_string(),
},
}
}
Content::RedactedMessage => TimelineItemContent::RedactedMessage,
Content::Sticker(sticker) => {
let content = sticker.content();
let media_source = RumaMediaSource::from(content.source.clone());
if let Err(error) = media_source.verify() {
return TimelineItemContent::FailedToParseMessageLike {
event_type: sticker.content().event_type().to_string(),
error: error.to_string(),
};
}
match TryInto::<ImageInfo>::try_into(&content.info) {
Ok(info) => TimelineItemContent::Sticker {
body: content.body.clone(),
info,
source: Arc::new(MediaSource { media_source }),
},
Err(error) => TimelineItemContent::FailedToParseMessageLike {
event_type: sticker.content().event_type().to_string(),
error: error.to_string(),
},
}
}
Content::Poll(poll_state) => TimelineItemContent::from(poll_state.results()),
Content::MsgLike(msg_like) => match msg_like.try_into() {
Ok(content) => TimelineItemContent::MsgLike { content },
Err((error, event_type)) => TimelineItemContent::FailedToParseMessageLike {
event_type,
error: error.to_string(),
},
},
Content::CallInvite => TimelineItemContent::CallInvite,
Content::CallNotify => TimelineItemContent::CallNotify,
Content::UnableToDecrypt(msg) => {
TimelineItemContent::UnableToDecrypt { msg: EncryptedMessage::new(&msg) }
}
Content::MembershipChange(membership) => {
let reason = match membership.content() {
FullStateEventContent::Original { content, .. } => content.reason.clone(),
@@ -136,65 +95,21 @@ impl From<matrix_sdk_ui::timeline::TimelineItemContent> for TimelineItemContent
}
}
#[derive(Clone, uniffi::Record)]
pub struct MessageContent {
pub msg_type: MessageType,
pub body: String,
pub in_reply_to: Option<Arc<InReplyToDetails>>,
pub thread_root: Option<String>,
pub is_edited: bool,
pub mentions: Option<Mentions>,
}
impl TryFrom<matrix_sdk_ui::timeline::Message> for MessageContent {
type Error = ClientError;
fn try_from(value: matrix_sdk_ui::timeline::Message) -> Result<Self, Self::Error> {
Ok(Self {
msg_type: value.msgtype().clone().try_into()?,
body: value.body().to_owned(),
in_reply_to: value.in_reply_to().map(|r| Arc::new(r.clone().into())),
is_edited: value.is_edited(),
thread_root: value.thread_root().map(|id| id.to_string()),
mentions: value.mentions().cloned().map(|m| m.into()),
})
}
}
impl From<ruma::events::Mentions> for Mentions {
fn from(value: ruma::events::Mentions) -> Self {
Self {
user_ids: value.user_ids.iter().map(|id| id.to_string()).collect(),
room: value.room,
}
}
}
#[derive(Clone, uniffi::Enum)]
// A note about this `allow(clippy::large_enum_variant)`.
// In order to reduce the size of `TimelineItemContent`, we would need to
// put some parts in a `Box`, or an `Arc`. Sadly, it doesn't play well with
// UniFFI. We would need to change the `uniffi::Record` of the subtypes into
// `uniffi::Object`, which is a radical change. It would simplify the memory
// usage, but it would slow down the performance around the FFI border. Thus,
// let's consider this is a false-positive lint in this particular case.
#[allow(clippy::large_enum_variant)]
pub enum TimelineItemContent {
Message {
content: MessageContent,
},
RedactedMessage,
Sticker {
body: String,
info: ImageInfo,
source: Arc<MediaSource>,
},
Poll {
question: String,
kind: PollKind,
max_selections: u64,
answers: Vec<PollAnswer>,
votes: HashMap<String, Vec<String>>,
end_time: Option<u64>,
has_been_edited: bool,
MsgLike {
content: MsgLikeContent,
},
CallInvite,
CallNotify,
UnableToDecrypt {
msg: EncryptedMessage,
},
RoomMembership {
user_id: String,
user_display_name: Option<String>,
@@ -222,94 +137,6 @@ pub enum TimelineItemContent {
},
}
#[derive(Clone, uniffi::Object)]
pub struct InReplyToDetails {
event_id: String,
event: RepliedToEventDetails,
}
impl InReplyToDetails {
pub(crate) fn new(event_id: String, event: RepliedToEventDetails) -> Self {
Self { event_id, event }
}
}
#[matrix_sdk_ffi_macros::export]
impl InReplyToDetails {
pub fn event_id(&self) -> String {
self.event_id.clone()
}
pub fn event(&self) -> RepliedToEventDetails {
self.event.clone()
}
}
impl From<matrix_sdk_ui::timeline::InReplyToDetails> for InReplyToDetails {
fn from(inner: matrix_sdk_ui::timeline::InReplyToDetails) -> Self {
let event_id = inner.event_id.to_string();
let event = match &inner.event {
TimelineDetails::Unavailable => RepliedToEventDetails::Unavailable,
TimelineDetails::Pending => RepliedToEventDetails::Pending,
TimelineDetails::Ready(event) => RepliedToEventDetails::Ready {
content: event.content().clone().into(),
sender: event.sender().to_string(),
sender_profile: event.sender_profile().into(),
},
TimelineDetails::Error(err) => {
RepliedToEventDetails::Error { message: err.to_string() }
}
};
Self { event_id, event }
}
}
#[derive(Clone, uniffi::Enum)]
pub enum RepliedToEventDetails {
Unavailable,
Pending,
Ready { content: TimelineItemContent, sender: String, sender_profile: ProfileDetails },
Error { message: String },
}
#[derive(Clone, uniffi::Enum)]
pub enum EncryptedMessage {
OlmV1Curve25519AesSha2 {
/// The Curve25519 key of the sender.
sender_key: String,
},
// Other fields not included because UniFFI doesn't have the concept of
// deprecated fields right now.
MegolmV1AesSha2 {
/// The ID of the session used to encrypt the message.
session_id: String,
/// What we know about what caused this UTD. E.g. was this event sent
/// when we were not a member of this room?
cause: UtdCause,
},
Unknown,
}
impl EncryptedMessage {
fn new(msg: &matrix_sdk_ui::timeline::EncryptedMessage) -> Self {
use matrix_sdk_ui::timeline::EncryptedMessage as Message;
match msg {
Message::OlmV1Curve25519AesSha2 { sender_key } => {
let sender_key = sender_key.clone();
Self::OlmV1Curve25519AesSha2 { sender_key }
}
Message::MegolmV1AesSha2 { session_id, cause, .. } => {
let session_id = session_id.clone();
Self::MegolmV1AesSha2 { session_id, cause: *cause }
}
Message::Unknown => Self::Unknown,
}
}
}
#[derive(Clone, uniffi::Record)]
pub struct Reaction {
pub key: String,
@@ -319,7 +146,7 @@ pub struct Reaction {
#[derive(Clone, uniffi::Record)]
pub struct ReactionSenderData {
pub sender_id: String,
pub timestamp: u64,
pub timestamp: Timestamp,
}
#[derive(Clone, uniffi::Enum)]
@@ -462,27 +289,3 @@ impl From<&matrix_sdk_ui::timeline::AnyOtherFullStateEventContent> for OtherStat
}
}
}
#[derive(Clone, uniffi::Record)]
pub struct PollAnswer {
pub id: String,
pub text: String,
}
impl From<PollResult> for TimelineItemContent {
fn from(value: PollResult) -> Self {
TimelineItemContent::Poll {
question: value.question,
kind: PollKind::from(value.kind),
max_selections: value.max_selections,
answers: value
.answers
.into_iter()
.map(|i| PollAnswer { id: i.id, text: i.text })
.collect(),
votes: value.votes,
end_time: value.end_time,
has_been_edited: value.has_been_edited,
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,265 @@
// Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::{collections::HashMap, sync::Arc};
use matrix_sdk::crypto::types::events::UtdCause;
use ruma::events::{room::MediaSource as RumaMediaSource, MessageLikeEventContent};
use super::{
content::Reaction,
reply::{EmbeddedEventDetails, InReplyToDetails},
};
use crate::{
error::ClientError,
ruma::{ImageInfo, MediaSource, MediaSourceExt, Mentions, MessageType, PollKind},
timeline::content::ReactionSenderData,
utils::Timestamp,
};
#[derive(Clone, uniffi::Enum)]
pub enum MsgLikeKind {
/// An `m.room.message` event or extensible event, including edits.
Message { content: MessageContent },
/// An `m.sticker` event.
Sticker { body: String, info: ImageInfo, source: Arc<MediaSource> },
/// An `m.poll.start` event.
Poll {
question: String,
kind: PollKind,
max_selections: u64,
answers: Vec<PollAnswer>,
votes: HashMap<String, Vec<String>>,
end_time: Option<Timestamp>,
has_been_edited: bool,
},
/// A redacted message.
Redacted,
/// An `m.room.encrypted` event that could not be decrypted.
UnableToDecrypt { msg: EncryptedMessage },
}
/// A special kind of [`super::TimelineItemContent`] that groups together
/// different room message types with their respective reactions and thread
/// information.
#[derive(Clone, uniffi::Record)]
pub struct MsgLikeContent {
pub kind: MsgLikeKind,
pub reactions: Vec<Reaction>,
/// The event this message is replying to, if any.
pub in_reply_to: Option<Arc<InReplyToDetails>>,
/// Event ID of the thread root, if this is a message in a thread.
pub thread_root: Option<String>,
/// Details about the thread this message is the root of.
pub thread_summary: Option<Arc<ThreadSummary>>,
}
#[derive(Clone, uniffi::Record)]
pub struct MessageContent {
pub msg_type: MessageType,
pub body: String,
pub is_edited: bool,
pub mentions: Option<Mentions>,
}
impl TryFrom<matrix_sdk_ui::timeline::MsgLikeContent> for MsgLikeContent {
type Error = (ClientError, String);
fn try_from(value: matrix_sdk_ui::timeline::MsgLikeContent) -> Result<Self, Self::Error> {
use matrix_sdk_ui::timeline::MsgLikeKind as Kind;
let reactions = value
.reactions
.iter()
.map(|(k, v)| Reaction {
key: k.to_owned(),
senders: v
.into_iter()
.map(|(sender_id, info)| ReactionSenderData {
sender_id: sender_id.to_string(),
timestamp: info.timestamp.into(),
})
.collect(),
})
.collect();
let in_reply_to = value.in_reply_to.map(|r| Arc::new(r.into()));
let thread_root = value.thread_root.map(|id| id.to_string());
let thread_summary = value.thread_summary.map(|t| Arc::new(t.into()));
Ok(match value.kind {
Kind::Message(message) => {
let msg_type = TryInto::<MessageType>::try_into(message.msgtype().clone())
.map_err(|e| (e, message.msgtype().msgtype().to_owned()))?;
Self {
kind: MsgLikeKind::Message {
content: MessageContent {
msg_type,
body: message.body().to_owned(),
is_edited: message.is_edited(),
mentions: message.mentions().cloned().map(|m| m.into()),
},
},
reactions,
in_reply_to,
thread_root,
thread_summary,
}
}
Kind::Sticker(sticker) => {
let content = sticker.content();
let media_source = RumaMediaSource::from(content.source.clone());
media_source
.verify()
.map_err(|e| (e, sticker.content().event_type().to_string()))?;
let image_info = TryInto::<ImageInfo>::try_into(&content.info)
.map_err(|e| (e, sticker.content().event_type().to_string()))?;
Self {
kind: MsgLikeKind::Sticker {
body: content.body.clone(),
info: image_info,
source: Arc::new(MediaSource { media_source }),
},
reactions,
in_reply_to,
thread_root,
thread_summary,
}
}
Kind::Poll(poll_state) => {
let results = poll_state.results();
Self {
kind: MsgLikeKind::Poll {
question: results.question,
kind: PollKind::from(results.kind),
max_selections: results.max_selections,
answers: results
.answers
.into_iter()
.map(|i| PollAnswer { id: i.id, text: i.text })
.collect(),
votes: results.votes,
end_time: results.end_time.map(|t| t.into()),
has_been_edited: results.has_been_edited,
},
reactions,
in_reply_to,
thread_root,
thread_summary,
}
}
Kind::Redacted => Self {
kind: MsgLikeKind::Redacted,
reactions,
in_reply_to,
thread_root,
thread_summary,
},
Kind::UnableToDecrypt(msg) => Self {
kind: MsgLikeKind::UnableToDecrypt { msg: EncryptedMessage::new(&msg) },
reactions,
in_reply_to,
thread_root,
thread_summary,
},
})
}
}
impl From<ruma::events::Mentions> for Mentions {
fn from(value: ruma::events::Mentions) -> Self {
Self {
user_ids: value.user_ids.iter().map(|id| id.to_string()).collect(),
room: value.room,
}
}
}
#[derive(Clone, uniffi::Enum)]
pub enum EncryptedMessage {
OlmV1Curve25519AesSha2 {
/// The Curve25519 key of the sender.
sender_key: String,
},
// Other fields not included because UniFFI doesn't have the concept of
// deprecated fields right now.
MegolmV1AesSha2 {
/// The ID of the session used to encrypt the message.
session_id: String,
/// What we know about what caused this UTD. E.g. was this event sent
/// when we were not a member of this room?
cause: UtdCause,
},
Unknown,
}
impl EncryptedMessage {
pub(crate) fn new(msg: &matrix_sdk_ui::timeline::EncryptedMessage) -> Self {
use matrix_sdk_ui::timeline::EncryptedMessage as Message;
match msg {
Message::OlmV1Curve25519AesSha2 { sender_key } => {
let sender_key = sender_key.clone();
Self::OlmV1Curve25519AesSha2 { sender_key }
}
Message::MegolmV1AesSha2 { session_id, cause, .. } => {
let session_id = session_id.clone();
Self::MegolmV1AesSha2 { session_id, cause: *cause }
}
Message::Unknown => Self::Unknown,
}
}
}
#[derive(Clone, uniffi::Record)]
pub struct PollAnswer {
pub id: String,
pub text: String,
}
#[derive(Clone, uniffi::Object)]
pub struct ThreadSummary {
pub latest_event: EmbeddedEventDetails,
pub num_replies: u32,
}
#[matrix_sdk_ffi_macros::export]
impl ThreadSummary {
pub fn latest_event(&self) -> EmbeddedEventDetails {
self.latest_event.clone()
}
pub fn num_replies(&self) -> u64 {
self.num_replies as u64
}
}
impl From<matrix_sdk_ui::timeline::ThreadSummary> for ThreadSummary {
fn from(value: matrix_sdk_ui::timeline::ThreadSummary) -> Self {
Self {
latest_event: EmbeddedEventDetails::from(value.latest_event),
num_replies: value.num_replies,
}
}
}
@@ -0,0 +1,81 @@
// Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use matrix_sdk_ui::timeline::{EmbeddedEvent, TimelineDetails};
use super::{content::TimelineItemContent, ProfileDetails};
use crate::{event::EventOrTransactionId, utils::Timestamp};
#[derive(Clone, uniffi::Object)]
pub struct InReplyToDetails {
event_id: String,
event: EmbeddedEventDetails,
}
impl InReplyToDetails {
pub(crate) fn new(event_id: String, event: EmbeddedEventDetails) -> Self {
Self { event_id, event }
}
}
#[matrix_sdk_ffi_macros::export]
impl InReplyToDetails {
pub fn event_id(&self) -> String {
self.event_id.clone()
}
pub fn event(&self) -> EmbeddedEventDetails {
self.event.clone()
}
}
impl From<matrix_sdk_ui::timeline::InReplyToDetails> for InReplyToDetails {
fn from(inner: matrix_sdk_ui::timeline::InReplyToDetails) -> Self {
Self { event_id: inner.event_id.to_string(), event: inner.event.into() }
}
}
#[derive(Clone, uniffi::Enum)]
#[allow(clippy::large_enum_variant)]
pub enum EmbeddedEventDetails {
Unavailable,
Pending,
Ready {
content: TimelineItemContent,
sender: String,
sender_profile: ProfileDetails,
timestamp: Timestamp,
event_or_transaction_id: EventOrTransactionId,
},
Error {
message: String,
},
}
impl From<TimelineDetails<Box<EmbeddedEvent>>> for EmbeddedEventDetails {
fn from(event: TimelineDetails<Box<EmbeddedEvent>>) -> Self {
match event {
TimelineDetails::Unavailable => EmbeddedEventDetails::Unavailable,
TimelineDetails::Pending => EmbeddedEventDetails::Pending,
TimelineDetails::Ready(event) => EmbeddedEventDetails::Ready {
content: event.content.into(),
sender: event.sender.to_string(),
sender_profile: event.sender_profile.into(),
timestamp: event.timestamp.into(),
event_or_transaction_id: event.identifier.into(),
},
TimelineDetails::Error(err) => EmbeddedEventDetails::Error { message: err.to_string() },
}
}
}
@@ -1,55 +0,0 @@
use std::sync::Arc;
use matrix_sdk_ui::timeline::event_type_filter::TimelineEventTypeFilter as InnerTimelineEventTypeFilter;
use ruma::events::{AnySyncTimelineEvent, TimelineEventType};
use crate::event::{MessageLikeEventType, StateEventType};
#[derive(uniffi::Object)]
pub struct TimelineEventTypeFilter {
inner: InnerTimelineEventTypeFilter,
}
#[matrix_sdk_ffi_macros::export]
impl TimelineEventTypeFilter {
#[uniffi::constructor]
pub fn include(event_types: Vec<FilterTimelineEventType>) -> Arc<Self> {
let event_types: Vec<TimelineEventType> =
event_types.iter().map(|t| t.clone().into()).collect();
Arc::new(Self { inner: InnerTimelineEventTypeFilter::Include(event_types) })
}
#[uniffi::constructor]
pub fn exclude(event_types: Vec<FilterTimelineEventType>) -> Arc<Self> {
let event_types: Vec<TimelineEventType> =
event_types.iter().map(|t| t.clone().into()).collect();
Arc::new(Self { inner: InnerTimelineEventTypeFilter::Exclude(event_types) })
}
}
impl TimelineEventTypeFilter {
/// Filters an [`event`] to decide whether it should be part of the timeline
/// based on [`AnySyncTimelineEvent::event_type()`].
pub(crate) fn filter(&self, event: &AnySyncTimelineEvent) -> bool {
self.inner.filter(event)
}
}
#[derive(uniffi::Enum, Clone)]
pub enum FilterTimelineEventType {
MessageLike { event_type: MessageLikeEventType },
State { event_type: StateEventType },
}
impl From<FilterTimelineEventType> for TimelineEventType {
fn from(value: FilterTimelineEventType) -> TimelineEventType {
match value {
FilterTimelineEventType::MessageLike { event_type } => {
ruma::events::MessageLikeEventType::from(event_type).into()
}
FilterTimelineEventType::State { event_type } => {
ruma::events::StateEventType::from(event_type).into()
}
}
}
}
+13 -3
View File
@@ -10,7 +10,7 @@ use tracing_core::{identify_callsite, metadata::Kind as MetadataKind};
/// Log an event.
///
/// The target should be something like a module path, and can be referenced in
/// the filter string given to `setup_tracing`. `level` and `target` for a
/// the filter string given to `init_platform`. `level` and `target` for a
/// callsite are fixed at the first `log_event` call for that callsite and can
/// not be changed afterwards, i.e. the level and target passed for second and
/// following `log_event`s with the same callsite will be ignored.
@@ -166,7 +166,7 @@ impl Span {
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, uniffi::Enum)]
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, uniffi::Enum)]
pub enum LogLevel {
Error,
Warn,
@@ -176,7 +176,7 @@ pub enum LogLevel {
}
impl LogLevel {
fn to_tracing_level(&self) -> tracing::Level {
fn to_tracing_level(self) -> tracing::Level {
match self {
LogLevel::Error => tracing::Level::ERROR,
LogLevel::Warn => tracing::Level::WARN,
@@ -185,6 +185,16 @@ impl LogLevel {
LogLevel::Trace => tracing::Level::TRACE,
}
}
pub(crate) fn as_str(&self) -> &'static str {
match self {
LogLevel::Error => "error",
LogLevel::Warn => "warn",
LogLevel::Info => "info",
LogLevel::Debug => "debug",
LogLevel::Trace => "trace",
}
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord)]
+101
View File
@@ -0,0 +1,101 @@
// Copyright 2025 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::{fmt::Debug, sync::Arc, time::Duration};
use matrix_sdk::crypto::types::events::UtdCause;
use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
use matrix_sdk_ui::unable_to_decrypt_hook::{
UnableToDecryptHook, UnableToDecryptInfo as SdkUnableToDecryptInfo,
};
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait UnableToDecryptDelegate: SyncOutsideWasm + SendOutsideWasm {
fn on_utd(&self, info: UnableToDecryptInfo);
}
pub struct UtdHook {
pub delegate: Arc<dyn UnableToDecryptDelegate>,
}
impl std::fmt::Debug for UtdHook {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UtdHook").finish_non_exhaustive()
}
}
impl UnableToDecryptHook for UtdHook {
fn on_utd(&self, info: SdkUnableToDecryptInfo) {
const IGNORE_UTD_PERIOD: Duration = Duration::from_secs(4);
// UTDs that have been decrypted in the `IGNORE_UTD_PERIOD` are just ignored and
// not considered UTDs.
if let Some(duration) = &info.time_to_decrypt {
if *duration < IGNORE_UTD_PERIOD {
return;
}
}
// Report the UTD to the client.
self.delegate.on_utd(info.into());
}
}
#[derive(uniffi::Record)]
pub struct UnableToDecryptInfo {
/// The identifier of the event that couldn't get decrypted.
event_id: String,
/// If the event could be decrypted late (that is, the event was encrypted
/// at first, but could be decrypted later on), then this indicates the
/// time it took to decrypt the event. If it is not set, this is
/// considered a definite UTD.
///
/// If set, this is in milliseconds.
pub time_to_decrypt_ms: Option<u64>,
/// What we know about what caused this UTD. E.g. was this event sent when
/// we were not a member of this room?
pub cause: UtdCause,
/// The difference between the event creation time (`origin_server_ts`) and
/// the time our device was created. If negative, this event was sent
/// *before* our device was created.
pub event_local_age_millis: i64,
/// Whether the user had verified their own identity at the point they
/// received the UTD event.
pub user_trusts_own_identity: bool,
/// The homeserver of the user that sent the undecryptable event.
pub sender_homeserver: String,
/// Our local user's own homeserver, or `None` if the client is not logged
/// in.
pub own_homeserver: Option<String>,
}
impl From<SdkUnableToDecryptInfo> for UnableToDecryptInfo {
fn from(value: SdkUnableToDecryptInfo) -> Self {
Self {
event_id: value.event_id.to_string(),
time_to_decrypt_ms: value.time_to_decrypt.map(|ttd| ttd.as_millis() as u64),
cause: value.cause,
event_local_age_millis: value.event_local_age_millis,
user_trusts_own_identity: value.user_trusts_own_identity,
sender_homeserver: value.sender_homeserver.to_string(),
own_homeserver: value.own_homeserver.map(String::from),
}
}
}
+15 -3
View File
@@ -14,10 +14,22 @@
use std::{mem::ManuallyDrop, ops::Deref};
use async_compat::TOKIO1 as RUNTIME;
use ruma::UInt;
use ruma::{MilliSecondsSinceUnixEpoch, UInt};
use tracing::warn;
use crate::runtime::get_runtime_handle;
#[derive(Debug, Clone)]
pub struct Timestamp(u64);
impl From<MilliSecondsSinceUnixEpoch> for Timestamp {
fn from(date: MilliSecondsSinceUnixEpoch) -> Self {
Self(date.0.into())
}
}
uniffi::custom_newtype!(Timestamp, u64);
pub(crate) fn u64_to_uint(u: u64) -> UInt {
UInt::new(u).unwrap_or_else(|| {
warn!("u64 -> UInt conversion overflowed, falling back to UInt::MAX");
@@ -43,7 +55,7 @@ impl<T> AsyncRuntimeDropped<T> {
impl<T> Drop for AsyncRuntimeDropped<T> {
fn drop(&mut self) {
let _guard = RUNTIME.enter();
let _guard = get_runtime_handle().enter();
// SAFETY: self.inner is never used again, which is the only requirement
// for ManuallyDrop::drop to be used safely.
unsafe {
+63 -139
View File
@@ -1,14 +1,12 @@
use std::sync::{Arc, Mutex};
use language_tags::LanguageTag;
use matrix_sdk::{
async_trait,
widget::{MessageLikeEventFilter, StateEventFilter},
};
use matrix_sdk::widget::{MessageLikeEventFilter, StateEventFilter, ToDeviceEventFilter};
use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
use ruma::events::MessageLikeEventType;
use tracing::error;
use crate::{room::Room, RUNTIME};
use crate::{room::Room, runtime::get_runtime_handle};
#[derive(uniffi::Record)]
pub struct WidgetDriverAndHandle {
@@ -94,7 +92,7 @@ impl From<matrix_sdk::widget::WidgetSettings> for WidgetSettings {
///
/// # Arguments
/// * `widget_settings` - The widget settings to generate the url for.
/// * `room` - A matrix room which is used to query the logged in username
/// * `room` - A Matrix room which is used to query the logged in username
/// * `props` - Properties from the client that can be used by a widget to adapt
/// to the client. e.g. language, font-scale...
#[matrix_sdk_ffi_macros::export]
@@ -112,123 +110,6 @@ pub async fn generate_webview_url(
.map(|url| url.to_string())?)
}
/// Defines if a call is encrypted and which encryption system should be used.
///
/// This controls the url parameters: `perParticipantE2EE`, `password`.
#[derive(uniffi::Enum, Clone)]
pub enum EncryptionSystem {
/// Equivalent to the element call url parameter: `enableE2EE=false`
Unencrypted,
/// Equivalent to the element call url parameter:
/// `perParticipantE2EE=true`
PerParticipantKeys,
/// Equivalent to the element call url parameter:
/// `password={secret}`
SharedSecret {
/// The secret/password which is used in the url.
secret: String,
},
}
impl From<EncryptionSystem> for matrix_sdk::widget::EncryptionSystem {
fn from(value: EncryptionSystem) -> Self {
match value {
EncryptionSystem::Unencrypted => Self::Unencrypted,
EncryptionSystem::PerParticipantKeys => Self::PerParticipantKeys,
EncryptionSystem::SharedSecret { secret } => Self::SharedSecret { secret },
}
}
}
/// Properties to create a new virtual Element Call widget.
#[derive(uniffi::Record, Clone)]
pub struct VirtualElementCallWidgetOptions {
/// The url to the app.
///
/// E.g. <https://call.element.io>, <https://call.element.dev>
pub element_call_url: String,
/// The widget id.
pub widget_id: String,
/// The url that is used as the target for the PostMessages sent
/// by the widget (to the client).
///
/// For a web app client this is the client url. In case of using other
/// platforms the client most likely is setup up to listen to
/// postmessages in the same webview the widget is hosted. In this case
/// the `parent_url` is set to the url of the webview with the widget. Be
/// aware that this means that the widget will receive its own postmessage
/// messages. The `matrix-widget-api` (js) ignores those so this works but
/// it might break custom implementations.
///
/// Defaults to `element_call_url` for the non-iframe (dedicated webview)
/// usecase.
pub parent_url: Option<String>,
/// Whether the branding header of Element call should be hidden.
///
/// Default: `true`
pub hide_header: Option<bool>,
/// If set, the lobby will be skipped and the widget will join the
/// call on the `io.element.join` action.
///
/// Default: `false`
pub preload: Option<bool>,
/// The font scale which will be used inside element call.
///
/// Default: `1`
pub font_scale: Option<f64>,
/// Whether element call should prompt the user to open in the browser or
/// the app.
///
/// Default: `false`
pub app_prompt: Option<bool>,
/// Don't show the lobby and join the call immediately.
///
/// Default: `false`
pub skip_lobby: Option<bool>,
/// Make it not possible to get to the calls list in the webview.
///
/// Default: `true`
pub confine_to_room: Option<bool>,
/// The font to use, to adapt to the system font.
pub font: Option<String>,
/// Can be used to pass a PostHog id to element call.
pub analytics_id: Option<String>,
/// The encryption system to use.
///
/// Use `EncryptionSystem::Unencrypted` to disable encryption.
pub encryption: EncryptionSystem,
}
impl From<VirtualElementCallWidgetOptions> for matrix_sdk::widget::VirtualElementCallWidgetOptions {
fn from(value: VirtualElementCallWidgetOptions) -> Self {
Self {
element_call_url: value.element_call_url,
widget_id: value.widget_id,
parent_url: value.parent_url,
hide_header: value.hide_header,
preload: value.preload,
font_scale: value.font_scale,
app_prompt: value.app_prompt,
skip_lobby: value.skip_lobby,
confine_to_room: value.confine_to_room,
font: value.font,
analytics_id: value.analytics_id,
encryption: value.encryption.into(),
}
}
}
/// `WidgetSettings` are usually created from a state event.
/// (currently unimplemented)
///
@@ -244,9 +125,9 @@ impl From<VirtualElementCallWidgetOptions> for matrix_sdk::widget::VirtualElemen
/// call widget.
#[matrix_sdk_ffi_macros::export]
pub fn new_virtual_element_call_widget(
props: VirtualElementCallWidgetOptions,
props: matrix_sdk::widget::VirtualElementCallWidgetOptions,
) -> Result<WidgetSettings, ParseError> {
Ok(matrix_sdk::widget::WidgetSettings::new_virtual_element_call_widget(props.into())
Ok(matrix_sdk::widget::WidgetSettings::new_virtual_element_call_widget(props)
.map(|w| w.into())?)
}
@@ -275,7 +156,9 @@ pub fn get_element_call_required_permissions(
event_type: "org.matrix.rageshake_request".to_owned(),
},
// To read and send encryption keys
WidgetEventFilter::ToDevice { event_type: "io.element.call.encryption_keys".to_owned() },
// TODO change this to the appropriate to-device version once ready
// remove this once all matrixRTC call apps supports to-device encryption.
WidgetEventFilter::MessageLikeWithType {
event_type: "io.element.call.encryption_keys".to_owned(),
},
@@ -298,6 +181,8 @@ pub fn get_element_call_required_permissions(
read: vec![
// To compute the current state of the matrixRTC session.
WidgetEventFilter::StateWithType { event_type: StateEventType::CallMember.to_string() },
// To display the name of the room.
WidgetEventFilter::StateWithType { event_type: StateEventType::RoomName.to_string() },
// To detect leaving/kicked room members during a call.
WidgetEventFilter::StateWithType { event_type: StateEventType::RoomMember.to_string() },
// To decide whether to encrypt the call streams based on the room encryption setting.
@@ -312,6 +197,15 @@ pub fn get_element_call_required_permissions(
.chain(read_send.clone())
.collect(),
send: vec![
// To notify other users that a call has started.
WidgetEventFilter::MessageLikeWithType {
event_type: "org.matrix.msc4075.rtc.notification".to_owned(),
},
// Also for call notifications, except this is the deprecated fallback type which
// Element Call still sends.
WidgetEventFilter::MessageLikeWithType {
event_type: MessageLikeEventType::CallNotify.to_string(),
},
// To send the call participation state event (main MatrixRTC event).
// This is required for legacy state events (using only one event for all devices with
// a membership array). TODO: remove once legacy call member events are
@@ -326,13 +220,24 @@ pub fn get_element_call_required_permissions(
event_type: StateEventType::CallMember.to_string(),
state_key: format!("{own_user_id}_{own_device_id}"),
},
// Same as above for [MSC3779] and [MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143),
// with application suffix
WidgetEventFilter::StateWithTypeAndStateKey {
event_type: StateEventType::CallMember.to_string(),
state_key: format!("{own_user_id}_{own_device_id}_m.call"),
},
// The same as above but with an underscore.
// To work around the issue that state events starting with `@` have to be matrix id's
// To work around the issue that state events starting with `@` have to be Matrix id's
// but we use mxId+deviceId.
WidgetEventFilter::StateWithTypeAndStateKey {
event_type: StateEventType::CallMember.to_string(),
state_key: format!("_{own_user_id}_{own_device_id}"),
},
// Same as above for [MSC4143], with application suffix
WidgetEventFilter::StateWithTypeAndStateKey {
event_type: StateEventType::CallMember.to_string(),
state_key: format!("_{own_user_id}_{own_device_id}_m.call"),
},
]
.into_iter()
.chain(read_send)
@@ -396,7 +301,7 @@ pub struct WidgetCapabilities {
/// Types of the messages that a widget wants to be able to send.
pub send: Vec<WidgetEventFilter>,
/// If this capability is requested by the widget, it can not operate
/// separately from the matrix client.
/// separately from the Matrix client.
///
/// This means clients should not offer to open the widget in a separate
/// browser/tab/webview that is not connected to the postmessage widget-api.
@@ -442,9 +347,11 @@ pub enum WidgetEventFilter {
StateWithType { event_type: String },
/// Matches state events with the given `type` and `state_key`.
StateWithTypeAndStateKey { event_type: String, state_key: String },
/// Matches to-device events with the given `event_type`.
ToDevice { event_type: String },
}
impl From<WidgetEventFilter> for matrix_sdk::widget::EventFilter {
impl From<WidgetEventFilter> for matrix_sdk::widget::Filter {
fn from(value: WidgetEventFilter) -> Self {
match value {
WidgetEventFilter::MessageLikeWithType { event_type } => {
@@ -459,13 +366,16 @@ impl From<WidgetEventFilter> for matrix_sdk::widget::EventFilter {
WidgetEventFilter::StateWithTypeAndStateKey { event_type, state_key } => {
Self::State(StateEventFilter::WithTypeAndStateKey(event_type.into(), state_key))
}
WidgetEventFilter::ToDevice { event_type } => {
Self::ToDevice(ToDeviceEventFilter { event_type: event_type.into() })
}
}
}
}
impl From<matrix_sdk::widget::EventFilter> for WidgetEventFilter {
fn from(value: matrix_sdk::widget::EventFilter) -> Self {
use matrix_sdk::widget::EventFilter as F;
impl From<matrix_sdk::widget::Filter> for WidgetEventFilter {
fn from(value: matrix_sdk::widget::Filter) -> Self {
use matrix_sdk::widget::Filter as F;
match value {
F::MessageLike(MessageLikeEventFilter::WithType(event_type)) => {
@@ -480,18 +390,20 @@ impl From<matrix_sdk::widget::EventFilter> for WidgetEventFilter {
F::State(StateEventFilter::WithTypeAndStateKey(event_type, state_key)) => {
Self::StateWithTypeAndStateKey { event_type: event_type.to_string(), state_key }
}
F::ToDevice(ToDeviceEventFilter { event_type }) => {
Self::ToDevice { event_type: event_type.to_string() }
}
}
}
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait WidgetCapabilitiesProvider: Send + Sync {
pub trait WidgetCapabilitiesProvider: SendOutsideWasm + SyncOutsideWasm {
fn acquire_capabilities(&self, capabilities: WidgetCapabilities) -> WidgetCapabilities;
}
struct CapabilitiesProviderWrap(Arc<dyn WidgetCapabilitiesProvider>);
#[async_trait]
impl matrix_sdk::widget::CapabilitiesProvider for CapabilitiesProviderWrap {
async fn acquire_capabilities(
&self,
@@ -501,7 +413,7 @@ impl matrix_sdk::widget::CapabilitiesProvider for CapabilitiesProviderWrap {
// This could require a prompt to the user. Ideally the callback
// interface would just be async, but that's not supported yet so use
// one of tokio's blocking task threads instead.
RUNTIME
get_runtime_handle()
.spawn_blocking(move || this.acquire_capabilities(capabilities.into()).into())
.await
// propagate panics from the blocking task
@@ -585,8 +497,7 @@ mod tests {
let cap_assert = |capability: &str| {
assert!(
permission_array.contains(&capability.to_owned()),
"The \"{}\" capability was missing from the element call capability list.",
capability
"The \"{capability}\" capability was missing from the element call capability list."
);
};
@@ -594,14 +505,27 @@ mod tests {
cap_assert("org.matrix.msc4157.update_delayed_event");
cap_assert("org.matrix.msc4157.send.delayed_event");
cap_assert("org.matrix.msc2762.receive.state_event:org.matrix.msc3401.call.member");
cap_assert("org.matrix.msc2762.receive.state_event:m.room.name");
cap_assert("org.matrix.msc2762.receive.state_event:m.room.member");
cap_assert("org.matrix.msc2762.receive.state_event:m.room.encryption");
cap_assert("org.matrix.msc2762.receive.event:org.matrix.rageshake_request");
cap_assert("org.matrix.msc2762.receive.event:io.element.call.encryption_keys");
cap_assert("org.matrix.msc2762.receive.state_event:m.room.create");
cap_assert("org.matrix.msc2762.send.state_event:org.matrix.msc3401.call.member#@my_user:my_domain.org");
cap_assert("org.matrix.msc2762.send.state_event:org.matrix.msc3401.call.member#@my_user:my_domain.org_ABCDEFGHI");
cap_assert("org.matrix.msc2762.send.state_event:org.matrix.msc3401.call.member#_@my_user:my_domain.org_ABCDEFGHI");
cap_assert(
"org.matrix.msc2762.send.state_event:org.matrix.msc3401.call.member#@my_user:my_domain.org",
);
cap_assert(
"org.matrix.msc2762.send.state_event:org.matrix.msc3401.call.member#@my_user:my_domain.org_ABCDEFGHI",
);
cap_assert(
"org.matrix.msc2762.send.state_event:org.matrix.msc3401.call.member#@my_user:my_domain.org_ABCDEFGHI_m.call",
);
cap_assert(
"org.matrix.msc2762.send.state_event:org.matrix.msc3401.call.member#_@my_user:my_domain.org_ABCDEFGHI",
);
cap_assert(
"org.matrix.msc2762.send.state_event:org.matrix.msc3401.call.member#_@my_user:my_domain.org_ABCDEFGHI_m.call",
);
cap_assert("org.matrix.msc2762.send.event:org.matrix.rageshake_request");
cap_assert("org.matrix.msc2762.send.event:io.element.call.encryption_keys");
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+1 -1
View File
@@ -1,4 +1,4 @@
{
"rust-analyzer.checkOnSave.command": "clippy",
"rust-analyzer.checkOnSave.command": "check",
"rust-analyzer.rustfmt.extraArgs": ["+nightly"]
}
+32
View File
@@ -0,0 +1,32 @@
<svg xmlns="http://www.w3.org/2000/svg" width="250" viewBox="0 0 512 512">
<style>
@media (prefers-color-scheme: dark) {
rect.bg { fill: none; }
}
@media (prefers-color-scheme: light) {
rect.bg { fill: #17191C; }
}
</style>
<g clip-path="url(#clip0_7151_5134)">
<rect class="bg" width="512" height="512" />
<path d="M437.539 522.597L437.062 517.672L451.585 504.129C454.537 501.365 453.431 495.837 449.663 494.43L431.107 487.495L429.649 482.721L441.207 466.64C443.569 463.374 441.396 458.16 437.438 457.507L417.865 454.328L415.516 449.931L423.744 431.878C425.428 428.197 422.3 423.498 418.254 423.649L398.404 424.34L395.264 420.533L399.824 401.186C400.741 397.253 396.759 393.271 392.826 394.188L373.479 398.748L369.66 395.608L370.351 375.758C370.501 371.737 365.803 368.597 362.134 370.268L344.093 378.496L339.696 376.135L336.505 356.561C335.877 352.591 330.638 350.43 327.372 352.792L311.291 364.35L306.517 362.905L299.582 344.35C298.175 340.581 292.634 339.475 289.883 342.415L276.34 356.938L271.415 356.461L260.962 339.563C258.852 336.146 253.173 336.146 251.075 339.563L240.622 356.461L235.697 356.938L222.129 342.39C219.365 339.45 213.837 340.543 212.43 344.324L205.495 362.88L200.721 364.325L184.64 352.767C181.374 350.405 176.148 352.578 175.507 356.536L172.316 376.109L167.919 378.471L149.878 370.242C146.209 368.571 141.498 371.712 141.661 375.733L142.352 395.583L138.533 398.723L119.186 394.163C115.253 393.246 111.271 397.228 112.188 401.161L116.748 420.508L113.608 424.315L93.7577 423.624C89.7374 423.498 86.5966 428.172 88.2675 431.853L96.4965 449.906L94.1346 454.303L74.561 457.482C70.591 458.11 68.4301 463.349 70.792 466.615L82.3502 482.696L80.8929 487.47L62.3369 494.405C58.568 495.812 57.4624 501.353 60.4148 504.104L74.9379 517.647L74.4605 522.572L57.5629 533.025C54.1457 535.135 54.1457 540.814 57.5629 542.912L74.4605 553.365L74.9379 558.289L60.3771 571.883C57.4373 574.647 58.5303 580.175 62.2993 581.582L80.8552 588.517L82.3125 593.291L70.7543 609.372C68.405 612.638 70.5659 617.864 74.5233 618.505L94.0843 621.684L96.4462 626.081L88.2173 644.122C86.5464 647.79 89.6997 652.501 93.7074 652.351L113.557 651.66L116.698 655.479L112.138 674.826C111.221 678.746 115.203 682.741 119.135 681.811L138.483 677.251L142.302 680.392L141.611 700.242C141.46 704.262 146.159 707.403 149.828 705.732L167.868 697.503L172.266 699.865L175.457 719.426C176.085 723.408 181.324 725.557 184.59 723.22L200.671 711.637L205.445 713.094L212.38 731.65C213.787 735.419 219.328 736.524 222.079 733.572L235.622 719.049L240.547 719.551L251 736.449C253.11 739.841 258.764 739.866 260.887 736.449L271.339 719.551L276.264 719.049L289.807 733.572C292.571 736.524 298.099 735.419 299.506 731.65L306.441 713.094L311.215 711.637L327.296 723.22C330.563 725.569 335.789 723.408 336.43 719.426L339.621 699.865L344.018 697.503L362.059 705.732C365.727 707.403 370.426 704.275 370.275 700.242L369.584 680.392L373.391 677.251L392.738 681.811C396.671 682.729 400.653 678.746 399.736 674.826L395.176 655.479L398.316 651.66L418.166 652.351C422.187 652.514 425.327 647.79 423.657 644.122L415.428 626.081L417.777 621.684L437.351 618.505C441.333 617.877 443.506 612.651 441.119 609.372L429.561 593.291L431.019 588.517L449.575 581.582C453.344 580.162 454.449 574.634 451.497 571.883L436.974 558.34L437.451 553.415L454.349 542.962C457.766 540.852 457.778 535.198 454.349 533.075L437.539 522.597Z" fill="#F74C00"/>
<ellipse cx="197.264" cy="427.256" rx="36.8364" ry="41.9173" fill="#17191C"/>
<ellipse cx="314.125" cy="427.256" rx="36.8364" ry="41.9173" fill="#17191C"/>
<path d="M230.597 485.777C236.497 497.197 246.672 504.739 258.235 504.739C271.41 504.739 282.782 494.948 288.083 480.787L230.597 485.777Z" fill="#17191C"/>
<ellipse cx="210.602" cy="442.181" rx="15.8777" ry="20.6411" fill="white"/>
<ellipse cx="326.827" cy="442.181" rx="15.8777" ry="20.6411" fill="white"/>
<g clip-path="url(#clip1_7151_5134)">
<path d="M186.081 188.249V206.978H186.604C191.603 199.815 197.647 194.293 204.66 190.413C211.674 186.459 219.807 184.519 228.91 184.519C237.64 184.519 245.624 186.235 252.862 189.592C260.1 192.95 265.547 198.994 269.352 207.5C273.456 201.456 279.052 196.084 286.066 191.458C293.08 186.832 301.437 184.519 311.062 184.519C318.374 184.519 325.164 185.414 331.432 187.205C337.7 188.995 342.997 191.831 347.474 195.785C351.951 199.74 355.384 204.814 357.92 211.156C360.383 217.499 361.651 225.109 361.651 234.063V326.661H323.672V248.24C323.672 243.614 323.523 239.212 323.15 235.108C322.777 231.004 321.807 227.422 320.24 224.438C318.598 221.379 316.285 218.991 313.151 217.2C310.017 215.409 305.764 214.514 300.467 214.514C295.094 214.514 290.767 215.559 287.484 217.573C284.2 219.662 281.589 222.274 279.724 225.632C277.858 228.915 276.59 232.645 275.993 236.899C275.396 241.077 275.023 245.33 275.023 249.583V326.661H237.044V249.061C237.044 244.957 236.969 240.928 236.745 236.899C236.596 232.869 235.775 229.213 234.432 225.781C233.089 222.423 230.85 219.662 227.717 217.648C224.583 215.633 220.031 214.589 213.913 214.589C212.122 214.589 209.734 214.962 206.824 215.782C203.914 216.603 201.004 218.095 198.244 220.334C195.483 222.572 193.095 225.781 191.155 229.959C189.215 234.138 188.245 239.659 188.245 246.449V326.735H150.266V188.249H186.081Z" fill="white"/>
<path d="M72.2223 70.8792V441.121H98.86V450H62V62H98.86V70.8792H72.2223Z" fill="white"/>
<path d="M439.785 441.121V70.8792H413.147V62H450.007V450H413.147V441.121H439.785Z" fill="white"/>
</g>
</g>
<defs>
<clipPath id="clip0_7151_5134">
<rect width="512" height="512" fill="white"/>
</clipPath>
<clipPath id="clip1_7151_5134">
<rect width="388" height="388" fill="white" transform="translate(62 62)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

+171
View File
@@ -6,6 +6,177 @@ All notable changes to this project will be documented in this file.
## [Unreleased] - ReleaseDate
## [0.14.0] - 2025-09-04
### Features
- Add `SyncResponse::RoomUpdates::is_empty` to check if there were any room updates.
([#5593](https://github.com/matrix-org/matrix-rust-sdk/pull/5593))
- Add `EncryptionState::StateEncrypted` to represent rooms supporting encrypted
state events. Feature-gated behind `experimental-encrypted-state-events`.
([#5523](https://github.com/matrix-org/matrix-rust-sdk/pull/5523))
- [**breaking**] The `state` field of `JoinedRoomUpdate` and `LeftRoomUpdate`
now uses the `State` enum, depending on whether the state changes were
received in the `state` field or the `state_after` field.
([#5488](https://github.com/matrix-org/matrix-rust-sdk/pull/5488))
- [**breaking**] `RoomCreateWithCreatorEventContent` has a new field
`additional_creators` that allows to specify additional room creators beside
the user sending the `m.room.create` event, introduced with room version 12.
([#5436](https://github.com/matrix-org/matrix-rust-sdk/pull/5436))
- [**breaking**] The `RoomInfo` method now remembers the inviter at the time
when the `BaseClient::room_joined()` method was called. The caller is
responsible to remember the inviter before a server request to join the room
is made. The `RoomInfo::invite_accepted_at` method was removed, the
`RoomInfo::invite_details` method returns both the timestamp and the
inviter.
([#5390](https://github.com/matrix-org/matrix-rust-sdk/pull/5390))
### Refactor
- [**breaking**] The `Stripped` variants of `RawAnySyncOrStrippedTimelineEvent`,
`RawAnySyncOrStrippedState` and `AnySyncOrStrippedState` use `StrippedState`
instead of `AnyStrippedStateEvent`.
([#5473](https://github.com/matrix-org/matrix-rust-sdk/pull/5473))
- [**breaking**] The `stripped_state` field of `StateChanges` uses
`StrippedState` instead of `AnyStrippedStateEvent`.
([#5473](https://github.com/matrix-org/matrix-rust-sdk/pull/5473))
- [**breaking**] `RelationalLinkedChunk::items` now takes a `RoomId` instead of an
`&OwnedLinkedChunkId` parameter.
([#5445](https://github.com/matrix-org/matrix-rust-sdk/pull/5445))
- [**breaking**] Add an `IsPrefix = False` bound to the
`get_state_event_static()`, `get_state_event_static_for_key()` and
`get_state_events_static()`, `get_account_data_event_static()` and
`get_room_account_data_event_static` methods of `StateStoreExt`. These methods
only worked for events where the full event type is statically-known, and this
is now enforced at compile-time. The matching non-`static` methods of
`StateStore` can be used instead for event types with a variable suffix.
([#5444](https://github.com/matrix-org/matrix-rust-sdk/pull/5444))
- [**breaking**] `SyncOrStrippedState<RoomPowerLevelsEventContent>::power_levels()`
takes `AuthorizationRules` and a list of creators, because creators can have
infinite power levels, as introduced in room version 12.
([#5436](https://github.com/matrix-org/matrix-rust-sdk/pull/5436))
- [**breaking**] `RoomMember::power_level()` and
`RoomMember::normalized_power_level()` now use `UserPowerLevel` to represent
power levels instead of `i64` to differentiate the infinite power level of
creators, as introduced in room version 12.
([#5436](https://github.com/matrix-org/matrix-rust-sdk/pull/5436))
- [**breaking**] The `creator()` methods of `Room` and `RoomInfo` have been
renamed to `creators()` and can now return a list of user IDs, to reflect that
a room can have several creators, as introduced in room version 12.
([#5436](https://github.com/matrix-org/matrix-rust-sdk/pull/5436))
- [**breaking**] `RoomInfo::room_version_or_default()` was replaced with
`room_version_rules_or_default()`. The room version should only be used for
display purposes. The rules contain flags for all the differences in behavior
between all known room versions.
([#5337](https://github.com/matrix-org/matrix-rust-sdk/pull/5337))
- [**breaking**] `MinimalStateEvent::redact()` takes `RedactionRules` instead of
a `RoomVersionId`.
([#5337](https://github.com/matrix-org/matrix-rust-sdk/pull/5337))
- [**breaking**] The `event_id` field of `PredecessorRoom` was removed, due to
its removal in the Matrix specification with MSC4291.
([#5419](https://github.com/matrix-org/matrix-rust-sdk/pull/5419))
## [0.13.0] - 2025-07-10
### Features
- The `RoomInfo` now remembers when an invite was explicitly accepted when the
`BaseClient::room_joined()` method was called. A new getter for this
timestamp exists, the `RoomInfo::invite_accepted_at()` method returns this
timestamp.
([#5333](https://github.com/matrix-org/matrix-rust-sdk/pull/5333))
- [**breaking**] The `BaseClient::new()` method now takes an additional `ThreadingSupport`
parameter controlling whether the client is supposed to do extra processing for threads. Right
now, it controls whether to exclude in-thread events from the room unread counts, but it may be
expanded in the future to support more threading-related features.
([#5325](https://github.com/matrix-org/matrix-rust-sdk/pull/5325))
### Refactor
- The cached `ServerCapabilities` has been renamed to `ServerInfo` and
additionally contains the well-known response alongside the existing server versions.
Despite the old name, it does not contain the server capabilities.
([#5167](https://github.com/matrix-org/matrix-rust-sdk/pull/5167))
- `Room::join_rule` and `Room::is_public` now return an `Option` to reflect that the join rule
state event might be missing, in which case they will return `None`.
([#5278](https://github.com/matrix-org/matrix-rust-sdk/pull/5278))
## [0.12.0] - 2025-06-10
No notable changes in this release.
## [0.11.0] - 2025-04-11
### Features
- [**breaking**] The `Client::subscribe_to_ignore_user_list_changes()`
method will now only trigger whenever the ignored user list has
changed from what was previously known, instead of triggering
every time an ignore-user-list event has been received from sync.
([#4779](https://github.com/matrix-org/matrix-rust-sdk/pull/4779))
- [**breaking**] The `MediaRetentionPolicy` can now trigger regular cleanups
with its new `cleanup_frequency` setting.
([#4603](https://github.com/matrix-org/matrix-rust-sdk/pull/4603))
- `Clone` is a supertrait of `EventCacheStoreMedia`.
- `EventCacheStoreMedia` has a new method `last_media_cleanup_time_inner`
- There are new `'static` bounds in `MediaService` for the media cache stores
- `event_cache::store::MemoryStore` implements `Clone`.
- `BaseClient` now has a `handle_verification_events` field which is `true` by
default and can be negated so the `NotificationClient` won't handle received
verification events too, causing errors in the `VerificationMachine`.
- [**breaking**] `Room::is_encryption_state_synced` has been removed
([#4777](https://github.com/matrix-org/matrix-rust-sdk/pull/4777))
- [**breaking**] `Room::is_encrypted` is replaced by `Room::encryption_state`
which returns a value of the new `EncryptionState` enum
([#4777](https://github.com/matrix-org/matrix-rust-sdk/pull/4777))
### Refactor
- [**breaking**] `BaseClient::store` is renamed `state_store`
([#4851](https://github.com/matrix-org/matrix-rust-sdk/pull/4851))
- [**breaking**] `BaseClient::with_store_config` is renamed `new`
([#4847](https://github.com/matrix-org/matrix-rust-sdk/pull/4847))
- [**breaking**] `BaseClient::set_session_metadata` is renamed
`activate`, and `BaseClient::logged_in` is renamed `is_activated`
([#4850](https://github.com/matrix-org/matrix-rust-sdk/pull/4850))
- [**breaking] `DependentQueuedRequestKind::UploadFileWithThumbnail`
was renamed to `DependentQueuedRequestKind::UploadFileOrThumbnail`.
Under the `unstable-msc4274` feature, `DependentQueuedRequestKind::UploadFileOrThumbnail`
and `SentMediaInfo` were generalized to allow chaining multiple dependent
file / thumbnail uploads.
([#4897](https://github.com/matrix-org/matrix-rust-sdk/pull/4897))
- [**breaking**] `RoomInfo::prev_state` has been removed due to being useless.
([#5054](https://github.com/matrix-org/matrix-rust-sdk/pull/5054))
## [0.10.0] - 2025-02-04
### Features
- [**breaking**] `EventCacheStore` allows to control which media content is
allowed in the media cache, and how long it should be kept, with a
`MediaRetentionPolicy`:
- `EventCacheStore::add_media_content()` has an extra argument,
`ignore_policy`, which decides whether a media content should ignore the
`MediaRetentionPolicy`. It should be stored alongside the media content.
- `EventCacheStore` has four new methods: `media_retention_policy()`,
`set_media_retention_policy()`, `set_ignore_media_retention_policy()` and
`clean_up_media_cache()`.
- `EventCacheStore` implementations should delegate media cache methods to the
methods of the same name of `MediaService` to use the `MediaRetentionPolicy`.
They need to implement the `EventCacheStoreMedia` trait that can be tested
with the `event_cache_store_media_integration_tests!` macro.
([#4571](https://github.com/matrix-org/matrix-rust-sdk/pull/4571))
### Refactor
- [**breaking**] Replaced `Room::compute_display_name` with the reintroduced
`Room::display_name()`. The new method computes a display name, or return a
cached value from the previous successful computation. If you need a sync
variant, consider using `Room::cached_display_name()`.
([#4470](https://github.com/matrix-org/matrix-rust-sdk/pull/4470))
- [**breaking**]: The reexported types `SyncTimelineEvent` and `TimelineEvent`
have been fused into a single type `TimelineEvent`, and its field
`push_actions` has been made `Option`al (it is set to `None` when we couldn't
compute the push actions, because we lacked some information).
([#4568](https://github.com/matrix-org/matrix-rust-sdk/pull/4568))
## [0.9.0] - 2024-12-18
### Features
+68 -38
View File
@@ -1,36 +1,54 @@
[package]
authors = ["Damir Jelić <poljar@termina.org.uk>"]
description = "The base component to build a Matrix client library."
edition = "2021"
edition = "2024"
homepage = "https://github.com/matrix-org/matrix-rust-sdk"
keywords = ["matrix", "chat", "messaging", "ruma", "nio"]
license = "Apache-2.0"
name = "matrix-sdk-base"
readme = "README.md"
repository = "https://github.com/matrix-org/matrix-rust-sdk"
rust-version = { workspace = true }
version = "0.9.0"
rust-version.workspace = true
version = "0.14.0"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"]
[features]
default = []
e2e-encryption = ["dep:matrix-sdk-crypto"]
js = ["matrix-sdk-common/js", "matrix-sdk-crypto?/js", "ruma/js", "matrix-sdk-store-encryption/js"]
qrcode = ["matrix-sdk-crypto?/qrcode"]
automatic-room-key-forwarding = ["matrix-sdk-crypto?/automatic-room-key-forwarding"]
experimental-sliding-sync = [
"ruma/unstable-msc3575",
"ruma/unstable-msc4186",
js = [
"matrix-sdk-common/js",
"matrix-sdk-crypto?/js",
"ruma/js",
"matrix-sdk-store-encryption/js",
]
qrcode = ["matrix-sdk-crypto?/qrcode"]
automatic-room-key-forwarding = [
"matrix-sdk-crypto?/automatic-room-key-forwarding",
]
experimental-send-custom-to-device = [
"matrix-sdk-crypto?/experimental-send-custom-to-device",
]
# Enable experimental support for encrypting state events; see
# https://github.com/matrix-org/matrix-rust-sdk/issues/5397.
experimental-encrypted-state-events = [
"e2e-encryption",
"ruma/unstable-msc3414",
"matrix-sdk-crypto?/experimental-encrypted-state-events"
]
uniffi = ["dep:uniffi", "matrix-sdk-crypto?/uniffi", "matrix-sdk-common/uniffi"]
# Private feature, see
# https://github.com/matrix-org/matrix-rust-sdk/pull/3749#issuecomment-2312939823 for the gory
# details.
test-send-sync = ["matrix-sdk-crypto?/test-send-sync"]
test-send-sync = [
"matrix-sdk-common/test-send-sync",
"matrix-sdk-crypto?/test-send-sync",
]
# "message-ids" feature doesn't do anything and is deprecated.
message-ids = []
@@ -44,48 +62,60 @@ testing = [
"matrix-sdk-crypto?/testing",
]
# Add support for inline media galleries via msgtypes
unstable-msc4274 = []
[dependencies]
as_variant = { workspace = true }
as_variant.workspace = true
assert_matches = { workspace = true, optional = true }
assert_matches2 = { workspace = true, optional = true }
async-trait = { workspace = true }
bitflags = { version = "2.6.0", features = ["serde"] }
decancer = "3.2.8"
async-trait.workspace = true
bitflags = { workspace = true, features = ["serde"] }
decancer = "3.3.3"
eyeball = { workspace = true, features = ["async-lock"] }
eyeball-im = { workspace = true }
futures-util = { workspace = true }
growable-bloom-filter = { workspace = true }
eyeball-im.workspace = true
futures-util.workspace = true
growable-bloom-filter.workspace = true
http = { workspace = true, optional = true }
matrix-sdk-common = { workspace = true }
matrix-sdk-common.workspace = true
matrix-sdk-crypto = { workspace = true, optional = true }
matrix-sdk-store-encryption = { workspace = true }
matrix-sdk-store-encryption.workspace = true
matrix-sdk-test = { workspace = true, optional = true }
once_cell = { workspace = true }
regex = "1.11.1"
ruma = { workspace = true, features = ["canonical-json", "unstable-msc3381", "unstable-msc2867", "rand"] }
unicode-normalization = { workspace = true }
once_cell.workspace = true
regex = "1.11.2"
ruma = { workspace = true, features = [
"canonical-json",
"unstable-msc2867",
"unstable-msc3381",
"unstable-msc4186",
"rand",
] }
serde = { workspace = true, features = ["rc"] }
serde_json = { workspace = true }
tokio = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
serde_json.workspace = true
thiserror.workspace = true
tokio.workspace = true
tracing.workspace = true
unicode-normalization.workspace = true
uniffi = { workspace = true, optional = true }
[dev-dependencies]
assert_matches = { workspace = true }
assert_matches2 = { workspace = true }
anyhow.workspace = true
assert_matches.workspace = true
assert_matches2.workspace = true
assign = "1.1.1"
futures-executor = { workspace = true }
http = { workspace = true }
matrix-sdk-test = { workspace = true }
stream_assert = { workspace = true }
similar-asserts = { workspace = true }
futures-executor.workspace = true
http.workspace = true
matrix-sdk-test.workspace = true
matrix-sdk-test-utils.workspace = true
similar-asserts.workspace = true
stream_assert.workspace = true
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
[target.'cfg(not(target_family = "wasm"))'.dev-dependencies]
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
wasm-bindgen-test = { workspace = true }
[target.'cfg(target_family = "wasm")'.dev-dependencies]
wasm-bindgen-test.workspace = true
gloo-timers = { workspace = true, features = ["futures"] }
[lints]
workspace = true
File diff suppressed because it is too large Load Diff
+14
View File
@@ -17,6 +17,7 @@
use std::fmt;
pub use matrix_sdk_common::debug::*;
use matrix_sdk_common::deserialized_responses::ProcessedToDeviceEvent;
use ruma::{
api::client::sync::sync_events::v3::{InvitedRoom, KnockedRoom},
serde::Raw,
@@ -35,6 +36,19 @@ impl<T> fmt::Debug for DebugListOfRawEventsNoId<'_, T> {
}
}
/// A wrapper around a slice of `ProcessedToDeviceEvent` events that implements
/// `Debug` in a way that only prints the event type of each item.
pub struct DebugListOfProcessedToDeviceEvents<'a>(pub &'a [ProcessedToDeviceEvent]);
#[cfg(not(tarpaulin_include))]
impl fmt::Debug for DebugListOfProcessedToDeviceEvents<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut list = f.debug_list();
list.entries(self.0.iter().map(|e| DebugRawEventNoId(e.as_raw())));
list.finish()
}
}
/// A wrapper around an invited room as found in `/sync` responses that
/// implements `Debug` in a way that only prints the event ID and event type for
/// the raw events contained in `invite_state`.
@@ -20,17 +20,18 @@ pub use matrix_sdk_common::deserialized_responses::*;
use once_cell::sync::Lazy;
use regex::Regex;
use ruma::{
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedUserId, UInt, UserId,
events::{
AnyStrippedStateEvent, AnySyncStateEvent, AnySyncTimelineEvent, EventContentFromType,
PossiblyRedactedStateEventContent, RedactContent, RedactedStateEventContent,
StateEventContent, StaticStateEventContent, StrippedStateEvent, SyncStateEvent,
room::{
member::{MembershipState, RoomMemberEvent, RoomMemberEventContent},
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
},
AnyStrippedStateEvent, AnySyncStateEvent, AnySyncTimelineEvent, EventContentFromType,
PossiblyRedactedStateEventContent, RedactContent, RedactedStateEventContent,
StateEventContent, StaticStateEventContent, StrippedStateEvent, SyncStateEvent,
},
room_version_rules::AuthorizationRules,
serde::Raw,
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedUserId, UInt, UserId,
};
use serde::Serialize;
use unicode_normalization::UnicodeNormalization;
@@ -263,6 +264,18 @@ pub enum RawAnySyncOrStrippedTimelineEvent {
Stripped(Raw<AnyStrippedStateEvent>),
}
impl From<Raw<AnySyncTimelineEvent>> for RawAnySyncOrStrippedTimelineEvent {
fn from(event: Raw<AnySyncTimelineEvent>) -> Self {
Self::Sync(event)
}
}
impl From<Raw<AnyStrippedStateEvent>> for RawAnySyncOrStrippedTimelineEvent {
fn from(event: Raw<AnyStrippedStateEvent>) -> Self {
Self::Stripped(event)
}
}
/// Wrapper around both versions of any raw state event.
#[derive(Clone, Debug, Serialize)]
#[serde(untagged)]
@@ -277,8 +290,10 @@ impl RawAnySyncOrStrippedState {
/// Try to deserialize the inner JSON as the expected type.
pub fn deserialize(&self) -> serde_json::Result<AnySyncOrStrippedState> {
match self {
Self::Sync(raw) => Ok(AnySyncOrStrippedState::Sync(raw.deserialize()?)),
Self::Stripped(raw) => Ok(AnySyncOrStrippedState::Stripped(raw.deserialize()?)),
Self::Sync(raw) => Ok(AnySyncOrStrippedState::Sync(Box::new(raw.deserialize()?))),
Self::Stripped(raw) => {
Ok(AnySyncOrStrippedState::Stripped(Box::new(raw.deserialize()?)))
}
}
}
@@ -290,8 +305,8 @@ impl RawAnySyncOrStrippedState {
C::Redacted: RedactedStateEventContent,
{
match self {
Self::Sync(raw) => RawSyncOrStrippedState::Sync(raw.cast()),
Self::Stripped(raw) => RawSyncOrStrippedState::Stripped(raw.cast()),
Self::Sync(raw) => RawSyncOrStrippedState::Sync(raw.cast_unchecked()),
Self::Stripped(raw) => RawSyncOrStrippedState::Stripped(raw.cast_unchecked()),
}
}
}
@@ -300,9 +315,15 @@ impl RawAnySyncOrStrippedState {
#[derive(Clone, Debug)]
pub enum AnySyncOrStrippedState {
/// An event from a room in joined or left state.
Sync(AnySyncStateEvent),
///
/// The value is `Box`ed because it is quite large. Let's keep the size of
/// `Self` as small as possible.
Sync(Box<AnySyncStateEvent>),
/// An event from a room in invited state.
Stripped(AnyStrippedStateEvent),
///
/// The value is `Box`ed because it is quite large. Let's keep the size of
/// `Self` as small as possible.
Stripped(Box<AnyStrippedStateEvent>),
}
impl AnySyncOrStrippedState {
@@ -497,10 +518,14 @@ impl MemberEvent {
impl SyncOrStrippedState<RoomPowerLevelsEventContent> {
/// The power levels of the event.
pub fn power_levels(&self) -> RoomPowerLevels {
pub fn power_levels(
&self,
rules: &AuthorizationRules,
creators: Vec<OwnedUserId>,
) -> RoomPowerLevels {
match self {
Self::Sync(e) => e.power_levels(),
Self::Stripped(e) => e.power_levels(),
Self::Sync(e) => e.power_levels(rules, creators),
Self::Stripped(e) => e.power_levels(rules, creators),
}
}
}
@@ -602,7 +627,7 @@ mod test {
}
#[test]
fn test_display_name_equality_cyrilic() {
fn test_display_name_equality_cyrillic() {
// Display name with scritpure symbols
assert_display_name_eq!("alice", "аlice");
}
+11
View File
@@ -15,10 +15,13 @@
//! Error conditions.
use matrix_sdk_common::store_locks::LockStoreError;
#[cfg(feature = "e2e-encryption")]
use matrix_sdk_crypto::{CryptoStoreError, MegolmError, OlmError};
use thiserror::Error;
use crate::event_cache::store::EventCacheStoreError;
/// Result type of the rust-sdk.
pub type Result<T, E = Error> = std::result::Result<T, E>;
@@ -42,6 +45,14 @@ pub enum Error {
#[error(transparent)]
StateStore(#[from] crate::store::StoreError),
/// An error happened while manipulating the event cache store.
#[error(transparent)]
EventCacheStore(#[from] EventCacheStoreError),
/// An error happened while attempting to lock the event cache store.
#[error(transparent)]
EventCacheLock(#[from] LockStoreError),
/// An error occurred in the crypto store.
#[cfg(feature = "e2e-encryption")]
#[error(transparent)]
@@ -14,12 +14,12 @@
//! Event cache store and common types shared with `matrix_sdk::event_cache`.
use matrix_sdk_common::deserialized_responses::SyncTimelineEvent;
use matrix_sdk_common::deserialized_responses::TimelineEvent;
pub mod store;
/// The kind of event the event storage holds.
pub type Event = SyncTimelineEvent;
pub type Event = TimelineEvent;
/// The kind of gap the event storage holds.
#[derive(Clone, Debug)]
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,405 @@
// Copyright 2025 Kévin Commaille
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Configuration to decide whether or not to keep media in the cache, allowing
//! to do periodic cleanups to avoid to have the size of the media cache grow
//! indefinitely.
//!
//! To proceed to a cleanup, first set the [`MediaRetentionPolicy`] to use with
//! [`EventCacheStore::set_media_retention_policy()`]. Then call
//! [`EventCacheStore::clean_up_media_cache()`].
//!
//! In the future, other settings will allow to run automatic periodic cleanup
//! jobs.
//!
//! [`EventCacheStore::set_media_retention_policy()`]: crate::event_cache::store::EventCacheStore::set_media_retention_policy
//! [`EventCacheStore::clean_up_media_cache()`]: crate::event_cache::store::EventCacheStore::clean_up_media_cache
use ruma::time::{Duration, SystemTime};
use serde::{Deserialize, Serialize};
/// The retention policy for media content used by the [`EventCacheStore`].
///
/// [`EventCacheStore`]: crate::event_cache::store::EventCacheStore
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[non_exhaustive]
pub struct MediaRetentionPolicy {
/// The maximum authorized size of the overall media cache, in bytes.
///
/// The cache size is defined as the sum of the sizes of all the (possibly
/// encrypted) media contents in the cache, excluding any metadata
/// associated with them.
///
/// If this is set and the cache size is bigger than this value, the oldest
/// media contents in the cache will be removed during a cleanup until the
/// cache size is below this threshold.
///
/// Note that it is possible for the cache size to temporarily exceed this
/// value between two cleanups.
///
/// Defaults to 400 MiB.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_cache_size: Option<u64>,
/// The maximum authorized size of a single media content, in bytes.
///
/// The size of a media content is the size taken by the content in the
/// database, after it was possibly encrypted, so it might differ from the
/// initial size of the content.
///
/// The maximum authorized size of a single media content is actually the
/// lowest value between `max_cache_size` and `max_file_size`.
///
/// If it is set, media content bigger than the maximum size will not be
/// cached. If the maximum size changed after media content that exceeds the
/// new value was cached, the corresponding content will be removed
/// during a cleanup.
///
/// Defaults to 20 MiB.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_file_size: Option<u64>,
/// The duration after which unaccessed media content is considered
/// expired.
///
/// If this is set, media content whose last access is older than this
/// duration will be removed from the media cache during a cleanup.
///
/// Defaults to 60 days.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_access_expiry: Option<Duration>,
/// The duration between two automatic media cache cleanups.
///
/// If this is set, a cleanup will be triggered after the given duration
/// is elapsed, at the next call to the media cache API. If this is set to
/// zero, each call to the media cache API will trigger a cleanup. If this
/// is `None`, cleanups will only occur if they are triggered manually.
///
/// Defaults to running cleanups daily.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cleanup_frequency: Option<Duration>,
}
impl MediaRetentionPolicy {
/// Create a [`MediaRetentionPolicy`] with the default values.
pub fn new() -> Self {
Self::default()
}
/// Create an empty [`MediaRetentionPolicy`].
///
/// This means that all media will be cached and cleanups have no effect.
pub fn empty() -> Self {
Self {
max_cache_size: None,
max_file_size: None,
last_access_expiry: None,
cleanup_frequency: None,
}
}
/// Set the maximum authorized size of the overall media cache, in bytes.
pub fn with_max_cache_size(mut self, size: Option<u64>) -> Self {
self.max_cache_size = size;
self
}
/// Set the maximum authorized size of a single media content, in bytes.
pub fn with_max_file_size(mut self, size: Option<u64>) -> Self {
self.max_file_size = size;
self
}
/// Set the duration before which unaccessed media content is considered
/// expired.
pub fn with_last_access_expiry(mut self, duration: Option<Duration>) -> Self {
self.last_access_expiry = duration;
self
}
/// Set the duration between two automatic media cache cleanups.
pub fn with_cleanup_frequency(mut self, duration: Option<Duration>) -> Self {
self.cleanup_frequency = duration;
self
}
/// Whether this policy has limitations.
///
/// If this policy has no limitations, a cleanup job would have no effect.
///
/// Returns `true` if at least one limitation is set.
pub fn has_limitations(&self) -> bool {
self.max_cache_size.is_some()
|| self.max_file_size.is_some()
|| self.last_access_expiry.is_some()
}
/// Whether the given size exceeds the maximum authorized size of the media
/// cache.
///
/// # Arguments
///
/// * `size` - The overall size of the media cache to check, in bytes.
pub fn exceeds_max_cache_size(&self, size: u64) -> bool {
self.max_cache_size.is_some_and(|max_size| size > max_size)
}
/// The computed maximum authorized size of a single media content, in
/// bytes.
///
/// This is the lowest value between `max_cache_size` and `max_file_size`.
pub fn computed_max_file_size(&self) -> Option<u64> {
match (self.max_cache_size, self.max_file_size) {
(None, None) => None,
(None, Some(size)) => Some(size),
(Some(size), None) => Some(size),
(Some(max_cache_size), Some(max_file_size)) => Some(max_cache_size.min(max_file_size)),
}
}
/// Whether the given size, in bytes, exceeds the computed maximum
/// authorized size of a single media content.
///
/// # Arguments
///
/// * `size` - The size of the media content to check, in bytes.
pub fn exceeds_max_file_size(&self, size: u64) -> bool {
self.computed_max_file_size().is_some_and(|max_size| size > max_size)
}
/// Whether a content whose last access was at the given time has expired.
///
/// # Arguments
///
/// * `current_time` - The current time.
///
/// * `last_access_time` - The time when the media content to check was last
/// accessed.
pub fn has_content_expired(
&self,
current_time: SystemTime,
last_access_time: SystemTime,
) -> bool {
self.last_access_expiry.is_some_and(|max_duration| {
current_time
.duration_since(last_access_time)
// If this returns an error, the last access time is newer than the current time.
// This shouldn't happen but in this case the content cannot be expired.
.is_ok_and(|elapsed| elapsed >= max_duration)
})
}
/// Whether an automatic media cache cleanup should be triggered given the
/// time of the last cleanup.
///
/// # Arguments
///
/// * `current_time` - The current time.
///
/// * `last_cleanup_time` - The time of the last media cache cleanup.
pub fn should_clean_up(&self, current_time: SystemTime, last_cleanup_time: SystemTime) -> bool {
self.cleanup_frequency.is_some_and(|max_duration| {
current_time
.duration_since(last_cleanup_time)
// If this returns an error, the last cleanup time is newer than the current time.
// This shouldn't happen but in this case no cleanup job is needed.
.is_ok_and(|elapsed| elapsed >= max_duration)
})
}
}
impl Default for MediaRetentionPolicy {
fn default() -> Self {
Self {
// 400 MiB.
max_cache_size: Some(400 * 1024 * 1024),
// 20 MiB.
max_file_size: Some(20 * 1024 * 1024),
// 60 days.
last_access_expiry: Some(Duration::from_secs(60 * 24 * 60 * 60)),
// 1 day.
cleanup_frequency: Some(Duration::from_secs(24 * 60 * 60)),
}
}
}
#[cfg(test)]
mod tests {
use ruma::time::{Duration, SystemTime};
use super::MediaRetentionPolicy;
#[test]
fn test_media_retention_policy_has_limitations() {
let mut policy = MediaRetentionPolicy::empty();
assert!(!policy.has_limitations());
policy = policy.with_last_access_expiry(Some(Duration::from_secs(60)));
assert!(policy.has_limitations());
policy = policy.with_last_access_expiry(None);
assert!(!policy.has_limitations());
policy = policy.with_max_cache_size(Some(1_024));
assert!(policy.has_limitations());
policy = policy.with_max_cache_size(None);
assert!(!policy.has_limitations());
policy = policy.with_max_file_size(Some(1_024));
assert!(policy.has_limitations());
policy = policy.with_max_file_size(None);
assert!(!policy.has_limitations());
// With default values.
assert!(MediaRetentionPolicy::new().has_limitations());
}
#[test]
fn test_media_retention_policy_max_cache_size() {
let file_size = 2_048;
let mut policy = MediaRetentionPolicy::empty();
assert!(!policy.exceeds_max_cache_size(file_size));
assert_eq!(policy.computed_max_file_size(), None);
assert!(!policy.exceeds_max_file_size(file_size));
policy = policy.with_max_cache_size(Some(4_096));
assert!(!policy.exceeds_max_cache_size(file_size));
assert_eq!(policy.computed_max_file_size(), Some(4_096));
assert!(!policy.exceeds_max_file_size(file_size));
policy = policy.with_max_cache_size(Some(2_048));
assert!(!policy.exceeds_max_cache_size(file_size));
assert_eq!(policy.computed_max_file_size(), Some(2_048));
assert!(!policy.exceeds_max_file_size(file_size));
policy = policy.with_max_cache_size(Some(1_024));
assert!(policy.exceeds_max_cache_size(file_size));
assert_eq!(policy.computed_max_file_size(), Some(1_024));
assert!(policy.exceeds_max_file_size(file_size));
}
#[test]
fn test_media_retention_policy_max_file_size() {
let file_size = 2_048;
let mut policy = MediaRetentionPolicy::empty();
assert_eq!(policy.computed_max_file_size(), None);
assert!(!policy.exceeds_max_file_size(file_size));
// With max_file_size only.
policy = policy.with_max_file_size(Some(4_096));
assert_eq!(policy.computed_max_file_size(), Some(4_096));
assert!(!policy.exceeds_max_file_size(file_size));
policy = policy.with_max_file_size(Some(2_048));
assert_eq!(policy.computed_max_file_size(), Some(2_048));
assert!(!policy.exceeds_max_file_size(file_size));
policy = policy.with_max_file_size(Some(1_024));
assert_eq!(policy.computed_max_file_size(), Some(1_024));
assert!(policy.exceeds_max_file_size(file_size));
// With max_cache_size as well.
policy = policy.with_max_cache_size(Some(2_048));
assert_eq!(policy.computed_max_file_size(), Some(1_024));
assert!(policy.exceeds_max_file_size(file_size));
policy = policy.with_max_file_size(Some(2_048));
assert_eq!(policy.computed_max_file_size(), Some(2_048));
assert!(!policy.exceeds_max_file_size(file_size));
policy = policy.with_max_file_size(Some(4_096));
assert_eq!(policy.computed_max_file_size(), Some(2_048));
assert!(!policy.exceeds_max_file_size(file_size));
policy = policy.with_max_cache_size(Some(1_024));
assert_eq!(policy.computed_max_file_size(), Some(1_024));
assert!(policy.exceeds_max_file_size(file_size));
}
#[test]
fn test_media_retention_policy_has_content_expired() {
let epoch = SystemTime::UNIX_EPOCH;
let last_access_time = epoch + Duration::from_secs(30);
let epoch_plus_60 = epoch + Duration::from_secs(60);
let epoch_plus_120 = epoch + Duration::from_secs(120);
let mut policy = MediaRetentionPolicy::empty();
assert!(!policy.has_content_expired(epoch, last_access_time));
assert!(!policy.has_content_expired(last_access_time, last_access_time));
assert!(!policy.has_content_expired(epoch_plus_60, last_access_time));
assert!(!policy.has_content_expired(epoch_plus_120, last_access_time));
policy = policy.with_last_access_expiry(Some(Duration::from_secs(120)));
assert!(!policy.has_content_expired(epoch, last_access_time));
assert!(!policy.has_content_expired(last_access_time, last_access_time));
assert!(!policy.has_content_expired(epoch_plus_60, last_access_time));
assert!(!policy.has_content_expired(epoch_plus_120, last_access_time));
policy = policy.with_last_access_expiry(Some(Duration::from_secs(60)));
assert!(!policy.has_content_expired(epoch, last_access_time));
assert!(!policy.has_content_expired(last_access_time, last_access_time));
assert!(!policy.has_content_expired(epoch_plus_60, last_access_time));
assert!(policy.has_content_expired(epoch_plus_120, last_access_time));
policy = policy.with_last_access_expiry(Some(Duration::from_secs(30)));
assert!(!policy.has_content_expired(epoch, last_access_time));
assert!(!policy.has_content_expired(last_access_time, last_access_time));
assert!(policy.has_content_expired(epoch_plus_60, last_access_time));
assert!(policy.has_content_expired(epoch_plus_120, last_access_time));
policy = policy.with_last_access_expiry(Some(Duration::from_secs(0)));
assert!(!policy.has_content_expired(epoch, last_access_time));
assert!(policy.has_content_expired(last_access_time, last_access_time));
assert!(policy.has_content_expired(epoch_plus_60, last_access_time));
assert!(policy.has_content_expired(epoch_plus_120, last_access_time));
}
#[test]
fn test_media_retention_policy_cleanup_frequency() {
let epoch = SystemTime::UNIX_EPOCH;
let epoch_plus_60 = epoch + Duration::from_secs(60);
let epoch_plus_120 = epoch + Duration::from_secs(120);
let mut policy = MediaRetentionPolicy::empty();
assert!(!policy.should_clean_up(epoch_plus_60, epoch));
assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_60));
assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_120));
policy = policy.with_cleanup_frequency(Some(Duration::from_secs(0)));
assert!(policy.should_clean_up(epoch_plus_60, epoch));
assert!(policy.should_clean_up(epoch_plus_60, epoch_plus_60));
assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_120));
policy = policy.with_cleanup_frequency(Some(Duration::from_secs(30)));
assert!(policy.should_clean_up(epoch_plus_60, epoch));
assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_60));
assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_120));
policy = policy.with_cleanup_frequency(Some(Duration::from_secs(60)));
assert!(policy.should_clean_up(epoch_plus_60, epoch));
assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_60));
assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_120));
policy = policy.with_cleanup_frequency(Some(Duration::from_secs(90)));
assert!(!policy.should_clean_up(epoch_plus_60, epoch));
assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_60));
assert!(!policy.should_clean_up(epoch_plus_60, epoch_plus_120));
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,28 @@
// Copyright 2025 Kévin Commaille
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Types and traits regarding media caching of the event cache store.
mod media_retention_policy;
mod media_service;
#[cfg(any(test, feature = "testing"))]
#[macro_use]
pub mod integration_tests;
#[cfg(any(test, feature = "testing"))]
pub use self::integration_tests::EventCacheStoreMediaIntegrationTests;
pub use self::{
media_retention_policy::MediaRetentionPolicy,
media_service::{EventCacheStoreMedia, IgnoreMediaRetentionPolicy, MediaService},
};
@@ -12,17 +12,32 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::{collections::HashMap, num::NonZeroUsize, sync::RwLock as StdRwLock, time::Instant};
use std::{
collections::HashMap,
num::NonZeroUsize,
sync::{Arc, RwLock as StdRwLock},
};
use async_trait::async_trait;
use matrix_sdk_common::{
linked_chunk::{relational::RelationalLinkedChunk, RawChunk, Update},
linked_chunk::{
ChunkIdentifier, ChunkIdentifierGenerator, ChunkMetadata, LinkedChunkId, Position,
RawChunk, Update, relational::RelationalLinkedChunk,
},
ring_buffer::RingBuffer,
store_locks::memory_store_helper::try_take_leased_lock,
};
use ruma::{MxcUri, OwnedMxcUri, RoomId};
use ruma::{
EventId, MxcUri, OwnedEventId, OwnedMxcUri, RoomId,
events::relation::RelationType,
time::{Instant, SystemTime},
};
use tracing::error;
use super::{EventCacheStore, EventCacheStoreError, Result};
use super::{
EventCacheStore, EventCacheStoreError, Result, compute_filters_string, extract_event_relation,
media::{EventCacheStoreMedia, IgnoreMediaRetentionPolicy, MediaRetentionPolicy, MediaService},
};
use crate::{
event_cache::{Event, Gap},
media::{MediaRequestParameters, UniqueKey as _},
@@ -31,30 +46,58 @@ use crate::{
/// In-memory, non-persistent implementation of the `EventCacheStore`.
///
/// Default if no other is configured at startup.
#[allow(clippy::type_complexity)]
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct MemoryStore {
inner: StdRwLock<MemoryStoreInner>,
inner: Arc<StdRwLock<MemoryStoreInner>>,
media_service: MediaService,
}
#[derive(Debug)]
struct MemoryStoreInner {
media: RingBuffer<(OwnedMxcUri, String /* unique key */, Vec<u8>)>,
media: RingBuffer<MediaContent>,
leases: HashMap<String, (String, Instant)>,
events: RelationalLinkedChunk<Event, Gap>,
events: RelationalLinkedChunk<OwnedEventId, Event, Gap>,
media_retention_policy: Option<MediaRetentionPolicy>,
last_media_cleanup_time: SystemTime,
}
// SAFETY: `new_unchecked` is safe because 20 is not zero.
const NUMBER_OF_MEDIAS: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(20) };
/// A media content in the `MemoryStore`.
#[derive(Debug)]
struct MediaContent {
/// The URI of the content.
uri: OwnedMxcUri,
/// The unique key of the content.
key: String,
/// The bytes of the content.
data: Vec<u8>,
/// Whether we should ignore the [`MediaRetentionPolicy`] for this content.
ignore_policy: bool,
/// The time of the last access of the content.
last_access: SystemTime,
}
const NUMBER_OF_MEDIAS: NonZeroUsize = NonZeroUsize::new(20).unwrap();
impl Default for MemoryStore {
fn default() -> Self {
// Given that the store is empty, we won't need to clean it up right away.
let last_media_cleanup_time = SystemTime::now();
let media_service = MediaService::new();
media_service.restore(None, Some(last_media_cleanup_time));
Self {
inner: StdRwLock::new(MemoryStoreInner {
inner: Arc::new(StdRwLock::new(MemoryStoreInner {
media: RingBuffer::new(NUMBER_OF_MEDIAS),
leases: Default::default(),
events: RelationalLinkedChunk::new(),
}),
media_retention_policy: None,
last_media_cleanup_time,
})),
media_service,
}
}
}
@@ -66,8 +109,8 @@ impl MemoryStore {
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_family = "wasm", async_trait(?Send))]
#[cfg_attr(not(target_family = "wasm"), async_trait)]
impl EventCacheStore for MemoryStore {
type Error = EventCacheStoreError;
@@ -84,44 +127,159 @@ impl EventCacheStore for MemoryStore {
async fn handle_linked_chunk_updates(
&self,
room_id: &RoomId,
linked_chunk_id: LinkedChunkId<'_>,
updates: Vec<Update<Event, Gap>>,
) -> Result<(), Self::Error> {
let mut inner = self.inner.write().unwrap();
inner.events.apply_updates(room_id, updates);
inner.events.apply_updates(linked_chunk_id, updates);
Ok(())
}
async fn reload_linked_chunk(
async fn load_all_chunks(
&self,
room_id: &RoomId,
linked_chunk_id: LinkedChunkId<'_>,
) -> Result<Vec<RawChunk<Event, Gap>>, Self::Error> {
let inner = self.inner.read().unwrap();
inner
.events
.reload_chunks(room_id)
.load_all_chunks(linked_chunk_id)
.map_err(|err| EventCacheStoreError::InvalidData { details: err })
}
async fn clear_all_rooms_chunks(&self) -> Result<(), Self::Error> {
async fn load_all_chunks_metadata(
&self,
linked_chunk_id: LinkedChunkId<'_>,
) -> Result<Vec<ChunkMetadata>, Self::Error> {
let inner = self.inner.read().unwrap();
inner
.events
.load_all_chunks_metadata(linked_chunk_id)
.map_err(|err| EventCacheStoreError::InvalidData { details: err })
}
async fn load_last_chunk(
&self,
linked_chunk_id: LinkedChunkId<'_>,
) -> Result<(Option<RawChunk<Event, Gap>>, ChunkIdentifierGenerator), Self::Error> {
let inner = self.inner.read().unwrap();
inner
.events
.load_last_chunk(linked_chunk_id)
.map_err(|err| EventCacheStoreError::InvalidData { details: err })
}
async fn load_previous_chunk(
&self,
linked_chunk_id: LinkedChunkId<'_>,
before_chunk_identifier: ChunkIdentifier,
) -> Result<Option<RawChunk<Event, Gap>>, Self::Error> {
let inner = self.inner.read().unwrap();
inner
.events
.load_previous_chunk(linked_chunk_id, before_chunk_identifier)
.map_err(|err| EventCacheStoreError::InvalidData { details: err })
}
async fn clear_all_linked_chunks(&self) -> Result<(), Self::Error> {
self.inner.write().unwrap().events.clear();
Ok(())
}
async fn filter_duplicated_events(
&self,
linked_chunk_id: LinkedChunkId<'_>,
mut events: Vec<OwnedEventId>,
) -> Result<Vec<(OwnedEventId, Position)>, Self::Error> {
if events.is_empty() {
return Ok(Vec::new());
}
let inner = self.inner.read().unwrap();
let mut duplicated_events = Vec::new();
for (event, position) in
inner.events.unordered_linked_chunk_items(&linked_chunk_id.to_owned())
{
if let Some(known_event_id) = event.event_id() {
// This event is a duplicate!
if let Some(index) =
events.iter().position(|new_event_id| &known_event_id == new_event_id)
{
duplicated_events.push((events.remove(index), position));
}
}
}
Ok(duplicated_events)
}
async fn find_event(
&self,
room_id: &RoomId,
event_id: &EventId,
) -> Result<Option<Event>, Self::Error> {
let inner = self.inner.read().unwrap();
let event = inner
.events
.items(room_id)
.find_map(|(event, _pos)| (event.event_id()? == event_id).then_some(event.clone()));
Ok(event)
}
async fn find_event_relations(
&self,
room_id: &RoomId,
event_id: &EventId,
filters: Option<&[RelationType]>,
) -> Result<Vec<(Event, Option<Position>)>, Self::Error> {
let inner = self.inner.read().unwrap();
let filters = compute_filters_string(filters);
let related_events = inner
.events
.items(room_id)
.filter_map(|(event, pos)| {
// Must have a relation.
let (related_to, rel_type) = extract_event_relation(event.raw())?;
// Must relate to the target item.
if related_to != event_id {
return None;
}
// Must not be filtered out.
if let Some(filters) = &filters {
filters.contains(&rel_type).then_some((event.clone(), pos))
} else {
Some((event.clone(), pos))
}
})
.collect();
Ok(related_events)
}
async fn save_event(&self, room_id: &RoomId, event: Event) -> Result<(), Self::Error> {
if event.event_id().is_none() {
error!(%room_id, "Trying to save an event with no ID");
return Ok(());
}
self.inner.write().unwrap().events.save_item(room_id.to_owned(), event);
Ok(())
}
async fn add_media_content(
&self,
request: &MediaRequestParameters,
data: Vec<u8>,
ignore_policy: IgnoreMediaRetentionPolicy,
) -> Result<()> {
// Avoid duplication. Let's try to remove it first.
self.remove_media_content(request).await?;
// Now, let's add it.
let mut inner = self.inner.write().unwrap();
inner.media.push((request.uri().to_owned(), request.unique_key(), data));
Ok(())
self.media_service.add_media_content(self, request, data, ignore_policy).await
}
async fn replace_media_key(
@@ -133,23 +291,18 @@ impl EventCacheStore for MemoryStore {
let mut inner = self.inner.write().unwrap();
if let Some((mxc, key, _)) = inner.media.iter_mut().find(|(_, key, _)| *key == expected_key)
if let Some(media_content) =
inner.media.iter_mut().find(|media_content| media_content.key == expected_key)
{
*mxc = to.uri().to_owned();
*key = to.unique_key();
media_content.uri = to.uri().to_owned();
media_content.key = to.unique_key();
}
Ok(())
}
async fn get_media_content(&self, request: &MediaRequestParameters) -> Result<Option<Vec<u8>>> {
let expected_key = request.unique_key();
let inner = self.inner.read().unwrap();
Ok(inner.media.iter().find_map(|(_media_uri, media_key, media_content)| {
(media_key == &expected_key).then(|| media_content.to_owned())
}))
self.media_service.get_media_content(self, request).await
}
async fn remove_media_content(&self, request: &MediaRequestParameters) -> Result<()> {
@@ -157,10 +310,8 @@ impl EventCacheStore for MemoryStore {
let mut inner = self.inner.write().unwrap();
let Some(index) = inner
.media
.iter()
.position(|(_media_uri, media_key, _media_content)| media_key == &expected_key)
let Some(index) =
inner.media.iter().position(|media_content| media_content.key == expected_key)
else {
return Ok(());
};
@@ -174,24 +325,17 @@ impl EventCacheStore for MemoryStore {
&self,
uri: &MxcUri,
) -> Result<Option<Vec<u8>>, Self::Error> {
let inner = self.inner.read().unwrap();
Ok(inner.media.iter().find_map(|(media_uri, _media_key, media_content)| {
(media_uri == uri).then(|| media_content.to_owned())
}))
self.media_service.get_media_content_for_uri(self, uri).await
}
async fn remove_media_content_for_uri(&self, uri: &MxcUri) -> Result<()> {
let mut inner = self.inner.write().unwrap();
let expected_key = uri.to_owned();
let positions = inner
.media
.iter()
.enumerate()
.filter_map(|(position, (media_uri, _media_key, _media_content))| {
(media_uri == &expected_key).then_some(position)
})
.filter_map(|(position, media_content)| (media_content.uri == uri).then_some(position))
.collect::<Vec<_>>();
// Iterate in reverse-order so that positions stay valid after first removals.
@@ -201,16 +345,246 @@ impl EventCacheStore for MemoryStore {
Ok(())
}
async fn set_media_retention_policy(
&self,
policy: MediaRetentionPolicy,
) -> Result<(), Self::Error> {
self.media_service.set_media_retention_policy(self, policy).await
}
fn media_retention_policy(&self) -> MediaRetentionPolicy {
self.media_service.media_retention_policy()
}
async fn set_ignore_media_retention_policy(
&self,
request: &MediaRequestParameters,
ignore_policy: IgnoreMediaRetentionPolicy,
) -> Result<(), Self::Error> {
self.media_service.set_ignore_media_retention_policy(self, request, ignore_policy).await
}
async fn clean_up_media_cache(&self) -> Result<(), Self::Error> {
self.media_service.clean_up_media_cache(self).await
}
}
#[cfg_attr(target_family = "wasm", async_trait(?Send))]
#[cfg_attr(not(target_family = "wasm"), async_trait)]
impl EventCacheStoreMedia for MemoryStore {
type Error = EventCacheStoreError;
async fn media_retention_policy_inner(
&self,
) -> Result<Option<MediaRetentionPolicy>, Self::Error> {
Ok(self.inner.read().unwrap().media_retention_policy)
}
async fn set_media_retention_policy_inner(
&self,
policy: MediaRetentionPolicy,
) -> Result<(), Self::Error> {
self.inner.write().unwrap().media_retention_policy = Some(policy);
Ok(())
}
async fn add_media_content_inner(
&self,
request: &MediaRequestParameters,
data: Vec<u8>,
last_access: SystemTime,
policy: MediaRetentionPolicy,
ignore_policy: IgnoreMediaRetentionPolicy,
) -> Result<(), Self::Error> {
// Avoid duplication. Let's try to remove it first.
self.remove_media_content(request).await?;
let ignore_policy = ignore_policy.is_yes();
if !ignore_policy && policy.exceeds_max_file_size(data.len() as u64) {
// Do not store it.
return Ok(());
}
// Now, let's add it.
let mut inner = self.inner.write().unwrap();
inner.media.push(MediaContent {
uri: request.uri().to_owned(),
key: request.unique_key(),
data,
ignore_policy,
last_access,
});
Ok(())
}
async fn set_ignore_media_retention_policy_inner(
&self,
request: &MediaRequestParameters,
ignore_policy: IgnoreMediaRetentionPolicy,
) -> Result<(), Self::Error> {
let mut inner = self.inner.write().unwrap();
let expected_key = request.unique_key();
if let Some(media_content) = inner.media.iter_mut().find(|media| media.key == expected_key)
{
media_content.ignore_policy = ignore_policy.is_yes();
}
Ok(())
}
async fn get_media_content_inner(
&self,
request: &MediaRequestParameters,
current_time: SystemTime,
) -> Result<Option<Vec<u8>>, Self::Error> {
let mut inner = self.inner.write().unwrap();
let expected_key = request.unique_key();
// First get the content out of the buffer, we are going to put it back at the
// end.
let Some(index) = inner.media.iter().position(|media| media.key == expected_key) else {
return Ok(None);
};
let Some(mut content) = inner.media.remove(index) else {
return Ok(None);
};
// Clone the data.
let data = content.data.clone();
// Update the last access time.
content.last_access = current_time;
// Put it back in the buffer.
inner.media.push(content);
Ok(Some(data))
}
async fn get_media_content_for_uri_inner(
&self,
expected_uri: &MxcUri,
current_time: SystemTime,
) -> Result<Option<Vec<u8>>, Self::Error> {
let mut inner = self.inner.write().unwrap();
// First get the content out of the buffer, we are going to put it back at the
// end.
let Some(index) = inner.media.iter().position(|media| media.uri == expected_uri) else {
return Ok(None);
};
let Some(mut content) = inner.media.remove(index) else {
return Ok(None);
};
// Clone the data.
let data = content.data.clone();
// Update the last access time.
content.last_access = current_time;
// Put it back in the buffer.
inner.media.push(content);
Ok(Some(data))
}
async fn clean_up_media_cache_inner(
&self,
policy: MediaRetentionPolicy,
current_time: SystemTime,
) -> Result<(), Self::Error> {
if !policy.has_limitations() {
// We can safely skip all the checks.
return Ok(());
}
let mut inner = self.inner.write().unwrap();
// First, check media content that exceed the max filesize.
if policy.computed_max_file_size().is_some() {
inner.media.retain(|content| {
content.ignore_policy || !policy.exceeds_max_file_size(content.data.len() as u64)
});
}
// Then, clean up expired media content.
if policy.last_access_expiry.is_some() {
inner.media.retain(|content| {
content.ignore_policy
|| !policy.has_content_expired(current_time, content.last_access)
});
}
// Finally, if the cache size is too big, remove old items until it fits.
if let Some(max_cache_size) = policy.max_cache_size {
// Reverse the iterator because in case the cache size is overflowing, we want
// to count the number of old items to remove. Items are sorted by last access
// and old items are at the start.
let (_, items_to_remove) = inner.media.iter().enumerate().rev().fold(
(0u64, Vec::with_capacity(NUMBER_OF_MEDIAS.into())),
|(mut cache_size, mut items_to_remove), (index, content)| {
if content.ignore_policy {
// Do not count it.
return (cache_size, items_to_remove);
}
let remove_item = if items_to_remove.is_empty() {
// We have not reached the max cache size yet.
if let Some(sum) = cache_size.checked_add(content.data.len() as u64) {
cache_size = sum;
// Start removing items if we have exceeded the max cache size.
cache_size > max_cache_size
} else {
// The cache size is overflowing, remove the remaining items, since the
// max cache size cannot be bigger than
// usize::MAX.
true
}
} else {
// We have reached the max cache size already, just remove it.
true
};
if remove_item {
items_to_remove.push(index);
}
(cache_size, items_to_remove)
},
);
// The indexes are already in reverse order so we can just iterate in that order
// to remove them starting by the end.
for index in items_to_remove {
inner.media.remove(index);
}
}
inner.last_media_cleanup_time = current_time;
Ok(())
}
async fn last_media_cleanup_time_inner(&self) -> Result<Option<SystemTime>, Self::Error> {
Ok(Some(self.inner.read().unwrap().last_media_cleanup_time))
}
}
#[cfg(test)]
mod tests {
use super::{EventCacheStore, MemoryStore, Result};
use super::{MemoryStore, Result};
use crate::event_cache_store_media_integration_tests;
async fn get_event_cache_store() -> Result<impl EventCacheStore> {
async fn get_event_cache_store() -> Result<MemoryStore> {
Ok(MemoryStore::new())
}
event_cache_store_integration_tests!();
event_cache_store_integration_tests_time!();
event_cache_store_media_integration_tests!(with_media_size_tests);
}

Some files were not shown because too many files have changed in this diff Show More