Compare commits

...

2808 Commits

Author SHA1 Message Date
ganfra f92ccd8d40 feat(ffi): expose BeaconError from sdk to ffi
Benchmarks / Run Benchmarks (crypto_bench) (push) Failing after 1m29s
Benchmarks / Run Benchmarks (event_cache) (push) Failing after 6s
Benchmarks / Run Benchmarks (linked_chunk) (push) Failing after 6s
Benchmarks / Run Benchmarks (room_list) (push) Failing after 7s
Benchmarks / Run Benchmarks (store_bench) (push) Failing after 7s
Benchmarks / Run Benchmarks (timeline) (push) Failing after 7s
Bindings tests / xtask (push) Failing after 2m6s
Bindings tests / Run Complement Crypto tests (push) Failing after 0s
Rust tests / xtask (push) Failing after 42s
Rust tests / 🐧 [m], experimental-encrypted-state-events (push) Has been skipped
Rust tests / 🐧 [m], markdown (push) Has been skipped
Rust tests / 🐧 [m], no-encryption (push) Has been skipped
Rust tests / 🐧 [m], no-encryption-and-sqlite (push) Has been skipped
Rust tests / 🐧 [m], no-sqlite (push) Has been skipped
Rust tests / 🐧 [m], search (push) Has been skipped
Rust tests / 🐧 [m], socks (push) Has been skipped
Rust tests / 🐧 [m], sqlite-cryptostore (push) Has been skipped
Rust tests / 🐧 [m], sso-login (push) Has been skipped
Rust tests / 🐧 [m]-examples (push) Has been skipped
Rust tests / 🐧 [m]-crypto (push) Has been skipped
Rust tests / 🕸️ [m]-indexeddb (push) Has been skipped
Rust tests / 🕸️ [m]-base (push) Has been skipped
Rust tests / 🕸️ [m]-common (push) Has been skipped
Rust tests / 🕸️ [m], indexeddb stores (push) Has been skipped
Rust tests / 🕸️ [m], indexeddb stores, no crypto (push) Has been skipped
Rust tests / 🕸️ [m], no-default (push) Has been skipped
Rust tests / 🕸️ [m]-qrcode (push) Has been skipped
Rust tests / 🕸️ [m]-ui (push) Has been skipped
Rust tests / Lint (push) Has been skipped
Rust tests / 🐧 all crates, 🦀 beta (push) Failing after 1m4s
Rust tests / 🐧 all crates, 🦀 stable (push) Failing after 31s
Rust tests / Spell Check with Typos (push) Failing after 1m43s
Rust tests / Integration test (features: default) (push) Failing after 1m20s
Rust tests / Integration test (features: experimental-encrypted-state-events) (push) Failing after 36s
Code Coverage / xtask (push) Failing after 29s
Code Coverage / Code Coverage (push) Has been skipped
Documentation / All crates (push) Failing after 2m1s
Rust version / msrv (push) Failing after 40s
Lint GHA workflows with zizmor / Run zizmor (push) Failing after 9m53s
Bindings tests / Test UniFFI bindings generation (push) Has been skipped
Bindings tests / matrix-rust-components-kotlin (push) Has been skipped
Bindings tests / Generate Crypto FFI Apple XCFramework (push) Has been cancelled
Rust tests / 🍏 all crates, 🦀 stable (push) Has been cancelled
Bindings tests / matrix-rust-components-swift (push) Has been cancelled
2026-04-23 11:25:24 +02:00
Jorge Martín 2c2e0f1b15 doc: Add doc comments and changelog entries 2026-04-22 17:26:51 +02:00
Jorge Martín f314578df5 feat(ffi): Add Client::get_dm_rooms
Expose the new function in the FFI layer
2026-04-22 17:26:51 +02:00
Jorge Martín 739289cd82 feat(ui): Add Client::get_dm_rooms
This function returns an iterator with all the available rooms that match the behaviour previously in `Client::get_dm`. `Client::get_dm` now just calls this function and returns the first item.
2026-04-22 17:26:51 +02:00
Kévin Commaille 1d7d6c943b feat(sdk): Add an expiry to the cached homeserver capabilities
I believe that it is a footgun to cache the data indefinitely by default
so this copies the same behavior as for the supported versions in the
ClientCaches: it sets an expiry duration of 1 day and refreshes the data
in the background when it has expired.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-04-22 14:21:34 +03:00
Damir Jelić 917f9ee298 chore: Bump vodozemac 2026-04-22 12:30:38 +02:00
Andy Balaam c7d44ddad3 Change in to into
Signed-off-by: Andy Balaam <mail@artificialworlds.net>
2026-04-22 10:52:25 +01:00
Kévin Commaille 1a170ddf90 fix(common): The expiry duration of TtlValue is 1 day
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-04-22 10:52:25 +01:00
Kévin Commaille 696d1cacbe refactor(common): Don't check expiry for TtlValue::into_data()
This API is no longer in use so we can remove it. We also rename
`into_data_unchecked()` to `into_data()`.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-04-22 10:52:25 +01:00
Kévin Commaille c6d1cf20b6 feat(sdk): Refresh well-known cache in the background
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-04-22 10:52:25 +01:00
Jonas Platte 38f34e66eb Collapse an else { if ... }
as suggested by clippy.
2026-04-22 08:52:04 +02:00
Jonas Platte 8fa411aaa0 Clean up a lint 2026-04-22 08:52:04 +02:00
Jonas Platte b4b3b5258c Clean up some imports 2026-04-22 08:52:04 +02:00
Andy Balaam fd6cb6647f Support stable prefix for MSC4287 m.key_backup 2026-04-21 14:17:26 +01:00
Andy Balaam 103ff7d3ec Use stable identifier for m.shared_history 2026-04-21 13:47:19 +01:00
Andy Balaam efa28a1ffd Use stable identifiers for m.room_key_bundle and m.history_not_shared 2026-04-21 13:47:19 +01:00
Andy Balaam c4640897d4 Test for serialization and deserialization of a room key bundle 2026-04-21 13:47:19 +01:00
Benjamin Bouvier 0487a143ed test(integration): workaround non-working subscription to make the test pass in CI 2026-04-21 14:43:55 +02:00
Benjamin Bouvier 7ab97d59b8 test(integration): add an integration test for computing latest events in a few rooms 2026-04-21 14:43:55 +02:00
Benjamin Bouvier 940862ebed chore: bump the integration testing synapse version to v1.151.0 2026-04-21 14:43:55 +02:00
Jorge Martín e507eaabf6 feat(ffi): Expose ffi::NotificationRoomInfo::service_members
This is needed in some clients to know if a direct room is a DM or not
2026-04-21 13:22:58 +02:00
Jorge Martín a2bbc33920 doc(ffi): Add changelog entry 2026-04-21 10:38:14 +01:00
Jorge Martín db787c28b8 refactor(sdk-crypto): Fix several issues related to the newly enabled experimental-push-secrets feature 2026-04-21 10:38:14 +01:00
Jorge Martín a3d6114ddd refactor(ffi): Use the shared default features for the Kotlin bindings
This should align them with iOS.
2026-04-21 10:38:14 +01:00
Jorge Martín 58a139bf96 feat(ffi): Enable experimental-push-secrets feature by default 2026-04-21 10:38:14 +01:00
Kévin Commaille 012bc03014 refactor: Use TaskMonitor::spawn_finite_task
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-04-21 11:18:43 +02:00
Kévin Commaille 38adf952bd chore: Fix typo
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-04-21 11:18:43 +02:00
Kévin Commaille 99d675d85a feat(sdk): Refresh supported versions cache in the background
The previous behavior was making the data unavailable as soon as it is
expired, forcing the user to make a request to access fresh data. This
change keeps the data after it is expired, allowing the function to
return quickly, and triggers a background task to refresh it so that
later calls will be able to use the updated data.

This is a step towards #6090. While it's not clear in the issue what
should be the trigger for the background refreshes, this implements at
least the background refresh while keeping the expiry time as a trigger.

The next steps are to apply this to other cached values and shorten the
expiry time to something like 1 day, which should be more helpful.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-04-21 11:18:43 +02:00
Kévin Commaille be5eebaa35 refactor(base): Move and rename TtlStoreValue
Its is now available as `matrix_sdk_common::ttl_cache::TtlValue`,
allowing to use it outside of a store.

It also supports deserializing data without a timestamp, to allow to
add an expiration time to data that was already persisted.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-04-21 11:18:43 +02:00
Benjamin Bouvier cf4d3cb492 doc(ffi): clarify when to call Client::enable_automatic_backpagination
Since the boolean is read only once when subscribing the event cache,
let's make it clear when the FFI function should be called (based on
most prominent types used at the FFI layer).
2026-04-21 10:14:52 +02:00
dependabot[bot] ad5241b2e5 chore(deps): bump bnjbvr/cargo-machete
Bumps [bnjbvr/cargo-machete](https://github.com/bnjbvr/cargo-machete) from e82b6af0d3610e62ddf49bf7c62e662d7e31db0d to ac30a525c0a8d163a92d727b3ff079ee3f6ecb08.
- [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/e82b6af0d3610e62ddf49bf7c62e662d7e31db0d...e82b6af0d3610e62ddf49bf7c62e662d7e31db0d)

---
updated-dependencies:
- dependency-name: bnjbvr/cargo-machete
  dependency-version: e82b6af0d3610e62ddf49bf7c62e662d7e31db0d
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-21 10:13:59 +02:00
dependabot[bot] d083c56ccb chore(deps): bump codecov/codecov-action from 5.5.2 to 6.0.0
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.5.2 to 6.0.0.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/671740ac38dd9b0130fbe1cec585b89eea48d3de...57e3a136b779b570ffcdbf80b3bdc90e7fab3de2)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-21 10:02:08 +02:00
dependabot[bot] eb493de1b8 chore(deps): bump actions/cache from 5.0.4 to 5.0.5
Bumps [actions/cache](https://github.com/actions/cache) from 5.0.4 to 5.0.5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/668228422ae6a00e4ad889ee87cd7109ec5666a7...27d5ce7f107fe9357f9df03efb73ab90386fccae)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: 5.0.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-21 10:01:44 +02:00
dependabot[bot] 6aa4fdaed4 chore(deps): bump actions/upload-pages-artifact from 4.0.0 to 5.0.0
Bumps [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact) from 4.0.0 to 5.0.0.
- [Release notes](https://github.com/actions/upload-pages-artifact/releases)
- [Commits](https://github.com/actions/upload-pages-artifact/compare/7b1f4a764d45c48632c6b24a0339c27f5614fb0b...fc324d3547104276b827a68afc52ff2a11cc49c9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-21 09:59:22 +02:00
dependabot[bot] 9c6bbd98a7 chore(deps): bump taiki-e/install-action from 2.68.26 to 2.75.10
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.68.26 to 2.75.10.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.68.26...85b24a67ef0c632dfefad70b9d5ce8fddb040754)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.75.10
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-21 09:47:28 +02:00
Benjamin Bouvier 8b14a8920c doc(sdk): add a module doc code example for search 2026-04-20 12:18:52 +02:00
Benjamin Bouvier 98b36fe5e0 chore: update changelogs 2026-04-20 12:18:52 +02:00
Benjamin Bouvier a1f32d741f refactor(ffi): remove unused struct 2026-04-20 12:18:52 +02:00
Benjamin Bouvier ce01854078 refactor(sdk): Room::search_messages and Client::search_messages 2026-04-20 12:18:52 +02:00
Benjamin Bouvier 7f8c9b0244 refactor(search): pass the number of results per batch in the ctors 2026-04-20 12:18:52 +02:00
Benjamin Bouvier 3621acb445 refactor(sdk): move the high-level search helpers back to the SDK crate 2026-04-20 12:18:52 +02:00
Benjamin Bouvier 3789374951 refactor(sdk): move search related functions to a message_search module 2026-04-20 12:18:52 +02:00
Benjamin Bouvier e54a36a1ca refactor(search): simplify global search more by avoiding is_done tracking 2026-04-20 12:18:52 +02:00
Benjamin Bouvier cf95c3f4cc refactor(search): avoid using a HashMap for linear iteration 2026-04-20 12:18:52 +02:00
Benjamin Bouvier 6ccfc92139 refactor(search): simplify code around global search 2026-04-20 12:18:52 +02:00
Benjamin Bouvier 4ee20befa3 chore: address first batch of review comments 2026-04-20 12:18:52 +02:00
Benjamin Bouvier 4348d9de23 chore: add changelog entries for search! 2026-04-20 12:18:52 +02:00
Benjamin Bouvier 477ee70e1c feat(ffi): bindings for search 2026-04-20 12:18:52 +02:00
Benjamin Bouvier 83989c030a feat(ffi): allow enabling search with a search index store 2026-04-20 12:18:52 +02:00
Benjamin Bouvier 717b6371f8 feat(multiverse): add support for global search (ctrl+g) 2026-04-20 12:18:52 +02:00
Benjamin Bouvier 6fa832cfc8 feat(search): higher-level Search API 2026-04-20 12:18:52 +02:00
Ivan Enderlin 1bff8cbe5d chore(ffi): Do not log errors in ClientError::from_str.
This patch stops logging errors in `ClientError::from_str` as it was an
artifact of some old debugging sessions.
2026-04-20 11:33:15 +02:00
Ivan Enderlin 9bb67e8422 fix(ffi): Use the ERROR log level for errors.
This patch changes a `warn!` to an `error!` in `ClientError`. There are
errors, let's use the proper log level here.
2026-04-20 11:33:15 +02:00
Damir Jelić c962a52f73 ci: Add a zizmor action 2026-04-20 09:49:55 +02:00
Damir Jelić 4d20b481c1 ci: Upload the coverage directly as part of the coverage workflow 2026-04-20 09:49:55 +02:00
Damir Jelić a5136c7384 ci: Add a zizmor config and use correct hashes for our github actions 2026-04-20 09:49:55 +02:00
Andy Balaam 0e120549ce Remove unneeded TODO in RoomKeyBundleContent
Quoting @poljar:

> If you follow the stack till you get where the secret key is, you end up here:
> https://github.com/ruma/ruma/blob/6282fd8838fd2c84f252f85973d8a43577ced91a/crates/ruma-events/src/room.rs#L269-L273
> And that is now zeroized.
> So yeah, the TODO was resolved in Ruma.
2026-04-17 16:18:36 +02:00
Damir Jelić 1e74c48351 fix(thread): Only apply valid edits in the thread summary 2026-04-17 12:34:47 +02:00
Damir Jelić 9e242abda9 test(latest-event): Check that we don't apply invalid edits in the latest event 2026-04-17 12:34:47 +02:00
Damir Jelić 62ff895ce8 fix(latest-event): Don't apply invalid edits on the latest event 2026-04-17 12:34:47 +02:00
Damir Jelić d0d22ece78 refactor(latest-event): Ensure we have access to the event JSON when deciding on a latest event 2026-04-17 12:34:47 +02:00
Damir Jelić d49d86e8b0 fix(ui): Follow the spec more closely in how we reject invalid edits 2026-04-17 12:34:47 +02:00
Damir Jelić b226191bf4 test(common): Add some tests to validate the edit validation logic 2026-04-17 12:34:47 +02:00
Damir Jelić 21d7cd1b9d feat(common): Add a function to validate edits 2026-04-17 12:34:47 +02:00
Ivan Enderlin b066260554 doc(ui): Add #6459 in the `CHANGELOG.md. 2026-04-17 11:02:21 +02:00
Ivan Enderlin dbc6e3c3aa fix(ui): Fix a possible panic in Switch by updating async-rx.
This patch updates `async-rx` to fix a possible panic in `Switch`. It
now requires the outer stream to implement `FusedStream` (for the moment).
2026-04-17 11:02:21 +02:00
Benoit Marty 3293c94451 feat(ffi): Expose Client.request_openid_token() in order to let sdk clients request an openId token 2026-04-17 08:29:43 +00:00
Andy Balaam 53385798f6 Changelog update for #6457 2026-04-17 09:15:58 +01:00
Andy Balaam d49736455b Discard room key if someone leaves a room for a non-leave reason e.g. ban 2026-04-17 09:15:58 +01:00
Andy Balaam e278a99a20 Factor out parts of test_history_share_on_invite_room_key_rotation 2026-04-17 09:15:58 +01:00
Andy Balaam d5c7c886a1 Add a note helping to understand that an existing test is sufficient to cover e.g. Ban events as well as Leave 2026-04-17 09:15:58 +01:00
Ivan Enderlin b9b40c0ef2 doc(sdk): Add #6453 in the `CHANGELOG.md. 2026-04-15 17:49:46 +02:00
Ivan Enderlin 99d60bcc70 fix(sdk): Fix an infinite loop when loading pinned events from the storage.
This patch fixes a critical infinite loop when loading pinned events
from the storage if and only if there is more than one chunk for the
pinned events.

The previous implementation was broken in two ways:

1. if there was no previous chunk, then `prev` was set to `None`, but
   `previous` was never updated, so it was loop forever,
2. `load_previous_chunk` was called with the `last_chunk.previous` chunk
   identifier, which is incorrect: it must be called with the last chunk
   identifier, not the previous': otherwise it skips one chunk every time.

This patch adds a test to ensure everything works as expected.
2026-04-15 17:49:46 +02:00
Damir Jelić 64d602141a chore: Bump rustls and rustls-webpki
There appears to be a security issue.
2026-04-15 15:04:02 +02:00
Floriane TUERNAL SABOTINOV 59d2f8d4af refactor(examples): update get-profiles example to use Account::fetch_user_profile
Fixes #5902

## What this PR does

Keeps the existing `Client::send()` demo but extends it with:

- A doc comment on `get_profile` explaining the spec behaviour and the 401 
condition on  (Synapse's `require_auth_for_profile_requests`)
- A `get_profile_authenticated()` fallback using `account().fetch_user_profile()` 
which internally uses `force_auth()`
2026-04-15 13:00:41 +00:00
Kévin Commaille 80a253ada9 test(sdk): Add tests for profile capabilities
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-04-15 13:14:27 +02:00
Kévin Commaille ec5082bb9c fix(client): Fix logic for profile fields capabilities
The current implementation assumes that if the `m.profile_fields`
capability is missing, it means that the capability is not enabled, but
this is not what the spec says. However the spec says that it depends on
the Matrix versions advertised by the homeserver. So this adds a method
that properly computes the capability depending on the supported
versions too.

Similarly, for the display name and avatar URL fields the implementation
assumes that if the `m.profile_fields` capability is present but
disabled, we should fallback to the legacy capabilities. This behavior
is not present in the spec, so the code is changed to always use the new
capability when it is present, whether it is enabled or not. The
legacy capabilities are only used if the new capability is missing and
the homeserver doesn't advertise support for extended profile fields.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-04-15 13:14:27 +02:00
Johannes Marbach 63f5be6935 feat(sync): Allow setting a custom Sliding Sync connection ID and timeline limit on RoomListService
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2026-04-15 11:06:57 +02:00
dependabot[bot] dab87b7731 chore(deps): bump CodSpeedHQ/action from 4.12.1 to 4.13.0
Bumps [CodSpeedHQ/action](https://github.com/codspeedhq/action) from 4.12.1 to 4.13.0.
- [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/1c8ae4843586d3ba879736b7f6b7b0c990757fab...db35df748deb45fdef0960669f57d627c1956c30)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-14 16:03:20 +02:00
dependabot[bot] 63f33a081f chore(deps): bump taiki-e/install-action from 2.68.26 to 2.74.0
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.68.26 to 2.74.0.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.68.26...v2.74.0)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.74.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-14 15:42:51 +02:00
Damir Jelić 8b762b04df ci: Install cargo-llvm-cov explicitly for the coverage action as well 2026-04-14 15:05:28 +02:00
Ivan Enderlin a9ef675b1c chore(sdk): Move `insert_sent_event_from_send_queue. 2026-04-14 14:45:14 +02:00
Ivan Enderlin e69bb96144 test(integration): Fix the test_local_echo_to_send_event_has_encryption_info test.
This test forgot to subscribe to the Event Cache, hence the result
was partially okay. It was working by luck before. With the previous
commits, it was not possible to work without the Event Cache being
listening to the sync.
2026-04-14 14:45:14 +02:00
Ivan Enderlin 63b8fc8b47 test: Adjust tests according to previous patches.
This patch adjust tests since the Event Cache ignores events from the
Send Queue if the cache is empty. It mostly impacts the tests as this
scenario is pretty rare in real world use cases.
2026-04-14 14:45:14 +02:00
Ivan Enderlin d68879d0aa test(sdk): Test Send Queue inserts in the Event Cache as expected. 2026-04-14 14:45:14 +02:00
Ivan Enderlin 1afebd6d4e fix(sdk): Events from the Send Queue are inserted in the Event Cache if and only if it is no empty.
This patch fixes a bug where inserting an event from the Send Queue in
an empty Event Cache will break the back-pagination logic. Indeed, the
detection of the start of the timeline during the back-pagination is
conditioned to the emptiness of the cache:

- if there is no gap, and if the cache is not empty, then we consider
  the start of the timeline has been reached.

However, if an event from the Send Queue has been inserted, with no
previous batch token (because none can be computed at this step), no gap
is present and the cache won't be empty, so… this is wrongly assumed to
be the start of the timeline.

The solution to this problem is to insert the event from the Send Queue
if the cache is not empty.
2026-04-14 14:45:14 +02:00
Damir Jelić db4c1f8dbd ci: Explicitly state that we want cargo-hack installed 2026-04-14 14:44:58 +02:00
dependabot[bot] c1b89a2751 chore(deps): bump crate-ci/typos from 1.44.0 to 1.45.0
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.44.0 to 1.45.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/631208b7aac2daa8b707f55e7331f9112b0e062d...cf5f1c29a8ac336af8568821ec41919923b05a83)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-14 14:43:37 +02:00
Damir Jelić 9d39e4bae8 ci: Explicitly state which tool we want to install, namely nextest 2026-04-14 13:55:26 +02:00
Ivan Enderlin 936f173e6f doc(sdk): Add #6396 in the CHANGELOG.md. 2026-04-14 12:51:02 +02:00
Ivan Enderlin 9eb4a5a24c test(sdk): Add a test to ensure a redacted latest event is removed. 2026-04-14 12:51:02 +02:00
Ivan Enderlin 49bba4d471 test(sdk): A TODO has been resolved. 2026-04-14 12:51:02 +02:00
Ivan Enderlin f23deacec4 fix(sdk): Do not update the LatestEventValue if its event ID matches the previous value.
This patch updates the logic to update a `LatestEventValue`. We still
can't compare two `LatestEventValue` because the type doesn't implement
`PartialEq`. However, we can use the `EventId` in some case.

It fixes https://github.com/matrix-org/matrix-rust-sdk/issues/6381.
2026-04-14 12:51:02 +02:00
Jorge Martín 9c28ee7041 feat(ffi): Add ffi::Client::set_avatar_url
This method uses `Account::set_avatar_url` under the hood to update the user's avatar to the provided MXC url
2026-04-14 12:08:47 +02:00
dependabot[bot] 5d420b683f chore(deps): bump bnjbvr/cargo-machete
Bumps [bnjbvr/cargo-machete](https://github.com/bnjbvr/cargo-machete) from b81ce1560c5fbd0210cb66d88bf210329ff04266 to e82b6af0d3610e62ddf49bf7c62e662d7e31db0d.
- [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/b81ce1560c5fbd0210cb66d88bf210329ff04266...e82b6af0d3610e62ddf49bf7c62e662d7e31db0d)

---
updated-dependencies:
- dependency-name: bnjbvr/cargo-machete
  dependency-version: e82b6af0d3610e62ddf49bf7c62e662d7e31db0d
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-14 11:12:08 +02:00
dependabot[bot] 2d5b95e45e chore(deps): bump rand from 0.10.0 to 0.10.1
Bumps [rand](https://github.com/rust-random/rand) from 0.10.0 to 0.10.1.
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.0...0.10.1)

---
updated-dependencies:
- dependency-name: rand
  dependency-version: 0.10.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-14 09:49:29 +02:00
Benjamin Bouvier 3d29db5ebb refactor(tests): simplify timeline/reactions tests to remove custom respond_with 2026-04-13 17:20:57 +02:00
Benjamin Bouvier 8e041d6958 refactor(tests): use MatrixMockServer even more in room_list_service 2026-04-13 17:20:57 +02:00
Benjamin Bouvier c02d0f1d01 refactor(tests): make more use of the MatrixMockServer in ui tests 2026-04-13 17:20:57 +02:00
Benjamin Bouvier 2da262e56c refactor(tests): make use of MatrixMockServer in other room_list_service tests 2026-04-13 17:20:57 +02:00
Benjamin Bouvier 31e4657718 refactor(tests): make use of MatrixMockServer in room_list_service 2026-04-13 17:20:57 +02:00
Benjamin Bouvier 5b2fe3cf47 refactor(tests): make use of MatrixMockServer in notification_client 2026-04-13 17:20:57 +02:00
Benjamin Bouvier 49d7d4528d refactor(tests): make use of MatrixMockServer in sync_service 2026-04-13 17:20:57 +02:00
Benjamin Bouvier 3d7e505530 refactor(tests): make use of MatrixMockServer in encryption_sync_service 2026-04-13 17:20:57 +02:00
Benjamin Bouvier 56d4163982 refactor(tests): make use of MatrixMockServer in timeline/pagination 2026-04-13 17:20:57 +02:00
Benjamin Bouvier 4cafc48cc4 refactor(tests): make use of MatrixMockServer in timeline/focus_event 2026-04-13 17:20:57 +02:00
Benjamin Bouvier 8ea55b30bc refactor(tests): make use of MatrixMockServer in timeline/sliding_sync
We could do better, and have even a specialized sliding sync endpoint
helper, but that'll be for another day :)
2026-04-13 17:20:57 +02:00
Benjamin Bouvier d438ee2ca0 refactor(tests): add a test helper to send with a delay, use it in timeline/queue 2026-04-13 17:20:57 +02:00
Benjamin Bouvier a3fe9eef12 refactor(tests): make use of MatrixMockServer in timeline/queue 2026-04-13 17:20:57 +02:00
Benjamin Bouvier 60056ba205 refactor(tests): make use of MatrixMockServer in timeline/subscribe 2026-04-13 17:20:57 +02:00
Benjamin Bouvier ff62ed667c refactor(tests): make use of MatrixMockServer in timeline/pinned_event 2026-04-13 17:20:57 +02:00
Benjamin Bouvier 4168362912 refactor(tests): make use of MatrixMockServer in the timeline/profiles tests 2026-04-13 17:20:57 +02:00
ganfra 96d63b7c8d doc: update changelogs 2026-04-13 16:57:57 +02:00
ganfra 260eaeea2b feat (live location): rewrite live location shares subscription logic 2026-04-13 16:57:57 +02:00
ganfra f7442fa279 fix(live location): include BeaconInfo in required state 2026-04-13 16:57:57 +02:00
ganfra da3cf3d54b doc: update changelog 2026-04-13 16:57:06 +02:00
ganfra 872861e90e Handle beacon stop aggregation in latest event
Support BeaconStop aggregation to properly display stopped
live location sharing in the latest event timeline content.
2026-04-13 16:57:06 +02:00
Hubert Chathi 36cca4e444 add comment 2026-04-10 14:52:39 +01:00
Hubert Chathi bb6202c76b fix lint 2026-04-10 14:52:39 +01:00
Hubert Chathi 5458a5afd2 add changelog 2026-04-10 14:52:39 +01:00
Hubert Chathi 45fa860b6b add secret pushing feature to FFI 2026-04-10 14:52:39 +01:00
Hubert Chathi 4ad25e32af handle the backup key being pushed from other devices 2026-04-10 14:52:39 +01:00
Hubert Chathi 423ebf9502 push the backup key to our other devices when we create a new backup 2026-04-10 14:52:39 +01:00
Damir Jelić 7fbb8cf9e5 fix(spaces): Ensure space children ordering is transitive
This patch fixes the SpaceRoom::compare_rooms method to be transitive
(If A ≤ B and B ≤ C then A ≤ C). The transitive property of a comparison
function is required by the sorting functions we are using.

If the property doesn't hold, sorting will panic.

The previous logic violated strict weak ordering by falling back towards
room ID comparison as soon as one of the values doesn't have a
SpaceRoomChildState.

This introduced inconsistent ordering paths where:
    - A and B would be compared using the SpaceRoomChildState
    - B and C would be compared using only the room ID
    - A and C would be compared using only the room ID

Leading to the case where:
    - A < B due to the state
    - B < C due to the room ID
    - and finally A > C due to the room ID.

As a result, transitivity could be broken (A < B, B < C, but C < A),
leading to a panic during the sorting.
2026-04-10 15:49:26 +02:00
Damir Jelić e0cac4eb7a test(spaces): Check that space children ordering is total 2026-04-10 15:49:26 +02:00
Damir Jelić aa9cc67f32 refactor(spaces): Make the space children comparison function simpler 2026-04-10 15:49:26 +02:00
Damir Jelić 7d6569ff27 test(spaces): Test that the top-level space ordering is total 2026-04-10 15:49:26 +02:00
Damir Jelić 4d7b19999b refactor(space): Move the top-level space comparison logic into a separate function 2026-04-10 15:49:26 +02:00
Daniel Salinas d47306045f Add cfg around inadvertent use of sqlite in matrix-sdk-ffi crate
Several new functions implicitly rely on the sqlite config.
sqlite might not be present if you are using the indexedb store, in which case
these functions are likely irrelevant.

This change conditionalizes their compilation.
2026-04-10 14:11:36 +02:00
Philippe Bertin ba461598b2 feat(ffi): expose latest json
Signed-off-by: Philippe Bertin <pbertin@teladochealth.com>
2026-04-09 15:57:29 +02:00
Stanislav Skobelkin 0233d03d4e fix(ffi): Export Eq and Hash traits in enums used as HashMap keys
matrix_sdk_ffi::ruma::TagName and matrix_sdk_ffi::event::TimelineEventType enums
that are used as HashMap keys by matrix_sdk_ffi::ruma::Tag and
matrix_sdk_ffi::room::power_levels::RoomPowerLevels respectively should probably
export Eq and Hash traits, so that we can generate bindings for languages that
implement uniffi's record/HashMap type using a hash table (e.g.
std::unordered_map in C++)

Signed-off-by: Stanislav Skobelkin <stanislav@skobelk.in>
2026-04-09 12:12:57 +02:00
Damir Jelić 725f74cbe8 fix(ffi): Correctly return that the backup is complete if the backup key is valid 2026-04-09 12:01:04 +02:00
Benjamin Bouvier 98aceb40c2 refactor(timeline): make sure all the timeline background tasks are aborted on drop
Otherwise, we might run into leaks!
2026-04-08 16:19:09 +02:00
Damir Jelić 34a35ad32a feat(ffi): Allow passing in a SecretsBundle after logging in (#6212)
This allows people to get a secrets bundle out of band or out of a database and import it
after logging in a new client.

Mainly targeted to support the Element Classic -> Element X migration.

Signed-off-by: Damir Jelić <poljar@termina.org.uk>
Co-authored-by: Benoit Marty <benoitm@matrix.org>
2026-04-08 14:07:57 +00:00
Benjamin Bouvier 99971a2347 chore(sdk): add a changelog entry for the task monitor 2026-04-08 15:14:44 +02:00
Benjamin Bouvier b5f5a9c90e refactor(sdk): rename functions to spawn background tasks 2026-04-08 15:14:44 +02:00
Benjamin Bouvier ec4e228620 feat(sdk): use the new method in the context of two background jobs 2026-04-08 15:14:44 +02:00
Benjamin Bouvier a2c254d7da feat(sdk): add a way to spawn one-off jobs 2026-04-08 15:14:44 +02:00
Kevin Boos 557c0698ba Spaces: add suggested field to SpaceRoom (#6417)
Previously there wasn't a way to determine whether a given sub-space or
room within a given space had been marked as "suggested".
This was one of the things missing in support for spaces (#227) even
though it wasn't explicitly mentioned.
   
I tested that this fix does work properly in
[Robrix](https://github.com/project-robius/robrix).

Signed-off-by: Kevin Boos kevinaboos@gmail.com
2026-04-08 13:58:56 +02:00
Bryant Mairs 99a5568dbf feat: add missing APIs to HomeserverCapabilities
Add `m.room_versions` & `m.account_moderation` that were missed in the
first implementation.

Signed-off-by: Bryant Mairs <bryant@mai.rs>
2026-04-08 13:48:20 +02:00
Jonas Platte 94d8674709 refactor(ffi): Deduplicate thumbnail conversion code 2026-04-08 07:58:36 +02:00
Jonas Platte dfc4e03d5b refactor(ffi): Replace nested match with if-let chain 2026-04-08 07:58:36 +02:00
Jonas Platte 14b8248e4b refactor(ffi): Extract closure as fn to reduce indentation 2026-04-08 07:58:36 +02:00
Jonas Platte 447c89aed4 refactor(ffi): Merge separate impl blocks 2026-04-08 07:58:36 +02:00
Jonas Platte 894f94c383 Finish Rust Edition 2024 migration 2026-04-08 07:56:03 +02:00
Johannes Marbach 1f3dea778b feat(send_queue): send redactions via the send queue (#6250)
This is a first step towards
https://github.com/matrix-org/matrix-rust-sdk/issues/4162 and adds a way
to send redactions (including their local echoes) via the send queue.

I had to introduce new variants for `SentRequestKey` and
`LocalEchoContent` because in some room versions the redacted event ID
sits at the top-level of the event rather than in `content`.

At the timeline level redactions are handled via a new boolean flag in
`AggregationKind::Redaction`. Local echoes of redactions merely set a
flag on the timeline event whereas remote echoes of redactions lead to
actual redactions as before.

The FFI bindings will be updated in a follow-up PR.

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2026-04-07 18:02:59 +02:00
Damir Jelić b07069170f Merge pull request #6409 from mgoldenberg/remove-native-tls
Remove support for `native-tls`
2026-04-07 16:03:09 +02:00
Michael Goldenberg f685feed40 doc(sdk): update change log
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-04-07 09:45:29 -04:00
Michael Goldenberg 610d4ca4e5 doc(ui): update change log
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-04-07 09:45:27 -04:00
Michael Goldenberg 41edb08283 doc(ffi): update change log
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-04-07 09:44:34 -04:00
Michael Goldenberg 44aad14732 doc: update references to tls features
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-04-07 09:44:34 -04:00
Michael Goldenberg b1fbf31c2e feat(sdk): remove rustls-tls feature flag
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-04-07 09:44:33 -04:00
Michael Goldenberg 2bcab72dbb ci: remove reference to matrix-sdk/rustls-tls feature flag
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-04-07 09:44:33 -04:00
Michael Goldenberg 3bac5d250d refactor(benchmarks): remove reference to matrix-sdk/rustls-tls feature flag
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-04-07 09:44:33 -04:00
Michael Goldenberg 4ec75b5b5c feat(ui): remove rustls-tls feature flag
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-04-07 09:44:33 -04:00
Michael Goldenberg c8c9a94995 feat(ffi): remove rustls-tls feature flag
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-04-07 09:44:33 -04:00
Michael Goldenberg 08b80c533a feat(sdk): enforce use of rustls + remove extraneous build checks
Note that the rustls-tls feature flag is temporarily left in place
to keep other crates from breaking, but it becomes a trivial flag.
It will be removed altogether in a later commit.

Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-04-07 09:44:33 -04:00
Michael Goldenberg 0e679904e3 feat(sdk): remove support for native-tls
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-04-07 09:44:33 -04:00
Michael Goldenberg 89c50c7ecd feat(ffi): remove support for native-tls
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-04-07 09:44:33 -04:00
Michael Goldenberg d2857ee20a feat(ui): remove support for native-tls
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-04-07 09:44:33 -04:00
Hubert Chathi c1274cea14 Add support for pushing secrets and receiving secret pushes (#6164)
see MSC4385

Pushing secrets allow devices to send secrets to other devices without waiting for a secret request.
2026-04-07 12:26:17 +01:00
Jonas Platte 12cd1effdc refactor(ffi): Fix new clippy lints 2026-04-07 12:39:12 +02:00
Jonas Platte 3a14b73258 chore(ffi): Rerun cargo fmt 2026-04-07 12:39:12 +02:00
Jonas Platte d97f4ab8b3 chore(ffi): Upgrade to Rust edition 2024 2026-04-07 12:39:12 +02:00
Bryant Mairs 3539487c49 feat: expose call_intent for m.rtc.notification
This allows for notifications to use the intent for deciding how to
render the message (e.g. whether to call it a "call" or a "video call").

Signed-off-by: Bryant Mairs <bryant@mai.rs>
2026-04-07 10:26:26 +02:00
dependabot[bot] 526f35e3d3 chore(deps): bump codecov/codecov-action from 5.5.2 to 6.0.0
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.5.2 to 6.0.0.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/671740ac38dd9b0130fbe1cec585b89eea48d3de...57e3a136b779b570ffcdbf80b3bdc90e7fab3de2)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-07 10:18:38 +02:00
dependabot[bot] 6b4e2655cc chore(deps): bump actions/deploy-pages from 4.0.5 to 5.0.0
Bumps [actions/deploy-pages](https://github.com/actions/deploy-pages) from 4.0.5 to 5.0.0.
- [Release notes](https://github.com/actions/deploy-pages/releases)
- [Commits](https://github.com/actions/deploy-pages/compare/d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e...cd2ce8fcbc39b97be8ca5fce6e763baed58fa128)

---
updated-dependencies:
- dependency-name: actions/deploy-pages
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-07 10:18:11 +02:00
Kévin Commaille 30de5372eb Upgrade Ruma after api::Error breaking change
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-04-07 10:17:42 +02:00
Kévin Commaille ba451b67c9 Upgrade Ruma after RoomAliases removal
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-04-07 10:17:42 +02:00
Benjamin Bouvier a65fec3175 refactor(event cache): use a const array for receipt types 2026-04-02 14:29:09 +02:00
Benjamin Bouvier 6298b5c176 feat(event cache): include read receipts that were dormant in the state store 2026-04-02 14:29:09 +02:00
Benjamin Bouvier 0283ae14d9 doc(base): clarify comments around read receipts related store methods 2026-04-02 14:29:09 +02:00
Benjamin Bouvier a1157d2c38 refactor(event cache): clarify warning when a pinned event couldn't be loaded
The previous warning could trigger with abnormal numbers, since there
could be related events applying to a pinned event, the `loaded_events`
list could be bigger than the list of `pinned_event_ids`, due to the
eager flattening. This patch makes it display the correct number, by
postponing the flattening after the warning.
2026-04-02 12:39:18 +02:00
Valere Fedronic fcce02fc11 feat(widget): Support for avatars in calls via msc4039 (#6354)
Allows to get the avatars in Element Call widget !
Using
[MSC4039](https://github.com/nordeck/matrix-spec-proposals/blob/nic/feat/widgetapi-upload-files/proposals/4039-widget-api-media.md)

Counter part of web support:
- https://github.com/element-hq/element-web/pull/32755
- https://github.com/element-hq/element-call/pull/3780

Needs a EC PR to support passing data as base64 via the widget json API
- https://github.com/element-hq/element-call/pull/3818
2026-04-02 13:35:22 +03:00
Benjamin Bouvier 7cea5be9e2 refactor(timeline): allow creating a TimelineItemContent from a sdk::TimelineEvent 2026-04-02 12:34:01 +02:00
Bertin Philippe 32e9626e0f feat(ffi): expose event types and content (#6387)
Signed-off-by: Philippe Bertin <pbertin@teladochealth.com>
2026-04-02 09:14:57 +02:00
Jonas Platte e376670af3 refactor(xtask): Fix clippy lints 2026-04-02 09:10:51 +02:00
Jonas Platte 7f08793360 chore(base): Delete outdated comments 2026-04-02 09:09:55 +02:00
Benjamin Bouvier 1fc7b34016 test: add an integration test reflecting the "bouncing" behavior of the timeline after sending an event 2026-04-01 14:59:33 +02:00
Benjamin Bouvier 37447e16e5 refactor(sdk): use the TimelineEvent::sender() helper in more places
This new helper has been added recently, and it can be reused in many
more places which were doing the same thing more verbously in many
places.
2026-04-01 14:59:13 +02:00
Benjamin Bouvier 782355b556 fix(timeline): fix timeline initialization races
For non-live timelines, the recurring pattern was the following:

- first, fill the timeline with initial events, with a first call to
some form of subscribe() method,
- then, subscribe to updates in a second time later, using a second call
to the same subscribe() method.

Unfortunately, this is wrong, and opens a window for a very small race:

- first call to subscribe() to get the initial events
- new updates are emitted in the receiver, that lead to new events
- second call to subscribe() to get the receiver, and subscribe to it

In this case, the new updates would be lost by an observer.

The patch consists in refactoring timeline initialization, such that the
initial events and receiver are created at the same time; if there are
some updates happening after the now single subscribe() call, the
updates are accumulated and will be observable by external users.

This also has the nice benefit of tidying up the number of background
task handles for non-live timelines: there should be at most one such
focus task, which is now cleanly reflected in the timeline drop handles.
2026-04-01 14:44:43 +02:00
Damir Jelić eb51c862ce chore: Add missing changelog entries for changes caused by the rand bump 2026-04-01 12:48:09 +02:00
Kévin Commaille 676c81ca80 Upgrade Ruma to latest commit
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-04-01 10:20:21 +01:00
Kévin Commaille 644c5e8a4c Upgrade Ruma to commit after PushCondition breaking changes
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-04-01 10:20:21 +01:00
Kévin Commaille 34d5e6da2a Upgrade Ruma to commit after UserIdentifier breaking changes
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-04-01 10:20:21 +01:00
Kévin Commaille 5936a7285f Upgrade Ruma to commit after push Action breaking changes
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-04-01 10:20:21 +01:00
Mauro 62d58a21ab fix: include also stopped live location events in latest_event (#6373)
The issue is that when the most recent message was an live location event that started as live and later transitioned to a stopped state, the room list summary would ignore it entirely, even though the timeline still showed it as the latest item.

That behaviour feels incorrect. At most, the client should update the displayed description based on whether isLive is true or not.

This patch prevents the stopped live location events from being filtered out in the latest events.
2026-04-01 10:56:56 +02:00
Benjamin Bouvier 2e334fafa2 refactor(multiverse): reuse load_or_fetch_event instead of implementing it manually 2026-04-01 10:43:01 +02:00
Benjamin Bouvier e66967c46f refactor(multiverse): simplify code around search 2026-04-01 10:43:01 +02:00
Benjamin Bouvier c237659aca feat(multiverse): don't display "no results found" if the search input is empty 2026-04-01 10:43:01 +02:00
Benjamin Bouvier 8cb6f74996 chore: replace incorrect trace message in search indexing task 2026-04-01 10:43:01 +02:00
Doug 0afd7c9528 fix: Update the iOS platform version to match the deployment target. 2026-04-01 11:12:18 +03:00
Benjamin Bouvier 388ced09a6 Merge pull request #6344 from matrix-org/bnjbvr/automatic-backpagination-unread-counts
feat(event cache): automatic back-pagination for unread counts, MVP
2026-04-01 09:26:55 +02:00
Doug 4071a55cd7 chore: Use a single Swift module when building the bindings.
This fixes support for Xcode 26.4.
2026-04-01 09:20:38 +02:00
Michael Goldenberg c04a97f706 feat(common): ensure cross-process lock generation is opaque
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-03-31 17:34:11 +02:00
Michael Goldenberg 5b0c6bbafc doc: update change logs
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-03-31 17:34:11 +02:00
Michael Goldenberg 1fb0f7f56a refactor(indexeddb): remove old crypto store generation key-value
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-03-31 17:34:11 +02:00
Michael Goldenberg ee9a05defe refactor(sqlite): remove old crypto store generation key-value
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-03-31 17:34:11 +02:00
Michael Goldenberg c0f746f88f refactor(crypto): remove old store generation logic
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-03-31 17:34:11 +02:00
Michael Goldenberg 839786f810 refactor(sdk): generalize client encryption store lock fns
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-03-31 17:34:11 +02:00
Michael Goldenberg 4e4b508c38 refactor(sdk): use consistent errors for client encryption store locks
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-03-31 17:34:11 +02:00
Michael Goldenberg 313e634996 refactor(sdk): clean up errors in client encryption
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-03-31 17:34:11 +02:00
Michael Goldenberg ae72fcf663 refactor(sdk): rely on dirtiness reported by crypto store's cross-process lock
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-03-31 17:34:11 +02:00
Michael Goldenberg 12e9a2a159 feat(common): expose generation in cross-process lock
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-03-31 17:34:11 +02:00
Damir Jelić 16c1b9b57f chore: Bump rand 2026-03-31 16:57:50 +02:00
Benjamin Bouvier 06e6dd05c3 chore(docs): publicly expose the AutomaticPagination API object for external users 2026-03-31 16:36:13 +02:00
Benjamin Bouvier 1cf84e203e chore(event cache): make clippy pass on automatic pagination 2026-03-31 15:57:36 +02:00
Benjamin Bouvier 9913cce946 Merge branch 'main' into bnjbvr/automatic-backpagination-unread-counts 2026-03-31 15:55:44 +02:00
Benjamin Bouvier 1ee88b176c refactor(event cache): create a standalone AutomaticPagination API object 2026-03-31 15:46:46 +02:00
Daniel Salinas f0193cc8ac Fix indexedb migration to clean up gaps + chunks properly 2026-03-31 15:37:36 +02:00
Jorge Martín 5d2eab119d feat: Improve getting homeserver capabilities
This extracts the `/capabilities` logic to its own `HomeserverCapabilities` component in the SDK that can be manually asked to fetch, cache locally and return these capabilities.
2026-03-31 15:25:37 +02:00
Benjamin Bouvier ca91b6a278 fix(event cache): handle read receipt after a duplicate-only sync response 2026-03-31 12:52:18 +02:00
Benjamin Bouvier 260fec8750 test(event cache): regression test for lack of unread count updates after a duplicate-only sync response 2026-03-31 12:52:18 +02:00
Benjamin Bouvier 950fcd289d refactor(ffi): only start with a reset diff if the timeline isn't empty 2026-03-31 11:17:34 +02:00
Bertin Philippe 2214aded9d feat(ffi): expose Client.sync() (#6359)
### Expose sync v2 API through FFI via `Client.sync()` and
`Client.sync_once()` #6348

Mobile clients can now sync using the traditional `/sync` v2 endpoint
through the FFI bindings, without requiring Sliding Sync (MSC4186)
support on the homeserver.

This PR introduces a new API (non-breaking change).

#### Implementation notes

I chose to expose only the **list of room IDs** (invited, joined, left,
knocked) in the `SyncResponse`, rather than forwarding the full per-room
event payloads. This keeps the FFI surface simple. Clients that need
room details can query them through the existing `Room` APIs after being
notified of changes via the listener.

Two entry points are provided:

- **`Client.sync(settings, listener)`** starts a continuous sync loop,
returning a `TaskHandle` for cancellation. The `SyncListener` callback
fires after each successful sync response.
- **`Client.sync_once(settings)`** performs a single sync call, useful
for initial sync or one-off use cases.

`SyncSettings` exposes `timeout_ms` and `full_state`.

Signed-off-by: Philippe Bertin <pbertin@teladochealth.com>
2026-03-31 10:40:54 +02:00
Doug 02c41e20ad chore: Remove uniffi Swift mocks workaround now that we're using 0.31.0. 2026-03-30 21:43:07 +02:00
dependabot[bot] 0e627b4ba2 chore(deps): bump actions/cache from 5.0.3 to 5.0.4
Bumps [actions/cache](https://github.com/actions/cache) from 5.0.3 to 5.0.4.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/cdf6c1fa76f9f475f3d7449005a359c84ca0f306...668228422ae6a00e4ad889ee87cd7109ec5666a7)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: 5.0.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-30 21:42:29 +02:00
dependabot[bot] 55d2e6a740 chore(deps): bump CodSpeedHQ/action from 4.11.1 to 4.12.1
Bumps [CodSpeedHQ/action](https://github.com/codspeedhq/action) from 4.11.1 to 4.12.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/281164b0f014a4e7badd2c02cecad9b595b70537...1c8ae4843586d3ba879736b7f6b7b0c990757fab)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-30 21:42:01 +02:00
dependabot[bot] ee2ed6033e chore(deps): bump moonrepo/setup-rust from 1.2.2 to 1.3.0
Bumps [moonrepo/setup-rust](https://github.com/moonrepo/setup-rust) from 1.2.2 to 1.3.0.
- [Release notes](https://github.com/moonrepo/setup-rust/releases)
- [Changelog](https://github.com/moonrepo/setup-rust/blob/master/CHANGELOG.md)
- [Commits](https://github.com/moonrepo/setup-rust/compare/ede6de059f8046a5e236c94046823e2af11ca670...abb2d32350334249b178c401e5ec5836e0cd88d3)

---
updated-dependencies:
- dependency-name: moonrepo/setup-rust
  dependency-version: 1.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-30 21:41:36 +02:00
Damir Jelić fb97922a1c Revert "feat(sdk): Use stable OAuth 2.0 login scopes"
This reverts commit 14b21e2a9a.
2026-03-30 21:40:31 +02:00
dependabot[bot] 85b8c83d51 chore(deps): bump bnjbvr/cargo-machete from bf9b52599f213cb8a6d6cee568fc61a413c79975 to b81ce1560c5fbd0210cb66d88bf210329ff04266 (#6375)
Bumps [bnjbvr/cargo-machete](https://github.com/bnjbvr/cargo-machete)
from bf9b52599f213cb8a6d6cee568fc61a413c79975 to
b81ce1560c5fbd0210cb66d88bf210329ff04266.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/bnjbvr/cargo-machete/blob/main/CHANGELOG.md">bnjbvr/cargo-machete's
changelog</a>.</em></p>
<blockquote>
<h1>unreleased</h1>
<ul>
<li>Improved: add <code>renamed</code> table to track renamed crates (<a
href="https://redirect.github.com/bnjbvr/cargo-machete/issues/152">#152</a>
<a
href="https://redirect.github.com/bnjbvr/cargo-machete/issues/153">#153</a>).</li>
</ul>
<h1>0.7.0 (released on 2024-09-25)</h1>
<ul>
<li>Breaking change: Don't search in ignored files (those specified in
.ignore/.gitignore) by default. It's possible to use
<code>--no-ignore</code> to search in these directories by default (<a
href="https://redirect.github.com/bnjbvr/cargo-machete/issues/137">#137</a>).</li>
<li>Improved: fix false positives for multi dependencies single use
statements (<a
href="https://redirect.github.com/bnjbvr/cargo-machete/issues/120">#120</a>).
This improves precision at the cost of a small performance hit.</li>
<li>Improved: make usage of <code>--with-metadata</code> more accurate
(<a
href="https://redirect.github.com/bnjbvr/cargo-machete/issues/122">#122</a>,
<a
href="https://redirect.github.com/bnjbvr/cargo-machete/issues/132">#132</a>).</li>
<li>Improved: instead of displaying <code>.</code> for the current
directory, <code>cargo-machete</code> will now display <code>this
directory</code> (<a
href="https://redirect.github.com/bnjbvr/cargo-machete/issues/109">#109</a>).</li>
<li>Added: There's now an automated docker image build that publishes to
the <a
href="https://github.com/bnjbvr/cargo-machete/pkgs/container/cargo-machete">github
repository</a> (<a
href="https://redirect.github.com/bnjbvr/cargo-machete/issues/121">#121</a>).</li>
<li>Added: <code>--ignore</code> flag which make cargo-machete respect
.ignore and .gitignore files when searching for files (<a
href="https://redirect.github.com/bnjbvr/cargo-machete/issues/95">#95</a>).</li>
</ul>
<h1>0.6.2 (released on 2024-03-24)</h1>
<ul>
<li>Added: shorter display when scanning the current directory (<a
href="https://redirect.github.com/bnjbvr/cargo-machete/issues/109">#109</a>).</li>
<li>Fix: adapt to latest pkgid specification, so as not to crash with
<code>--with-metadata</code> (<a
href="https://redirect.github.com/bnjbvr/cargo-machete/issues/106">#106</a>).</li>
</ul>
<h1>0.6.1 (released on 2024-02-21)</h1>
<ul>
<li>Chore: bump major dependencies, to fix parsing issues of Cargo.toml
files (<a
href="https://redirect.github.com/bnjbvr/cargo-machete/issues/101">#101</a>,
<a
href="https://redirect.github.com/bnjbvr/cargo-machete/issues/105">#105</a>).</li>
</ul>
<h1>0.6.0 (released on 2023-09-23)</h1>
<ul>
<li><em>Breaking</em>/improved: match against crate name
case-insensitive (<a
href="https://redirect.github.com/bnjbvr/cargo-machete/issues/69">#69</a>).</li>
<li>Added: Github action (<a
href="https://redirect.github.com/bnjbvr/cargo-machete/issues/85">#85</a>).
See README for documentation.</li>
<li>Added: support for ignored workspace dependencies (<a
href="https://redirect.github.com/bnjbvr/cargo-machete/issues/57">#57</a>,
<a
href="https://redirect.github.com/bnjbvr/cargo-machete/issues/86">#86</a>).
See README for documentation.</li>
<li>Added: <code>--version</code> switch to print the version (<a
href="https://redirect.github.com/bnjbvr/cargo-machete/issues/66">#66</a>).</li>
<li>Fix: avoid searching for workspace Cargo.toml longer than needed (<a
href="https://redirect.github.com/bnjbvr/cargo-machete/issues/84">#84</a>).</li>
<li>Chore: better documentation and reporting (<a
href="https://redirect.github.com/bnjbvr/cargo-machete/issues/63">#63</a>,
<a
href="https://redirect.github.com/bnjbvr/cargo-machete/issues/72">#72</a>,
<a
href="https://redirect.github.com/bnjbvr/cargo-machete/issues/80">#80</a>).</li>
</ul>
<h1>0.5.0 (released on 2022-11-15)</h1>
<ul>
<li><em>Breaking</em>: Use <code>argh</code> for parsing. Now, paths of
directories to scan must be passed in the last
position, when running from the command line (<a
href="https://redirect.github.com/bnjbvr/cargo-machete/issues/51">#51</a>).</li>
<li>Fix rare false positive and speed up most common case (<a
href="https://redirect.github.com/bnjbvr/cargo-machete/issues/53">#53</a>).</li>
<li>Fix loading properties from workspace (<a
href="https://redirect.github.com/bnjbvr/cargo-machete/issues/54">#54</a>).</li>
</ul>
<h1>0.4.0 (released on 2022-10-16)</h1>
<ul>
<li>Added <code>--skip-target-dir</code> to not analyze
<code>target/</code> directories.</li>
<li>Added a message indicating of any unused dependencies were found or
not.</li>
<li>Support for workspace properties</li>
</ul>
<h1>0.3.1 (released on 2022-06-12)</h1>
<ul>
<li>Support empty global prefix, e.g. <code>use ::log;</code>.</li>
</ul>
<h1>0.3.0 (released on 2022-05-09)</h1>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/bnjbvr/cargo-machete/commit/b81ce1560c5fbd0210cb66d88bf210329ff04266"><code>b81ce15</code></a>
build(deps): bump argh from 0.1.18 to 0.1.19</li>
<li><a
href="https://github.com/bnjbvr/cargo-machete/commit/5675104e9dc2192adf3acec0535353003045d586"><code>5675104</code></a>
build(deps): bump toml_edit from 0.25.4+spec-1.1.0 to
0.25.5+spec-1.1.0</li>
<li>See full diff in <a
href="https://github.com/bnjbvr/cargo-machete/compare/bf9b52599f213cb8a6d6cee568fc61a413c79975...b81ce1560c5fbd0210cb66d88bf210329ff04266">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-30 16:26:07 +00:00
Michael Goldenberg 179f38b252 refactor(sdk): use default crypto provider in rustls
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-03-30 16:40:51 +02:00
Damir Jelić 2c6409cb86 Merge pull request #6235 from Hywan/feat-sdk-event-cache-log-error-to-sentry
chore(sdk): Log errors from `EventLinkedChunk`
2026-03-30 14:03:39 +02:00
JoFrost 4d2f8bb82c feat(base): improve profile updates for left/banned users (#6097)
This avoids profile updates when a user decides to leave on their own (in which case their profile is kept as is), and when a user is banned (in which case their profile is removed).

Signed-off-by: JoFrost <20685007+JoFrost@users.noreply.github.com>
2026-03-30 11:59:43 +02:00
Benjamin Bouvier c0b26b6f25 refactor(event cache): move the thread subscriber and search indexing tasks to the EventCacheDropHandles struct
This means that when some caller is subscribing to the `EventCache`,
even if the event cache drops in the background, the task will remain
active until the end of the subscriber.
2026-03-30 11:49:16 +02:00
Benjamin Bouvier 8f96c32d56 refactor(event cache): use abort_on_drop for the background task handle in EventCacheDropHandles
This avoids a few manual `abort()` call in a manual `Drop` impl.
2026-03-30 11:49:16 +02:00
Benjamin Bouvier bdd0162831 tests(event cache): make tests more resilient to the order of event cache updates 2026-03-30 11:40:19 +02:00
Benjamin Bouvier 752695c6ff chore: rename background request to automatic pagination request
And associated vocabulary and fields.
2026-03-30 11:28:05 +02:00
Benjamin Bouvier 552639763e chore: move the BackgroundRequest type, tests and task to its own file 2026-03-30 11:19:01 +02:00
Jorge Martín e65d4c44b4 fix(ffi): Replace libloading with jvm-getter for getting a JVM
The `libloading` approach only works on Android 12+
2026-03-30 11:17:58 +02:00
Benjamin Bouvier d151340882 chore(review): use an unbounded sender for BackgroundRequest 2026-03-30 11:12:39 +02:00
Benjamin Bouvier 27415dc64d chore(review): Use assert_matches and remove PartialEq/Eq impls for EventsOrigin 2026-03-30 11:04:41 +02:00
Benjamin Bouvier 4feeaf0cf5 chore(review): address first review comments 2026-03-30 11:01:57 +02:00
Benjamin Bouvier 87a2f8fab7 feat(multiverse): enable automatic back pagination in multiverse 2026-03-30 10:40:31 +02:00
Benjamin Bouvier 871cb2221b test(event cache): add an integration test for automatically paginating a room with missing receipts 2026-03-30 10:40:31 +02:00
Benjamin Bouvier d1d174d137 test(event cache): test the credit system of automatic pagination 2026-03-30 10:40:31 +02:00
Benjamin Bouvier bd7dca45ef feat(event cache): make some background pagination parameters configurable 2026-03-30 10:40:31 +02:00
Benjamin Bouvier 856b2be992 test(event cache): unit test automatic background pagination 2026-03-30 10:40:31 +02:00
Benjamin Bouvier 161750f6d0 refactor(event cache): add PartialEq to EventsOrigin 2026-03-30 10:40:31 +02:00
Benjamin Bouvier c4a95f9bbe feat(event cache): automatically request paginations when no receipt has been found when computing unread counts 2026-03-30 10:40:31 +02:00
Benjamin Bouvier ac5552807b refactor(event cache): have select_best_receipt return a result struct
This makes it simpler to test its expected behavior, and will make it
trivial to add a system to request automatic paginations in the
background.
2026-03-30 10:40:31 +02:00
Benjamin Bouvier ff8b27f99b feat(event cache): add an automatic background requests task 2026-03-30 10:40:31 +02:00
Benjamin Bouvier bf9ffd7d09 refactor(event cache): make EventCache::config/config_mut sync by using a sync lock
There wasn't good reason to use an async lock, as this lock is always
super short-lived, it can be sync, which avoids complications in the
subsequent commit when calling sync init code.
2026-03-30 10:40:31 +02:00
Benjamin Bouvier 9f18d76355 feat(event cache): add a feature toggle to enable automatic back-pagination 2026-03-30 10:40:30 +02:00
Andrew Ferrazzutti 83c6a7fdf4 Fix location & name of the built shared object 2026-03-30 10:31:07 +02:00
Andrew Ferrazzutti 67fbba1e87 Tweak suggested ANDROID_NDK path
- Remove redundant trailing slash
- Clarify what the version component may be
2026-03-30 10:31:07 +02:00
Andrew Ferrazzutti 9c66221830 Fix crypto-ffi build instructions in README
- Change the example config file to not make it appear as though it
  processes environment variables
- Remove the `ar` setting from the example config file, as that setting
  is deprecated and unused (see
  https://doc.rust-lang.org/cargo/reference/config.html#targettriplear)
- Replace references of `.cargo/config` to `.cargo/config.toml`, as the
  former is deprecated
- Add example of how to set the linker through an environment variable
- Add instruction to include the NDK binary tools directory to PATH,
  because builds may fail without it
  (see https://github.com/matrix-org/matrix-rust-sdk/issues/4042)
2026-03-30 10:31:07 +02:00
Philippe Bertin f00d09597c feat(ffi): expose Room.send_state_event_raw() for sending arbitrary state events
Closes #6349

Signed-off-by: Philippe Bertin <pbertin@teladochealth.com>
2026-03-30 10:27:00 +02:00
dependabot[bot] 339f4be49c chore(deps): bump bnjbvr/cargo-machete
Bumps [bnjbvr/cargo-machete](https://github.com/bnjbvr/cargo-machete) from 2e2703d664c176156a22936b9203c38cc7a273dd to bf9b52599f213cb8a6d6cee568fc61a413c79975.
- [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/2e2703d664c176156a22936b9203c38cc7a273dd...bf9b52599f213cb8a6d6cee568fc61a413c79975)

---
updated-dependencies:
- dependency-name: bnjbvr/cargo-machete
  dependency-version: bf9b52599f213cb8a6d6cee568fc61a413c79975
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-30 10:54:13 +03:00
dependabot[bot] f5e3e7db83 chore(deps): bump Swatinem/rust-cache from 2.9.0 to 2.9.1
Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.9.0 to 2.9.1.
- [Release notes](https://github.com/swatinem/rust-cache/releases)
- [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md)
- [Commits](https://github.com/swatinem/rust-cache/compare/c676846f29d98ff6b0106d3608c7ffd4048af17b...c19371144df3bb44fab255c43d04cbc2ab54d1c4)

---
updated-dependencies:
- dependency-name: Swatinem/rust-cache
  dependency-version: 2.9.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-30 09:48:48 +02:00
Stefan Ceriu c3a0ce15cc change(project): set the IPHONEOS_DEPLOYMENT_TARGET to 16.0
This is mainly to avoid compilation errors coming out of `aws-lc-sys` on `aarch64-apple-ios`
2026-03-30 10:39:20 +03:00
Daniel Salinas c708ed18c0 No reqwest::Certificate on wasm platforms 2026-03-27 09:06:43 +01:00
Stefan Ceriu 3db4767523 change(thread_list_service): reuse the same event construction logic for both the root and the latest thread events 2026-03-25 16:46:42 +02:00
Stefan Ceriu 050fc3d845 change(latest_events): use TimelineEvent fields instead of deserialised ones for security reasons 2026-03-25 16:46:42 +02:00
Stefan Ceriu fd572d9b33 fix(ffi): manually enter the runtime on background tasks spawning paths instead of forcing async constructors 2026-03-25 16:46:42 +02:00
Stefan Ceriu 1dd413e4bc feat(ui): subscribe to room event cache for live thread updates
When ThreadListService is created it now immediately spawns a background
task (via the client's TaskMonitor) that subscribes to the room event
cache and listens for RoomEventCacheUpdate::UpdateTimelineEvents.

For every incoming event that carries an m.thread relation pointing to a
root we are already tracking, the service rebuilds a ThreadListItemEvent via the
existing build_latest_event helper and replaces latest_event on the matching
ThreadListItem and increments num_replies by 1
2026-03-25 16:46:42 +02:00
Stefan Ceriu fe45ba5cc2 feat(ui): enrich ThreadListItem with root_event, latest_event and num_replies
Introduce ThreadListItemEvent as a shared struct for both the thread
root and the latest reply, replacing the previous flat fields on
ThreadListItem

The `latest_event` is populated from the bundled thread summary returned by the
server alongside the root event, as is the number of replies.
2026-03-25 16:46:42 +02:00
Skye Elliot 46a2ba29a0 Merge pull request #6339 from matrix-org/kaylendog/history-sharing/sync-rotate
feat(base): Discard room key on full member list update
2026-03-25 13:12:02 +00:00
Skye Elliot e877466fdc docs(base): Update CHANGELOG.md
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-03-25 12:57:53 +00:00
Skye Elliot be47a6435b tests: Add extra sync to history sharing session merge test
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-03-25 12:57:53 +00:00
Skye Elliot 3bad26e39f test(sdk): Assert room key rotated under a gappy sync v3
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-03-25 12:57:53 +00:00
Skye Elliot 0c3392d1d2 feat(base): Discard room key on full member list update
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-03-25 12:57:49 +00:00
Kévin Commaille 8dcbac3b16 Upgrade Ruma after EncryptedFile breaking changes
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-25 10:34:22 +01:00
Jonas Platte ec3657d707 Upgrade const_panic
I rebased my branch from 2023. Diff still applied cleanly.
2026-03-24 23:01:19 +01:00
Benjamin Bouvier 1bf831fbb7 refactor(event cache): avoid cloning the whole RoomInfo to throw it away immediately 2026-03-24 18:07:31 +01:00
Benjamin Bouvier 708ee63ffd fix(event cache): make sure the room info is as up to date as it can be before updating read receipts
We think this might be a cause for intermittent failures of the
`room_unread_count` test on CI, as the `room_info` might be outdated
after we decided to process it.

More generally, the room info is a critical resources that should be
protected in better ways, as it's inner mutable state that can be
modified by all code claling `set_room_info` in any place, but that'll
be a problem for later.
2026-03-24 18:07:31 +01:00
Michael Goldenberg f6ff8621dc chore(deps): update sentry
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-03-24 13:41:25 +00:00
Stefan Ceriu 96b82a4f5d chore(docs): add ThreadListService UI and FFI changelog entries. 2026-03-24 14:51:59 +02:00
Stefan Ceriu 84bdf604e5 change(ui): remove the Room::load_thread_list method in favor of the new ThreadListService 2026-03-24 14:51:59 +02:00
Stefan Ceriu 56b13806bd feat(ffi): expose the newly introduced ThreadListService 2026-03-24 14:51:59 +02:00
Stefan Ceriu b6edf826b0 feat(ui): introduce a new ThreadListService
`ThreadListService` is the FFI-facing wrapper around
[`matrix_sdk_ui::timeline::thread_list_service::ThreadListService`]. It
maintains an observable list of [`ThreadListItem`]s and exposes a
pagination state publisher, making it straightforward to build reactive UIs
on top of the thread list.
2026-03-24 14:51:59 +02:00
ganfra e335cac8f7 fix(timeline): validate beacon_info content for session matching 2026-03-24 10:34:47 +01:00
ganfra cad9c42a7a fix(timeline): check live field directly for beacon_info item creation 2026-03-24 10:34:47 +01:00
Bryant Mairs 907a23f252 feat: add Room:is_call to check for Call rooms (MSC3417)
Signed-off-by: Bryant Mairs <bryant@mai.rs>
2026-03-24 10:32:31 +01:00
Bryant Mairs 0505edc380 Upgrade Ruma
Signed-off-by: Bryant Mairs <bryant@mai.rs>
2026-03-24 10:32:31 +01:00
Richard van der Hoff 47c7a205c7 Merge pull request #6342 from matrix-org/rav/trust_requirement_docs
Add some more docs to `CollectStrategy` and `TrustRequirement`
2026-03-23 15:29:24 +00:00
Richard van der Hoff 7986a6627e Add some more docs to CollectStrategy and TrustRequirement
Some notes to help us/me remember how these relate to MSC4153.
2026-03-23 15:14:01 +00:00
Ivan Enderlin 0358552086 feat(ui): Disable smart case for the Room List fuzzy filter.
This patch updates the fuzzy filter from using _smart case_ to _ignore
case_. Most of the time, the keyboard might activate an upper case for
the first character pressed in the search bar. Some users aren't used
to smart case and will believe there is a bug. Let's disable smart case
here.
2026-03-23 14:13:52 +01:00
Jorge Martín 46462599c0 fix(sdk): Custom certificates on Android after the rustls upgrade
Add separate methods and field to temporarily apply these custom certs using the webkpi approach.
2026-03-23 14:05:22 +01:00
Jorge Martín 98a5b05c4b fix(sdk): Allow disabling SSL verification again on Android
This will skip using the custom workaround for Android and the latest rustls, but since that was to fix an issue with the verifier and the verifier is not used anymore, it should be fine
2026-03-23 14:05:22 +01:00
Benjamin Bouvier 727af9e654 chore(event cache): remove the non-decreasing hack when computing unread counts
There was a hack added that the unread count of a room shouldn't
decrease, if the room's latest active receipt hasn't changed.
Unfortunately, this heuristic doesn't hold: if an event was counted
because it was encrypted, and it turns out that after decrypting it as a
UTD, it should now be uncounted, then the read count would in fact
legitimately decrease.

Let's remove this hack, and in the meantime keep on working on automatic
backpagination, which should help fix this kind of issues.
2026-03-23 12:30:11 +01:00
dependabot[bot] 0d59b43dfd chore(deps): bump rustls-webpki from 0.103.9 to 0.103.10
Bumps [rustls-webpki](https://github.com/rustls/webpki) from 0.103.9 to 0.103.10.
- [Release notes](https://github.com/rustls/webpki/releases)
- [Commits](https://github.com/rustls/webpki/compare/v/0.103.9...v/0.103.10)

---
updated-dependencies:
- dependency-name: rustls-webpki
  dependency-version: 0.103.10
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-23 10:46:47 +01:00
Mauro Romito d2a019a25f change: updated tests to include also the encrypted beacon case 2026-03-23 10:46:31 +01:00
Mauro Romito cc4c296f74 change: security improvement 2026-03-23 10:46:31 +01:00
Mauro Romito 61b3966cf9 docs: changelog 2026-03-23 10:46:31 +01:00
Mauro Romito b31b743435 fix: live location shield doesn't get flagged as sent in clear 2026-03-23 10:46:31 +01:00
Valere Fedronic 2a00401e79 feat(rtc): get active call consus intent (audio/video) (#6274)
<!-- description of the changes in this PR -->

Depends on: https://github.com/ruma/ruma/pull/2414

Partity with web/js-sdk.
Allows to get the consus intent for the current call using the
membership `m.call.intent`

EW use it to change the icon in the room list
<img width="298" height="117" alt="image"
src="https://github.com/user-attachments/assets/21e59f69-e099-40a6-ae27-d9246df35b64"
/>


- [x] I've documented the public API Changes in the appropriate
`CHANGELOG.md` files.
- [ ] This PR was made with the help of AI.

<!-- Sign-off, if not part of the commits -->
<!-- See CONTRIBUTING.md if you don't know what this is -->
Signed-off-by:
2026-03-23 10:42:45 +01:00
Stefan Ceriu 39255c0542 chore(live_location): expose the inner beacon_infos location sharing session start timestamp. 2026-03-20 15:26:19 +02:00
Benjamin Bouvier d9ed4f61af fix(event cache): don't select an implicit threaded receipt for counting unreads
The test shows a situation where an in-thread message would be counted
as an implicit receipt, while, when we're in the `main` timeline mode,
we're counting unreads only for the main timeline. In this case, the
test would fail because the unread counts would be set to 0, since
select_best_receipt would have selected $3.

The patch is to *not* select an implicit receipt, in that mode, when
it's in a thread.
2026-03-20 13:52:38 +01:00
Benjamin Bouvier 7beda7990f fix(timeline): don't send a main-thread receipt on a thread aggregation 2026-03-20 12:33:14 +02:00
Andy Balaam ec975094e2 Throttle our retries when attempting cross-signing reset, and give up after a time limit (#6325)
Related to https://github.com/element-hq/element-x-android/pull/6420

Part of https://github.com/element-hq/element-x-android/issues/5075

I noticed when investigating a bug about resetting your identity when
using a MAS login that we poll the server checking whether the user has
given us permission with no limit on how fast we poll, and with no
ability to give up if it's not working.

This change causes us to retry only twice per second, and give up after
2 minutes. These are guesses as to the right values and I am open to
discussion.
2026-03-20 10:13:44 +00:00
Jorge Martín cbfecf520c refactor(sdk): Remove the unwraps, and add HttpError::VerifierBuilder variant 2026-03-19 17:33:20 +01:00
Jorge Martín 6151120621 fix(sdk): False positives for revoked HTTPS certs in Android
This adds back the `webpki` verifier and sets it to a custom rustls instance created only for Android, instead of using the platform verifier that results in false positives with Let's Encrypt certs (and from other CAs).

See https://github.com/matrix-org/matrix-rust-sdk/issues/6319
2026-03-19 17:33:20 +01:00
Benjamin Bouvier 84ddafbd6c chore: rename SharedPagination to SharedPaginationTask 2026-03-19 14:32:07 +01:00
Benjamin Bouvier 57e9225152 chore: address review comments related to tests 2026-03-19 14:32:07 +01:00
Benjamin Bouvier 36e49c2702 refactor(event cache): merge the pagination status with the shared pagination state 2026-03-19 14:32:07 +01:00
Benjamin Bouvier 6bce60ed8f chore: address first batch of review comments 2026-03-19 14:32:07 +01:00
Benjamin Bouvier 43005ba0c1 chore: add changelog entry for the new behavior of pagination 2026-03-19 14:32:07 +01:00
Benjamin Bouvier 4540dd7eef fix(test): fix test expectations in test_skip_count…
For some reason, the previous test didn't take the initial skip count
value into account, and now it does. Oh well.
2026-03-19 14:32:07 +01:00
Benjamin Bouvier 91074ed3da refactor(timeline): simplify code flow in live pagination 2026-03-19 14:32:07 +01:00
Benjamin Bouvier 3606dc1ed9 fix(test): fix a few test expectations with respect to ordering of timeline updates
My only guess for this semantic change, is that the pagination status
update and the end of the pagination now happen at different times, or
close enough that they're regrouped in the same stream update. This
doesn't fundamentally change the semantics, so we'll see if this holds
on slower machines (e.g. CI).
2026-03-19 14:32:07 +01:00
Benjamin Bouvier 7f1d49f8f5 refactor(test): make test_back_pagination_aborted run faster
- use the modern MatrixMockServer facilities
- provide a previous-batch token so as not to wait for the initial batch
token
- lower the delay for the /messages responses to 1 sec
2026-03-19 14:32:07 +01:00
Benjamin Bouvier 1afd4e9fa1 fix(test): fix test_back_pagination_aborted expectations
Even though the task which started the back-pagination is aborted, since
it's now the event cache owning it, it keeps on running in the
background.
2026-03-19 14:32:07 +01:00
Benjamin Bouvier b5153a8b23 feat(event cache): have the event cache own the back-pagination task 2026-03-19 14:32:07 +01:00
Benjamin Bouvier 9c4a47b20b refactor(event cache): allow deref state lock read/write guards to the state
This makes it possible to common out the implementations of functions
that should be available to both kinds.

Also, moves a bit of code that could easily live in the
`RoomEventCacheState` impl block, instead of being free functions.
2026-03-19 14:21:27 +01:00
Benjamin Bouvier 43e468e149 refactor(timeline): replace a bool::to_owned() call with a dereference
booleans can be copied \o/
2026-03-19 11:53:30 +01:00
Benjamin Bouvier 993e02e02c doc(event cache): explain when the missing active receipt case can happen
And simplify a double `map()` call.
2026-03-19 11:51:34 +01:00
Benjamin Bouvier cd99520a6e chore(event cache): add logs in select_best_receipt
These will make debugging much easier in the future, even though they're
quite verbose.
2026-03-19 11:51:34 +01:00
Benjamin Bouvier 69a1bd5019 fix(event cache): stop at the active receipt, if found, when computing unread counts
There was a subtle bug that a receipt would be considered active, and
then on the subsequent call to `select_best_receipt`, it could be
forgotten in favor of an older receipt. The regression test shows one
such case, where before this patch, the count would incorrectly say 3,
not 2, because the active read receipt moved backwards to the implicit
receipt.

The solution is to stop looking for a better receipt, if we run into the
latest active read receipt. Having `found` set to `None` in this case
means we hadn't found any better read receipt anyways.
2026-03-19 11:51:34 +01:00
Kévin Commaille 1d87b33b79 Upgrade Ruma
Handle a breaking change around request `AuthScheme`s.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-19 09:55:25 +01:00
Ivan Enderlin cbc7228e08 doc(sdk): Add #6316 in the `CHANGELOG.md. 2026-03-19 09:18:37 +01:00
Ivan Enderlin affce2d43c fix(sdk): Remove an unwrap on a Result<T, JoinError>.
This patch removes the `unwrap` in `spawn().await.unwrap()` to replace
it by a proper error propagation.
2026-03-19 09:18:37 +01:00
Jorge Martín 29aa3c2e08 fix(ffi): Using rustls in Android
It turns out on Android, rustls needs [a custom setup](https://github.com/rustls/rustls-platform-verifier#android) and adding the `rustls-platform-verifier-android` library that's [not available on Maven](https://github.com/rustls/rustls-platform-verifier/issues/115).

Then, from the Android clients we'd need to call some exposed JNI function so we can provide a JVM context from where Rust can take the `Application` component and access its contents to read its credentials storage. Thanks to some tricks we can use `libloading` to simulate this call from Rust itself and properly initialise the platform verifier.

Note self-signed certificates will no longer work with these changes on Android, and providing them in `ClientBuilder::add_root_certificates` will make most requests fail. This can be handled separately.
2026-03-18 17:07:55 +01:00
Skye Elliot 81bed18550 Merge pull request #6292 from matrix-org/kaylendog/history-sharing/respect-visibility
feat(sdk): Rotate session keys when a member leaves the room
2026-03-18 14:44:03 +00:00
Skye Elliot 9082cb94a9 docs(sdk): Update CHANGELOG.md 2026-03-18 14:30:53 +00:00
Skye Elliot fd806a9c11 tests: Assert room key rotated on leave event after being offline
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-03-18 14:25:22 +00:00
Skye Elliot 7a26db66b5 tests: Assert room key is rotated when a member leaves the room
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-03-18 14:25:22 +00:00
Skye Elliot 635fdd3f80 refactor(crypto): Lock GroupSessionCache::get to cfg(test)
This method is now only used by tests, so I opted to lock it
behind the test configuration to appease CI.

Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-03-18 14:25:21 +00:00
Skye Elliot ba9e876dfe docs(sdk): Outline key rotation scenario on discard handler
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-03-18 14:25:21 +00:00
Skye Elliot bfe24d90cd feat(sdk): Rotate session key when member leaves the room
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-03-18 14:25:19 +00:00
Andy Balaam 60ce062ac5 Shorten the impl of reset on IdentityResetHandle 2026-03-18 12:59:45 +00:00
Ivan Enderlin daa1120286 chore(ui): Always enable the latest event Room List sorter.
This patch removes the `enable_latest_event_sorter` flag in
`RoomList::entry_with_dynamic_adapters_with`. This sorter is now stable
enough, we can always enable it.
2026-03-18 10:47:31 +01:00
Stefan Ceriu 4ad046d4b8 change(thread_list): update public documentation 2026-03-17 19:29:28 +02:00
Stefan Ceriu 33c156f231 feat(threads): convert the types exposed from Room.list_threads to UI crates TimelineItem types
... and closer to what we do for latest events, reply details etc.
2026-03-17 19:29:28 +02:00
Stefan Ceriu 9405908eb8 fix(ffi): actually expose the ThreadRoot details over FFI 2026-03-17 19:29:28 +02:00
Mauro Romito 5742f49886 docs: changelog updated
# Conflicts:
#	crates/matrix-sdk/CHANGELOG.md
2026-03-17 18:19:44 +01:00
Mauro Romito cabaede7fc feat: added beacon_info start event to the latest_event that can be displayed 2026-03-17 18:19:44 +01:00
Benjamin Bouvier a4bb2f3ff4 chore: add changelog entry for the cloneable EventCacheError 2026-03-17 15:47:39 +01:00
Benjamin Bouvier 816530c89c feat(event cache): make EventCacheError cloneable
This makes it possible to share futures which output is a result that
has the error type set to `EventCacheError`. See also
https://github.com/matrix-org/matrix-rust-sdk/pull/6304 for usage.
2026-03-17 15:47:39 +01:00
Ivan Enderlin e72950ea15 refactor(sdk): Inline RoomEventCacheStateLockWriteGuard::load_more_events_backwards in RoomPagination.
The method
`RoomEventCacheStateLockWriteGuard::load_more_events_backwards` is used
only in one-place: in `RoomPagination::load_more_events_backwards`. This
patch inlines this method as it aims at living in `RoomPagination`, not
somewhere else.

It's purely code move. Nothing else has changed.

This patch also updates a test that was accessing
`load_more_events_backwards` directly. Now it runs it via
`RoomPagination`.
2026-03-17 13:49:00 +01:00
Ivan Enderlin 793f62049a chore(sdk): Rename *Lock to Locked*. 2026-03-17 13:44:14 +01:00
Ivan Enderlin 2652db661b chore(sdk): Add a temporary `allow(clippy). 2026-03-17 13:44:14 +01:00
Ivan Enderlin 2459348b2e doc(sdk): Add #6280 in the CHANGELOG.md. 2026-03-17 13:44:14 +01:00
Ivan Enderlin b85bb5463f refactor(sdk): In ThreadPagination, replace RoomEventCacheInner by ThreadEventCacheInner.
This patch updates `ThreadPagination` to hold a `ThreadEventCacheInner`
instead of a `RoomEventCacheInner`! It makes more sense and it
splits/isolates the types even more.

`RoomEventCache::thread_pagination` is now async and returns a
`Result<ThreadPagination>` because it needs to load its state to fetch
a `ThreadEventCache`. Later, accessing a thread wouldn't happen in
`RoomEventCache` but in `Caches`, one step at a time.
2026-03-17 13:44:14 +01:00
Ivan Enderlin 7929bcc956 chore(sdk): Add ThreadEventCacheInner::weak_room.
This patch adds the `weak_room: WeakRoom` field to
`ThreadEventCacheInner`. This is a prerequisite to have
`ThreadPagination` uses `ThreadEventCacheInner` instead of
`RoomEventCacheInner`!
2026-03-17 13:44:14 +01:00
Ivan Enderlin a91d24c53e refactor(sdk): Create ThreadEventCacheState.
This patch creates the `ThreadEventCacheState` type. It uses
`caches::lock::StateLock`, just like `RoomEventCacheState`. It allows
to have the `read()` and `write()` method to access the state, and to
reload it when necessary, see the `caches::lock::Store` implementation.

This patch thus creates `ThreadEventCacheStateLockReadGuard` and
`ThreadEventCacheStateLockWriteGuard`. The methods touching the state in
`ThreadEventCacheInner` are moved to these lock types.

They are purely code moves (plus changes to reach the correct data): no
change in the semantics.
2026-03-17 13:44:14 +01:00
Ivan Enderlin e55db521a4 refactor(sdk): Create ThreadEventCacheInner.
This patch creates `ThreadEventCacheInner` so that `ThreadEventCache`
can be shallow cloned (which will be useful for `ThreadPagination`).
That's also the first step to introduce `ThreadEventCacheState`!
2026-03-17 13:44:14 +01:00
Ivan Enderlin c71e5c14c1 chore(sdk): Move the has_new_gap definition a bit earlier.
This patch moves up the `has_new_gap` definition to clarify the code
even more.
2026-03-17 13:44:14 +01:00
dependabot[bot] 33f3a67020 chore(deps): update dtolnay/rust-toolchain requirement to 631a55b12751854ce901bb631d5902ceb48146f7
Updates the requirements on [dtolnay/rust-toolchain](https://github.com/dtolnay/rust-toolchain) to permit the latest version.
- [Release notes](https://github.com/dtolnay/rust-toolchain/releases)
- [Commits](https://github.com/dtolnay/rust-toolchain/commits/631a55b12751854ce901bb631d5902ceb48146f7)

---
updated-dependencies:
- dependency-name: dtolnay/rust-toolchain
  dependency-version: 631a55b12751854ce901bb631d5902ceb48146f7
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-17 13:29:15 +01:00
Stefan Ceriu 290650c6b1 change(live_location): Move LiveLocation out of TimelineItemContent and into MsgLikeKind
... so that it has access to `MsgLikeContent` reactions
2026-03-17 13:39:29 +02:00
dependabot[bot] 538afaf252 chore(deps): bump actions/checkout from 6.0.0 to 6.0.2
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.0 to 6.0.2.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...de0fac2e4500dabe0009e67214ff5f5447ce83dd)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-17 10:52:30 +01:00
dependabot[bot] 6a408d673f chore(deps): bump bnjbvr/cargo-machete
Bumps [bnjbvr/cargo-machete](https://github.com/bnjbvr/cargo-machete) from 8a8e0cd1046f54206f6d2e050cf028e6db9dbc75 to 2e2703d664c176156a22936b9203c38cc7a273dd.
- [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/8a8e0cd1046f54206f6d2e050cf028e6db9dbc75...2e2703d664c176156a22936b9203c38cc7a273dd)

---
updated-dependencies:
- dependency-name: bnjbvr/cargo-machete
  dependency-version: 2e2703d664c176156a22936b9203c38cc7a273dd
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-17 10:48:41 +01:00
dependabot[bot] 8a6f822993 chore(deps): bump codecov/test-results-action from 1.1.1 to 1.2.1
Bumps [codecov/test-results-action](https://github.com/codecov/test-results-action) from 1.1.1 to 1.2.1.
- [Release notes](https://github.com/codecov/test-results-action/releases)
- [Commits](https://github.com/codecov/test-results-action/compare/v1.1.1...0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3)

---
updated-dependencies:
- dependency-name: codecov/test-results-action
  dependency-version: 1.2.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-17 10:43:36 +01:00
Damir Jelić aa669585b4 ci: Pin our github actions to commit hashes 2026-03-17 10:24:44 +01:00
dependabot[bot] 109db5ee58 chore(deps): bump lz4_flex from 0.11.5 to 0.11.6
Bumps [lz4_flex](https://github.com/pseitz/lz4_flex) from 0.11.5 to 0.11.6.
- [Release notes](https://github.com/pseitz/lz4_flex/releases)
- [Changelog](https://github.com/PSeitz/lz4_flex/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pseitz/lz4_flex/compare/0.11.5...0.11.6)

---
updated-dependencies:
- dependency-name: lz4_flex
  dependency-version: 0.11.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-17 00:28:31 +01:00
Benjamin Bouvier abc6b4ad01 doc(event cache): improve documentation around select_best_receipt 2026-03-16 17:03:00 +01:00
Benjamin Bouvier 241584366d refactor(event cache): remove ReceiptSelector and associated tests
Now that we use `select_best_receipt` and that it's properly tested, we
can entirely get rid of the `ReceiptSelector` and associated tests.
2026-03-16 17:03:00 +01:00
Benjamin Bouvier 8a611305cf test(event cache): add unit tests for select_best_receipt 2026-03-16 17:03:00 +01:00
Benjamin Bouvier 0c149155be refactor(event cache): directly use the linked chunk for computing unread counts 2026-03-16 17:03:00 +01:00
Benjamin Bouvier bc69d36d3e refactor(event cache): remove test-only compute_unread_counts_legacy and associated tests
The new integration tests for the event cache cover the same situations
that were tested by the previous tests on compute_unread_count_legacy,
so we're fine here.
2026-03-16 17:03:00 +01:00
Benjamin Bouvier a453f019d9 fix(unread counts): after a gappy sync, don't decrease unread counts
A gappy sync may cause a linked chunk to shrink, waiting for callers to
lazy-reload it again in the future. But, because the unread counts
computation rely on the in-memory linked chunk, this means that the
values computed for the unread count may be incorrect (and decrease).

Fortunately, this situation is rather easy to detect, because the latest
active read receipt doesn't change in this case, so we can first check
that, and then manually readjust the unread counts, if they've
decreased.

Future work should trigger back-pagination in those cases, so the unread
counts keeps on being precise, despite the gappy sync.
2026-03-16 16:41:31 +01:00
Benjamin Bouvier f1a50bb68f test(event cache): cache shrinking confuses the unread count computation
This test exhibits an edge case: when a room event cache is shrunk
(because of a gappy/limited sync), then the unread count might decrease,
without the latest active read receipt changing.
2026-03-16 16:41:31 +01:00
Benjamin Bouvier 9cae7ba44f doc(base): improve the doc comments around unread_notification_counts
And also move the `Room::read_receipts` method below the three `unread_`
methods, for tidiness.
2026-03-16 15:50:34 +01:00
Benjamin Bouvier f00f6103c8 fix(test): bump the wait time between room-info diffs in integration testing
`test_room_notification_count` started to intermittently fail on main,
because the computation of unread counts has moved from the sliding sync
processing to the event cache. As a result, new irrelevant RoomInfo
updates (related to the unread counts) can happen, and they might happen
quickly enough that the server reponse for sending an event happens
after 2 seconds (remember, we need to factor in the time to do the e2ee
key exchange, and so on and so forth).

Bumping the time between two RoomInfo updates should be sufficient to
avoid the intermittent failure.
2026-03-16 15:50:34 +01:00
Jorge Martín d0980b6608 refactor(ffi): Remove unnecessary match branch in ffi::Client::new
This was just instantiating a value that was never used.
2026-03-16 14:22:49 +01:00
Kévin Commaille 74c1044b7d Add changelog
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-16 12:53:47 +01:00
Kévin Commaille 14b21e2a9a feat(sdk): Use stable OAuth 2.0 login scopes
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-16 12:53:47 +01:00
Ivan Enderlin d6c666a88d fix(xtask): The pos field can be absent when computing log sync.
This patch fixes a bug in `xtask log sync` which can miss a `sync_once`
log when the `pos` field is absent. It happens when there is no `pos`!

Example where `pos` is absent before `timeout`. Note the double space
before `timeout`:

```
… > sync_once{conn_id="room-list"  timeout=0} > send{request_id="REQ-15" …
```

While when the `pos` is present, it's:

```
… > sync_once{conn_id="room-list" pos="0/m67590980…" timeout=30000} > send{request_id="REQ-23" …
```
2026-03-16 11:16:44 +01:00
Hugh Nimmo-Smith acda2e88e3 Add HumanQrLoginError::UnsupportedQrCodeType and HumanQrGrantLoginError::UnsupportedQrCodeType 2026-03-13 08:10:19 +01:00
Skye Elliot b6c4bca5a0 Merge pull request #6275 from matrix-org/kaylendog/history-sharing/respect-visibility
feat(sdk): Only share history if room history visibility is shared
2026-03-12 14:55:15 +00:00
Damir Jelić 3f7d53a3ce feat(ffi): Add some more getters to the QRcodeData struct 2026-03-12 15:13:38 +01:00
Benjamin Bouvier a99e79ac5d chore(sdk): add a changelog entry for the new unread count computation 2026-03-12 13:28:13 +01:00
Benjamin Bouvier a191ab45ad doc(event cache): update read receipts module comment 2026-03-12 13:28:13 +01:00
Benjamin Bouvier b5cf360111 doc(event cache): precise strategy to avoid all_events cloning 2026-03-12 13:28:13 +01:00
Benjamin Bouvier 56d5086aa4 doc: depersonalize doc comment 2026-03-12 13:28:13 +01:00
Benjamin Bouvier 6309498f20 refactor(event cache): parse as little as possible when computing read receipts 2026-03-12 13:28:13 +01:00
Benjamin Bouvier f4bee4b7a5 test(event cache): add event-cache specific tests for read receipts 2026-03-12 13:28:13 +01:00
Benjamin Bouvier 9a257a4ca3 test(unread count): check that the unread counts get updated after event decryption 2026-03-12 13:28:13 +01:00
Benjamin Bouvier cca8b0898f refactor(unread counts): use a bool to indicate thread support 2026-03-12 13:28:13 +01:00
Benjamin Bouvier a0ce0cfaf2 feat(event cache): compute read receipts in the event cache (part 2)
This is a basic implementation that works, but it should unlock
improvements already (getting the unread count updated whenever a UTD
has been resolved) and it will pave the way for future improvements
(notably with respect to performance).
2026-03-12 13:28:13 +01:00
Benjamin Bouvier 627e118ef3 refactor(sdk): move the read receipt code handling to the event cache (part 1)
This is almost only code motion in this commit.

At this point, some tests don't pass, as the support for using the read
receipt code in the event cache isn't plugged in to the event cache
itself.
2026-03-12 13:28:13 +01:00
Benjamin Bouvier c55935f92c fix(base): don't accumulate unreads when a receipt is pending 2026-03-12 13:28:13 +01:00
Benjamin Bouvier be1f525ccc refactor(base): rejigger unread count code to use iterators on all events
This will allow it to run on the room linked chunk's events immediately,
with minimal changes.
2026-03-12 13:28:13 +01:00
Ivan Enderlin a48f23881d chore(sdk): Make RoomEventCacheInner more private.
This patch makes `RoomEventCacheInner` more private, from `event_cache`
to `event_cache::caches`.

Consequently, `PinnedEventCacheState`, `RoomPagination::new` and
`ThreadPagination::new` follows the same restriction.
2026-03-12 10:07:05 +01:00
Ivan Enderlin 95aaa6558c chore(sdk): Make RoomEventCache::new, handle_*_room_update more private.
This patch makes `RoomEventCache::new`,
`RoomEventCache::handle_joined_room_update` and
`RoomEventCache::handle_left_room_update` more private. They are no more
accessible from `event_cache` but only from `event_cache::caches`.
2026-03-12 10:07:05 +01:00
Ivan Enderlin 375d3a4921 test(sdk): Move the test_uniq_read_marker test.
This patch moves `test_uniq_read_marker` from `event_cache` to
`event-cache::caches::room`.

This patch is a prerequisite to the next patch where the
`RoomEventCache::handle_joined_room_update` will become more private.
Moving this test is necessary.
2026-03-12 10:07:05 +01:00
Ivan Enderlin 8b29fc8c08 chore(sdk): Move EventLocation.
This patch moves `EventLocation` from `event_cache::caches::room` to
`event_cache::caches`.

This type is used by the redecryptor, and can possibly be used by other
event caches. So let's move it in the `caches` module.
2026-03-12 10:07:05 +01:00
Ivan Enderlin 50f97f3377 chore(sdk): EventCacheInner::thread_subscriber_receiver is behind feature = "testing".
This patch puts the `EventCache::subscribe_thread_subscriber_updates`
method behind `#[cfg(feature = "testing")]` along with the
`EventCacheInner::thread_subscriber_receiver` field.

This data is only used for tests, as the documentation tells so. Let
make it clear. Also, it reduces the size `EventCacheInner` by 16 bytes
for non-testing builds.
2026-03-12 10:07:05 +01:00
Ivan Enderlin 0ce36d0bfb fix(sdk): EventCacheInner::all_caches_for_room returns a reference to Caches.
This patch updates `EventCacheInner::all_caches_for_rooms` by returning
an `OwnedRwLockReadGuard` instead of `Caches`. `Caches` no longer
implement `Clone`, which prevents cloning all the event caches per room
when we need only one for example.

`Caches` has two new methods: `handle_joined_room_update` and
`handle_left_room_update`. This type acts more like a dispatcher now,
which was the initial idea. That way, we don't need to dispatch manually
on all event caches: `Caches` is responsible for that.

We need to be a bit careful now since `all_caches_for_rooms` returns an
owned read-lock over `EventCacheInner::by_room`.
2026-03-12 10:07:05 +01:00
Ivan Enderlin 9608aa5840 chore(sdk): Add Caches::all_events.
This patch adds the `Caches::all_events` to iterate over all events from
all caches.
2026-03-12 10:07:05 +01:00
Ivan Enderlin 99c7e27fcf fix(sdk): Introduce ResetCaches.
This patch introduces the `Caches::prepare_to_reset` method, which
returns a `ResetCaches` type. This new type is responsible to lock all
the event caches related to a room, and to reset their in-memory state
with `ResetCaches::reset_all`. This patch fixes 2 bugs (see below).

This type is acquiring exclusive locks over all the event caches
managed by `Caches`. Once dropped, all the locks are released. Note that
`ResetCaches::reset_all` takes `self`, not `&self` or `&mut self`, i.e.
it consumes `ResetCaches`, ensuring the locks are _always_ released.

This patch also fixes a possible bug when acquiring the exclusive
locks could have failed, but the error wasn't stopping the execution;
the database would have been reset anyway. Now, `try_join_all` is used
before and after resetting the database (prior to this patch, it was
using `join_all` before resetting the database).

This patch also ensures that only one reset per `Caches` can
happen at a time, by making `Caches::prepare_to_reset` taking a
`&mut self`. This was partially supported by the write lock over
`EventCacheInner::by_room`, but it would have still been possible to
reset the same `Caches` concurrently.
2026-03-12 10:07:05 +01:00
Ivan Enderlin 74fc96eec2 chore(sdk): Creates the Caches type.
This patch is the first step for the `Caches` type. It creates it, along
with the `new` constructor. More changes are required, but this is a
first step.

The pattern `Caches { room }` is used everywhere where a
`RoomEventCache` was read previously, so that we are sure the type
system will complain when we will add more fields to `Caches` (like for
threads and pinned events).
2026-03-12 10:07:05 +01:00
Skye Elliot ea74fda969 test(sdk): Make room history visibility explicit
This isn't strictly necessary, but the lack of these events was
causing spurious CI timeouts when tested with the logic that
assumed a stricter history visibility than the spec required.
2026-03-11 15:50:42 +00:00
Skye Elliot 18ea1cafc7 fix(sdk): Assume history visibility is shared if no event found 2026-03-11 15:46:39 +00:00
Skye Elliot d58873f1c3 docs(sdk): Update CHANGELOG.md 2026-03-11 14:37:46 +00:00
Skye Elliot f7d818a8ba tests(sdk): Ensure shared history respects current history visibility
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-03-11 14:37:42 +00:00
Skye Elliot f62fbfc4ee tests(sdk): Create helper assert_utd_with_withheld code
This commit additionally flips the order of a few assertions to
meet developer expectations, i.e. errors are reported as diffs
from expected.

Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-03-11 14:33:55 +00:00
Skye Elliot 260e227f24 feat(sdk): Only share history if room history visibility is shared
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-03-11 14:33:55 +00:00
Stefan Ceriu 791f4bc871 feat(sentry): add final app version and platform configuration options next to the sentry dsn 2026-03-11 15:59:32 +02:00
rasmus f812afca21 doc(sdk): add CHANGELOG.md entry for add_mentions field in Event
Signed-off-by: rasmus <mail@rasmusantons.de>
2026-03-11 14:20:40 +01:00
rasmus dd1c16a873 test(sdk): add tests for reply events with and without add_mentions
Signed-off-by: rasmus <mail@rasmusantons.de>
2026-03-11 14:20:40 +01:00
rasmus 999d612a33 feat(sdk): add add_mentions toggle to Reply
Signed-off-by: rasmus <mail@rasmusantons.de>
2026-03-11 14:20:40 +01:00
Kévin Commaille f25af6be6b fix(ffi): Don't assume that all redacted state events have an empty content
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-11 10:34:41 +01:00
dependabot[bot] af8236d708 chore(deps): bump quinn-proto from 0.11.13 to 0.11.14
Bumps [quinn-proto](https://github.com/quinn-rs/quinn) from 0.11.13 to 0.11.14.
- [Release notes](https://github.com/quinn-rs/quinn/releases)
- [Commits](https://github.com/quinn-rs/quinn/compare/quinn-proto-0.11.13...quinn-proto-0.11.14)

---
updated-dependencies:
- dependency-name: quinn-proto
  dependency-version: 0.11.14
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-11 09:04:19 +02:00
Stefan Ceriu 0cdaf8d794 change(live_location): emit the last becon_info asset type when subscribing to live location shares instead of none 2026-03-11 09:02:37 +02:00
Stefan Ceriu 6b414b7791 change(location_sharing): make the asset type non-optional as per original MSC instructions
- https://github.com/matrix-org/matrix-spec-proposals/blob/matthew/location/proposals/3488-location.md
- `If m.asset is missing from the location's content the client should render it as m.self as that will be the most common use case. Otherwise, if it's not missing but the type is invalid or unknown the client should attempt to render it as a generic location. Clients should be able to distinguish between m.self and explicit assets for this feature to be correctly implemented as interpreting everything as m.self is unwanted.`
- this aligns the behavior with the newly introduced live location asset type handling
2026-03-11 09:02:37 +02:00
mgoldenberg 4ec9124ce1 Allow storing the same Event in multiple LinkedChunks of the same Room (#6200)
# Overview

There are scenarios in which it is sensible to have an event exist in
the same room more than once. Notably, this is true in the context of a
thread, where an event exists in the main timeline of a room, as well as
in a thread of that same room.

Support for this behavior has been implemented in the
`SQLiteEventCacheStore` in #6065; however, this was never implemented
for the `IndexeddbEventCacheStore` or the `MemoryStore`. This pull
request extends this behavior to both of those stores.

# Changes

## Integration Tests
First, `test_event_chunks_allows_same_event_in_room_and_thread` was
moved from `matrix_sdk_sqlite::event_cache_store` to
`matrix_sdk_base::event_cache::store::integration_tests`. Then, a few
additional integration tests were added to ensure that behavior is
consistent across implementations of `EventCacheStore`.

## `IndexeddbEventCacheStore`
In order to accommodate the behavioral changes specified by the
integration tests, it was necessary to modify the schema in the
IndexedDB implementation of `EventCacheStore`. Namely, the events object
store was cleared and removed and then replaced with a nearly identical
one, the only difference being the removal of a uniqueness constraint on
one of the indices.

The remaining changes mostly involved updating the behavior of top-level
`EventCacheStore` functions - e.g., filtering out events where they were
duplicated or removing positioning information where it was not
relevant.

## `MemoryStore`
The changes to `MemoryStore` mostly involved updating the behavior of
top-level `EventCacheStore` function - e.g., filtering out events where
they were duplicated or removing positioning information where it was
not relevant.

That being said, it also involved some breaking changes to
`RelationalLinkedChunk`.

1. `RelationalLinkedChunk::items` - this function returned an `Iterator`
that did not contain information about the `LinkedChunkId`, so this
information was added to the items in the `Iterator`.
2. `RelationalLinkedChunk::save_item` - this function did not update the
item in all linked chunks of the provided `Room`. It now does this, but
requires that the provided `Item` be `Clone`.

(1) could probably have been a new function, but I thought a nicer
interface was worth the breaking change. (2) could probably be prevented
by re-organizing `RelationalLinkedChunk`'s internal data structures to
remove the `Clone` requirement, but that seemed like it could turn into
a large refactoring project, so I opted for something simpler albeit
somewhat crude.

In both cases, I'm open to suggestions and would be happy to revisit if
something else is preferred.

---
Closes #6094.

- [x] I've documented the public API Changes in the appropriate
`CHANGELOG.md` files.
- [x] I've read [the `CONTRIBUTING.md`
file](https://github.com/matrix-org/matrix-rust-sdk/blob/main/CONTRIBUTING.md),
notably the sections about Pull requests, Commit message format, and AI
policy.
- [ ] This PR was made with the help of AI.

Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>

---------

Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-03-10 14:55:02 +01:00
mgoldenberg b65e450813 Support TLS v1.3 on all platforms (#6053)
**Note:** _this pull request has a companion pull request in the
[`complement-crypto`](https://github.com/matrix-org/complement-crypto/pull/229)
repository, which must be merged in conjunction with this one._

_Before merging, this should be tested in conjunction with the Element X
iOS client to ensure that TLS v1.3 is working properly._ @stefanceriu
has agreed to work on this.

## Overview

The primary change in this pull request upgrades the `reqwest`
dependency to its latest version, which defaults to using `rustls` with
support for `rustls-platform-verifier` instead of `native-tls` (see
[`reqwest@0.13.0`](https://github.com/seanmonstar/reqwest/releases/tag/v0.13.0)).
The benefit here is that `rustls` supports TLS v1.3 on all platforms,
whereas [`native-tls` does
not](https://github.com/sfackler/rust-native-tls/pull/278).

Additionally, this pull request makes `rustls` the default TLS
implementation in all the crates in this repository.

This will be particularly helpful with element-hq/element-x-ios#786.

## Changes

- `reqwest` bumped to `0.13.1`
- The API for adding/replacing certificates has changed a bit, so this
required some updating in `HttpSettings::make_client`
- `oauth2-reqwest` added in favor of `oauth2/reqwest` 
    - This is required in order to be compatible with `reqwest^0.13`
- _**`oauth2-reqwest` is currently in alpha release, so it probably
makes sense to let this stabilize a bit.**_ For details, see
https://github.com/ramosbugs/oauth2-rs/issues/333#issuecomment-3906712203.
- `getrandom` bumped to `0.3.4`
    - This is required in order to be compatible with `oauth2@5.1.0`
- `proptest` bumped to `1.9.0`
    - This is required in order to be compatible with `getrandom@0.3.4` 
- Make `rustls` the default TLS implementation

## Questions

### Mirror feature flag names?

A number of feature flags have been replaced in the dependencies above.

1. _**`reqwest/rustls-tls` => `reqwest/rustls`**_ - this is simply a
name change, but is semantically identical (see
[`reqwest@0.13.0`](https://github.com/seanmonstar/reqwest/releases/tag/v0.13.0)).
2. _**`getrandom/js` => `getrandom/wasm_js`**_ - the semantics here have
changed slightly, but it seems to just make it easier to enable the
`wasm_js` backend (see
[`getrandom@0.3.4`](https://github.com/rust-random/getrandom/blob/master/CHANGELOG.md#major-change-to-wasm_js-backend)).

At any rate, I have updated references to these flags in each of the
various `Cargo.toml` files, but have not changed the names of our
exposed features to mimic those in the dependencies.

Any thoughts or preferences on whether to mirror those names? That
would, of course, result in a breaking change.

### Default to using `rustls`? Deprecate `native-tls`?

Now that the dependencies have all been bumped, we can use `rustls` on
all platforms. Should this be the new default given that `native-tls`
will very likely never support TLS v1.3 on Apple devices? And should
`native-tls` be deprecated as a result?

**UPDATE:** _The consensus here seems to be that we should default to
using `rustls`, but that `native-tls` should still be available._

---
Fixes #5800.


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

Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>

---------

Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-03-10 13:38:18 +01:00
dependabot[bot] cee74d4965 chore(deps): bump bnjbvr/cargo-machete
Bumps [bnjbvr/cargo-machete](https://github.com/bnjbvr/cargo-machete) from 6456c7b34a8c1245e0c186ac50c56666ccbd44f1 to 8a8e0cd1046f54206f6d2e050cf028e6db9dbc75.
- [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/6456c7b34a8c1245e0c186ac50c56666ccbd44f1...8a8e0cd1046f54206f6d2e050cf028e6db9dbc75)

---
updated-dependencies:
- dependency-name: bnjbvr/cargo-machete
  dependency-version: 8a8e0cd1046f54206f6d2e050cf028e6db9dbc75
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-10 10:41:01 +01:00
dependabot[bot] a845bdf26a chore(deps): bump CodSpeedHQ/action from 4.11.0 to 4.11.1
Bumps [CodSpeedHQ/action](https://github.com/codspeedhq/action) from 4.11.0 to 4.11.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/2ac572851726409c88c02a307f1ea2632a9ea59b...281164b0f014a4e7badd2c02cecad9b595b70537)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-10 11:25:57 +02:00
dependabot[bot] 40ff661358 chore(deps): bump tj-actions/changed-files from 47.0.4 to 47.0.5
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 47.0.4 to 47.0.5.
- [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/v47.0.4...v47.0.5)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-version: 47.0.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-09 17:29:19 +01:00
Andy Balaam 9de2d70bad Provide recover_and_fix_backup method to fix backup if needed (#6252)
Part of https://github.com/element-hq/element-meta/issues/3059
2026-03-09 13:32:04 +00:00
Benjamin Bouvier 6d141c07bc refactor(test): use the event factory in the notable reason read receipt update test
Turns out, the `m.read` event was invalid, because it should've been
part of the ephemeral events in the sync response (and not part of the
room's timeline response), so the event was dismissed. The test already
passed while behaving this way, so let's make it reflect what it did
indeed.
2026-03-09 14:16:52 +01:00
Benjamin Bouvier 608091fbe7 fix(test): fix test_read_receipt_can_trigger_a_notable_update_reason expectations
The test was aborted just a bit too early, in that if you introduce
arbitrary sleep statements, it would fail because the stream of notable
reasons updates wouldn't be empty, and include two `LATEST_EVENT`
updates instead.

It's not clear why we get the second update, but this isn't critical to
fix at the moment, so I'll leave this as an exercise to the reader.
2026-03-09 12:45:40 +01:00
Kévin Commaille 5e1c917459 refactor(sdk): Take a UrlOrQuery in MatrixAuth::login_with_sso_callback
To make it more convenient to use with `LocalServerBuilder` /
`LocalServerRedirectHandle`.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-09 11:49:51 +01:00
Kévin Commaille 3e5e6efb31 refactor(sdk): Move UrlOrQuery into utils module
This will allow to reuse it outside of the `oauth` module.

It can now also be converted from a `QueryString`, for improved
compatibility with `LocalServerRedirectHandle`.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-09 11:49:51 +01:00
Ivan Enderlin 5902a857d9 fix(xtask): log overview sanitizes HTML.
The log message can contain HTML data. It happens notably when the
homeserver is broken, and an HTML document is returned in some errors.
We don't want to parse the fields in this case, because HTML breaks
everything.
2026-03-09 11:26:44 +01:00
Kévin Commaille 168fd7232e refactor(sdk): Remove TODOs by using new Ruma API
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-09 10:44:56 +01:00
Kévin Commaille e17ca1071c Upgrade Ruma after Typing breaking change
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-09 09:18:19 +00:00
Kévin Commaille 99ac38eb20 Upgrade Ruma after SecretEncryptedData breaking change
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-09 09:18:19 +00:00
Kévin Commaille 698ffba88e Upgrade Ruma after report_content breaking change
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-09 09:18:19 +00:00
Kévin Commaille 23a312ed68 Upgrade ruma after ErrorKind::Forbidden breaking change
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-09 09:18:19 +00:00
Kévin Commaille 1f69fcb80b Upgrade Ruma after RequestAction breaking changes
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-09 09:18:19 +00:00
Kévin Commaille deabb8b6e7 refactor(ui): Construct PossiblyRedactedPinnedEventsEventContent from PinnedEventsEventContent
It is simpler than going through deserialization.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-09 09:01:39 +01:00
Kévin Commaille d8cbb1b77b refactor(ui): Call membership_change() on the event rather than providing the parts manually
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-09 09:01:39 +01:00
Kévin Commaille 7c16d673fb benchmarks: Use realistic Matrix IDs in timeline
We rely on the EventFactory to generate a hash of the events for the
event IDs, and we use the appropriate length for room IDs.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-06 15:00:45 +01:00
Kévin Commaille e9fbbb52d5 benchmarks: Use realistic Matrix IDs in store_bench
Use the appropriate length for room IDs.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-06 15:00:45 +01:00
Kévin Commaille c05df39132 benchmarks: Use realistic Matrix IDs in room_list
We rely on the EventFactory to generate a hash of the events for the
event IDs, and we use the appropriate length for room IDs.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-06 15:00:45 +01:00
Kévin Commaille d572d201c9 benchmarks: Use realistic Matrix IDs in room_bench
We generate an arbitrary hash for the event IDs, and we use the
appropriate length for room IDs.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-06 15:00:45 +01:00
Kévin Commaille 39aa777b9b benchmarks: Use realistic Matrix IDs in linked_chunk
We rely on the EventFactory to generate a hash of the events for the
event IDs, and we use the appropriate length for room IDs.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-06 15:00:45 +01:00
Kévin Commaille f0ae0e53f9 benchmarks: Use realistic Matrix IDs in event_cache
We rely on the EventFactory to generate a hash of the events for the
event IDs, and we use the appropriate length for room IDs.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-06 15:00:45 +01:00
Kévin Commaille 7ec331c842 testing: Create realistic modern event IDs in EventFactory
As a fallback when the ID is not provided when constructing the event.
It allows to work with data that looks like what we would get in
production, which is important for benchmarks.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-06 15:00:45 +01:00
Skye Elliot 8472b5504e Merge pull request #6215 from matrix-org/kaylendog/history-sharing/restart-import
feat: Try import stored key bundles on client start
2026-03-05 16:23:36 +00:00
Skye Elliot 2ca717b4af docs: Update CHANGELOGs
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-03-05 15:55:03 +00:00
Skye Elliot c864fac823 test(sdk): De-duplicate shared history integration tests
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-03-05 15:55:03 +00:00
Skye Elliot c62da4a026 tests(sdk): Add integration test for bundle import crash recovery
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-03-05 15:55:03 +00:00
Skye Elliot 471e045ea3 feat(sdk): Remove pending key bundle on HTTP failure
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-03-05 15:55:02 +00:00
Skye Elliot 29ca03bb81 feat(sdk): Clear room pending key bundle for expired details
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-03-05 15:55:02 +00:00
Skye Elliot 29e3b7766d feat(sdk): Separate startup bundle import to different task
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-03-05 15:55:02 +00:00
Skye Elliot 2dd43fc9de feat(sdk): Add proper error logging and comments
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-03-05 15:55:02 +00:00
Skye Elliot 7fbc3e78e9 feat(sdk): Try import stored room key bundles on startup
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-03-05 15:55:02 +00:00
Skye Elliot ac6ccd3384 refactor(sdk): Move should_accept_bundle into shared_room_history
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-03-05 15:55:02 +00:00
Benjamin Bouvier f49784a15e refactor(crypto): use an async closure in Store::with_transaction
This allows passing the `Transaction` by mutable reference, instead of
passing it by ownership and requiring the callback to pass it back,
which is slightly better in terms of ergonomics. This was hard to
achieve without async closures, but now that we have them, this is
trivial.
2026-03-05 15:51:34 +01:00
Benjamin Bouvier b0aadd1574 refactor(test): use assert_let_timeout more often
Many of our tests make use of `assert_let` for checking that some value
comes out of a stream, while they could use `assert_let_timeout`, which
provides better ergonomics when the expected value doesn't arrive
immediately, by failing quickly.

This converts a few instances, making those tests easier to debug in the
future, would they fail again.
2026-03-05 15:36:41 +01:00
Stefan Ceriu 37ae2af67e fix(timeline): handle out of order beacon_info stop events/aggregations 2026-03-05 15:55:31 +02:00
Stefan Ceriu 4d64f3885a chore(timeline): add location sharing stop aggregation without timeline item test 2026-03-05 15:55:31 +02:00
Stefan Ceriu 436c598da5 chore(timeline): handle live location sharing stops similar to aggregations 2026-03-05 15:55:31 +02:00
Stefan Ceriu 034667cf3f chore(timeline): expose live location sharing asset type and cleanup public methods. 2026-03-05 15:55:31 +02:00
Stefan Ceriu 5f867ee982 chore(timeline): add live location changelogs 2026-03-05 15:55:31 +02:00
Stefan Ceriu 8521b7b65b chore(ffi): expose MSC3489 timeline types 2026-03-05 15:55:31 +02:00
Stefan Ceriu eca633b1cf chore(multiverse): handle TimelineItemContent::LiveLocation 2026-03-05 15:55:31 +02:00
Stefan Ceriu 6c769d1d33 chore(ui): add timeline live location tests 2026-03-05 15:55:31 +02:00
Stefan Ceriu 862a0e6f57 feat(ui_timeline): handle org.matrix.msc3672.beacon_info and related org.matrix.msc3672.beacon events and aggregate them onto the timeline's LiveLocation
# Conflicts:
#	crates/matrix-sdk-ui/src/timeline/event_handler.rs
2026-03-05 15:55:31 +02:00
Stefan Ceriu 2083e20592 chore(ui_timeline): explicitly filter out org.matrix.msc3672.beacon timeline events 2026-03-05 15:55:31 +02:00
Stefan Ceriu e61e86c3b4 feat(ui): add UI crate timeline types for handling MSC3489 live location sharing
# Conflicts:
#	crates/matrix-sdk-ui/src/timeline/event_item/mod.rs
#	crates/matrix-sdk-ui/src/timeline/mod.rs
2026-03-05 15:55:31 +02:00
Stefan Ceriu 17a9ab41e4 chore(tests): add event factory method for building MSC3672 beacon_info state events 2026-03-05 15:55:31 +02:00
Benjamin Bouvier 007eb15bce refactor(test): use with_versions/with_feature in a few tests and remove ok_custom 2026-03-05 14:36:42 +01:00
Benjamin Bouvier d37d65614e refactor(test): remove MockEndpoint<VersionsEndpoint>::ok_with_unstable_features
As it's now unused because of the previous commits.
2026-03-05 14:36:42 +01:00
Benjamin Bouvier 38afc0b1cd refactor(test): spell out use of the e2e_cross_signing feature in tests 2026-03-05 14:36:42 +01:00
Benjamin Bouvier 00bfffed99 refactor(test): don't use unstable features mocking when it's not needed 2026-03-05 14:36:42 +01:00
Benjamin Bouvier 964f6c8638 refactor(test): use the versions mock builder pattern in tests 2026-03-05 14:36:42 +01:00
Benjamin Bouvier df7823f1cf refactor(test): introduce a builder pattern for the VersionsEndpoint versions and features 2026-03-05 14:36:42 +01:00
Benjamin Bouvier 3457b5fa79 refactor(test): rename MockEndpoint<VersionsEndpoint>::versions to commonly_supported_versions 2026-03-05 14:36:42 +01:00
Benjamin Bouvier 21e8138805 chore: depersonalize some TODO code comments
Removed a few TODOs that were not applicable anymore, because they were
either very low value (in timeline test code) or already done (in event
cache, with respect to the cross-process locking).

Also removed my nick from some TODOs and comments, as code comments
aren't the best way to store assignees for issues.
2026-03-05 13:35:23 +01:00
Benjamin Bouvier 20d1087658 chore: remove spurious semicolon after match statement 2026-03-04 16:47:25 +01:00
Benjamin Bouvier 1edbad0bd8 refactor(client): monitor the thread subscriptions catchup task 2026-03-04 16:47:25 +01:00
Benjamin Bouvier 1825cd5816 chore: add changelog entry for the previous commit 2026-03-04 16:47:25 +01:00
Benjamin Bouvier 97e2b1c1b2 refactor(client): also consider server support when checking for thread subscriptions enablement 2026-03-04 16:47:25 +01:00
Kévin Commaille 02d0298b66 test(sdk): Add thorough tests about receiving stripped state events
Including stripped state events that fail to deserialize.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-04 12:23:26 +00:00
Kévin Commaille 6fd4988849 docs(base): Clarify comment for m.room.encryption
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-04 12:23:26 +00:00
Kévin Commaille ed319eed64 fix(base): Handle stripped state events that fail to deserialize
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-04 12:23:26 +00:00
Kévin Commaille 359f50f368 refactor(base): Make RoomInfo::handle_state_event generic over the enum type
This will allow to use the same method for stripped state.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-04 12:23:26 +00:00
Kévin Commaille c6bf11e836 Upgrade Ruma after Any(Sync)StateEvent breaking change
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-04 12:23:26 +00:00
Benjamin Bouvier 3aec150697 doc(event cache): clarify comment about Gap::token 2026-03-04 12:13:22 +01:00
Benjamin Bouvier ddd07443f1 chore: add entry in the changelog for the renaming of the Gap::prev_token field 2026-03-04 12:13:22 +01:00
Benjamin Bouvier 8dbb6e5c1d refactor(event cache): rename Gap::prev_token to token
As it is now used for both a previous batch token (backwards pagination)
or a next batch token (forwards pagination).
2026-03-04 12:13:22 +01:00
Benjamin Bouvier cdd40d0308 chore: add changelog entry for the renaming of BackPaginationError 2026-03-04 12:06:47 +01:00
Benjamin Bouvier e9f398472e refactor(event cache): rename BackpaginationError to PaginationError 2026-03-04 12:06:47 +01:00
Benjamin Bouvier 88eb4a0da9 Merge pull request #6185 from matrix-org/bnjbvr/permalink-linked-chunk
feat: introduce an event-focused linked chunk for event-focused timelines
2026-03-04 11:39:01 +01:00
Kévin Commaille 77dddf2540 Upgrade Ruma after breaking changes of ErrorKind
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-04 10:30:25 +00:00
Kévin Commaille 2cf8ce2a7d Upgrade Ruma after breaking change of InvitationRecipient
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-04 10:30:25 +00:00
Kévin Commaille 12b1102ca9 Upgrade Ruma after breaking change about Reply relations
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-04 10:30:25 +00:00
Kévin Commaille 81286ad1e7 Upgrade Ruma after breaking change about EventId constructors
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-04 10:30:25 +00:00
Benjamin Bouvier 280eae0b71 refactor(event cache): extract get_event_focused_cache as a separate helper function 2026-03-04 11:15:51 +01:00
Benjamin Bouvier bc88e96e62 chore: adjust some doc comments 2026-03-04 10:52:06 +01:00
Benjamin Bouvier 37708d509c chore: add a missing feature annotation 2026-03-03 16:00:09 +01:00
Ivan Enderlin 619ded3147 chore(sdk): Log errors from EventLinkedChunk.
This patch logs errors from `EventLinkedChunk` and send them to Sentry
(if Sentry is enabled).

The trick is to use [`#[instrument(err)]`][0] for logging errors.
Quoting the documentation:

> If the function returns a `Result<T, E>` and `E` implements
> `std::fmt::Display`, adding `err` or `err(Display)` will emit error
> events when the function returns `Err`:
>
> ```rust
> #[instrument(err)]
> fn my_function(arg: usize) -> Result<(), std::io::Error> {
>     Ok(())
> }
> ```
>
> The level of the error value event defaults to `ERROR`.

It sounds exactly what we need.

[0]: https://docs.rs/tracing-attributes/0.1.31/tracing_attributes/attr.instrument.html#examples-2
2026-03-03 15:55:00 +01:00
Benjamin Bouvier 30de09aef6 refactor(event cache): rename finish_forward_pagination to push_forwards_pagination_events 2026-03-03 15:50:53 +01:00
Benjamin Bouvier 0b6d00cfad Merge branch 'main' into bnjbvr/permalink-linked-chunk-backup 2026-03-03 15:49:13 +01:00
Kévin Commaille 029148ef6e refactor(sqlite): Get rid of the DATABASE_VERSION constants
They are error prone because they need to be bumped for every migration
otherwise the new migration will not happen because we exit early.

So instead we get rid of the early returns and log each individual
upgrade separately. It makes more noise when creating a new database,
but since it is logged at the DEBUG level it is not much of a problem.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-03-03 15:26:42 +01:00
Benjamin Bouvier 8d38f6109e fix(event cache): avoid racy initialization of event-focused linked chunk 2026-03-03 15:20:04 +01:00
Benjamin Bouvier 75c5528942 refactor(event cache): add last_chunk_as_gap to EventLinkedChunk 2026-03-03 15:20:04 +01:00
Benjamin Bouvier dd5cb220a0 refactor(event cache): add first_chunk_as_gap() in EventLinkedChunk too 2026-03-03 15:09:47 +01:00
Benjamin Bouvier 4b23378d29 refactor(event cache): extract pushing a gap as a standalone function 2026-03-03 15:07:04 +01:00
Benjamin Bouvier 66c97dc02f chore: address review comments 2026-03-03 15:02:38 +01:00
Richard van der Hoff a2f77c79bc Merge pull request #6233 from matrix-org/rav/history_sharing/get_all_rooms_pending_key_bundle
Add `CryptoStore::get_all_rooms_pending_key_bundle`
2026-03-03 12:56:10 +00:00
Benjamin Bouvier 2f791d19e3 chore: add changelog entry for the recycling of internal timeline ids 2026-03-03 13:51:57 +01:00
Benjamin Bouvier 066dd4aa21 test(timeline): add test for the previous feature 2026-03-03 13:51:57 +01:00
Benjamin Bouvier a4c3a4eb87 feat(timeline): recycle internal timeline ids upon deduplication
When the event cache decides to deduplicate items, it will remove the
duplicate items, then push them back at a position further down the
line.

This can lead to spurious re-creation of items, which may show up, in
embeddings, as different items as they don't share the same internal id.

This patch makes it so that if the same transaction includes a move of
an event (i.e. it's removed then reinserted elsewhere), then the
internal ID will be reused in this case.
2026-03-03 13:51:57 +01:00
Richard van der Hoff c23dd1ec0a update changelogs 2026-03-03 12:37:15 +00:00
Richard van der Hoff 80ac2b8c38 Add integration test for get_all_rooms_pending_key_bundle 2026-03-03 12:23:57 +00:00
dependabot[bot] 617c646a52 chore(deps): bump actions/upload-artifact from 6 to 7
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-03 10:06:34 +01:00
dependabot[bot] 29b506f301 chore(deps): bump crate-ci/typos from 1.43.5 to 1.44.0
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.43.5 to 1.44.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.43.5...v1.44.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-02 17:28:52 +01:00
dependabot[bot] cf84f2ff18 chore(deps): bump bnjbvr/cargo-machete
Bumps [bnjbvr/cargo-machete](https://github.com/bnjbvr/cargo-machete) from 3026399e8ccbe119a9624e9376afc8c5f21fb60f to 6456c7b34a8c1245e0c186ac50c56666ccbd44f1.
- [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/3026399e8ccbe119a9624e9376afc8c5f21fb60f...6456c7b34a8c1245e0c186ac50c56666ccbd44f1)

---
updated-dependencies:
- dependency-name: bnjbvr/cargo-machete
  dependency-version: 6456c7b34a8c1245e0c186ac50c56666ccbd44f1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-02 16:28:27 +00:00
Johannes Marbach 949bdb5bb3 fix(latest_event): handle race between send queue update and remote echo (#6220)
There seems to be a race condition where the
`RoomSendQueueUpdate::SentEvent` update arrives after the remote echo
has already been processed which leads to the latest event value getting
stuck at `LocalHasBeenSent`. This fixes the issue by trying to retrieve
the event from the cache first.

- [x] I've documented the public API Changes in the appropriate
`CHANGELOG.md` files.
- [x] I've read [the `CONTRIBUTING.md`
file](https://github.com/matrix-org/matrix-rust-sdk/blob/main/CONTRIBUTING.md),
notably the sections about Pull requests, Commit message format, and AI
policy.
- [ ] This PR was made with the help of AI.

---------

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2026-03-02 15:20:17 +00:00
Valere Fedronic cc8d6d8482 bindings: Support audio/video intent in rtc notification event content (#6207)
<!-- description of the changes in this PR -->

Updates ruma from
https://github.com/ruma/ruma/commit/289bee87974bd3c2ad14a6c15801c80b683b67dc
to
https://github.com/ruma/ruma/commit/89dab44660889ab02e5921adbf6b79a3262deb99
see
https://github.com/matrix-org/matrix-rust-sdk/pull/6207/commits/4250d657564039d21f63f2fbb954b0e2eb17d9eb

Depends on this ruma PR github.com/ruma/ruma/pull/2383

Support for reading the intent from the notification event
(m.call.intent in notification event as per
https://github.com/matrix-org/matrix-spec-proposals/pull/4075)

Parity with js-sdk implementation

https://github.com/matrix-org/matrix-js-sdk/blob/bd6547c0814e11b41e79de3cc9d0d4ecf7648272/src/matrixrtc/types.ts#L150

- [ ] I've documented the public API Changes in the appropriate
`CHANGELOG.md` files.
- [ ] I've read [the `CONTRIBUTING.md`
file](https://github.com/matrix-org/matrix-rust-sdk/blob/main/CONTRIBUTING.md),
notably the sections about Pull requests, Commit message format, and AI
policy.
- [ ] This PR was made with the help of AI.

<!-- Sign-off, if not part of the commits -->
<!-- See CONTRIBUTING.md if you don't know what this is -->
Signed-off-by:
2026-03-02 15:31:29 +01:00
Johannes Marbach 779d6a0925 fix(threads): include secondary relations when re-initializing a threaded timeline after a lag (#6209)
This is equivalent to what happens in `init_focus` when the timeline is
initialized from scratch.

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2026-03-02 15:07:24 +01:00
Benjamin Bouvier 1fdebd7d56 chore: remove the bullet point about reading the CONTRIBUTING.md file in the PR template
The PR template includes an item for a warning that is automatically
shown once (and once and for all) by the Github UI. In the previous
review of the changes to the pull request template, Ivan agreed that
this was redundant, so it seems there's no point in keeping it in the
pull request template itself.
2026-03-02 11:47:52 +01:00
Damir Jelić 39c0ee1f9b ci: Enable the MSRV check for all of our crates 2026-02-27 17:40:08 +01:00
Damir Jelić 1542a5b79e chore: Fix some new clippy warnings 2026-02-27 17:40:08 +01:00
Damir Jelić ae53b62762 chore: Bump the MSRV 2026-02-27 17:40:08 +01:00
Damir Jelić 4576013878 fix(ffi): Enable rustls by default
It's important to have one of those enabled so a simple cargo check on
the crate works.
2026-02-27 17:40:08 +01:00
Damir Jelić 73028a834e chore: Define the MSRV in all of our private crates 2026-02-27 17:40:08 +01:00
Damir Jelić 8c4f5c60b7 fix(ffi-macros): Fix compilation in case we only build this single crate 2026-02-27 17:40:08 +01:00
Ivan Enderlin aaeab050da chore(sdk): Extract EventCache::auto_shrink_linked_chunk_task to tasks.rs. 2026-02-27 16:34:28 +01:00
Ivan Enderlin 6b120505b8 chore(sdk): Extract EventCache::ignore_user_list_update_task to tasks.rs. 2026-02-27 16:34:28 +01:00
Ivan Enderlin a791243202 chore(sdk): Extract EventCache::listen_task to tasks.rs.
This patch also renames `listen_task` to `room_updates_task`.
2026-02-27 16:34:28 +01:00
Ivan Enderlin c5b7dbd0c0 chore(sdk): Extract EventCache::search_indexing_task into tasks.rs. 2026-02-27 16:34:28 +01:00
Ivan Enderlin 16e09dfefa chore(sdk): Extract EventCache::thread_subscriber_task into tasks.rs. 2026-02-27 16:34:28 +01:00
Kévin Commaille 2d13a682a2 refactor(sdk): Remove methods on OAuth API for account management URL
Instead encourage users to use the ones available on
`AuthorizationServerMetadata` because they support both the stable and
unstable actions.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-02-27 15:17:03 +01:00
Kévin Commaille c50bab4847 Upgrade Ruma
Brings in a few bug fixes

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-02-27 15:17:03 +01:00
Ivan Enderlin 768ded4b90 doc(sdk): Fix intra-links. 2026-02-27 12:49:31 +01:00
Ivan Enderlin 19ffa2fe0e chore(sdk): Inline RoomEventCacheStateLockWriteGuard::handle_backpagination.
The method `RoomEventCacheStateLockWriteGuard::handle_backpagination`
is used in a single place. This patch inlines it in…
`RoomEventCachePagination`, where it's supposed to be
declared! More precisely, inside the `PaginatedCache`
implementation for the `RoomEventCachePagination`, in the
`conclude_backwards_pagination_from_network`. The `state` module is
lighter with this change, and the code lives in the correct place.

This patch also renames `EventLinkedChunk::finish_back_pagination` to
`push_backwards_pagination_events`. The naming follows other names, like
`push_live_events`. Moreover, it removes the entire concept of “this
is part of a flow of methods”, it's just a single standalone method. On
this `EventLinkedChunk` alone, it is absolutely stateless.

This patch is purely code move, nothing changes.
2026-02-27 12:49:31 +01:00
Ivan Enderlin 3a57b547da chore(sdk): Add the RoomEventCacheUpdateSender type.
This patch creates the new `RoomEventCacheUpdateSender` type to group
both the room update sender, and the room generic update sender. It
simplifies a couple of constructor and makes the code more robust by
isolating this logic in a single type instead of two types.
2026-02-27 12:49:31 +01:00
Ivan Enderlin c3e238d2d6 chore(sdk): Make more fields of RoomEventCacheInner private. 2026-02-27 12:49:31 +01:00
Ivan Enderlin c257525b5a chore(sdk): Make RoomEventCacheInner private. 2026-02-27 12:49:31 +01:00
Ivan Enderlin 909dbd7b78 chore(sdk): Extract RoomEventCacheUpdate into caches/room/update.rs. 2026-02-27 12:49:31 +01:00
Ivan Enderlin 6c9892ee84 chore(sdk): Extract RoomEvetCacheLinkedChunkUpdate into caches/room/updates.rs. 2026-02-27 12:49:31 +01:00
Ivan Enderlin fe4381d38c chore(sdk): Extract RoomEventCacheGenericUpdate into caches/room/updates.rs. 2026-02-27 12:49:31 +01:00
Ivan Enderlin ea41e03ca3 chore(sdk): Add RoomEventCache::send_updates.
Firstly, the idea is to avoid accessing `RoomEventCache::inner`. Second,
by having a `send_updates` method, we increase the chances to forget
about one update (like `RoomEventcacheGenericUpdate` as it was the case
in the past).
2026-02-27 12:49:31 +01:00
Ivan Enderlin 9fb54074ee chore(sdk): Add RoomEventCache::state.
This patch adds `RoomEventCache::state` to replace all the `inner.state`
accesses. The idea is to make `inner` private.
2026-02-27 12:49:31 +01:00
Ivan Enderlin 597f66f71a chore(sdk): Use RoomEventCache::room_id().
This patch avoids calling `RoomEventCache::inner`.
2026-02-27 12:49:31 +01:00
Ivan Enderlin 44c71eea1b chore(sdk): Restrict the visiblity for handle_(joined|left)_room_update. 2026-02-27 12:49:31 +01:00
Ivan Enderlin d291be4235 chore(sdk): Move handle_joined_room_update from RoomEventCacheInner to RoomEventCache.
The idea of this patch is to make `RoomEventCache::inner` private. This
is required to achieve that goal.
2026-02-27 12:49:31 +01:00
Ivan Enderlin 1342daf381 chore(sdk): Move handle_left_room_update from RoomEventCacheInner to RoomEventCache.
The idea of this patch is to make `RoomEventCache::inner` private. This
is required to achieve that goal.
2026-02-27 12:49:31 +01:00
Ivan Enderlin 07f9e998e9 doc(sdk): Add missing, or fix existing documentation. 2026-02-27 12:49:31 +01:00
Ivan Enderlin a9f946d365 chore(sdk): Fix the visibility of several symbols.
This patch simplifies a lot of `pub(in super::…)` to just `pub`. The
visibility is now defined by the type itself, onto which the methods
are implemented.
2026-02-27 12:49:31 +01:00
Ivan Enderlin 248b65d2d5 chore(sdk): Extract RoomEventCacheState into caches/room/state.rs. 2026-02-27 12:49:31 +01:00
Ivan Enderlin 500a637bc4 chore(sdk): Extract RoomEventCacheSubscriber to its own subscriber module.
Purely code move, except the addition of `RoomEventCacheSubscriber::new`
to keep data isolated.
2026-02-27 12:49:31 +01:00
Hugh Nimmo-Smith 7da0d903e5 refactor: remove QRCodeGrantLoginError::UnableToCreateDevice and HumanQrGrantLoginError::UnableToCreateDevice 2026-02-27 11:40:27 +01:00
Hugh Nimmo-Smith 3cf4faec83 feat(ffi): additional HumanQrGrantLoginError enum values 2026-02-27 11:40:27 +01:00
Hugh Nimmo-Smith 2cfd53e64d feat(qr-login): additional QRCodeGrantLoginError enum values 2026-02-27 11:40:27 +01:00
Ivan Enderlin bbdfe7b38f chore(sdk): Move RoomEventCache in caches/room/mod.rs. 2026-02-26 11:39:40 +01:00
Ivan Enderlin b4977bbe5d chore(sdk): Move EventLinkedChunk in caches/event_linked_chunk.rs. 2026-02-26 11:39:40 +01:00
Ivan Enderlin 4bc4263e1c chore(sdk): Move ThreadEventCache in caches/thread/mod.rs. 2026-02-26 11:39:40 +01:00
Ivan Enderlin ef734e0876 chore(sdk): Move PinnedEvents in caches/pinned_events/mod.rs. 2026-02-26 11:39:40 +01:00
Damir Jelić a2881d5aca ci: Update our benchmarks workflow
This switches to OICD for authentication as recommended by the docs:
    https://codspeed.io/docs/integrations/ci/github-actions/configuration#oidc-recommended

Also switch to simulation mode as the instrumentation mode got renamed to
it.
2026-02-26 11:03:27 +01:00
Skye Elliot fadfd98bee feat(crypto): Add get_all_rooms_pending_key_bundle to store trait
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-02-25 16:02:41 +00:00
Skye Elliot 640fa4854f feat(sqlite): Add query_many helper trait method
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-02-25 16:02:35 +00:00
Richard van der Hoff a90cf28d3c Merge pull request #6199 from matrix-org/rav/history_sharing/invite_details_in_crypto_store
Move `invite_acceptance_details` out of `RoomInfo` and into crypto store
2026-02-25 15:16:51 +00:00
Ivan Enderlin aa11e9f062 fix(xtask): Fix a broken gap in the fields grid of overview. 2026-02-25 16:03:57 +01:00
Ivan Enderlin 4d9268d104 fix(xtask): Fix an infinite loop when parsing the fields of a span.
The error was coming from the subslice from `message` instead of
`fields`.

This patch also fixes an HTML error (`</li>` right after `<ul>`).
2026-02-25 16:03:57 +01:00
Benoit Marty b7b96d49c5 fix(ui): Check if an Event is redacted before trying to decrypt it. (#6203)
Check if an Event is redacted before trying to decrypt it.

Use a new enum `NotificationStatus.EventRedacted` when trying to resolve
an event for a notification.

PR done with the help of AI: GitHub copilot chat in VisualStudio.

I confirm that the fix is working in EXA with the code in
https://github.com/element-hq/element-x-android/pull/6241

Fixes #5796

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

- [x] I've documented the public API Changes in the appropriate
`CHANGELOG.md` files.
- [x] I've read [the `CONTRIBUTING.md`
file](https://github.com/matrix-org/matrix-rust-sdk/blob/main/CONTRIBUTING.md),
notably the sections about Pull requests, Commit message format, and AI
policy.
- [x] This PR was made with the help of AI.

<!-- Sign-off, if not part of the commits -->
<!-- See CONTRIBUTING.md if you don't know what this is -->
Signed-off-by: benoitm@element.io
2026-02-25 15:02:13 +00:00
Skye Elliot 37823df753 feat(crypto): Add room_id to RoomPendingKeyBundleDetails 2026-02-25 14:54:14 +00:00
Richard van der Hoff 096dd59c07 Add comments on race conditions 2026-02-25 14:47:56 +00:00
Richard van der Hoff 13fb3e76c5 base: switch from storing invite acceptance details in RoomInfo to crypto store 2026-02-25 14:27:18 +00:00
Richard van der Hoff 8e614c23eb base: pass the e2ee context into update_{knocked,invited}_room
... for consistency with `update_{left,joined}_room`, and because we will need
it shortly
2026-02-25 14:26:02 +00:00
Richard van der Hoff f324191c7f base: pull logic for clearing invite acceptance details up to higher level
Previously, invite acceptance details were cleared in `RoomInfo::set_state`. We
can't do that any more, because (a) that method is synchronous (b) it doesn't
have access to the crypto store (c) it would be a bit of a layering violation
even if it did.

Instead, we pull the logic up to higher-level methods which do have access to
the crypto store (though, for now, we don't use it).
2026-02-25 14:23:14 +00:00
Richard van der Hoff dc66994d66 sdk: make should_accept_bundle async
We're going to need to call async methods here, so prepare for it by making it
async
2026-02-25 14:23:14 +00:00
Richard van der Hoff 8ac5d42fbe crypto: add rooms_pending_key_bundles interface to crypto store
Add a place to store details about rooms where we are waiting for a key bundle
2026-02-25 14:23:14 +00:00
Richard van der Hoff 8f14ee976a crypto: Move InviteAcceptanceDetails to matrix-sdk-crypto, and rename
Step two in a series of refactoring: move and rename
`matrix_sdk_base::InviteAcceptanceDetails` to
`matrix_sdk_crypto::store::types::RoomPendingKeyBundleDetails`.

We're going to store this in the crypto store instead, so need to move it
down. I also want to rename it to better reflect how we interpret it.
2026-02-25 14:23:14 +00:00
Richard van der Hoff da92379e22 base: gate RoomInfo::invite_acceptance_details behind e2e-encryption
This field is only ever used when encryption is enabled, and we want to move it
to the crypto crate. So, for a starting point, gate it behind the
e2e-encryption feature flag.
2026-02-25 14:23:14 +00:00
Ivan Enderlin 0cbe48e986 feat(xtask): Add cargo xtask log timer.
This patch adds a new `timer` command. It helps to visualise the
`timer!` log in a table with their duration.
2026-02-25 15:15:36 +01:00
Ivan Enderlin 99bfb94d6d fix(xtask): Fix an expect message. 2026-02-25 15:15:36 +01:00
Ivan Enderlin 3aa424117c fix(xtask): Fix a CSS custom property naming error. 2026-02-25 15:15:36 +01:00
Ivan Enderlin 4160192709 refactor(common): Add the “Timer” prefix to timer!'s message.
The idea of adding the “Timer” prefix is to ease parsing the logs: it
gives an anchor for a regex engine (e.g. `Timer _[^_]+_ finished in`).
2026-02-25 15:15:36 +01:00
Richard van der Hoff 6bc09cbbd9 refactor(base): pass references to e2ee context instead of cloning
`matrix_sdk_base::response_processors::e2ee::E2EE` exists to pass E2EE context to the
logic within `response_processors`. Currently we pass it around by value in
many places, and hence have repetitive code for building the context, as well
as lots of calls to `.clone`.

Cloning of this type is cheap, but we can do better, by passing it around by
reference instead.
2026-02-25 15:07:27 +01:00
Doug 6c18f73b51 chore: Use multi-process lock config as the signal for enabling the OIDC cross process lock.
# Conflicts:
#	bindings/matrix-sdk-ffi/CHANGELOG.md
2026-02-24 18:11:18 +02:00
Ivan Enderlin a8e4630f56 chore(sqlite): Update deadpool to 0.13 and deadpool-sync to 0.2.
These releases include our patch to fix a panic (see
https://github.com/deadpool-rs/deadpool/pull/461).
2026-02-24 16:26:45 +01:00
Ivan Enderlin 45d16919a0 doc(ffi,sdk): Add #6174 in the CHANGELOG.md. 2026-02-24 14:08:23 +01:00
Ivan Enderlin 9fc10e90a3 doc(sdk): Add #6174 to the CHANGELOG.md. 2026-02-24 14:08:23 +01:00
Ivan Enderlin d00aa2ccd2 feat(sdk): Introduce RoomEventCache::thread_pagination. 2026-02-24 14:08:23 +01:00
Ivan Enderlin 79155bb59b refactor(sdk): Implement ThreadPagination based on Pagination. 2026-02-24 14:08:23 +01:00
Ivan Enderlin 2f26e346ed chore(sdk): Move the pagination.rs module to caches/room/pagination.rs. 2026-02-24 14:08:23 +01:00
Ivan Enderlin 2d32096534 refactor(sdk): Re-implement RoomPagination with the new Pagination type. 2026-02-24 14:08:23 +01:00
Ivan Enderlin 73814c5be0 refactor(sdk): Introduce the Pagination type along with the PaginatedCache trait. 2026-02-24 14:08:23 +01:00
Ivan Enderlin e84e83c716 doc(sdk): Fix internal doc.
This patch moves a doc paragraph from one method to another. It was
misplaced.
2026-02-24 14:08:23 +01:00
Ivan Enderlin 0edff99390 refactor(sdk): Add waited_for_initial_prev_token in LoadMoreEventsBackwardsOutcome::Gap.
This patch adds the `waited_for_initial_prev_token` field directly
inside `LoadMoreEventsBackwardsOutcome::Gap`. It removes the need
to manage the `state_guard` manually (which can be error-prone). It
also removes one access to the `state_guard` in the pagination module.
Finally, it paves the road for a shared `Pagination<C>` type for
`RoomEventCache` and `ThreadEventCache`.
2026-02-24 14:08:23 +01:00
Ivan Enderlin cdd7be13f1 refactor(sdk): Rename RoomPaginationStatus to PaginationStatus.
This patch renames the `RoomPaginationStatus` enum to `PaginationStatus`
because it won't be restricted to the `RoomEventCache` only: indeed, the
`ThreadEventCache` will soon be able to yield it too. Let's do the
renaming now.
2026-02-24 14:08:23 +01:00
Ivan Enderlin 9729e633f2 chore(sdk): Promote a debug! to error! when an HTTP request fails.
This patch increases a log from `debug` to `error` when an HTTP request
fails. This is an error, and it must appear as such in the logs.
2026-02-24 13:28:47 +01:00
dependabot[bot] f796232220 chore(deps): bump tj-actions/changed-files from 47.0.2 to 47.0.4
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 47.0.2 to 47.0.4.
- [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/v47.0.2...v47.0.4)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-version: 47.0.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-24 13:28:29 +01:00
dependabot[bot] 13d46e6aeb chore(deps): bump CodSpeedHQ/action from 4.10.6 to 4.11.0
Bumps [CodSpeedHQ/action](https://github.com/codspeedhq/action) from 4.10.6 to 4.11.0.
- [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/4deb3275dd364fb96fb074c953133d29ec96f80f...2ac572851726409c88c02a307f1ea2632a9ea59b)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-24 13:27:56 +01:00
Kévin Commaille 6c75571c38 crypto: Avoid dereferencing to clone identifier
The code was dereferencing to the borrowed type, only to convert it
back to an owned type. We can just use `.clone()` for this.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-02-24 13:09:55 +01:00
Benjamin Bouvier ec719bbb6b refactor(send queue): monitor the send queue background task 2026-02-23 18:55:31 +02:00
Benjamin Bouvier f3cb3c7317 refactor(utd hook): monitor the background task for the UTD hook reporting 2026-02-23 18:55:31 +02:00
Benjamin Bouvier eab7dd02d2 refactor(timeline): monitor background tasks spawned by the timeline 2026-02-23 18:55:31 +02:00
Benjamin Bouvier ed8d75289e refactor(room list): monitor background loading state task 2026-02-23 18:55:31 +02:00
Benjamin Bouvier 540abe2a7c refactor(spaces): monitor background tasks spawned by the space service 2026-02-23 18:55:31 +02:00
Ivan Enderlin c017ce0928 feat(sqlite): SqliteCryptoStore has 1 write connection.
Similarly to #5382 and #5744, this patch introduces a write-only
connection in `SqliteCryptoStore`. The idea is to get many read-only
connections, and a single write-only connection behind a lock, so that
there is a single writer at a time.

This patch renames the `acquire` method to `read`, and it introduces a
new `write` connection.
2026-02-23 14:31:13 +01:00
Kévin Commaille 747b5db764 refactor: Remove unnecessary clones with .to_owned()
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-02-23 14:29:55 +01:00
Kévin Commaille 5d18820120 refactor: Use owned_*_id! macros rather than *_id!().to_owned()
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-02-23 14:29:55 +01:00
Jorge Martín 9870228a76 doc(ui): Improve logs with variable inlining 2026-02-23 12:41:57 +01:00
Jorge Martín 3c8666ae2c doc(ui): Fix comment about the event_count + invite_count check 2026-02-23 12:41:57 +01:00
Jorge Martín 3783f4925e test(ui): Try asserting we received the right invite event in test_try_sliding_sync_ignores_invites_for_non_subscribed_rooms 2026-02-23 12:41:57 +01:00
Jorge Martín 1667db12ec refactor(ui): Make MAX_SLIDING_SYNC_ATTEMPTS a const 2026-02-23 12:41:57 +01:00
Jorge Martín 8c114ad57e fix(ui): Unexpected invites are ignored for non-subscribed rooms in NotificationClient::try_sliding_sync 2026-02-23 12:41:57 +01:00
OneProg 862126800d fix: Fix a small typo in ClientBuildError::MissingHomeserver error message 2026-02-23 12:13:44 +02:00
Kévin Commaille b1e4e16f8e Add PR number to changelog
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-02-23 10:10:14 +00:00
Kévin Commaille f283126e6e base: Remove once_cell dependency
Use the types that were stabilized in the standard library instead.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-02-23 10:10:14 +00:00
Kévin Commaille fc70a7da2c sdk: Remove once_cell dependency
Use the types that were stabilized in the standard library instead.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-02-23 10:10:14 +00:00
Kévin Commaille aff26b1ed9 ffi: Remove once_cell dependency
Use the types that were stabilized in the standard library instead.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-02-23 10:10:14 +00:00
Kévin Commaille fd5c1d847e Remove once_cell dependency
Use the types that were stabilized in the standard library instead.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-02-23 10:10:14 +00:00
Kévin Commaille 956a5d46f1 ui: Remove once_cell dependency
Use the types that were stabilized in the standard library instead.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-02-23 10:10:14 +00:00
Kévin Commaille 1dbd2caeb2 testing: Remove once_cell dependency
Use the types that were stabilized in the standard library instead.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-02-23 10:10:14 +00:00
Jorge Martín 4257649933 refactor(sdk): Put use behind e2e-encryption feature to appease the linter 2026-02-19 17:04:57 +01:00
Jorge Martín c996307265 doc: Add changelog entries 2026-02-19 17:04:57 +01:00
Jorge Martín fc97b04ec8 refactor(ffi): Fixed some more missing renames and added a changelog entry 2026-02-19 17:04:57 +01:00
Jorge Martín e1abe99ad0 refactor: Several renames and fixes to comments 2026-02-19 17:04:57 +01:00
Jorge Martín 07f894c2a5 refactor: Remove unrelated code added for testing 2026-02-19 17:04:57 +01:00
Jorge Martín 11f2bf0ad6 refactor: Minor improvements, some extra comments 2026-02-19 17:04:57 +01:00
Jorge Martín 54e4cb2d10 refactor: Rename CrossProcessStoreConfig into CrossProcessLockConfig, move it into matrix_sdk_common::cross_process_lock and use it inside CrossProcessLock instead of the previous lock_holder: Option<String> 2026-02-19 17:04:57 +01:00
Jorge Martín 855bdadbad doc: Fix example in docs 2026-02-19 17:04:57 +01:00
Jorge Martín 287049c719 refactor: Rename CrossProcessStoreMode to CrossProcessStoreConfig 2026-02-19 17:04:57 +01:00
Jorge Martín 12e377d296 refactor: Make CrossProcessLock act as a no-op implementation when no lock_holder is present 2026-02-19 17:04:57 +01:00
Jorge Martín 4f7f4a054f doc: More doc fixes 2026-02-19 17:04:57 +01:00
Jorge Martín c18b1cfab9 fix: Fix weird rebase issue 2026-02-19 17:04:57 +01:00
Jorge Martín d6b6eb9e31 doc: Fix documentation mentioning the removed cross process lock holder name 2026-02-19 17:04:57 +01:00
Jorge Martín 52981a839a refactor(sdk): Try making IndexedDB use CrossProcessStoreMode 2026-02-19 17:04:57 +01:00
Jorge Martín 50b1e3baf2 refactor: Use the new APIs everywhere 2026-02-19 17:04:57 +01:00
Jorge Martín 3169c65bf2 refactor(ui): Make NotificationClient use the new CrossProcessStoreMode internally 2026-02-19 17:04:57 +01:00
Jorge Martín 1b724f288f refactor(ffi): Allow creating Client instances with cross-process store mode 2026-02-19 17:04:57 +01:00
Jorge Martín def89d2661 refactor(sdk): Allow creating Client instances with cross-process store mode 2026-02-19 17:04:57 +01:00
Jorge Martín d0cce0eafe refactor(sdk-base): Allow passing a cross-process store mode for BaseClient::clone_with_in_memory_state_store 2026-02-19 17:04:57 +01:00
Jorge Martín 901e280d5c refactor(sdk-common): Allow creating dummy CrossProcessLockGuard for the CrossProcessStoreMode::SingleProcess case 2026-02-19 17:04:57 +01:00
Jorge Martín a44dc4b70c refactor(ui): Simplify EncryptionSyncService by getting rid of WithLocking, we can use CrossProcessStoreMode instead 2026-02-19 17:04:57 +01:00
Jorge Martín 7d474c1415 refactor(sdk-base): Make cross_process_lock optional for MediaStoreLock 2026-02-19 17:04:57 +01:00
Jorge Martín f95d174d9a refactor(sdk-base): Make cross_process_lock optional for EventCacheStoreLock 2026-02-19 17:04:57 +01:00
Jorge Martín 4fe4e9cb7c refactor(sdk-base): Add CrossProcessStoreMode to make cross-process lock optional 2026-02-19 17:04:57 +01:00
Ivan Enderlin 209595e66e doc: Add another checkbox about AI policy in pull_request_template.md
This patch adds a new checkbox to ensure the user has read the `CONTRIBUTING.md` file, notably the Pull requests, Commit message format, and AI policy Sections.

Signed-off-by: Ivan Enderlin <ivan@mnt.io>
2026-02-19 16:29:17 +01:00
Benjamin Bouvier 7e69880148 test: make the introduced test more resilient to races
This avoids the sleep statement by only listening to the room's event
cache, instead of listening to the background send task and then wait
for it to save the sent event in the room cache. The test is more
resilient this way.
2026-02-19 14:56:31 +01:00
Benjamin Bouvier ad1e93473d fix(event cache): don't include our own events when computing non_empty_all_duplicates
See the new paragraph in the code comment around
`non_empty_all_duplicates`.
2026-02-19 14:56:31 +01:00
Benjamin Bouvier 449fbd3ad4 test(send queue): don't ditch gaps if the sync response included only our own events 2026-02-19 14:56:31 +01:00
Benjamin Bouvier c3e3cdabf8 refactor(event cache): introduce notify_subscribers for the pinned event cache too 2026-02-18 11:35:11 +01:00
Benjamin Bouvier 258e520cee refactor(event cache): move replace_utds to the EventLinkedChunk and reuse it everywhere 2026-02-18 11:35:11 +01:00
Benjamin Bouvier 17622911d4 refactor(event cache): move find_event to the EventLinkedChunk and reuse it from everywhere 2026-02-18 11:35:11 +01:00
Benjamin Bouvier 19c22a1883 fix(event cache): have redecryption update the event-focused caches too 2026-02-18 11:35:11 +01:00
Benjamin Bouvier 188d3a2ead feat(timeline): use the event-focused caches in the timeline 2026-02-18 11:35:11 +01:00
Benjamin Bouvier 4ec7d99d71 feat(event cache): introduce the event-focused linked chunk 2026-02-18 11:35:11 +01:00
Benjamin Bouvier 01b21bd5e8 feat(linked chunk): add a new linked chunk id for event-focused timelines 2026-02-18 10:57:43 +01:00
Benjamin Bouvier a6663718d0 refactor(event factory): simplify tag() helper 2026-02-18 10:57:04 +01:00
Benjamin Bouvier 72c6dc8e08 refactor(test): get rid of RoomAccountDataTestEvent 2026-02-18 10:57:04 +01:00
Benjamin Bouvier 961edaf4b9 test(spaces): add an EventFactory method to create an m.space_order event 2026-02-18 10:57:04 +01:00
Benjamin Bouvier ea0a81989a refactor(test): replace usage of custom JSON with EventFactory::tag() 2026-02-18 10:57:04 +01:00
Benjamin Bouvier b68c22bd56 refactor(test): replace usage of MarkedUnread with EventFactory::marked_unread() in tests 2026-02-18 10:57:04 +01:00
Benjamin Bouvier 130e3ad04b refactor(test): replace RoomAccountDataTestEvent in read_receipts.rs 2026-02-18 10:57:04 +01:00
Benjamin Bouvier 9252c72dea test(timeline): replace FullyRead with EventFactory 2026-02-18 10:57:04 +01:00
Benjamin Bouvier 6d2f787623 test(sync_builder): make add_account_data accept impl Into<Raw<...>>
Change JoinedRoomBuilder::add_account_data() to accept any type that
implements Into<Raw<AnyRoomAccountDataEvent>>, preparing for migration
away from RoomAccountDataTestEvent enum.
2026-02-18 10:57:04 +01:00
Benjamin Bouvier e3042e664b test: delete sync_events.rs
Remove the sync_events module entirely now that all usages have been
migrated to EventFactory.
2026-02-18 10:57:04 +01:00
Benjamin Bouvier e401ece78f test(room): use the EventFactory::member function instea of the fixture MEMBER event 2026-02-18 10:57:04 +01:00
Benjamin Bouvier e79654c80b test(client): replace POWER_LEVELS, NAME, TAG with EventFactory 2026-02-18 10:57:04 +01:00
Benjamin Bouvier 0777425a73 test(notification): replace POWER_LEVELS with EventFactory 2026-02-18 10:57:04 +01:00
Benjamin Bouvier e83d085e77 test(state_store): replace sync_events usages with EventFactory 2026-02-18 10:57:04 +01:00
Benjamin Bouvier 026acf7f45 test(indexeddb): replace MEMBER_INVITE/BAN/STRIPPED with EventFactory 2026-02-18 10:57:04 +01:00
Benjamin Bouvier ed4454fcbb test(event_factory): add presence() helper and PresenceBuilder 2026-02-18 10:57:04 +01:00
Benjamin Bouvier 3749c9c616 test(event_factory): add tag() helper for m.tag events 2026-02-18 10:57:04 +01:00
Benjamin Bouvier b95b0ba1c4 test(event_factory): add room account data support
Add support for room account data events in EventFactory:
- Add RoomAccountData format to EventFormat enum
- Add From implementations for Raw<AnyRoomAccountDataEvent>
- Add room_account_data() generic method
- Add fully_read() helper for m.fully_read events
- Add marked_unread() helper for m.marked_unread events
2026-02-18 10:57:04 +01:00
Benjamin Bouvier 4c6d5f9654 test: replace ENCRYPTION_CONTENT usages with EventFactory
Replace all usages of ENCRYPTION_CONTENT and
ENCRYPTION_WITH_ENCRYPTED_STATE_EVENTS_CONTENT static values
with EventFactory::room_encryption().into_content() calls.

This uses the new into_content() method to get just the event
content for HTTP response mocking.
2026-02-18 10:57:04 +01:00
Benjamin Bouvier 6b99f37268 test(event_factory): add into_content() method to EventBuilder
Add a generic method to EventBuilder that returns just the event
content as a serde_json::Value. This is useful when mocking HTTP
responses that return event content rather than full events.
2026-02-18 10:57:04 +01:00
Benjamin Bouvier 818d0ef233 test: remove unused static JSON values from sync_events.rs 2026-02-18 10:57:04 +01:00
Jonas Jelten 2022017b29 feat(ffi): export is_low_priority for a room
Signed-off-by: Jonas Jelten <jj@sft.lol>
2026-02-17 15:23:26 +01:00
Hugh Nimmo-Smith 3c13b3edd4 test(qr-login): assert expect error codes in grant test cases 2026-02-17 13:01:13 +01:00
Johannes Marbach 3bd3ba7aff fix(latest_event): handle edits when the target event is not directly preceding
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2026-02-17 10:12:21 +01:00
dependabot[bot] 9c6a3d6cf4 chore(deps): bump tj-actions/changed-files from 47.0.1 to 47.0.2
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 47.0.1 to 47.0.2.
- [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/v47.0.1...v47.0.2)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-version: 47.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-16 16:56:12 +01:00
dependabot[bot] 86ae50d1bd chore(deps): bump crate-ci/typos from 1.43.4 to 1.43.5
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.43.4 to 1.43.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.43.4...v1.43.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-16 16:56:03 +01:00
Benjamin Bouvier 347d348bf4 fix(bench): add a sender to the room encryption event 2026-02-16 10:53:31 +01:00
Benjamin Bouvier 079c8175f2 refactor(test): remove the PresenceTestEvent enum that's unused 2026-02-16 10:53:31 +01:00
Benjamin Bouvier 64163c32f6 refactor(test): add an invite_room_state method to the EventBuilder
So we can pass the room state when receiving an invite, using more event
factory!
2026-02-16 10:53:31 +01:00
Benjamin Bouvier bcb84cc27c refactor(test): remove StrippedStateTestEvent 2026-02-16 10:53:31 +01:00
Benjamin Bouvier e299adbdeb refactor(test): remove more unused test event variants 2026-02-16 10:53:31 +01:00
Benjamin Bouvier 2ab1d8a985 refactor(test): remove StateTestEvent \o/\o/\o/ 2026-02-16 10:26:11 +01:00
Benjamin Bouvier d71387e7f7 refactor(test): remove StateTestEvent::Custom and replace it with usage of the event factory
There's one case where we want to create a custom join rule, and thus
must parse directly from JSON, but the rest can reuse helpers that
already exist.
2026-02-16 10:26:11 +01:00
Benjamin Bouvier fa638ae630 refactor(test): remove StateTestEvent::EncryptionWithEncryptedStateEvents 2026-02-16 10:26:11 +01:00
Benjamin Bouvier 4965800a0a refactor(test): remove StateTestEvent::RoomName and use the event factory instead 2026-02-16 10:26:11 +01:00
Benjamin Bouvier f7b55bfb5c refactor(test): remove StateTestEvent::PowerLevels and use the event factory instead
Since it's always using the default power levels, provide a default ctor
for it, and use it throughout this commit.
2026-02-16 10:26:11 +01:00
Benjamin Bouvier 1fdf23a456 refactor(test): remove StateTestEvent::MemberInvite/Leave 2026-02-16 10:26:11 +01:00
Benjamin Bouvier 5c1ee281a9 refactor(test): remove StateTestEvent variants that were unused 2026-02-16 10:26:11 +01:00
Benjamin Bouvier f4b401ac58 refactor(test): remove StateTestEvent::MemberAdditional and use the EventFactory instead 2026-02-16 10:26:11 +01:00
Benjamin Bouvier 47e4c44d1b refactor(test): remove StateTestEvent::Member and use the event factory instead 2026-02-16 10:26:11 +01:00
Benjamin Bouvier 1ba8704941 refactor(test): remove StateTestEvent::JoinRules 2026-02-16 10:26:11 +01:00
Benjamin Bouvier 7f13f86912 refactor(test): remove StateTestEvent::HistoryVisibility 2026-02-16 10:26:11 +01:00
Benjamin Bouvier 622825f099 refactor(test): remove StateTestEvent::Encryption 2026-02-16 10:26:11 +01:00
Benjamin Bouvier 16e50ee484 refactor(test): remove StateTestEvent::Create and replace it with the event factory 2026-02-16 10:26:11 +01:00
Benjamin Bouvier 7dc76fa423 refactor(test): remove StateTestEvent::Aliases which was simply unused 2026-02-16 10:26:11 +01:00
Benjamin Bouvier db3308981b refactor(test): remove StateTestEvent::Alias and use the event factory instead 2026-02-16 10:26:11 +01:00
Ivan Enderlin e5cf097e32 fix(sdk): LatestEventValue::RemoteInvite is computed once per room.
An invite room is only constitued of stripped-state events. These events
do not have an `origin_server_ts` field. It means we cannot compute
the timestamp of the `LatestEventValue`. To workaround this, we set
the timestamp to `now()`. See `Builder::new_remote_for_invite` to learn
more. If an invite room receives a new event, its `LatestEventValue`'s
timestamp can be updated to `now()` again, which will make the room
bumps to the top of the room list for example. This is not an acceptable
behaviour because it can be an “attack vector”, i.e. a way to annoy
people with spammy invites. That's why, once a `RemoteInvite` has been
computed, we do not refresh it.

The fix is 2 instructions. The rest are comments and tests.
2026-02-13 11:03:24 +01:00
Benjamin Bouvier 3ac2339475 Revert "refactor: don't build the benchmarks by default either"
This reverts commit 66ea5d990d.
2026-02-12 15:16:41 +01:00
Benjamin Bouvier 9b4e60d075 fixup! refactor: don't build the benchmarks by default either 2026-02-12 14:36:45 +01:00
Benjamin Bouvier 66ea5d990d refactor: don't build the benchmarks by default either 2026-02-12 14:36:45 +01:00
Benjamin Bouvier ea40bf35b8 refactor: don't build multiverse by default 2026-02-12 14:36:45 +01:00
Stefan Ceriu 4ca1e45271 feat(ffi): replace tracing's RollingFileAppender with SizeAndDateRollingWriter (#6162)
This new file writer supports both total size and age based rolling, and
will take care of a whole class of edge cases that make rageshake
uploading fail.

* It will rotate log files when the configured time period elapses
(e.g., hourly, daily).
* When the total size of all log files exceeds the configured limit, the
oldest files are removed.
* Logs older than the configured max age are automatically removed
during cleanup.
* Only files matching the configured prefix and suffix are managed by
any given writer instance.
2026-02-12 13:28:03 +00:00
Benjamin Bouvier 771de205b3 review(event cache): rename TimelineVectorUpdate to TimelineVectorDiffs 2026-02-12 13:57:21 +01:00
Benjamin Bouvier 07fe503cd4 refactor(event cache): use TimelineVectorUpdate in the UpdateTimelineEvents variant too, for consistency 2026-02-12 13:57:21 +01:00
Benjamin Bouvier deeaa862d8 refactor(event cache): reuse the TimelineVectorUpdate for the pinned event cache
The pinned event timeline only cares about updates to the vector of
events, not the other types of updates, at the moment. As such, it
doesn't need to use the fully-fledged `RoomEventCacheUpdate`, but a
simpler version, like the one that is being used for threads. Hence, we
can reuse `TimelineVectorUpdate` here.
2026-02-12 13:57:21 +01:00
Benjamin Bouvier 91ae6a1eab refactor(event cache): move TimelineVectorUpdate to the new caches module 2026-02-12 13:57:21 +01:00
Benjamin Bouvier 86480cb37a refactor(event cache): rename ThreadEventCacheUpdate to TimelineVectorUpdate 2026-02-12 13:57:21 +01:00
Benjamin Bouvier feed4f725b fix(event cache): make sure the pinned event cache task is aborted on drop
Otherwise, it would leak if a client was killed, after it's been
created.
2026-02-12 13:57:21 +01:00
Benjamin Bouvier e7d2c729c2 chore(test): remove unused test JSONs 2026-02-12 13:00:13 +01:00
Michael Goldenberg 75d39c8186 doc(ui): move latest change log entry to the top
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-02-11 15:17:43 +01:00
Michael Goldenberg 726e5c95b1 doc(ui): re-word change log entry
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-02-11 15:17:43 +01:00
Michael Goldenberg 19572c9712 doc(ui): update change log
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-02-11 15:17:43 +01:00
Michael Goldenberg ce1ec8bab3 test(notification-client): replace unit test with crate-specific integration tests
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-02-11 15:17:43 +01:00
Michael Goldenberg 1833e9185d test(notification-client): ensure tests check for member hints in required state
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-02-11 15:17:43 +01:00
Michael Goldenberg d7c6af9677 test(notification-client): replace integration test with unit test
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
Co-authored-by: Jorge Martín <jorgem@element.io>
2026-02-11 15:17:43 +01:00
Michael Goldenberg 26d76d128a refactor(notification-client): add member hints to required state
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
Co-authored-by: Jorge Martín <jorgem@element.io>
2026-02-11 15:17:43 +01:00
Michael Goldenberg b35bea6c2b test(notification-client): show room names wrong in sliding sync notifications
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-02-11 15:17:43 +01:00
Benjamin Bouvier f69e154e30 refactor(event cache): keep the same summary check if not post-processing events after a redecryption 2026-02-11 14:56:38 +01:00
Benjamin Bouvier 4d3e7f7336 fix(event cache): have redecryption cause a thread summary update 2026-02-11 14:56:38 +01:00
Benjamin Bouvier ee1eb99134 test: write a test for a UTD as the latest thread reply
The latest thread reply in a summary is UTD, then should be resolved
automatically. It doesn't, currently, so this test acts as a regression
test.
2026-02-11 14:56:38 +01:00
Johannes Marbach 70665a84aa feat(client): make it possible to subscribe to key upload errors (#6135)
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2026-02-11 14:11:51 +01:00
Joe Groocock c6b58b0e2b feat: Expose MSC4171 service members
Signed-off-by: Joe Groocock <me@frebib.net>
2026-02-11 12:49:46 +00:00
Benjamin Bouvier ce0bba5e6d test: make more use of the EventFactory for the room topic
And get rid of test events. This implied adding a way to provide the
previous content of a state event, in the event factory.
2026-02-11 09:48:46 +01:00
Ivan Enderlin 87787dc04e refactor(sdk): Re-implement PinnedEventCacheStateLock with StateLock. 2026-02-11 08:23:51 +01:00
Ivan Enderlin a12f659653 refactor(sdk): Re-implement RoomEventCacheStateLock with StateLock. 2026-02-11 08:23:51 +01:00
Ivan Enderlin f4da1fa582 feat(sdk): Introduce the StateLock type in EventCache.
This patch introduces the `StateLock` type. It's a copy of the
`RoomEventCacheStateLock` logic, but generalised to any state `S`.
2026-02-11 08:23:51 +01:00
Benjamin Bouvier 7fbb2ddae1 test: add a new method for the state pinned events in the event factory 2026-02-10 18:41:15 +01:00
dependabot[bot] 1e0c930a12 chore(deps): bump bnjbvr/cargo-machete
Bumps [bnjbvr/cargo-machete](https://github.com/bnjbvr/cargo-machete) from 78beac95c8fd7c25bdfb194415128523e41512d5 to 3026399e8ccbe119a9624e9376afc8c5f21fb60f.
- [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/78beac95c8fd7c25bdfb194415128523e41512d5...3026399e8ccbe119a9624e9376afc8c5f21fb60f)

---
updated-dependencies:
- dependency-name: bnjbvr/cargo-machete
  dependency-version: 3026399e8ccbe119a9624e9376afc8c5f21fb60f
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-10 15:50:41 +01:00
Kevin Boos 0715da0626 Enable ruma's compat-unset-avatar feature
Without this, the `Account::set_avatar_url()` function does not work
on homeservers that don't advertise support for the new endpoints
for setting profile fields.
2026-02-10 13:39:25 +01:00
dependabot[bot] b1d878ddc2 chore(deps): bump crate-ci/typos from 1.43.0 to 1.43.4
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.43.0 to 1.43.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.43.0...v1.43.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-10 13:37:59 +01:00
Ivan Enderlin 13b07c3ece feat(xtask): Always open leaf nodes.
This patch adds the `open` attribute to the `<details>` for displaying
leaf nodes of the tree. My experience shows that it's nice to always
have them open. The “occurences” list is still folded, so it shouldn't
take to much space on the screen most of the time (one line per level of
logs in this node).
2026-02-10 13:33:48 +01:00
Ivan Enderlin 00e58056a4 fix(xtask): Remove a bad calculation to timeout bar. 2026-02-10 13:33:48 +01:00
Ivan Enderlin e1e86a78c9 feat(xtask): Add log overview.
This patch adds the `log overview` task that generates a standalone HTML
report representing the logs as a tree where each node is a target, and
each leaf is a log location with occurrences, spans and fields.

Each node displays the sum of errors and warnings for this node.
It helps to quickly spot the problematic targets, and it guides to
exploration of the node without taking the reader's hand by trying to
draw conclusions. We don't want to guide the reader to a mistake: we
just want to guide the reader to draw its own conclusions.

Each line can be highlighted. When a node is folded/closed, it is also
highlighted if at least one of its child is highlighted.

The occurrences of logs are displayed on a timeline.
2026-02-10 13:33:48 +01:00
Doug f1e41222f8 fix: Fix a deserialisation failure when the avatar_url is null. 2026-02-10 11:32:52 +01:00
Ivan Enderlin df6fc21576 refactor(sdk): Remove assume_has_waited_for_initial_prev_token.
This patch removes the `assume_has_waited_for_initial_prev_token` by
making `waited_for_initial_prev_token` to return a `&mut bool` instead
of `bool`.
2026-02-10 11:25:41 +01:00
Ivan Enderlin 72c1a655f2 refactor(sdk): waited_for_initial_prev_token is no more an Arc<AtomicBool>.
This patch changes `RoomEventCacheState::waited_for_initial_prev_token`
from `Arc<AtomicBool>` to `bool`. First off, the `Arc` wasn't used in
any useful way (never cloned for example). Second, the `AtomicBool` was
always used as a regular bool, no atomicity was really used. Lastly,
this patch adds the `assume_has_waited_for_initial_prev_token` method to
replace the `= true` operation to make code a bit more readable.
2026-02-10 11:25:41 +01:00
dependabot[bot] ae36751c9d chore(deps): bump CodSpeedHQ/action from 4.10.4 to 4.10.6
Bumps [CodSpeedHQ/action](https://github.com/codspeedhq/action) from 4.10.4 to 4.10.6.
- [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/fa0c9b1770f933c1bc025c83a9b42946b102f4e6...4deb3275dd364fb96fb074c953133d29ec96f80f)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-10 11:15:25 +01:00
Ivan Enderlin b1c4e45657 refactor(sdk): RoomEventCache::subscribe_to_pinned_events no longer takes a write lock.
This patch removes the write lock acquisition in
`RoomEventCache::subscribe_to_pinned_events` to replace it by read lock
acquisition. Nothing requires a `&mut self` at any point in this flow,
since `OnceLock::get_or_init` needs a `&self`.

The `RoomEventCacheStateLockWriteGuard::subscribe_to_pinned_events`
method is moved onto `RoomEventCacheStateLockReadGuard`.
2026-02-10 10:14:49 +01:00
Ivan Enderlin f0ee76c27f refactor(sdk): Remove one Vec allocation.
`LinkedChunk::push_items_back` expects an `IntoIter<Item = Event>`.
`EventLinkedChunk::push_live_events` has an `events` of kind `&[Event]`.
Using `events.iter().cloned()` instead of `events.to_vec()` not only
removes one `Vec` allocation, but it allows the compiler to apply more
optimisation.

Checking on godbolt.org, I see twice fewer LLVM IR lines, and 2.8x times
less ASM code.
2026-02-10 09:34:08 +01:00
Ivan Enderlin c1c48c2309 doc(sdk): Fix a typo for subscribe_to_pinned_events. 2026-02-10 08:36:31 +01:00
Ivan Enderlin b2fb7ad4e9 doc(sdk): Add #6143 to the CHANGELOG.md file. 2026-02-09 16:10:43 +01:00
Ivan Enderlin 6e20fb60fb fix(sdk): Restrict when m.room.member represents a LatestEvent candidate.
This patch relies on `MembershipChange` to decide when a `m.room.member`
represents a `LatestEvent` candidate. It was a mistale to rely on the
`membership` strictly, because the `prev_content` must be taken into
account.

Thus, this patch adds the following cases to `Knocked`, `Joined` and
`Invited`: `InvitationAccepted` and `KnockedAccepted`. Moreover, this
patch excludes other cases, including `ProfileChanged`, which was a bug
previously! When the user had a new display name, it was considered as a
`LatestEvent` candidate.
2026-02-09 16:10:43 +01:00
Benjamin Bouvier bc3ea6854b doc(event cache): explicit why we're stripping bundled relations from events before storing them in the event cache 2026-02-09 15:50:59 +01:00
Benjamin Bouvier 0ca9a0e6d1 refactor(event cache): extract send_updates_to_store as a common helper function 2026-02-09 15:50:59 +01:00
Benjamin Bouvier cc852c661f refactor(event cache): introduce persistence utils for the event cache
A bit of code has been duplicated for implementing the pinned events
linked chunk. This new module starts to common out a bit of it.
2026-02-09 15:50:59 +01:00
Ivan Enderlin 37c85fc774 chore(sdk): Downgrade a log from info to trace.
This patch changes an `info!` log to a `trace!`. Latest Events are
pretty stable now, and we don't get an info for each new computation
except when we want a proper trace.
2026-02-09 13:53:37 +01:00
Ivan Enderlin 6ef549e29a chore(sdk): Downgrade a log from error to info.
This patch changes an `error!` log to a `info!`. Indeed, this is not an
error to compute a Latest Event that doesn't exist yet. The system is
lazy purposely.
2026-02-09 13:53:37 +01:00
Doug b288ecbd25 Spaces: Check the power levels before removing an m.space.parent event. (#6132)
Small bug fix that makes `remove_child_from_parent` behave like
`add_child_to_parent`.

Additionally introduces a new Error case so that clients can decide to
ignore any failures updating the child → parent relationship.
2026-02-09 13:48:11 +02:00
Damir Jelić de3c5550a5 refactor(qr-login): Use the proper casing for an enum variant 2026-02-09 11:13:58 +01:00
Damir Jelić 8f04a48d1e refactor(qr-login): Remove the rendezvous_url method
MSC4388 doesn't use the full rendezvous URL in the QR code, instead we
just get the rendezvous ID.
2026-02-09 11:13:58 +01:00
Lakshya Nayak f787511616 fix(sliding-sync): re-dispatch invited/knocked rooms after reinvite (#6126)
## Problem

When a room goes through:

join -> leave/kick -> re-invite
Sliding Sync responses still include the room, but the SDK does not emit
an
Invited/Knocked update. Because of this, RoomListService is never
notified and
the room disappears from the room list until the application restarts.

## Root cause
"update_any_room()" only returned a RoomUpdateKind when one was
explicitly
produced. For re-invites it could return "None", causing the room to be
skipped.

## Fix
Always emit a default:
- "RoomUpdateKind::Invited"
- "RoomUpdateKind::Knocked"
when the room state is Invited/Knocked but no update kind was generated.

## Tests
Added a regression test covering:
join -> leave -> re-invite to ensure the room is surfaced again in the
invited list.

---------

Signed-off-by: Lakshya Nayak <89520692+VEL0C1TY22@users.noreply.github.com>
2026-02-09 10:44:59 +02:00
Damir Jelić ea58bc8139 Update ruma 2026-02-06 15:36:17 +01:00
dependabot[bot] 872bb095b6 chore(deps): bump time from 0.3.44 to 0.3.47
Bumps [time](https://github.com/time-rs/time) from 0.3.44 to 0.3.47.
- [Release notes](https://github.com/time-rs/time/releases)
- [Changelog](https://github.com/time-rs/time/blob/main/CHANGELOG.md)
- [Commits](https://github.com/time-rs/time/compare/v0.3.44...v0.3.47)

---
updated-dependencies:
- dependency-name: time
  dependency-version: 0.3.47
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-06 10:12:37 +00:00
Ivan Enderlin bff5c2f0e1 doc(base): Add #6130 to CHANGELOG.md. 2026-02-06 11:09:28 +01:00
Ivan Enderlin 627fe82ef3 test(base): Ensure that each state store has its own channel for RoomInfoNotableUpdates. 2026-02-06 11:09:28 +01:00
Ivan Enderlin 70faf2c3f3 fix(base): Move BaseClient::room_info_notable_update_sender into BaseStateStore.
This patch fixes a design issue. The
`BaseClient::room_info_notable_update_sender` is moved inside
`BaseStateStore` so that, when creating a new `BaseStateStore`, the
updates are not shared with other state stores. Updates are isolated to
the state store.

This bug has surfaced in `BaseClient::clone_with_in_memory_state_store`,
where a new `BaseStateStore` is created, but the
`room_info_notable_update_sender` was _cloned_, and that is a bug! We
could have re-created a new channel from scratch, but it would have
been hacky. Semantically, this channel should be part of the state store
itself. One proof is how it simplifies many call-sites, functions,
methods and structs: the `room_info_notable_update_sender` was passed
to multiple methods on `BaseStateStore`.
2026-02-06 11:09:28 +01:00
Jonas Richard Richter 0ec3db59f8 chore: move change to top of changelog 2026-02-06 09:38:50 +01:00
Jonas Richard Richter caa75981bb chore: add PR link to CHANGELOG.md 2026-02-06 09:38:50 +01:00
Jonas Richard Richter c49df20cd3 feat(ffi): include raw JSON of the underlying event in NotificationItem 2026-02-06 09:38:50 +01:00
Damir Jelić 036c5d35cd feat(crypto): Add a constructor for the MSC4388 variant of the QRCodeData struct 2026-02-05 21:17:44 +01:00
Benjamin Bouvier 80920e9ff2 refactor(common): use ruma's RelationType in the extractor functions 2026-02-05 16:43:25 +01:00
Benjamin Bouvier 071f982eed fix(event cache): don't include thread responses in the pinned event cache
There were two issues:

- first, `load_or_fetch_event_with_relations()` allowed to pass a
filter, but the filter wasn't taken into account when fetching relations
from the network. This would cause the initial load of pinned events to
also include thread responses, which we don't want.
- similarly, when adding related events from sync, we'd only look if an
event had a `m.relates_to` field; but it could be a thread response
being added in live.

The two issues are fixed similarly, by using a new `extract_relation`
serde helper that gives both the related_to event and the relation type.
That way, we can apply a manual filter in
`load_or_fetch_event_with_relations` after fetching relations from
network, and we can filter out live events based on the relation type.
2026-02-05 16:43:25 +01:00
Benjamin Bouvier 4aff3b566f refactor(event cache): use the background job monitoring for the pinned events task 2026-02-05 16:43:25 +01:00
Benjamin Bouvier eb8bd2d0b1 refactor(event cache): address review comments from 6085 2026-02-05 16:43:25 +01:00
Benjamin Bouvier 59fd7530f9 test(event cache): declare victory \o/ 2026-02-05 16:43:25 +01:00
Benjamin Bouvier 5778685352 feat(event cache): reload the pinned event cache at startup \o/ 2026-02-05 16:43:25 +01:00
Benjamin Bouvier d3742d2b30 feat(event cache): add support for redecryption in the pinned event cache 2026-02-05 16:43:25 +01:00
Benjamin Bouvier fcf0b87489 chore: address typos and cosmetic changes in an integration test 2026-02-05 16:43:25 +01:00
Benjamin Bouvier d7ecbc3c83 refactor(event cache): use the Room::load_or_fetch_events_with_relations now that it's widely available 2026-02-05 16:43:25 +01:00
Benjamin Bouvier f2279cd737 refactor(timeline): move load_event_with_relations to the RoomDataProvider trait
And get rid of the `PinnedEventsRoom` trait, and the accompanying file.
2026-02-05 16:43:25 +01:00
Benjamin Bouvier da5ac9e3e6 refactor(event cache): using proper locking for the pinned event cache 2026-02-05 16:43:25 +01:00
Benjamin Bouvier ca027f7eb6 feat(event cache): add a global EventCacheConfig struct to globally configure the event cache 2026-02-05 16:43:25 +01:00
Benjamin Bouvier 17840e52f5 refactor(timeline): start removing unused stuff in the timeline code
Now that this logic has moved to the event cache, it's not required in
the timeline anymore.
2026-02-05 16:43:25 +01:00
Benjamin Bouvier 2904735786 feat(timeline): subscribe to the pinned event cache from the timeline 2026-02-05 16:43:25 +01:00
Benjamin Bouvier 790a410474 feat(event cache): add a cache for a room's pinned events 2026-02-05 16:43:25 +01:00
Benjamin Bouvier 01bd672d05 feat(linked chunk): add a new ID for the pinned events linked chunk 2026-02-05 16:43:25 +01:00
Kévin Commaille 9ad7cf9662 refactor(base): Use PossiblyRedactedStateEventContent bound in MinimalStateEvent
We usually don't care if the event was redacted or not, we usually want
to no whether a field is set or not, so we don't need `Original` and
`Redacted` variants.

This simplifies several parts of the code since we don't have to handle
the intermediate enum to access the content now. Due to new APIs in
Ruma we can also just convert original and redacted event contents to
possibly redacted event contents.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-02-05 11:46:05 +01:00
Ivan Enderlin 8f156cbba8 feat(xtask): Display timeout in the duration graph of log sync.
This patch displays the timeout in the duration graph of `log sync`:

```
xxxxxxxxxuuuuu
```

where `x` represents the timeout, and `u` the duration.
2026-02-05 10:58:39 +01:00
Ivan Enderlin ae9fe44a7c feat(xtask): Add <time> in log sync reports.
This patch adds `<time>` around the values in the Time Column.
2026-02-05 10:58:39 +01:00
Ivan Enderlin edbddbdb55 feat(xtask): Display pos and timeout for sync report.
This patch updates the `cargo xtask log sync` command to extract the
`pos` and `timeout` fields so that we can display them. The `timeout`
is displayed in its own column, while the `pos` is displayed in the
line summary.
2026-02-05 10:58:39 +01:00
Ivan Enderlin 2ff33cd354 Merge pull request #6061 from mgoldenberg/consolidate-event-cache-store-tests
Consolidate integration tests for `EventCacheStore`
2026-02-05 09:20:04 +01:00
Ivan Enderlin 7675d22676 chore: Adjust uniffi features. 2026-02-05 09:02:29 +01:00
Ivan Enderlin af11c34399 chore: Adjust rand features. 2026-02-05 09:02:29 +01:00
Ivan Enderlin f51809d524 chore: Add default-features = false to more deps. 2026-02-05 09:02:29 +01:00
Ivan Enderlin 3ff9b55077 chore: Add more default-features = false to more deps. 2026-02-05 09:02:29 +01:00
Ivan Enderlin a35770b5d2 chore(crypto): Remove the indoc dep.
`indoc` is used once to declare a string, but it's easily doable with
pure Rust without too much boilerplate. Let's remove one macro dep.
2026-02-05 09:02:29 +01:00
Ivan Enderlin eb8b2c81f2 chore: Add more default-features to more deps. 2026-02-05 09:02:29 +01:00
Ivan Enderlin 034c1fcbcf chore: Disable default-features for decancer. 2026-02-05 09:02:29 +01:00
Ivan Enderlin f56f168b99 chore: Add more default-features = false to more deps. 2026-02-05 09:02:29 +01:00
Ivan Enderlin 386c5ee338 chore: Update camino to 1.2.2. 2026-02-05 09:02:29 +01:00
Ivan Enderlin 1b0b6a2f9a chore: Use the same version of quote, proc-macro2 and syn everywhere. 2026-02-05 09:02:29 +01:00
Ivan Enderlin d52d0d4a83 chore: xtask uses the workspace-defined clap.
Not only it uses the same version of `clap` everywhere, but it removes
7 dependencies.
2026-02-05 09:02:29 +01:00
Ivan Enderlin dc680b8594 chore: benchmarks uses the workspace-defined tokio. 2026-02-05 09:02:29 +01:00
Ivan Enderlin 44c2c01642 chore: Use default-features = false for all dependencies.
This patch adds `default-features = false` to all dependencies to avoid
fetching useless dependencies by default.

Numbers:

- `cargo check --workspace --tests` jumpbs from 725 to 715,
- `cargo tree --all-features --edges all --prefix none | rg -v '^$' |
  cut -d' ' -f 1 | sort | uniq | wc -l` jumps from 538 to 529.
2026-02-05 09:02:29 +01:00
Hugh Nimmo-Smith 7708087019 doc: clarify that IO_ELEMENT_MSC4388 is the unstable prefix 2026-02-04 20:45:00 +01:00
Ivan Enderlin 52014dc0bb chore(sdk): Log the pos and the timeout as part of the sync_once span.
This patch adds the `pos` and `timeout` value as new fields of the
`sync_once` span.

How to test it?

```sh
$ cargo nextest run --retries 0 --no-fail-fast -E "not test(ensure_no_max_concurrent)" -p matrix-sdk-ui --nocapture -- test_sync_all_states | rg sync_once
2026-02-04T14:02:17.043301Z DEBUG sync_once{conn_id="room-list" pos="0" timeout=0}:send{request_id="REQ-3" method=POST uri="http://127.0.0.1:49663/_matrix/client/unstable/org.matrix.simplified_msc3575/sync" request_size="647B" status=200 response_size="72B" request_duration=316.959µs}: matrix_sdk::http_client: Got response
2026-02-04T14:02:17.043783Z DEBUG sync_once{conn_id="room-list" pos="1" timeout=0}:send{request_id="REQ-4" method=POST uri="http://127.0.0.1:49663/_matrix/client/unstable/org.matrix.simplified_msc3575/sync" request_size="648B"}: matrix_sdk::http_client::native: Sending request num_attempt=1
2026-02-04T14:02:17.044093Z DEBUG sync_once{conn_id="room-list" pos="1" timeout=0}:send{request_id="REQ-4" method=POST uri="http://127.0.0.1:49663/_matrix/client/unstable/org.matrix.simplified_msc3575/sync" request_size="648B" status=200 response_size="72B" request_duration=283.75µs}: matrix_sdk::http_client: Got response
2026-02-04T14:02:17.044527Z DEBUG sync_once{conn_id="room-list" pos="2" timeout=0}:send{request_id="REQ-5" method=POST uri="http://127.0.0.1:49663/_matrix/client/unstable/org.matrix.simplified_msc3575/sync" request_size="648B"}: matrix_sdk::http_client::native: Sending request num_attempt=1
2026-02-04T14:02:17.044808Z DEBUG sync_once{conn_id="room-list" pos="2" timeout=0}:send{request_id="REQ-5" method=POST uri="http://127.0.0.1:49663/_matrix/client/unstable/org.matrix.simplified_msc3575/sync" request_size="648B" status=200 response_size="72B" request_duration=254.875µs}: matrix_sdk::http_client: Got response
2026-02-04T14:02:17.045245Z DEBUG sync_once{conn_id="room-list" pos="3" timeout=30000}:send{request_id="REQ-6" method=POST uri="http://127.0.0.1:49663/_matrix/client/unstable/org.matrix.simplified_msc3575/sync" request_size="648B"}: matrix_sdk::http_client::native: Sending request num_attempt=1
2026-02-04T14:02:17.045517Z DEBUG sync_once{conn_id="room-list" pos="3" timeout=30000}:send{request_id="REQ-6" method=POST uri="http://127.0.0.1:49663/_matrix/client/unstable/org.matrix.simplified_msc3575/sync" request_size="648B" status=200 response_size="72B" request_duration=247.417µs}: matrix_sdk::http_client: Got response
```
2026-02-04 16:24:37 +01:00
Benjamin Bouvier f3b9a01904 refactor(event cache): rename the read_lock_acquisition mutex 2026-02-04 16:24:23 +01:00
Benjamin Bouvier 2fb5a32db2 refactor(event cache): rename RoomEventCacheStateLockInner to RoomEventCacheState
There was nothing called `RoomEventCacheState` anymore, and the `Inner`
suffix is dubious, at best. Also, we can get rid of the `Lock`
component, since indeed it's locked, but it's a detail from the point of
view of the `RoomEventCacheState` itself. This makes for a shorter and
nicer name.
2026-02-04 16:24:23 +01:00
Benjamin Bouvier 9be5bc1977 feat(multiverse): render display names for messages and latest thread event 2026-02-04 14:27:10 +01:00
Jorge Martín 70dd02012b fix(ffi): Don't override the default package_name and cdylib_name values for Kotlin bindings
Otherwise, the bindings expect the generated JAR/AAR files to contain separate `.so` libraries for each crate
2026-02-04 10:53:29 +01:00
Jorge Martín f05e0b1b81 doc: Add missing changelog entry 2026-02-04 10:23:16 +01:00
Jorge Martín 7ffcf72483 fix(ffi): Remove UniFFI checksums in matrix-sdk crate
This was forgotten in a previous PR about removing the checksums for all crates exporting bindings.
2026-02-04 10:23:16 +01:00
Jorge Martín bb420360a4 Create reldev profile:
It allows having way smaller binaries while still being able to have proper backtraces: `reldbg` is great for iOS because it allows inline debugging using LLDB in Xcode, but it produces enormous binaries, while for Android we can't use that properly and we'd only be interested in having symbolicated backtraces, which this profile achieves with binaries an order of magnitude smaller.
2026-02-04 10:21:27 +01:00
Jorge Martín 9b6d54ef30 refactor: Enable debug-images feature for Sentry
This makes it possible to link a Sentry trace to the debug symbols so it can be symbolicated
2026-02-04 10:21:27 +01:00
Jorge Martín 9dc2901268 ci: Add dist profile
This is intended for reducing the binary size of the SDK distributed in Android/iOS bindings.

Its optimization level is 'binary size', it contains LTO optimizations and by default removes part of the debug info - the rest can be removed later if needed.
2026-02-04 10:21:27 +01:00
Benjamin Bouvier 50cc5f4102 refactor(pinned events): move pin_event/unpin_event from the Timeline to the Room
These make sense in general, and they will help getting rid of one of
the `PinnedEventsRoom` trait methods in a subsequent PR.
2026-02-04 09:59:47 +01:00
Benjamin Bouvier 1152eb6d37 refactor(room): move PinnedEventsRoom::load_event_with_relations to the Room object
The method is kept on the pinned loader trait at the moment, because
it's too inconvenient to remove it quite yet. This will happen in a
subsequent PR.
2026-02-04 09:39:18 +01:00
dependabot[bot] f270eda75d chore(deps): bump bytes from 1.11.0 to 1.11.1
Bumps [bytes](https://github.com/tokio-rs/bytes) from 1.11.0 to 1.11.1.
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v1.11.0...v1.11.1)

---
updated-dependencies:
- dependency-name: bytes
  dependency-version: 1.11.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-04 10:23:35 +02:00
Jorge Martín 2b298fbb85 fix(ffi): Remove checksums in all crates using UniFFI
Previously, this was only applied to the FFI bindings crate, but it's not the only one affected by the issue with 32bit ARM checksum validations.
2026-02-04 09:06:06 +01:00
Michael Goldenberg f2abc555e2 doc(event-cache): update relevant change logs
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-02-03 12:14:47 -05:00
Michael Goldenberg d6293c75a9 test(event-cache): move test_linked_chunk_exists_before_referenced to integration tests
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-02-03 11:47:05 -05:00
Michael Goldenberg d92b3ef781 feat(linked-chunk): use errors in RelationalLinkedChunk::apply_updates
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-02-03 11:08:46 -05:00
Michael Goldenberg ee1c95f6a3 refactor(linked-chunk): use errors in RelationalLinkedChunk::insert_chunk
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-02-03 10:50:46 -05:00
Michael Goldenberg f68696c2a6 feat(linked-chunk): add error type for relational linked chunk
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-02-03 10:43:22 -05:00
Benjamin Bouvier 40c6c330b0 chore(typos): fix new typos 2026-02-02 18:36:01 +01:00
dependabot[bot] 7c86e1b896 chore(deps): bump crate-ci/typos from 1.42.1 to 1.43.0
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.42.1 to 1.43.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.42.1...v1.43.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-02 18:36:01 +01:00
dependabot[bot] 06700cdf38 chore(deps): bump CodSpeedHQ/action from 4.8.2 to 4.10.4
Bumps [CodSpeedHQ/action](https://github.com/codspeedhq/action) from 4.8.2 to 4.10.4.
- [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/e736f0d2aeb36da38e9f08eca4dff7967408d154...fa0c9b1770f933c1bc025c83a9b42946b102f4e6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-02 18:17:41 +01:00
Benjamin Bouvier 19234abf32 review(task_monitor): address comments, watchdog -> task_monitor, misc doc adjustments and renamings 2026-02-02 16:13:00 +01:00
Benjamin Bouvier 24d3ee4433 doc(common): add a note around unwind safety for the task monitor 2026-02-02 16:13:00 +01:00
Benjamin Bouvier 27965cf177 feat(multiverse): add a background job error observer in multiverse 2026-02-02 16:13:00 +01:00
Benjamin Bouvier 50b06e8e5b feat(ffi): add support for listening to background jobs errors 2026-02-02 16:13:00 +01:00
Benjamin Bouvier 3d5679954b refactor(event cache): use the task monitor for event cache long-running jobs 2026-02-02 16:13:00 +01:00
Benjamin Bouvier 561b04cab0 feat(client): have the Client hold one task monitor 2026-02-02 16:13:00 +01:00
Benjamin Bouvier 334bd04252 feat(common): allow aborting on drop for background tasks 2026-02-02 16:13:00 +01:00
Benjamin Bouvier 9195c000f3 feat(common): add a background task handler 2026-02-02 16:13:00 +01:00
Nashwan Azhari 28d1bd7ce3 doc: update Ruma doc links in get_profile example 2026-02-02 10:24:11 +01:00
Nashwan Azhari cb9690ecba doc: change matrix.org/docs/spec links to spec.matrix.org 2026-02-02 10:24:11 +01:00
Michael Goldenberg 78d681f24f style(event-cache): fmt
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-01-31 15:42:56 -05:00
Michael Goldenberg 709d4cb80d Merge branch 'main' into test-merge-consolidate-event-cache-store-tests
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-01-31 15:07:52 -05:00
Michael Goldenberg 3d32981dd4 doc(event-cache): document that MemoryStore is not transactional
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-01-30 23:46:26 -05:00
Michael Goldenberg d5de36badb test(event-cache): restore test_linked_chunk_update_is_a_transaction to indexeddb tests
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-01-30 23:31:27 -05:00
Michael Goldenberg ab3995fe26 test(event-cache): remove test_linked_chunk_update_is_a_transaction from integration tests
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-01-30 22:59:29 -05:00
Michael Goldenberg c641f5aea8 feat(event-cache): ensure chunks exist before referenced in indexeddb store
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-01-30 22:50:24 -05:00
Michael Goldenberg 1b4e6aa0e7 feat(event-cache): ensure chunks exist before referenced in sqlite store
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-01-30 22:36:22 -05:00
Michael Goldenberg 6e34585dca test(event-cache): remove extraneous integration tests
The tests removed are either covered by other tests or
ensure properties that shouldn't exist - e.g., that
new chunks can link to chunks that haven't been put
into the store yet.

Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-01-30 13:42:40 -05:00
Damir Jelić 2f7887d607 refactor(auth): Change the secure channel methods to operate more on strings
This will come in handy once we attempt to support MSC4388 as this MSC
changes how the client-server API operates. The rendezvous channel will
in the future operate on JSON messages where we'll include our sealed
ciphertext as a base64 encoded string.
2026-01-30 17:14:05 +01:00
Damir Jelić 837590d90e refactor(auth): Move the MSC4108-specific rendezvous channel implementation into a submodule 2026-01-30 17:14:05 +01:00
Damir Jelić 94bc870686 refactor(qr-login): Create a submodule for the rendezvous channel 2026-01-30 17:14:05 +01:00
Damir Jelić 911b559113 feat(crypto): Add support for the QR code data type defined in MSC4388 2026-01-30 16:37:17 +01:00
Ivan Enderlin 2ffe003136 doc(sdk) Add #6056 to the CHANGELOG.md. 2026-01-30 15:34:48 +01:00
Ivan Enderlin d348fe62f2 refactor(base): Use Iterator::find and Iterator::any. 2026-01-30 15:34:48 +01:00
Ivan Enderlin be2c1d44e0 feat(ffi) Add LatestEventValue::RemoteInvite. 2026-01-30 15:34:48 +01:00
Ivan Enderlin 8b26751a66 feat(ui): Add LatestEventValue::RemoteInvite.
This patch maps a `BaseLatestEventValue::RemoteInvite` to
`LatestEventValue::RemoteInvite`.
2026-01-30 15:34:48 +01:00
Ivan Enderlin 1dc1d2a3f0 feat(sdk): Add LatestEventValue::RemoteInvite.
This patch adds the `LatestEventValue::RemoteInvite` variant. The goal
of this is to be able to compute a `LatestEventValue` for an invite to
a room. Using `LatestEventValue::Remote` isn't possible because it's
usually built from the `RoomEventCache`. However, the `EventCache`
doesn't handle invites for one reason: invites only manipulate stripped
state-events, whist the `EventCache` manipulates non-stripped (state)
events.

The `LatestEvents` API receives a stream of `RoomInfoNotableUpdate`. It
reacts to update from the `RoomInfo`. It filters out all reasons except
`MEMBERSHIP`. When the `MEMBERSHIP` is updated, and the room' state is
`Invited`, then a `RemoteInvite` is computed.

The `Invite` type is updated to include the `inviter_id` in case the
`inviter` is missing. Indeed, we always know the user ID of the inviter,
this information isn't optional.
2026-01-30 15:34:48 +01:00
Ivan Enderlin 99b83b98b6 fix(base): Emit RoomInfoNotableUpdateReasons for invited and knocked rooms creation. 2026-01-30 15:34:48 +01:00
Ivan Enderlin 21448ab874 fix(sdk): Skip updates for missing rooms only.
This patch skips updates for missing rooms only, i.e. it doesn't early
return and then miss all other rooms.
2026-01-30 15:34:48 +01:00
Ivan Enderlin b203b43bb2 doc(sqlite): Add #6091 in the CHANGELOG.md. 2026-01-30 15:08:39 +01:00
Ivan Enderlin 09a1bf8ec2 fix(sqlite): Replace unwrap when using interact.
This patch replaces the `interact(…).unwrap()` by a proper error.

So far, `interact()` was only returning `InteractError::Panic`
despites `InteractError::Aborted` exists. With
https://github.com/deadpool-rs/deadpool/pull/461, we now get
`InteractError::Aborted` when the SDK is shutdown, sometimes. This
results in hitting the `unwrap` and having a panic again. This patch
solves the problem by changing the `unwrap` to a proper error. Note: in
case of `InteractError::Panic`, we continue to panic.

This patch makes sense with or without the merge of the PR on
`deadpool`.
2026-01-30 15:08:39 +01:00
Ivan Enderlin 7918a1817c doc(xtask): Add missing documentation for log and log sync. 2026-01-30 13:57:26 +01:00
Ivan Enderlin 43e1932691 feat(xtask): Introduce xtask log sync to visualise logs about sync.
This patch introduces a new family of commands: `xtask log`. The goal
is to manipulate logs, to extract the right amount of data we need to
solve specific problems. The first member of this family is `sync` to
visualise logs about the sync process. It presents the sync requests and
responses in a table, with a "timeline" _à la_ network profiler graph.
The code is rather simple, on purpose. The generated HTML reports are
lightweight, and fully standalone: no JavaScript, pure HTML and CSS, no
external resources. These reports can be shared or archived super
easily.

Features:

- requests/responses are grouped by connection ID
- permalink to specific request ID
- status have colours
- time is displayed in a human form
- duration is calculated from the log timestamps
- view syncs in a "tree-like" flavor, a "time graph", super quick to
  spot long requests
- each line can be "opened" to see details, so far only log line numbers
  to get more context manually
2026-01-30 13:57:26 +01:00
Damir Jelić 87ce49e14f refactor(crypto): Convert the QrCodeData type to a struct with named fields 2026-01-30 10:19:46 +01:00
Damir Jelić d4e8731edd Add a changelog entry about the QrCodeData struct updates 2026-01-30 10:19:46 +01:00
Damir Jelić b03c482c95 refactor(crypto): Remove the rendezvous_url method from the QrCode data type
MSC4388 won't have the full rendezvous URL encoded in the QR code,
instead it'll have a rendezvous ID.

So let's remove this accessors and use the `intent_data()` getter
instead.

MSC4388: https://github.com/matrix-org/matrix-spec-proposals/pull/4388
2026-01-30 10:19:46 +01:00
Damir Jelić addafe2878 refactor(crypto): Create a MSC-agnostic QrCodeIntent enum
Since the intent is encoded differently in MSC4108 and MSC4388 it
doesn't make sense to publicly expose the binary constants for a
specific MSC in the public API.

This patch removes access to the binary constants and you only access to
a generic enum.
2026-01-30 10:19:46 +01:00
Damir Jelić c4a04eee97 refactor(crypto): Create a MSC and intent specific accessor for the QR login data type
This patch adds a view into the MSC-specific and intent specific data
fields of the QR login data type.

MSC4108 and MSC4388 have subtle differences in the way the rendezvous
URL and the server name are shared, this new getter allows us to access
all of those fields in a consistent manner.

MSC4108: https://github.com/matrix-org/matrix-spec-proposals/pull/4108
MSC4388: https://github.com/matrix-org/matrix-spec-proposals/pull/4388
2026-01-30 10:19:46 +01:00
Damir Jelić 97157c8fcf refactor(crypto): Use intent instead of mode in the field names of the qr code data type 2026-01-30 10:19:46 +01:00
Damir Jelić bfe42d9ccf refactor(crypto): Rename the QrLoginError into InvalidIntent 2026-01-30 10:19:46 +01:00
Damir Jelić 036fa5ca82 refactor(crypto): Rename QrCodeModeData into QrCodeIntentData 2026-01-30 10:19:46 +01:00
Damir Jelić 90651a3067 refactor(crypto): Rename QrCodeMode into QRCodeIntent 2026-01-30 10:19:46 +01:00
Damir Jelić c19ac306a1 refactor: Rename invalid version into invalid type 2026-01-30 10:19:46 +01:00
Damir Jelić 38731f12de refactor(crypto): Abstract away QrLoginData so we can support multiple MSC versions
This patch modifies the QrLoginData, it now hides all its public fields
and appropriate getters have been created for it instead.

This is necessary to hide the MSC specific parts of the data type thus
allowing support of multiple versions of the data type.
2026-01-30 10:19:46 +01:00
Damir Jelić 2d4053bed3 refactor(crypto): Create a MSC4108 submodule for the qr login types
Our current implementation of this QR code data type corresponds to the
data type defined in MSC4108. The data format has been updated a bit in
MSC4833 and thus we'll need to support both formats for a while.

This moves al the MSC4108-specific parts into a separate MSC-specific
submodule.

MSC4108: https://github.com/matrix-org/matrix-spec-proposals/pull/4108
2026-01-30 10:19:46 +01:00
Damir Jelić faf7f5577a refactor(crypto): Create a submodule tree for the qr login types 2026-01-30 10:19:46 +01:00
Kévin Commaille 4feaa0ba49 Upgrade Ruma
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-01-30 10:16:06 +01:00
Damir Jelić 4e7b77d7d0 refactor(qr-login): Attempt to export the secrets bundle sooner in the login process
This ensures that, if we don't have a valid secrets bundle at hand we
error out as early es possible.
2026-01-30 10:15:26 +01:00
Stefan Ceriu 712c99f3cf chore(spaces): reduce m.space.child and m.space.parent deserialisation failure log levels as they are expected when removing either from the hierarchy 2026-01-29 19:42:16 +02:00
Andy Balaam 5d1382f507 Log our cross-signing and backup status after we receive a secret 2026-01-29 17:38:44 +00:00
Andy Balaam 8932869423 Log more information about gossip requests
So we can track which `m.secret.send` messages were successfully sent or
retried, and which secrets were contained in them.

Part of #6058
2026-01-29 13:24:15 +00:00
Tobias Fella 225644111c docs(sdk): Fix grammar 2026-01-29 14:18:18 +01:00
Kévin Commaille 1807a8765b refactor(crypto): Use to_canonical_value() directly
Instead of going through `serde_json::to_value()` and then converting it
to canonical JSON to serialize it.

Currently `to_canonical_value()` has the same behavior internally as
here, but an upcoming change in Ruma makes it use its own `Serializer`
so it is directly serialized as a `CanonicalJsonValue` which should be
somewhat more efficient.

This upcoming change also removes the `CanonicalJsonError::SerDe`
variant, so it is not as straightforward to propagate
`serde_json::Error`s.

This commit also removes outdated `#[allow(clippy::…)]` attributes.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-01-28 14:29:39 +01:00
Benjamin Bouvier c420d45e64 test(wasm): allow running the wasm tests with only one runner
Sometimes, running the wasm tests with a given runner will fail with
obscure, undecipherable reasons. As a result, it's convenient to be able
to locally run the wasm-pack tests with only a single runner, which this
commit allows.
2026-01-28 14:16:51 +01:00
Damir Jelić 1fb2ca5843 refactor(crypto-ffi): Serialize the secrets bundle when exporting it 2026-01-28 10:30:20 +01:00
Johannes Marbach 2ebab067b4 feat(timeline): enable focusing a thread root using TimelineFocus::Event
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2026-01-28 07:43:49 +01:00
Benjamin Bouvier e601c1d9b4 test: test that one can store the same event in multiple linked chunks 2026-01-27 16:02:20 +01:00
Benjamin Bouvier 2a968661e7 feat(sqlite): allow storing the same events in multiple linked chunks 2026-01-27 16:02:20 +01:00
Johannes Marbach 67a45b0772 feat(timeline): remove the obsolete event type filter
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2026-01-27 14:43:57 +01:00
Jonas Platte fccf44f285 fix: Add 'bellow' as a forbidden word (#6073)
Signed-off-by: Jonas Platte
2026-01-27 10:53:07 +00:00
Benjamin Bouvier 9f093737c4 doc(r2d2): tweak links and fix a few typos in the doc comment of r2d2 2026-01-27 11:27:12 +01:00
Jorge Martín 731cb0b426 fix(ffi): Temporarily remove checksums for Android bindings
There is an error in how JNA performs the API checksums that results in incorrect checks that make the app fail as soon as any SDK method is called, since initializing the SDK performs these checks
2026-01-27 11:21:11 +01:00
Benjamin Bouvier ae428446f9 test(http client): make the test_retry_limit_http_requests test more resilient
This test was setting a client-wide retry limit of 3 attempts for every
single network request. It happens that it was using the login method,
which unconditionally overrides this retry limit to 3 anyways, in
`LoginBuilder::send()`, so it worked only because the two retry limits
were accidentally in sync. Changing the retry limit in the test to 4
would make it thus fail; the test has been changed so it tries to use
the /whoami endpoint instead of login, as the former doesn't override
the retry limit.
2026-01-27 10:58:02 +01:00
Benjamin Bouvier d099239427 chore(http client): rename a variable to make it clearer what its role is
The `default_timeout` is a timeout value provided by `backon`, and
that's a suggestion of what the timeout value should be for the next
request (based on the backoff method used under the hood — in our case,
the exponential backoff). Since we have another concept of a default
timeout (the one present in the RequestConfig), it seems better to call
the timeout suggested by backon in a different manner, that's more
explicit in the given context.
2026-01-27 10:58:02 +01:00
dependabot[bot] b16d12568a chore(deps): bump oneshot from 0.1.11 to 0.1.13
Bumps [oneshot](https://github.com/faern/oneshot) from 0.1.11 to 0.1.13.
- [Changelog](https://github.com/faern/oneshot/blob/v0.1.13/CHANGELOG.md)
- [Commits](https://github.com/faern/oneshot/compare/v0.1.11...v0.1.13)

---
updated-dependencies:
- dependency-name: oneshot
  dependency-version: 0.1.13
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-27 10:02:51 +01:00
Jorge Martín db56d14177 fix(ui): Simplify event handler check in NotificationClient::try_sliding_sync
Past me: wtf?
2026-01-27 09:08:59 +01:00
Jorge Martín 4ee3a6d0a6 fix(ui): make m.room.avatar part of the required state for the sliding sync in the NotificationClient 2026-01-27 09:08:59 +01:00
Jorge Martín 19724cf2c2 fix(sdk): Fix latest event erasing RoomInfo
The sync lock was acquired too late in `LatestEvent::store`, which could lead to a race condition where we read some room info, that room info was modified and saved in parallel somewhere else, and then we modified the copy of the room info and overwrote that saved data with it, resulting in data loss.

In the clients, this was experienced as notifications sometimes lacking the room display name
2026-01-27 09:08:59 +01:00
dependabot[bot] fb0713275e chore(deps): bump CodSpeedHQ/action from 4.7.0 to 4.8.2
Bumps [CodSpeedHQ/action](https://github.com/codspeedhq/action) from 4.7.0 to 4.8.2.
- [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/0700edb451d0e9f2426f99bd6977027e550fb2a6...e736f0d2aeb36da38e9f08eca4dff7967408d154)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-27 08:52:17 +02:00
Johannes Marbach 8f4ac73ca9 feat(timeline): add filter for membership and profile changes
Use case: Display membership changes (join, leave, etc.) in the timeline but suppress profile changes (display name or avatar URL). This is currently not possible with `TimelineEventTypeFilter` because both types of changes have the same event type (`m.room.member`).

This pull request introduces a new `TimelineEventFilter` for filtering on either the event type or parts of its content. Content filters are only added for membership and profile changes but more enum variants can be added in future.
2026-01-26 14:50:49 +00:00
Skye Elliot 3924463c6d fix: Correctly store rooms with downloaded keys in SQLite and IndexedDB. (#6044)
While https://github.com/matrix-org/matrix-rust-sdk/pull/6017 is mostly
functional, there are two issues:

- I did not process `changes.room_key_bundles_fully_downloaded` in
`matrix-sdk-sqlite`, meaning any updates made via `Changes` would not be
persisted;
- I used a non-encrypting `JsValue` serialisation for the same field in
`matrix-sdk-indexeddb`, which causes errors when passed to the
decryption-enabled deserializer.

Solutions:

- Process the aforementioned changes such that keys are added to SQLite;
- Use a non-encrypting deserialiser, since this is effectively a
hash-set, and the contents aren't sensitive.

Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-01-26 14:12:54 +00:00
Johannes Marbach bd77f6673c chore(ffi): update uniffi to 0.31.0
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2026-01-26 14:05:04 +01:00
Kévin Commaille 3be8726afb test(ui): Test space child changes in SpaceService
Checks that adding and removing space children from sync works.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-01-26 10:23:45 +01:00
Michael Goldenberg e128f0fae3 test(event-cache): remove extraneous tests from indexeddb impl
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-01-25 19:12:39 -05:00
Michael Goldenberg 3c42e1b4b9 test(event-cache): move test_load_previous_chunk to integration tests
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-01-25 18:57:43 -05:00
Michael Goldenberg 12af5c4444 test(event-cache): move test_load_last_chunk_with_cycle to integration tests
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-01-25 18:48:11 -05:00
Michael Goldenberg 3738091689 test(event-cache): move test_load_last_chunk to integration tests
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-01-25 18:44:46 -05:00
Michael Goldenberg 750f59e4ea test(event-cache): move test_filter_duplicate_events_no_events to integration tests
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-01-25 18:41:52 -05:00
Michael Goldenberg 3486701602 test(event-cache): copy test_linked_chunk_update_is_a_transaction to integration tests
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-01-25 18:41:41 -05:00
Michael Goldenberg e60b204cda test(event-cache): move test_linked_chunk_multiple_rooms to integration tests
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-01-25 18:38:19 -05:00
Michael Goldenberg 0443615683 test(event-cache): copy test_linked_chunk_clear to integration tests
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-01-25 17:54:18 -05:00
Michael Goldenberg d93238d086 test(event-cache): move test_linked_chunk_start_end_reattach_items to integration tests
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-01-25 17:51:44 -05:00
Michael Goldenberg fe1ab18474 test(event-cache): move test_linked_chunk_detach_last_items to integration tests
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-01-25 17:49:17 -05:00
Michael Goldenberg 2c8a915018 test(event-cache): copy test_linked_chunk_remove_item to integration tests
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-01-25 17:46:19 -05:00
Michael Goldenberg 4f34b51bda test(event-cache): move test_linked_chunk_push_items to integration tests
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-01-25 17:43:12 -05:00
Michael Goldenberg cd1fbef0ea test(event-cache): copy test_linked_chunk_remove_chunk to integration tests
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-01-25 17:39:33 -05:00
Michael Goldenberg ca9c74d2c4 test(event-cache): move test_linked_chunk_new_gap_chunk to integration tests
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-01-25 17:34:56 -05:00
Michael Goldenberg a7ab53838e test(event-cache): move test_linked_chunk_new_items_chunk to integration tests
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-01-25 17:29:00 -05:00
Michael Goldenberg 680bc74543 test(event-cache): move test_linked_chunk_replace_item to integration tests
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-01-25 17:26:00 -05:00
Kévin Commaille 3852129612 refactor(base): Improve logs
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-01-23 16:24:47 +01:00
Kévin Commaille be8abdc1cf test(sdk): Add tests about handling invalid state events
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-01-23 16:24:47 +01:00
Kévin Commaille e77e2ae18f fix(base): Handle sync state events that fail to deserialize
If the homeserver provides a state event to a client, it means that it
considers the event to be valid. If a state event is valid, it always
updates the state map of the room. So ignoring events that fail to
deserialize means that the local state map is different than the one
from the server.

In some cases the Matrix spec even explicitly says that if a required
field is missing from the content of a state event, it should be treated
as if the event is missing from the state map. And if a required field
is missing, the event will fail to deserialize.

So this handles state events very closely to how a server would we only
deserialize the event type and the state key first to make sure that a
valid state event always updates the local state map. Then we only
deserialize the events lazily when we encounter an event type that
updates the `RoomInfo`. Because we deserialize the event lazily and some
methods might edit parts of an event before passing it to `RoomInfo`,
the (possibly edited) deserialized event is cached alongside the raw
event and its keys to be able to pass it further down the chain.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-01-23 16:24:47 +01:00
Kévin Commaille 9590b3c683 refactor(base): Move state event decryption logic in separate function
To simplify the match arms of `dispatch`.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-01-23 16:24:47 +01:00
Ivan Enderlin 9cf649eb60 fix(sdk): EventCacheInner::by_room can be a HashMap.
This patch replaces `HashMap` by `BTreeMap` in `EventCacheInner` as
we don't need any ordering. It is then faster to get (or insert) a new
`RoomEventCache`:

- an insert is O(1) for `HashMap` vs. O(log(n)) for `BTreeMap`,
- a get is O(1) for `HashMap` vs. O(log(n)) for `BTreeMap`.
2026-01-23 16:17:54 +01:00
Mauro Romito b0bc4b2af9 fix: better owner check comparison using the int type from ruma 2026-01-23 14:51:58 +01:00
Mauro Romito f524b28d36 fix: avoided creating an additional vector 2026-01-23 14:51:58 +01:00
Mauro Romito debc8e5e65 fix: adjusted an issue with the test not, including the creator in the PL which is not allowed on v12 2026-01-23 14:51:58 +01:00
Mauro Romito 50a5a1d0a8 refactor: refactored LeaveSpaceRoom to also account for v12 rooms where creators have infinite PL and users with PL 150 are considered owners. Included also are_creators_privileged to let clients know if we are in a v12 room.
# Conflicts:
#	crates/matrix-sdk-ui/src/spaces/leave.rs

# Conflicts:
#	crates/matrix-sdk-ui/src/spaces/leave.rs

# Conflicts:
#	bindings/matrix-sdk-ffi/src/spaces.rs
#	crates/matrix-sdk-ui/src/spaces/leave.rs
2026-01-23 14:51:58 +01:00
Damir Jelić 6d5a43b47a feat(crypto-ffi): Add bindings to export a secrets bundle 2026-01-23 14:46:16 +01:00
Damir Jelić 4fcb553880 feat: Add uniffi headers to some more types
This patch allows uniffi to re-export some types related to secret
bundles.
2026-01-23 14:46:16 +01:00
Ivan Enderlin a1b3cd48fe doc(common): Update the CHANGELOG.md. 2026-01-23 13:55:21 +01:00
Ivan Enderlin 77515dda8e fix(common): Fix an off-by-one index removal in LinkedChunk::remove_item_at.
This patch fix an off-by-one check for `Error::InvalidItemIndex` in
`LinkedChunk::remove_item_at`.

This patch updates the `test_remove_item_at` test to cover this bug.
2026-01-23 13:55:21 +01:00
Kévin Commaille e69f9e4f89 refactor(base): Remove unused beacons from BaseRoomInfo
This field was added already unused in the initial PR
https://github.com/matrix-org/matrix-rust-sdk/pull/3741 for live
location sharing.

The follow up live location PRs didn't make use of it either:

- https://github.com/matrix-org/matrix-rust-sdk/pull/3771
- https://github.com/matrix-org/matrix-rust-sdk/pull/3794
- https://github.com/matrix-org/matrix-rust-sdk/pull/4025

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-01-22 18:25:48 +02:00
Benjamin Bouvier 3989e483d5 chore(send queue): save the thumbnail as the thumbnail of itself 2026-01-22 13:31:49 +01:00
Benjamin Bouvier ab53ec7c67 test(timeline): add a test that a thumbnail can be loaded without network access, after we sent it 2026-01-22 13:31:49 +01:00
Benjamin Bouvier 3b339be422 refactor(media): remove the now unused make_local_thumbnail_media_request 2026-01-22 13:31:49 +01:00
Benjamin Bouvier 32666e4c24 feat(send queue): cache a thumbnail as such for the media that we just uploaded
Before: after uploading a media and a thumbnail with the send queue, the
thumbnail would be cached only as a "file", and not as a thumbnail. This
is wasteful, if the embedder is interested in getting a thumbnail of the
exact same dimensions, for the file they've updated; there's no good
reason to wait for the server to return it back.

However, there were good reasons to store it as a file in the past. So,
we're choosing here to duplicate the thumbnail in the media store:
- it's saved as a file for its own MXC URI (which preserves the previous
behavior)
- it's also saved as a thumbnail for the media MXC URI (which implements
the desired behavior).

Tests are updated to reflect this.
2026-01-22 13:31:49 +01:00
Benjamin Bouvier a32c11f5ee chore: make clippy happy 2026-01-20 17:07:46 +01:00
Benjamin Bouvier fe885360cc refactor: move the thread read receipt computation to its own function 2026-01-20 17:07:46 +01:00
Benjamin Bouvier 070d17a2d0 refactor: group common match arms together 2026-01-20 17:07:46 +01:00
Benjamin Bouvier 1c0bcf4a69 feat(timeline): update thread summaries when there's a new read receipt event 2026-01-20 17:07:46 +01:00
Benjamin Bouvier 8ae4acdc00 feat(timeline): expose the user's threaded receipt on each thread summary
This is half of the work: this will load the threaded receipt for each
thread, every time we add/update a timeline item for an event that had a
thread summary. Since we don't know which of the private or the public
receipt is the most advanced, we simply pass both, to start with; it's
expected that this code dies later, when we fold it in into the event
cache.

The second half will consist in updating the thread summaries when a new
read receipt event happens.
2026-01-20 17:07:46 +01:00
Doug 439fb9d9c9 chore: Remove the redundant LeaveSpaceRoom.joined_members_count property. 2026-01-20 15:45:59 +02:00
Ivan Enderlin b3aa849f87 feat(sdk): New state event candidate for LatestEventValue: m.room.member with join.
This patch adds a new state event candidate for `LatestEventValue`:
`m.room.member` when the `membership` is `join` and the `state_key`
is the current user ID. Put differently: when the current user joins a
room, we are able to compute a `LatestEventValue`.
2026-01-20 14:43:18 +01:00
Stefan Ceriu a9ccb443bc feat(room_list): Add a room identifier based list room list service filter 2026-01-20 15:05:59 +02:00
Stefan Ceriu 0cca375f03 feat(spaces): Expose a SpaceFilter API
The `SpaceFilter`s API provides a simple interface that can be used in conjuncture with the `RoomList` to filter down the hierarchy to a particular space or its descendants.

Per design, the first level `SpaceFilter`s will only contain direct descendants while the second level ones will contain the rest of the hierarchy recursively.

The full feature is defined in https://github.com/element-hq/element-meta/issues/2966
2026-01-20 15:05:59 +02:00
Stefan Ceriu c5bdeb26ed chore(spaces): Refactor SpaceRoom sorting and move the core logic outside of the space room list
Rooms will soon need to be sorted outside of /hierarchy responses and as such the sorting algorithm will need to be shared between multiple users.
2026-01-20 15:05:59 +02:00
Doug d278f20dd4 feat: Add a method to reset a SpaceRoomList. 2026-01-20 14:12:15 +02:00
Damir Jelić a3111e7541 Merge pull request #5931 from JoFrost/main
feat[bindings]: expose power level thresholds in corresponding timeline event
2026-01-20 11:50:00 +01:00
dependabot[bot] b96b7bc71f chore(deps): bump CodSpeedHQ/action from 4.5.2 to 4.7.0
Bumps [CodSpeedHQ/action](https://github.com/codspeedhq/action) from 4.5.2 to 4.7.0.
- [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/dbda7111f8ac363564b0c51b992d4ce76bb89f2f...0700edb451d0e9f2426f99bd6977027e550fb2a6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-20 11:15:48 +01:00
dependabot[bot] 3ff49becbe chore(deps): bump crate-ci/typos from 1.42.0 to 1.42.1
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.42.0 to 1.42.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.42.0...v1.42.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-20 08:45:25 +01:00
Mauro Romito 3150dd4628 change: code improvements suggested in the pr review 2026-01-20 08:41:49 +01:00
Mauro Romito a46b006473 refactor: added the check for the joined_members_count inside the test_leave 2026-01-20 08:41:49 +01:00
Mauro Romito cd5b8eefe0 doc: updated the changelog
# Conflicts:
#	bindings/matrix-sdk-ffi/CHANGELOG.md
2026-01-20 08:41:49 +01:00
Mauro Romito bd8041765f fix: updated the tests so that they also account for the membership state 2026-01-20 08:41:49 +01:00
Mauro Romito 9183c80ea6 fix: is_last_admin also accounts for the membership state and included the joined_members_count in LeaveSpaceRoom 2026-01-20 08:41:49 +01:00
dependabot[bot] b68b6b95b0 chore(deps): bump qmaru/wasm-pack-action from 0.5.2 to 0.5.3
Bumps [qmaru/wasm-pack-action](https://github.com/qmaru/wasm-pack-action) from 0.5.2 to 0.5.3.
- [Release notes](https://github.com/qmaru/wasm-pack-action/releases)
- [Commits](https://github.com/qmaru/wasm-pack-action/compare/v0.5.2...v0.5.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-20 09:14:53 +02:00
ganfra c9ac788172 fix(spaces): Allow removing unknown child rooms from spaces 2026-01-19 16:59:25 +00:00
JoFrost eb13eeeacd chore[changelog]: move new changes to the top 2026-01-19 17:52:00 +02:00
JoFrost 2ff1f66f37 chore[ffi]: rename previous to previous_users 2026-01-19 17:51:38 +02:00
Viktor Strate Kløvedal 0053ecb5f4 FFI: implement Room::list_threads (#5953)
Expose `Room::list_threads` to the FFI.

---------

Signed-off-by: viktorstrate <viktorstrate@gmail.com>
2026-01-19 13:32:29 +00:00
Michael Goldenberg d1c8b1090b fix(sdk): ensure IdentityStatusChanges not dropped prematurely
For details, see https://github.com/matrix-org/matrix-rust-sdk/issues/4599

Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-01-19 09:32:15 +01:00
Michael Goldenberg 13b3e0349e chore(deps): bump async-stream to 0.3.6
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2026-01-19 09:32:15 +01:00
Skye Elliot 90390d7488 Merge pull request #6017 from matrix-org/kaylendog/history-sharing/backup-download
feat: Download room keys from backup before building key bundle.
2026-01-16 17:03:46 +00:00
Benjamin Bouvier 5b8ff8a76d refactor(state store): remove StateStore::upsert_thread_subscription
There is `StateStore::upsert_thread_subscriptions` as a proper
replacement these days.
2026-01-16 17:01:03 +01:00
Ivan Enderlin e9f5ed108a test(sdk): Add test_latest_event_is_recomputed_when_a_user_is_ignored.
This patch adds a test to ensure the `LatestEventValue` is re-computed
(more specifically: erased) when a user is ignored.
2026-01-16 15:01:52 +01:00
Ivan Enderlin 34ef8fc324 fix(sdk): The LatestEventValue is erased when a room has been emptied.
This patch ensures that a `LatestEventValue` is erased when a room has
been emptied.

If we are computing a value from the Event Cache, it's because we
have received an update from the Event Cache. This update falls in two
categories: either an event has been added or updated, or the room has
been emptied. We consider the room has been emptied by default. If we
are able to scan at least one in-memory event, we consider the room has
not been emptied.

This patch adds one specific, and updates other tests that were using
an empty Event Cache (which now produces a different result in this
situation).
2026-01-16 15:01:52 +01:00
Ivan Enderlin 92a7d033a2 chore(sdk): Split latest_events::latest_event.
This patch splits the `latest_events/latest_event.rs`
module into `latest_events/latest_event/mod.rs` and
`latest_events/latest_event/builder.rs`. The file was too big and asked
for a diet. The `LatestEventValueBuilder` type has been renamed to
`Builder`, and the `LatestEventVAluesForLocalEvents` has been renamed
to `BufferOfValuesForLocalEvents` for the sake of clarity and shorter
names.
2026-01-16 14:37:46 +01:00
mlm-games cb8a9995ac fix(widget): Do not include uniffi attrs when the feature is not enabled for element_call.rs 2026-01-16 14:19:16 +01:00
Jorge Martín fb3c1f8ace doc: Add changelog 2026-01-16 12:48:15 +01:00
Jorge Martín 53b5f04b15 fix(ffi): Use the new RoomPowerLevelsContentOverride API when creating a room
This fixes an issue that prevented default values coming from the SDK from being uploaded in the create room request, which could mean rooms created with the wrong power levels if the default values in the homeserver didn't match those of the SDK
2026-01-16 12:48:15 +01:00
Jorge Martín 4f6b4bf709 feat: Bump Ruma to commit hash version to fix a room power level related bug 2026-01-16 12:48:15 +01:00
Skye Elliot 8a89726726 docs: Update CHANGELOGs. 2026-01-16 11:44:45 +00:00
Ivan Enderlin fbff1ee99a bench: Bump codspeed-criterion-compat to 4.2.1. 2026-01-15 12:50:25 +01:00
Ivan Enderlin 734cc5b77e fix: Patch ruma to fix #5979.
This patch includes https://github.com/ruma/ruma/pull/2329.
2026-01-15 12:50:25 +01:00
JoFrost 19c958add0 Merge branch 'main' into main
Signed-off-by: JoFrost <20685007+JoFrost@users.noreply.github.com>
2026-01-14 20:18:18 +02:00
Doug d64c990658 feat: Add a method to set your own user's display name within a room
For e.g. the /myroomnick slash command.
2026-01-14 14:15:22 +02:00
Skye Elliot e156d8e00c refactor: Use Try operator over Into::into, fixup comment. 2026-01-13 17:46:54 +00:00
Skye Elliot 831ab6d429 tests: Check historic room keys fetched from backup.
- Splits `test_secret_gossip_after_interactive_verification ` into a helper method.

Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-01-13 15:47:02 +00:00
Skye Elliot 3baaef5109 feat: Download room keys from backup before building key bundle.
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-01-13 15:19:28 +00:00
Ivan Enderlin 11d430563b doc(sdk): Update the `CHANGELOG.md. 2026-01-13 13:26:16 +01:00
Ivan Enderlin bfeafa9948 feat(sdk): Introduce the PollTimeout type.
This patch introduces the `PollTimeout` type to represent
either no timeout with `PollTimeout::None`, some timeout
with `PollTimeout::Some(_)`, or a default timeout with
`PollTimeout::Default`. It's finer than the previous `bool` that
was used, where `false` meant `PollTimeout::None`, and `true` meant
`PollTimeout::Default`. It's now possible to pass a precise timeout
value.
2026-01-13 13:26:16 +01:00
Johannes Marbach dfd607f195 feat(timeline): utilize the cache and include common relations when focusing on an event without context
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2026-01-13 11:37:36 +01:00
JoFrost 9b15400936 feat[ffi]: Add nonfavorite room filter (#5991)
Hello!

As the `Not` filter is not available on the FFI SDK due to UniFFI
constraints, this PR adds a NonFavorite filter to RoomList, implemented
as the negation of the existing Favorite filter. This was made to
address the issue #5978.

- [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:

---------

Signed-off-by: JoFrost <20685007+JoFrost@users.noreply.github.com>
2026-01-13 11:35:54 +01:00
dependabot[bot] edc1aee471 chore(deps): bump bnjbvr/cargo-machete
Bumps [bnjbvr/cargo-machete](https://github.com/bnjbvr/cargo-machete) from 6229aa757e3e8a028bd97a49e190207e108eefbd to 78beac95c8fd7c25bdfb194415128523e41512d5.
- [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/6229aa757e3e8a028bd97a49e190207e108eefbd...78beac95c8fd7c25bdfb194415128523e41512d5)

---
updated-dependencies:
- dependency-name: bnjbvr/cargo-machete
  dependency-version: 78beac95c8fd7c25bdfb194415128523e41512d5
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-13 09:01:32 +01:00
Benjamin Bouvier 387df77a6f fix(latest event): mark a failed but recoverable attempt to send as a sending
In the send queue, failures to send can be classified into two
categories:

- permanent failures (e.g. invalid parameters)
- recoverable failures (e.g. network is down; server responded with a
  transient error code)

The latest event system would classify all the failures as "cannot be
sent", which is slightly incorrect if the failure was recoverable. In
this case, we should still consider the local event as being sent, as
the system should try to send it some time soon.
2026-01-12 16:36:10 +01:00
Benjamin Bouvier b9ef07a719 refactor: commonize two arms of a match 2026-01-12 16:36:10 +01:00
Benjamin Bouvier df49cbb44d refactor(test): deduplicate test helper related to local latest event 2026-01-12 16:36:10 +01:00
dependabot[bot] b1ca2bbbb5 chore(deps): bump crate-ci/typos from 1.40.0 to 1.41.0
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.40.0 to 1.41.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.40.0...v1.41.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-12 13:28:00 +00:00
Ivan Enderlin ccbb7575c0 doc(base): Move an entry at its correct place. 2026-01-12 14:07:35 +01:00
Richard van der Hoff 5be3ea3d46 common: Fix up documentation on TimelineEvent
https://github.com/matrix-org/matrix-rust-sdk/pull/4568 merged together
`SyncTimelineEvent` and `TimelineEvent`, but removed some of the useful
documentation on `TimelineEvent`, and left behind some confusing
references. This change fixes it up.
2026-01-12 10:03:04 +01:00
Ivan Enderlin 7b16a32910 doc: Update CHANGELOG.mds. 2026-01-09 15:46:48 +01:00
Ivan Enderlin 7caac55580 chore(ui): RoomListService:subscribe_to_rooms replaces old subscriptions.
This patch changes `subscribe_to_rooms` in `RoomListService` to replace
the old subscriptions. It avoids accumulating subscriptions forever and
is closer to the old `visible_rooms` sliding sync list behaviour.
2026-01-09 15:46:48 +01:00
Ivan Enderlin f19249c47d feat(sdk): Add SlidingSync::(clear_and_subscribe|unsubscribe)_to_rooms.
This patch adds two methods on `SlidingSync`: `unsubscribe_to_rooms`
and `clear_and_subscribe_to_rooms` to respectively remove many room
subscriptions, and to replace room subscriptions by new ones.
2026-01-09 15:46:48 +01:00
Valere 6c067a7981 add changelog 2026-01-09 13:49:23 +01:00
Valere f2991134f2 test: Call intent serialization tests 2026-01-09 13:49:23 +01:00
Valere e39a23e5ae feat(call): Add new call intents for voice only 2026-01-09 13:49:23 +01:00
Richard van der Hoff eba2a57122 common: fix typos in changelog
Some typos in the #5959 changelog
2026-01-09 13:47:40 +01:00
Jorge Martín 7595ae0916 test(ffi): Add test for CreateRoomParameters -> create_room::v3::Request transformation 2026-01-09 12:08:56 +01:00
Jorge Martín faff2d70f8 feat(ffi): Add ffi::CreateRoomParameters::is_space
This allows us to create a room with a `RoomType::Space` from the clients
2026-01-09 12:08:56 +01:00
Ivan Enderlin f3aec8d33e doc(sdk): Remove 6002 as it's no more relevant. 2026-01-09 11:25:37 +01:00
Ivan Enderlin 632ca3f9bf chore(sdk): Sliding Sync room_subscriptions is definitely not sticky.
Our information were wrong. `room_subscriptions` is definitely not
sticky, we must send them for each request.

This patch removes `RoomSubscriptionState`. Subscriptions are not marked
as “applied” anymore, they are always sent.
2026-01-09 11:25:37 +01:00
JoFrost 5291ff2b4d Merge branch 'main' into main
Signed-off-by: JoFrost <20685007+JoFrost@users.noreply.github.com>
2026-01-07 19:58:20 +02:00
Richard van der Hoff 97110edbe1 Merge pull request #6007 from matrix-org/kaylendog/history-sharing/changelog-fix
docs(common): Correct `ForwarderInfo` changelog entry.
2026-01-07 16:57:59 +00:00
Skye Elliot fbc0981e8d docs(common): Correct ForwarderInfo changelog entry.
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-01-07 16:10:24 +00:00
Jorge Martín 6b0c1e2992 doc: Add doc and inline comments 2026-01-07 16:11:40 +01:00
Jorge Martín c65026b70a doc: Add changelog entry 2026-01-07 16:11:40 +01:00
Jorge Martín b5a0042e14 fix(sqlite): Add WAL checkpoints to the DBs when they're opened too 2026-01-07 16:11:40 +01:00
Jorge Martín 3be6fb1a80 fix(sqlite): Add WAL checkpoints when vacuuming
For some reason, the automatic WAL checkpoints don't seem to be working as expected. Since we should periodically run VACUUM operations, we might as well add checkpoints before vacuuming (so the WAL size is reset and can grow to fit the whole DB) and after (so we clean up after that).
2026-01-07 16:11:40 +01:00
Ivan Enderlin 5dcd877dcd test(ui): Sticky parameters have been removed. 2026-01-07 14:05:59 +01:00
Ivan Enderlin ed1c847e7b doc(sdk): Update the `CHANGELOG.md. 2026-01-07 14:05:59 +01:00
Ivan Enderlin 87962d10e2 test(sdk): Test Request::extensions::to_device::since is set.
This patch restores a couple of assertions from a recently removed
test where it is asserted that `Request::extensions::to_device::since`
is set from the Olm machine.
2026-01-07 14:05:59 +01:00
Ivan Enderlin 4ac4ad3440 chore(sdk): Remove the sticky_parameters module.
This patch removes the `sticky_parameters` module, which is now unused.
2026-01-07 14:05:59 +01:00
Ivan Enderlin 742b6e5200 chore(sdk): Sliding Sync list filters is no longer sticky.
This patch extracts `SlidingSyncListStickyParameters::filters` to
no longer make it sticky. We are dropping sticky parameters as it's not
part of the last MSC.
2026-01-07 14:05:59 +01:00
Ivan Enderlin 43657d8302 chore(sdk): Sliding Sync list required_state is no longer sticky.
THis patch extracts `SlidingSyncListStickyParameters::required_state` to
no longer make it sticky. We are dropping sticky parameters as it's not
part of the last MSC.
2026-01-07 14:05:59 +01:00
Ivan Enderlin b28999e40c chore(sdk): Inline and always apply SlidingSyncStickyManager.
This patch removes `SlidingSyncStickyManager` as it only contains
the logic for a single request field: `room_subscriptions`. Also,
previously, `room_subscriptions` was considered sent based on the
transaction ID. This is useless as it's always sticky per MSC4186. The
logic from `SlidingSyncStickyManager` is sent inlined, allowing to
effectively remove this type.
2026-01-07 14:05:59 +01:00
Ivan Enderlin 42de8307bf chore(sdk): Sliding Sync extensions is no longer sticky.
This patch extracts `SlidingSyncStickyParameters::extensions` to no
longer make it sticky. We are dropping sticky parameters as it's not
part of the last MSC.
2026-01-07 14:05:59 +01:00
Jorge Martín 48d1d1f80f doc: Add changelog entry 2026-01-07 11:25:31 +01:00
Jorge Martín d52e5cfb50 doc: Add more doc comments to Client::fetch_client_well_known 2026-01-07 11:25:31 +01:00
Jorge Martín fe46a0cce0 fix(sdk): When using fetch_client_well_known_with_url, use the server name from the Client::user_id as a possible fallback too 2026-01-07 11:25:31 +01:00
ragebreaker b081654c51 fix(search): Create key dirs if they don't exist. (#5992)
The issue is related to the encrypt_store_dir fn where, when creating
the key file, it doesn't ensure that the parent directory exists first.
It might be not optimal for the user of the crate to ensure in an non
hacky manner, as the sdk iterates through most of its directories
internally. Have a test to verify it, which can be removed later (if
being merged)

Needs a review, might not be the optimal solution as this is my first pr
with the crate and am not that familiar with it (although do use it in
one of my apps).
2026-01-07 11:07:36 +01:00
Skye Elliot cc622a56ea Merge pull request #6000 from matrix-org/kaylendog/history-sharing/ui
Expose information about room key bundle forwarder in `matrix-sdk-ui` and `matrix-sdk-ffi`.
2026-01-06 16:40:10 +00:00
Skye Elliot a5b1231f8c refactor: Deduplicate shared history test code to helper methods. 2026-01-06 16:18:33 +00:00
Skye Elliot a8c9257dfe docs: Update CHANGELOGs. 2026-01-06 16:16:20 +00:00
Skye Elliot bfdd3ccc07 test: Ensure forwarder info is not available on unshared events. 2026-01-06 15:00:33 +00:00
Skye Elliot 503234976f refactor: Extract forwarder data fetcing to a helper function. 2026-01-06 14:28:44 +00:00
Skye Elliot 6fdd83478a tests: Ensure forwarder info accessible via high-level API. 2026-01-06 12:59:54 +00:00
Skye Elliot 0a5a22ec6f feat(ui): Expose information about room key bundle forwarder.
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2026-01-06 12:15:27 +00:00
dependabot[bot] 21cad56213 chore(deps): bump CodSpeedHQ/action from 4.4.1 to 4.5.2
Bumps [CodSpeedHQ/action](https://github.com/codspeedhq/action) from 4.4.1 to 4.5.2.
- [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/346a2d8a8d9d38909abd0bc3d23f773110f076ad...dbda7111f8ac363564b0c51b992d4ce76bb89f2f)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-06 12:02:50 +01:00
Kévin Commaille b0a536aaeb Upgrade Ruma
Uses the newly released version.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-01-06 12:02:23 +01:00
Kévin Commaille 5ee379d588 fix(ui): Deduplicate aggregation local and remote echo
We can have 3 different states for the same aggregation in
related_events, in chronological order:

1. The local echo with a transaction ID.
2. The local echo with the event ID returned by the server after
   sending the event.
3. The remote echo received via sync.

The transition from states 1 to 2 was already handled in
`mark_aggregation_as_sent()`.
But the transition from states 2 to 3 was never handled and we ended up
with both the local echo and the remote echo in the related events.

This resulted in the local echo being chosen over the remote echo when
computing the latest edit only because it was first in the list, even
though it didn't contain the raw JSON of the edit.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2026-01-06 10:00:24 +00:00
Jonas Platte ebbf34e924 Fix more new clippy lints 2026-01-06 09:51:10 +00:00
Jonas Platte e4aff871de Refactor IndexeddbStateStore::save_changes 2026-01-06 09:51:10 +00:00
Jonas Platte 0a9994c529 Fix new clippy lints 2026-01-06 09:51:10 +00:00
Jonas Platte 06764e2542 Reformat matrix-sdk-indexeddb 2026-01-06 09:51:10 +00:00
Jonas Platte 84cd2a67d4 Upgrade matrix-sdk-indexeddb to Rust edition 2024 2026-01-06 09:51:10 +00:00
Richard van der Hoff e3867aa7df Merge pull request #5959 from matrix-org/rav/clean_up_shield_state
Remove unused `ShieldStateCode::SentInClear`

`VerificationState::to_shield_state_{strict,lax}` return a type `ShieldState`, whose inner
type `ShieldStateCode` currently includes a variant `SentInClear` for "unencrypted event".
This is very misleading, because those functions never actually set that variant.

Rather, there is a separate method in `matrix-sdk-ui`, `EventTimelineItem::get_shield`,
which uses the same type, but *does* set that variant where appropriate.

As a user of matrix-sdk-common, without the matrix-sdk-ui layer, this is dangerously
misleading: it gives the impression that we are checking for unencrypted events, when in
fact we are not.

The solution seems to be to use different types for the different levels of the stack.

While we're at it, we fix up some of the confusion of methods that return an `Option` of an
enum type which itself has a `None` variant.
2026-01-05 16:33:54 +00:00
JoFrost 4c59282822 chore[ffi]: clippy 2025-12-21 22:22:19 +02:00
JoFrost a68aeafb4b chore[ffi]: rebase on last changes introduced in the events pr 2025-12-21 22:18:55 +02:00
JoFrost bc33878c64 Merge branch 'main' into main
Signed-off-by: JoFrost <20685007+JoFrost@users.noreply.github.com>
2025-12-21 22:13:43 +02:00
Skye Elliot cd9f433358 Merge pull request #5945 from matrix-org/kaylendog/history-sharing/encryption-info
feat: Add `forwarder: ForwarderInfo` to `EncryptionInfo`.

Introduces `ForwarderInfo` which which exposes information about the forwarder of the  keys with which an event was encrypted if they were shared as part of an [MSC4268](https://github.com/matrix-org/matrix-spec-proposals/pull/4268) room key bundle.
2025-12-19 17:29:22 +00:00
Skye Elliot 42a5910d8f feat(crypto): Introduce ForwarderData for session forwader info. (#5980)
<!-- description of the changes in this PR -->

- Introduces a new enum `ForwarderData` as a wrapper for valid variants
of `SenderData` for which we can accept key bundles under MSC4268.
- Converts `forwarder_data` in `InboundGroupSession` and
`PickledInboundGroupSession` to use `Option<ForwarderData>` over
`Option<SenderData>`.

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

---------

Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2025-12-19 14:51:15 +00:00
Richard van der Hoff 66daf3f7d3 Merge remote-tracking branch 'origin/main' into rav/clean_up_shield_state 2025-12-19 13:44:41 +00:00
Ivan Enderlin c3c367c54c doc(base): Update CHANGELOG.md. 2025-12-19 14:03:11 +01:00
Ivan Enderlin bf3cb6ba84 chore(sdk): Make Clippy happy :-). 2025-12-19 14:03:11 +01:00
Ivan Enderlin 5c5dcaa027 test(sdk): Add tests for new_remote and erasable values. 2025-12-19 14:03:11 +01:00
Ivan Enderlin ca64af1390 feat(sdk): When a m.room.redaction targets the current [LatestEventValue], it must be erased.
This patch implements a new feature: when a `m.room.redaction` targets
the current [`LatestEventValue`], this one must be erased by the new
computed `LatestEventValue`.
2025-12-19 14:03:11 +01:00
Ivan Enderlin ebe00841fd refactor(sdk): Replace bool by ControlFlow.
This patch replaces the `bool`s returned by `filter_*` functions by
`ControlFlow`s.
2025-12-19 14:03:11 +01:00
Ivan Enderlin 85f321f30a refactor(sdk): LatestEventValueBuilder receives the current value's event ID.
This patch spreads the current value's event ID in
`LatestEventValueBuilder`.

This patch also changes `LatestEventValueBuilder::new_remote` to return
an `Option`, similarly to `new_local`. The `must_overwrite_existing`
variable is set to `true` to keep the existing behaviour, but it's going
to change in the next patch.

This patch is purely a refactoring with no feature change. Most of the
changes are in test to keep track of the _previous value_ so that the
current value's event ID can be calculated instead of hardcoded.
2025-12-19 14:03:11 +01:00
Ivan Enderlin e2ea84f3e3 chore(sdk): Rename a variable.
This patch renames a variable. Since `rfind_map_event_id_memory_by`
returns the previous event instead of the previous event ID, this
variable must have been renamed.
2025-12-19 14:03:11 +01:00
Ivan Enderlin d5898a64ab feat(base): Add LatestEventValue::event_id.
This patch adds the `event_id` method on `LatestEventValue`, along with
the tests.
2025-12-19 14:03:11 +01:00
Ivan Enderlin 42b79d7d8a feat(base): LatestEventValue::LocalHasBeenSent gains an event_id field.
This patch adds the `event_id: OwnedEventId` field to
`LatestEventValue::LocalHasBeenSent`.
2025-12-19 14:03:11 +01:00
Ivan Enderlin 9363745fb0 chore(sdk): Remove timer! logs.
This patch removes `timer!` logs, those are no longer useful.
2025-12-19 14:03:11 +01:00
Richard van der Hoff b5f2128db1 common: remove now-unused ShieldStateCode::SentInClear 2025-12-18 13:54:49 +00:00
Richard van der Hoff d5ce01acab ui: new type for EventTimelineItem::get_shield
Separate the shield types between common and UI, so that we can change common
without breaking UI.

The new type does not include a `message` field: since it cannot be localised,
clients should not be using it.
2025-12-18 13:54:49 +00:00
Richard van der Hoff dbefaef777 bindings: remove message from ShieldState
Since this can't be localised, apps shouldn't be using it.
2025-12-18 13:09:54 +00:00
Richard van der Hoff 7438c59acd bindings: get_shields: stop returning Option
Again, there is no need for an `Option` as well as a `None` variant
2025-12-18 13:07:34 +00:00
Richard van der Hoff f5cda21d59 ui: TimelineEventItem::get_shield: stop returning Option
The `ShieldState` enum has a `None` variant, so we don't need an `Option` on
top of it.
2025-12-18 13:07:34 +00:00
Jorge Martín 759c5a9fcd docs: Add changelog entries 2025-12-18 10:41:43 +01:00
Jorge Martín 1549194b2f feat(ffi): Add an actual ffi::TimelineEventType enum with only the type
Use that for `RoomPowerLevels::events` instead.
2025-12-18 10:41:43 +01:00
Jorge Martín 4665b4343d refactor(ffi): Rename TimelineEventType to TiemlineEventContent since it also contains the event contents for some of the types 2025-12-18 10:41:43 +01:00
Jorge Martín 1f94e9d20c feat(ffi): Add fn RoomPowerLevels::events
With this we can query the power level value for any event type
2025-12-18 10:41:43 +01:00
Skye Elliot b3f6df939b Merge pull request #5943 from matrix-org/kaylendog/history_sharing/store_history_sender_details
feat(crypto): Add `forwarder_data` to `InboundGroupSession` and pickle.
2025-12-17 17:16:57 +00:00
Skye Elliot 0e568a4ee6 refactor: Use impl Iterator<Item = InboundGroupSession> as param. 2025-12-17 16:50:59 +00:00
Skye Elliot f94ce7e91c docs: Improve doc comments, linkify MSC4268. 2025-12-17 16:43:54 +00:00
Stefan Ceriu 6042bc93f6 chore(spaces): move the SpaceServices setup logic to its constructor and make it async 2025-12-17 14:42:14 +02:00
Stefan Ceriu 80be172fdf chore(ui): Move the 5955's changelog to the right position 2025-12-17 14:42:14 +02:00
Stefan Ceriu 4cc863a9fb chore(spaces): add changelogs 2025-12-17 14:42:14 +02:00
Stefan Ceriu 51e07d9fba chore(spaces): Rename subscribe_to_joined_spaces to subscribe_to_top_level_joined_spaces 2025-12-17 14:42:14 +02:00
Stefan Ceriu 143d96e300 chore(spaces): rename the SpaceService's SpaceState's joined_rooms to top_level_joined_spaces 2025-12-17 14:42:14 +02:00
Stefan Ceriu a2fd2536c4 chore(spaces): Rename joined_spaces to top_level_joined_spaces 2025-12-17 14:42:14 +02:00
Stefan Ceriu f5d751b3eb chore(spaces): Rename update_joined_spaces_if_needed to update_space_state_if_needed 2025-12-17 14:42:14 +02:00
Stefan Ceriu 096dfd61cb chore(spaces): rename joined_spaces_for to build_space_state 2025-12-17 14:42:14 +02:00
Stefan Ceriu 89f66ecd10 chore(spaces): Extract the SpaceService's subscription for rooms outside the subscribe_to_joined_spaces and make it explicit.
This will avoid having to awkwardly call `space_service.joined_spaces` without it even being needed.
2025-12-17 14:42:14 +02:00
Skye Elliot 85f07b10ad chore: Remove unused TryFrom<&HistoricRoomKey> implementation. 2025-12-17 12:33:21 +00:00
Skye Elliot 809643a159 tests(crypto): Doctests, update snapshots to include forwarder_data. 2025-12-17 12:33:21 +00:00
Richard van der Hoff f753d478fa feat: Add forwarder_data to InboundGroupSession and pickle.
- Introduces `forwarder_data` to IGS and its pickled form, and a
  helper method to import them from `HistoricRoomKey`s.

Issue: https://github.com/matrix-org/matrix-rust-sdk/issues/5109

Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2025-12-17 12:33:16 +00:00
Damir Jelić 282a2bc8ef refactor(timeline): Don't request redecryptions in the timeline 2025-12-17 13:19:38 +01:00
Damir Jelić c6fb3c25f3 feat(r2d2): Let R2D2 attempt to update encryption info for in-memory events 2025-12-17 13:19:38 +01:00
Damir Jelić 5429106ab3 feat(r2d2): Let R2D2 attempt to redecrypt events that are in the memory of the event cache 2025-12-17 13:19:38 +01:00
Damir Jelić 5c387f2b81 refactor(r2d2): Convert the filter closure for decrypted events into function 2025-12-17 13:19:38 +01:00
Damir Jelić 6c68ca2a64 refactor(r2d2): Split out the encryption info update method into reusable components 2025-12-17 13:19:38 +01:00
Michael Goldenberg 4ee6906f47 doc(sdk): update changelog
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-12-17 12:56:09 +01:00
Michael Goldenberg 46b9c11139 doc(indexeddb): update changelog
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-12-17 12:56:09 +01:00
Michael Goldenberg 8e1510821b style(indexeddb): cargo fmt
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-12-17 12:56:09 +01:00
Michael Goldenberg 288f28620c refactor(indexeddb): add deprecation note on open_stores_with_name()
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-12-17 12:56:09 +01:00
Michael Goldenberg aa5497e385 feat(client): initialize all stores in indexeddb store config
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-12-17 12:56:09 +01:00
Michael Goldenberg 6bf121b58e refactor(indexeddb): ensure event-cache-store feature flag compiles in isolation
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-12-17 12:56:09 +01:00
Michael Goldenberg 01b130a401 refactor(indexeddb): ensure media-store feature flag compiles in isolation
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-12-17 12:56:09 +01:00
Michael Goldenberg 256fb0406d refactor(indexeddb): use finer-grain feature flags to include/exclude serializers
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-12-17 12:56:09 +01:00
Michael Goldenberg c57478ee18 feat(indexeddb): add media-store to default features
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-12-17 12:56:09 +01:00
Michael Goldenberg e74bf33178 feat(indexeddb): expose struct and fns for opening all stores
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-12-17 12:56:09 +01:00
Michael Goldenberg 2bec882c83 feat(indexeddb): add fn to media store builder for prefixing db name
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-12-17 12:56:09 +01:00
Michael Goldenberg 2cfcc957ca feat(indexeddb): add fn to event cache store builder for prefixing db name
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-12-17 12:56:09 +01:00
Damir Jelić 972b3dc88b test: Add a test which showcases that redecryption for timelines with an event focus is broken (#5975)
Co-authored-by: Andy Balaam <andy.balaam@matrix.org>
2025-12-17 11:38:08 +00:00
razvp ea43e3f5a8 feat(sdk): Bulk process thread subscription updates from sync and companion enpoint 2025-12-17 11:03:50 +01:00
razvp 67b1de613c feat(state-stores): Add StateStore::upsert_thread_subscriptions() method for bulk upsert 2025-12-17 11:03:50 +01:00
Jorge Martín 1af22a70b7 fix(sdk_common): TimelineEvent::from_bundled_latest_event can remove session_id
What's more, this is saved into the event cache and sometimes it overrides another instance of the same event that actually contains the right info. This results in unresolvables UTDs.

This change tries to fetch the session id from the existing event content. It's fixed these kind of UTDs when tested in a real client.
2025-12-16 16:21:51 +01:00
Damir Jelić 504d15f171 chore: Fix some spelling issues 2025-12-16 15:46:34 +01:00
Damir Jelić 1302afb844 test: Add another test for pinned timelines
This time we're testing the redecryption of pinned events that were not
part of the main timeline, more importantly we never backpaginated
enough for them to be part of the main timeline and thus never got put
into the event cache.

This test expectedly fails for now.
2025-12-16 15:46:34 +01:00
Damir Jelić 028d610397 fix: Only replace UTDs in pinned timeline with decrypted events that have the same event ID 2025-12-16 15:46:34 +01:00
Damir Jelić 7323c79dc2 test: Refactor the pinned timeline integration test a bit
This should allow us in the future to create more such tests with
many more events.
2025-12-16 15:46:34 +01:00
Damir Jelić 16c6b57c9a fix(timeline): Allow focused timelines to replace UTDs with decrypted events 2025-12-16 15:46:34 +01:00
Damir Jelić 329bdaa785 test(timeline): Add a test that pinned events get replaced after they get redecrypted 2025-12-16 15:46:34 +01:00
Ivan Enderlin e57185a009 fix(sdk): new_local returns an Option.
We can't use `LatestEventValue::None` as an optional value anymore,
since it erases the previous `LatestEventValue`. This patch updates
`LatestEventValueBuilder::new_local` to return an `Option` to handle all
the cases where a local value cannot be computed.
2025-12-16 15:42:37 +01:00
Ivan Enderlin 4e90ceae91 doc: Update the CHANGELOG.mds. 2025-12-16 13:45:00 +01:00
Ivan Enderlin ccf11ad041 feat(ui): latest_event sorter handles LatestEventValue::LocalHasBeenSent.
This patch changes the semantics of the Room List `latest_event`
sorter by changing “is local” to “is remote like”, to include the new
`LatestEventValue::LocalHasBeenSent` variant.
2025-12-16 13:45:00 +01:00
Ivan Enderlin 631671fb1c fix(sdk): Introduce LatestEventValue::LocalHasBeenSent.
The problem we are trying to solve is the following:

- a local event is being sent,
- the `LatestEventValue` is `LocalIsSending`,
- the local event is finally sent,
- the `LatestEventValue` is still `LocalIsSending` purposely, with the
  hope that an update from the Event Cache will replace it.

But sometimes, this update from the Event Cache comes **before** the
update from the Send Queue. Why is it problem? Because updates from the
Event Cache are ignored until the buffer of local `LatestEventValue`s
aren't empty, which means that if an update from the Event Cache is
received before `RoomSendQueueUpdate::SentEvent`, it is ignored, and the
`LatestEventValue` stays in the `LocalIsSending` state. That's annoying.

The idea is to introduce a new state: `LocalHasBeenSent` which mimics
`Remote`, but for a local event. It clarifies the state of a sent event,
without relying on the Event Cache.
2025-12-16 13:45:00 +01:00
Ivan Enderlin 1480ede8d4 chore(sdk): Format. 2025-12-16 12:33:04 +01:00
Ivan Enderlin e0b1f471fa doc(sdk): Document the With inner type. 2025-12-16 12:33:04 +01:00
Ivan Enderlin 58d25464c2 doc(sdk): Update CHANGELOG.md. 2025-12-16 12:33:04 +01:00
Ivan Enderlin 277bdce01d chore(sdk): Small refactoring.
This patch simplifies the code after the recent refactorings.

It uses `LatestEventValue::is_none()` to replace a `matches!`, and it
replaces the last use of `new_remote` by `new_remote_with_power_levels`
to finally rename this latter to `new_remote`.
2025-12-16 12:33:04 +01:00
Ivan Enderlin 9cf7719958 test(sdk): Fix a test on slow system. 2025-12-16 12:33:04 +01:00
Ivan Enderlin 46f313ac28 test(sdk): Simplify a test. 2025-12-16 12:33:04 +01:00
Ivan Enderlin a630904b41 perf(sdk): Do not replace a LatestEventValue::None by itself.
Replacing a `LatestEventValue::None` by a `LatestEventValue::None` is
ignored. It reduces the number of (useless) updates in the system.
2025-12-16 12:33:04 +01:00
Ivan Enderlin f80140d5ff feat(sdk): Compute LatestEventValue when initialized if None.
The `LatestEventValue` can be `None` (the default value) but the Event
Cache contains enough data to compute a `Remote(_)` one. The system
lazily triggers a `LatestEventQueueUpdate` to achieve that.
2025-12-16 12:33:04 +01:00
Ivan Enderlin a21079b2ac test(ui): Fix a test!
This `test_room_sorting` test was asserting a bug. With the last patch,
this bug is now fixed, and the test must be fixed too.
2025-12-16 12:33:04 +01:00
Ivan Enderlin f283a0aadf chore(sdk): Replace an Option<T> by OnceCell<T>. 2025-12-16 12:33:04 +01:00
Ivan Enderlin 248961fe31 feat(sdk): LatestEventValue is restored from RoomInfo.
Previously, `LatestEventValue` was always initialized from the
`RoomEventCache`. Now, it is restored from the `RoomInfo`. First off,
this is something we wanted to do since a long time. Second, it is
more performant. Third, it allows the system to be lazier. Indeed,
it's possible that when a `LatestEvent` is created, its correspond
`RoomEventCache` doesn't exist yet. It happens during a sync when a room
is new: latest events are registered, but their `RoomEventCache` aren't
yet created. By postponing the use of `RoomEventCache`, the system is
lazier and more solid.

Bonus, less methods are async, which simplifes the workflow.
2025-12-16 12:33:04 +01:00
Ivan Enderlin cdc39b69a1 fix(sdk): Remove latest_events::RoomRegistration.
This patch removes `RoomRegistration` along with the full room
registration mechanism. It's been introduced to remove contention
around the `RegisteredRooms` lock, but it actually creates more async
flows, which makes the `latest_events` logic a bit less predictable.
By removing this room registration mechanism, our hope is to make the
result more predictable and less buggy in appareance. Our real-life
tests have shown that the lock contention isn't problematic, especially
since `RoomLatestEventsReadGuard` and `RoomLatestEventsWriteGuard` have
been introduced.
2025-12-16 12:33:04 +01:00
dependabot[bot] 95dac018e3 chore(deps): bump actions/upload-artifact from 5 to 6
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-16 11:38:45 +01:00
dependabot[bot] 0e5077dab1 chore(deps): bump bnjbvr/cargo-machete
Bumps [bnjbvr/cargo-machete](https://github.com/bnjbvr/cargo-machete) from 72602674bc341ca927683caddbf578672c352476 to 6229aa757e3e8a028bd97a49e190207e108eefbd.
- [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/72602674bc341ca927683caddbf578672c352476...6229aa757e3e8a028bd97a49e190207e108eefbd)

---
updated-dependencies:
- dependency-name: bnjbvr/cargo-machete
  dependency-version: 6229aa757e3e8a028bd97a49e190207e108eefbd
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-16 11:38:15 +01:00
dependabot[bot] 3b5b0f81c4 chore(deps): bump actions/cache from 4 to 5
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-16 09:17:45 +01:00
dependabot[bot] a527439eae chore(deps): bump tj-actions/changed-files from 47.0.0 to 47.0.1
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 47.0.0 to 47.0.1.
- [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/v47.0.0...v47.0.1)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-version: 47.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 17:40:37 +02:00
Damir Jelić 4eb1981779 chore: Remove the dead message-ids feature 2025-12-15 14:43:52 +01:00
Damir Jelić 073f95436f refactor(timeline): Remove the backup_states_task
R2D2 now sends out a report when backups become available, so we can
just listen to that instead.
2025-12-15 14:43:43 +01:00
Damir Jelić fa1ebbfdb8 feat(r2d2): Send out a report when backups get enabled 2025-12-15 14:43:43 +01:00
Damir Jelić 9bdd2ae977 test: Ensure that the test_enabling_backups_retries_decryption test times out 2025-12-15 14:43:43 +01:00
Damir Jelić 3a63838cdb test: Disable the lease lock tests for the memory store on wasm
This test for one reason or the other sporadically panics with an:
    > RuntimeError: unreachable

Let's disable this test on Wasm for now since the memory store isn't
that relevant anyways, especially not on Wasm.
2025-12-15 13:56:11 +01:00
Damir Jelić 7a1a2202f8 feat(r2d2): Add logs for when the room key stream lags 2025-12-15 11:38:12 +01:00
Damir Jelić ce65317ab8 chore: Fix an incorrect warning due to the zeroize macro 2025-12-11 19:27:45 +01:00
Damir Jelić cd988e53f8 doc(encryption): Add a link to MSC4287 2025-12-11 19:27:45 +01:00
Jonas Platte ad58607013 Fix new clippy lints 2025-12-11 16:18:42 +01:00
Jonas Platte eae3006f8d Reformat matrix-sdk-sqlite 2025-12-11 16:18:42 +01:00
Jonas Platte 024fd99e71 Upgrade matrix-sdk-sqlite to Rust edition 2024 2025-12-11 16:18:42 +01:00
Damir Jelić 4d3125e58e fix(common): Fix the debug implementation of TimelineEventKind 2025-12-11 16:09:52 +01:00
Damir Jelić 17a4888481 chore: Convert some Note's to NOTE's
The later is more standard and some editor plugins will highlight those
for you.
2025-12-11 16:09:21 +01:00
Damir Jelić cc4cf3d54f refactor(r2d2): Create common report_lag method to reduce duplicated code 2025-12-11 13:34:48 +01:00
Mauro 9a6acd5334 Add space_room_from_id function (#5944)
Useful to get a specific space room if available given its id.

---------

Co-authored-by: Doug <douglase@element.io>
2025-12-11 12:11:48 +00:00
Damir Jelić 2522a3694f refactor(qr-login): Prepare the secure channel to be usable with HPKE
This patch abstracts away the cryptographic channel which is used in the
SecureChannel implementation for the QR code login support.

This will allow us to use HPKE alongside of ECIES since MSC4108 recently
proposed the switch to HPKE.
2025-12-11 09:24:55 +01:00
Jonas Platte f2ba338e12 Fix new rustc + clippy warnings 2025-12-10 16:37:43 +00:00
Jonas Platte 0035259e3d Reformat matrix-sdk-crypto 2025-12-10 16:37:43 +00:00
Jonas Platte 431eb88a2d Upgrade matrix-sdk-crypto to Rust edition 2024 2025-12-10 16:37:43 +00:00
Jonas Platte f8d5014921 Add + use<_> to impl Trait return types
(part of `cargo fix --edition`)
2025-12-10 16:37:43 +00:00
Jonas Platte b0072160af Remove unnecessary ref mut from patterns
(part of `cargo fix --edition`)
2025-12-10 16:37:43 +00:00
Jonas Platte 536f889649 Add a missing semicolon 2025-12-10 16:37:43 +00:00
Jonas Platte 2c4a718c1a Rename local variable for edition 2024 compatibility
`gen` is a keyword starting in edition 2024.
2025-12-10 16:37:43 +00:00
Ivan Enderlin b19377a96c fix(sdk): Better handling of redacted and redaction events in Latest Event (#5932)
This patch revisits the way redacted and redaction events are handled in
the Latest Event.

Previously, all redacted events were considered suitable candidate. It's
no longer the case.

Redaction and redacted events are no longer considered suitable.

This patch also revisits `rfind_map_event_in_memory_by` to return a
`&TimelineEvent` instead of an `OwnedEventId`, which could be more
performant in the future.

The tests have been updated accordingly.

---

* Fix https://github.com/matrix-org/matrix-rust-sdk/issues/5899
* Address https://github.com/matrix-org/matrix-rust-sdk/issues/4112

Signed-off-by: Stefan Ceriu <stefanc@matrix.org>
Co-authored-by: Stefan Ceriu <stefanc@matrix.org>
2025-12-10 12:37:01 +00:00
Ivan Enderlin 76cae09f37 test(sdk): Test RoomEventCacheGenericUpdates are broadcasted. 2025-12-10 12:46:02 +01:00
Ivan Enderlin 81eb466555 fix(sdk): Broadcast a RoomEventCacheGenericUpdate when redecrypting events.
This patch sends a missing `RoomEventCacheGenericUpdate` when
redecrypting events.
2025-12-10 12:46:02 +01:00
Ivan Enderlin 0a24622ca2 fix(sdk): Broadcast a RoomEventCacheGenericUpdate when paginating from the network.
This patch sends a missing `RoomEventCacheGenericUpdate` when paginating
events from the network.
2025-12-10 12:46:02 +01:00
Ivan Enderlin db0f4dd31c chore: Add logs in matrix_sdk::latest_events. 2025-12-10 12:46:02 +01:00
Ivan Enderlin fbc69837e1 perf(sdk): Increase various channel capacities. 2025-12-10 12:46:02 +01:00
Ivan Enderlin b9b5ead89e perf(sdk): Use an unbounded channel for room registration. 2025-12-10 12:46:02 +01:00
Ivan Enderlin 1c0f447632 perf(sdk): Increase the capacity of the room registration channel.
This patch increases the capacity of the room registration channel. The
hope is that it can reduce the need to wait on available permits under
heavy load.
2025-12-10 12:46:02 +01:00
Ivan Enderlin e21a95f631 refactor(sdk): Do not use Fuse in listen_to_event_cache_and_send_queue_updates task.
This patch removes the use of `Fuse` in the
`listen_to_event_cache_and_send_queue_updates` task. `mpsc::Receiver`
and `broadcast::Received` are cancellation safe.
2025-12-10 12:46:02 +01:00
Ivan Enderlin 5a05bcb6b7 refactor(sdk): Hold write lock as few as possible.
This patch reduces the lifetime of the write locks in
`RegisteredRooms::room_latest_event` when the `RoomLatestEvents`
doesn't exists. If the `room_registration_sender` channel is full,
it has to wait. When waiting, the write lock is still alive, probably
blocking other operations. The idea is to create the `RoomLatestEvents`,
to downgrade the write lock to a read lock, and then to send the
registration message onto the `room_registration_sender`.
2025-12-10 12:46:02 +01:00
Hugh Nimmo-Smith 183116a4b1 Changelog 2025-12-09 19:18:06 +01:00
Hugh Nimmo-Smith e916d9d374 fix(sdk): support device IDs that aren't Curve25519 public keys in GrantLoginWithGeneratedQrCode and GrantLoginWithScannedQrCode 2025-12-09 19:18:06 +01:00
Stefan Ceriu 604ce4acfd chore(sdk, crypto): add changelogs 2025-12-09 18:49:03 +01:00
Damir Jelić cd57da59ec fix: Correctly construct the decrypted event in the send queue 2025-12-09 18:49:03 +01:00
Damir Jelić 9ff90a9b4d Add an integration test that the send queue can insert encrypted events 2025-12-09 18:49:03 +01:00
Stefan Ceriu 98f34f010c change(room::futures): Use a proper struct instead of a tuple for SendMessageLikeEvent results 2025-12-09 18:49:03 +01:00
Stefan Ceriu d513807c9f change(send_queue): Use the Room:send_raw resulting EncryptionInfo to create a DecryptedRoomEvent and corresponding TimelineEvent and correctly populate the Event Cache. 2025-12-09 18:49:03 +01:00
Stefan Ceriu 8bd401b003 change(matrix_sdk::Room): Return the used EncryptionInfo when sending MessageLike and RawMessageLike events 2025-12-09 18:49:03 +01:00
Stefan Ceriu b7d22da9f0 change(crypto): provide encryption information back directly from the Olm machine's raw encryption methods 2025-12-09 18:49:03 +01:00
Hugh Nimmo-Smith b98a832c67 feat(ffi): Add QrCodeData::to_bytes() to allow generation of a QR code (#5939)
Signed-off-by: Hugh Nimmo-Smith <hughns@element.io>
2025-12-09 17:00:44 +00:00
Michael Goldenberg acf3a7a04b doc(indexeddb): update changelog
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-12-09 15:24:15 +01:00
Michael Goldenberg 790136b9db fix(indexeddb): skip encoding event id when constructing bounds
In the implementation of EventCacheStore, there are a number of
places where the upper and lower bounds of an EventId are
constructed. It is important to bypass hashing and encryption
when constructing these bounds, otherwise the values will be
modified and will no longer represent the bounds.

Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-12-09 15:24:15 +01:00
Michael Goldenberg 265cfc7710 test(indexeddb): initialize store cipher in encrypted tests for event cache and media store
Note that the encrypted tests were actually being run unencrypted.
Introducing a store cipher causes them to run encrypted, and
furthermore, reveals some bugs which are only visible when running
an encrypted event cache store.

Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-12-09 15:24:15 +01:00
Damir Jelić 29b693e625 chore: Update the changelog to include the CVE and GSA references 2025-12-08 12:45:42 +01:00
Johannes Marbach 8637bdce12 fixup! feat(room): make load_event_with_relations also load relations when falling back to the network
Try loading relations from the cache before falling back to the server

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-12-08 09:44:59 +00:00
Johannes Marbach db4e1b2c00 fixup! feat(room): make load_event_with_relations also load relations when falling back to the network
Cache related events after loading them

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-12-08 09:44:59 +00:00
Johannes Marbach c82e1b8ec3 fixup! feat(room): make load_event_with_relations also load relations when falling back to the network
Change limit to 256

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-12-08 09:44:59 +00:00
Johannes Marbach 9105db3d82 feat(room): make load_event_with_relations also load relations when falling back to the network
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-12-08 09:44:59 +00:00
JoFrost 465300560e feat[bindings]: move into specific structures the threshold and add previous values 2025-12-05 12:07:43 +02:00
JoFrost c97ab27052 chore[bindings]: clippy 2025-12-05 11:21:56 +02:00
JoFrost b776ae9090 feat[bindings]: expose power level thresholds in corresponding timeline event 2025-12-05 11:05:56 +02:00
Ivan Enderlin 238e4e8a87 doc: Mention #5624 in CHANGELOG.mds. 2025-12-05 09:40:47 +01:00
Ivan Enderlin 37c516f7f9 refactor: Rename new_latest_event to latest_event.
This patch removes the “new_” prefix to the latest event API.
2025-12-05 09:40:47 +01:00
Ivan Enderlin 81a8aa063b chore(base): Remove the old latest event API. 2025-12-05 09:40:47 +01:00
Ivan Enderlin 91091c7819 chore(ui): Remove the old latest event API.
So satisfying.
2025-12-05 09:40:47 +01:00
Ivan Enderlin e4141b216a chore(ffi): Remove the old latest event API. 2025-12-05 09:40:47 +01:00
Kévin Commaille b0574c1c2c feat(sdk): Add support for the stable m.oauth UIAA type
By replacing the custom implementation with the one in Ruma.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-12-05 09:20:48 +01:00
Jorge Martín bfd8cfa1c8 refactor: Add uniffi-rs entries to deny.toml 2025-12-04 15:17:26 +01:00
Jorge Martín 67815e7787 refactor: Remove unused cargo_metadata dependency 2025-12-04 15:17:26 +01:00
Jorge Martín 031d8457b6 refactor: Upgrade uniffi to the latest upstream 2025-12-04 15:17:26 +01:00
Ivan Enderlin c84f5965c3 test(ui): Fix tests according to last change.
This path adds events or state events to force the test to execute as
expected since a change in `bump_stamp` alone doesn't trigger a room
list update anymore.
2025-12-04 13:39:01 +01:00
Ivan Enderlin 638028ba7c fix(ui): Reduce number of updates in the room list.
This patch fixes a “bug” in the Room List. It's updated by
`RoomInfoNotableUpdateReasons`. However, 1 reason is creating
unnecessary updates: `RECENCY_STAMP`. The Room List is already updated
by the Latest Event. One usage of the Latest Event is to sort the Room
List by recency. Thus, since the Room List is updated by `LATEST_EVENT`,
we can ignore `RECENCY_STAMP`.
2025-12-04 13:39:01 +01:00
Ivan Enderlin ae9600e872 doc(sdk): Remove mention of #3941.
This patch removes the mention of #3941 now it's closed.
2025-12-04 12:06:24 +01:00
Damir Jelić b5d5a41453 Merge pull request #5926 from matrix-org/poljar/release-0.16.0
Release prep for 0.16.0
2025-12-04 10:32:26 +01:00
Damir Jelić 742a0db07b chore: Remove mentions of the 0.15 release
The 0.15.0 release was a misfire so we're skipping this version number.
2025-12-04 09:59:04 +01:00
Damir Jelić 4701faf039 chore: Release matrix-sdk version 0.16.0 2025-12-04 09:59:04 +01:00
Doug 93f71ba977 ffi: Add support for checking login with QR code availability. 2025-12-03 18:35:29 +02:00
Damir Jelić 4ea0418abe fix: Don't attempt to serialize custom join rules (#5924)
This is not supported by Ruma. The join_rule field, despite being
defined as a pure string, can have associated data to it based on the
join rule variant.

This means that custom and unknown enum variants might lose data when
reserializing.

Let's just skip the serialization of custom join rules in the RoomInfo,
the concrete value is still available in the state store, it's just not
kept at hand in the RoomInfo.

Signed-off-by: Damir Jelić <poljar@termina.org.uk>
Co-authored-by: Ivan Enderlin <ivan@mnt.io>
2025-12-03 16:54:58 +01:00
Jorge Martín 2412403a1e doc: Add changelogs 2025-12-03 15:41:42 +01:00
Jorge Martín ed72d3439a fix: avoid unwrap in Client::optimize_stores 2025-12-03 15:41:42 +01:00
Jorge Martín 9eee20b4f0 feat(ffi): add bindings for Client::get_store_sizes 2025-12-03 15:41:42 +01:00
Jorge Martín bc45457d0e feat: add Client::get_store_sizes
This method will retrieve the database sizes if available and expose it in the client.

Note: the actual database size measuring is only implemented for the SQLite based stores
2025-12-03 15:41:42 +01:00
Jorge Martín 76651aec69 refactor: don't use full namespace for std::Result 2025-12-03 15:41:42 +01:00
Jorge Martín e1cda064ee refactor: hopefully fix another lint error 2025-12-03 15:41:42 +01:00
Jorge Martín 94e5dbea0c refactor: hide optimize_store methods, add warnings to not use them in production
Also fix lint issue
2025-12-03 15:41:42 +01:00
Jorge Martín c6e7a17f65 feat(ffi) Add Client::optimize_stores method 2025-12-03 15:41:42 +01:00
Jorge Martín 1e7bc1286e refactor: Add trace log to ensure the VACUUM operation has finished successfully
This was a bit confusing, because I treated a lack of logs as success when in reality my code was calling an empty implementation
2025-12-03 15:41:42 +01:00
Jorge Martín b04cc9fe27 feat: Implement the new Store::optimize method added in the store traits
Only SQLite based stores will implement it for now, calling the `SqliteAsyncConnExt::vacuum` method
2025-12-03 15:41:42 +01:00
Jorge Martín 054dc31ce4 feat(sdk): Add Client::optimize_stores
This method should trigger any optimization/maintenance behaviours available to the stores, like `VACUUM` in SQLite
2025-12-03 15:41:42 +01:00
Ivan Enderlin aaff9c5d72 test: Update tests according to last patches. 2025-12-03 13:11:40 +01:00
Ivan Enderlin b1773d33c2 fix(sqlite): Make it possible to store the new SendRequestKey format. 2025-12-03 13:11:40 +01:00
Ivan Enderlin 090351c6ac doc(sqlite): Fix mention of a method.
This patch fixes a mention to a `save_send_queue_event` method. It
doesn't exist: it's `save_send_queue_request`.
2025-12-03 13:11:40 +01:00
Ivan Enderlin 045eb3486b test: Use SerializableEventContent::new instead of from_raw.
This patch replaces calls to `SerializableEventContent::from_raw` by
`new`: it's simpler and safer as it's not possible to use an invalid
event type.
2025-12-03 13:11:40 +01:00
Ivan Enderlin 40738ae119 feat(sdk): The Send Queue stores the sent event in the Event Cache.
The event has been sent to the server and the server has received it.
Yepee! Now, we usually wait on the server to give us back the event via
the sync.

Problem: sometimes the network lags, can be down, or the server may be
slow; well, anything can happen. It results in a weird situation where
the user sees its event being sent, then disappears before it's received
again from the server.

To avoid this situation, this patch eagerly saves the event in the Event
Cache. It's similar to what would happen if the event was echoed back
from the server via the sync, but we avoid any network issues. The Event
Cache is smart enought to deduplicate events based on the event ID, so
it's safe to do that.
2025-12-03 13:11:40 +01:00
Doug acc66266c7 fix: Don't show a syncing indicator until the sync service is started. 2025-12-03 12:04:31 +01:00
Ivan Enderlin e6094e6b07 chore: Use our own fork of indexed-db-futures.
This patch uses our own fork of `indexed-db-futures`: `matrix-indexed-db-futures`.
2025-12-03 11:59:50 +01:00
Johannes Marbach ea538351e9 docs(timeline): clarify what mark_as_read actually does
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-12-03 11:58:16 +01:00
Ivan Enderlin b4d7881a58 chore: Reduce the number of logs.
This patch removes some logs around the cross-process lock methods. This
is called pretty often by the cross-process lock task, which pollute the
log files.
2025-12-02 21:59:46 +01:00
Stefan Ceriu 8c4a19bb85 fix(ffi): remove undesired network request from the client builder
Making network requests before actually building a client interferes with offline support, especially so in lie-fi situations.
The method is exposed through FFI though and can be used at the final user's discretion (e.g. when submitting a bug report).
2025-12-02 18:37:10 +01:00
Doug 017644864a chore: Add tests for message-like read receipt tracking. 2025-12-02 15:36:34 +01:00
Doug 59604713e8 chore: Re-use the existing track_read_receipts setting to hide receipts on state events.
# Conflicts:
#	bindings/matrix-sdk-ffi/CHANGELOG.md
2025-12-02 15:36:34 +01:00
Doug d563cebcfc feat: Allow Timelines to be configured to hide read receipts on state events. 2025-12-02 15:36:34 +01:00
Ivan Enderlin 19b7036119 doc: Fix typos. 2025-12-02 11:54:31 +01:00
dependabot[bot] d6b942d3ac chore(deps): bump crate-ci/typos from 1.39.2 to 1.40.0
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.39.2 to 1.40.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.39.2...v1.40.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-02 11:54:31 +01:00
Kévin Commaille c1302c417a feat(sdk): Allow to refresh the token in Client::fetch_server_versions
We need to handle 2 possible deadlocks for this:

1. We cannot try to refresh an expired access token if this call happens
   while we are currently trying to refresh the token. The easiest way
   to handle this is to never try to refresh the token when making this
   call inside `get_path_builder_input()` so we implement a "failsafe"
   mode that disables refreshing the access token in case it expired.
   However it attempts the GET /versions again without the token.
2. We cannot access the cached supported versions if we are in the
   process of refreshing that cache because the RwLock has a write lock.
   So if the access token has expired and we try to refresh it, the
   possible calls to `get_path_builder_input()` must not wait for a read
   lock to be available. So the solution is to never wait for a read
   lock, and skip the cache if a read lock is not available.

This also gets rid of workarounds in other functions.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-12-02 10:51:49 +00:00
Kévin Commaille 176684a07c feat(sdk): Keep track of whether the access token is expired
This will allow to handle automatically whether to send an access token
or not on endpoints that don't require it in contexts were can't refresh
it.

We also don't cache calls to GET /versions that were not authenticated,
because they might lack some features compared to an authenticated
request.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-12-02 10:51:49 +00:00
Kévin Commaille be9e7ac9bf test(sdk): Handle more cases with ExpectedAccessToken
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-12-02 10:51:49 +00:00
Kévin Commaille 407621f055 refactor(sdk): Add constructor for AuthCtx
Allows to have stricter visibility for the fields and put less code in
ClientBuilder.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-12-02 10:51:49 +00:00
Kévin Commaille bbc6df78ae refactor(sdk): Put ruma-federation-api dependency behind a feature
In theory clients shouldn't make requests to the server-server API. A
way to work around it for this specific case would be to implement
MSC4383.

In the meantime, clients that don't want to use
`Client::server_vendor_info()` won't have to build the extra
dependencies added by ruma-federation-api.

The feature is enabled for the bindings, so it isn't a breaking change
for matrix-sdk-ffi.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-12-02 11:04:49 +01:00
Mauro 9f02dcd412 ffi(bindings): added is_space to the NotificationRoomInfo (#5907)
Exposes the `is_space` flag to FFI in the `NotificationRoomInfo`, so
that a client can tell through a notification if the room that generated
it, is a space or not.
2025-12-02 09:31:53 +00:00
Ivan Enderlin ab98028a2e feat(sdk): An edit can be a LatestEventValue if it targets the immediate previous event.
This patch changes the rule of what is a `LatestEventValue` candidate
in case of an edit. An edit must target/relate to its immediate previous
event to be a candidate. Otherwise it's easy to edit an old message
and create a “broken” `LatestEventValue` because it points to an older
message that the user may not be able to find easily.
2025-12-01 16:28:24 +01:00
Ivan Enderlin 7c7cbb2566 feat(sdk,ui): Support edits as LatestEventValue.
This patch supports any edits at a possible `LatestEventValue`
candidate.
2025-12-01 16:28:24 +01:00
Ivan Enderlin 32b4bbc1b0 test(ui): Use the EventFactory. 2025-12-01 16:28:24 +01:00
Kévin Commaille e47867f232 refactor(sdk): Split supported versions and well-known cache
The supported versions are necessary for querying almost all endpoints,
but after homeserver auto-discovery the well-known info is only
necessary to get the MatrixRTC foci advertised by the homeserver. So it
shouldn't be necessary to always request both at the same time.

Besides:

- Not all clients support MatrixRTC, so they don't need the well-known
  info.
- The well-known info is only supposed to be used for homeserver
  auto-discovery before login. In fact, the MatrixRTC MSC was changed to
  use a new endpoint for this.
- We don't have access to the server name after restoring the Client, so
  the well-known lookup is more likely to fail.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-12-01 15:22:48 +00:00
Kévin Commaille 4411274b12 refactor(base): Split TTL store logic from ServerInfo into new type
To make it reusable.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-12-01 15:22:48 +00:00
Kévin Commaille 32b72580da Commit changed Cargo.lock
This seems to come from a previous commit?

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-12-01 15:22:48 +00:00
Ivan Enderlin 73449f4f57 doc(sdk): Add #5908 in the CHANGELOG.md. 2025-11-27 17:23:47 +01:00
Ivan Enderlin bbe35e8190 fix(sdk): A new local LatestEventValue can be “cannot be sent”.
This patch fixes a bug where a new local `LatestEventValue`
was always created as `LocalIsSending`. It must be created as
`LocalCannotBeSent` if a previous local `LatestEventValue` exists and is
`LocalCannotBeSent`.

This patch adds the companion test too.
2025-11-27 17:23:47 +01:00
Marcel-Nordeck 107fc07d08 Wasm improvements for the bindings
This patch improves the Wasm support of the matrix-sdk-ffi crate.

First a uniffi feature needed to be enabled.
Secondly a bunch of methods which don't work under Wasm have been stubbed out.

Signed-off-by: MTRNord <MTRNord@users.noreply.github.com>
Co-authored-by: MTRNord <MTRNord@users.noreply.github.com>
2025-11-27 15:46:33 +01:00
Damir Jelić 4585d5f4d8 docs(search): Remove the example depending on the matrix-sdk crate
Examples are great, but the circular dependency this introduces is not
worth the trouble.
2025-11-27 14:34:35 +01:00
Damir Jelić 239203a813 fix: The search crate doesn't actually depend on the main crate
This removes a circular dependency we had resulting in a semi-broken
release process.
2025-11-27 14:34:35 +01:00
Damir Jelić 24d7518a01 chore: Allow release branches for cargo release as well 2025-11-27 11:10:32 +01:00
Damir Jelić e6059251d0 Merge pull request #5901 from matrix-org/poljar/release-0.15.0 2025-11-27 10:39:45 +01:00
Damir Jelić f4fef6e995 chore: Fix the dates of the 0.15.0 release 2025-11-27 10:07:41 +01:00
Damir Jelić 850b7dde6d chore: Release matrix-sdk version 0.15.0 2025-11-26 15:44:26 +01:00
Damir Jelić 700c17f383 release: Allow release preparation to work on the HEAD
This is to allow Jujutsu users to use the cargo-release tooling.
2025-11-26 15:38:29 +01:00
Doug 9e842a5d07 spaces: Add support for getting a flattened list of editable spaces. 2025-11-26 13:06:49 +01:00
Doug 18175c1cd0 chore: Use a common add_space_rooms function for tests. 2025-11-26 13:06:49 +01:00
dependabot[bot] 100a04ae2c chore(deps): bump CodSpeedHQ/action from 4.3.3 to 4.4.1
Bumps [CodSpeedHQ/action](https://github.com/codspeedhq/action) from 4.3.3 to 4.4.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/bb005fe1c1eea036d3894f02c049cb6b154a1c27...346a2d8a8d9d38909abd0bc3d23f773110f076ad)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-26 10:30:10 +01:00
dependabot[bot] 3a655083d6 chore(deps): bump bnjbvr/cargo-machete
Bumps [bnjbvr/cargo-machete](https://github.com/bnjbvr/cargo-machete) from 04b9adbd8c1c00963289b5628510dd907b27dc60 to 10aef304cba9ef99dacee57a756c14892391cdca.
- [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/04b9adbd8c1c00963289b5628510dd907b27dc60...10aef304cba9ef99dacee57a756c14892391cdca)

---
updated-dependencies:
- dependency-name: bnjbvr/cargo-machete
  dependency-version: 10aef304cba9ef99dacee57a756c14892391cdca
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-26 09:51:58 +01:00
dependabot[bot] 46947be662 chore(deps): bump crate-ci/typos from 1.39.0 to 1.39.2
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.39.0 to 1.39.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.39.0...v1.39.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-26 09:51:05 +01:00
Johannes Marbach fccafd8c80 feat(oauth): expose session expiration errors when requesting login with a QR code
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-11-26 09:50:11 +01:00
Johannes Marbach 1e30d5f0b0 feat(oauth): expose session expiration errors when granting login with a QR code
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-11-26 09:50:11 +01:00
Johannes Marbach 4ab12543ce feat(testing): allow specifying expiration duration in MockedRendezvousServer
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-11-26 09:50:11 +01:00
Johannes Marbach a82ccf1069 fix(oauth): expose client API errors when receiving messages on rendezvous channel
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-11-26 09:50:11 +01:00
Ivan Enderlin 45a9d96573 chore(sdk): Remove an unwrap in debug_string. 2025-11-25 15:41:27 +01:00
Ivan Enderlin c9324b2f30 refactor(sdk): Change a Semaphore(permit=1) for a Mutex.
This patch changes the `Semaphore(permit=1)` for a `Mutex`: the
semantics is strictly equivalent, but it removes the need to guarantee
there is a single permit.
2025-11-25 15:41:27 +01:00
Ivan Enderlin 8e93bb5373 chore: Replace unwrap by expect. 2025-11-25 15:41:27 +01:00
Ivan Enderlin 8df55fa3e7 test(sdk): Add a test for dirtiness handling in RoomEventCacheStateLock::new. 2025-11-25 15:41:27 +01:00
Ivan Enderlin 478df4af33 test(sdk): Ensure EventCacheStoreLockGuard::clear_dirty is called!
This patch ensures that the `EventCacheStoreLockGuard::clear_dirty`
method is correctly called.
2025-11-25 15:41:27 +01:00
Ivan Enderlin 04fdf7f2f6 feat(sdk): Send updates when RoomEventCacheStateLock is reloaded.
This patch updates the reloading of `RoomEventCacheStateLock`
when the cross-process lock over the store is dirty to broadcast
`RoomEventCacheUpdate` and `RoomEventCacheGenericUpdate`. That way the
`Timeline` and other components can react to this reload.
2025-11-25 15:41:27 +01:00
Ivan Enderlin 179136a9a4 refactor(sdk): Rename RoomEventCacheInner::sender to update_sender.
This patch renames the `sender` field of `RoomEventCacheInner` to
`update_sender` to clarify what the sender is about.
2025-11-25 15:41:27 +01:00
Ivan Enderlin e51996a47c test(sdk): Add the test_reset_when_dirty test.
This patch adds the new `test_reset_when_dirty` test, which ensures
the state is correctly reset when the cross-process lock over the store
becomes dirty.
2025-11-25 15:41:27 +01:00
Ivan Enderlin 12e5614fc8 feat(sdk): Allow shared access on RoomEventCacheStateLock::read.
This patch fixes a problem found in a test (not commited yet) where it
was impossible to do multiple calls to `read` if the first guard was
still alive. See the comments to learn more.
2025-11-25 15:41:27 +01:00
Ivan Enderlin 14d550739a feat(sdk): Implement RoomEventCacheStateLockWriteGuard::downgrade.
This patch implements `RoomEventCacheStateLockWriteGuard::downgrade` to
simplify the code a little bit.
2025-11-25 15:41:27 +01:00
Ivan Enderlin fbcd8ef546 test(common): Make tests run faster.
This patch replaces `sleep` by `yield_now`, which has the same effect in
this case, and makes the tests run faster.
2025-11-25 15:41:27 +01:00
Ivan Enderlin e5f6153f54 test(common): Test dirtiness of the cross-process lock. 2025-11-25 15:41:27 +01:00
Ivan Enderlin badba6eebc fix(sdk): Remove a warning for wasm32. 2025-11-25 15:41:27 +01:00
Ivan Enderlin 72f2296809 doc(sdk): Add missing or fix documentation. 2025-11-25 15:41:27 +01:00
Ivan Enderlin b1af16ef09 feat(sdk): Reset RoomEventCacheState when the cross-process lock is dirty.
This patch updates the `RoomEventCacheStateLock::read` and `write`
methods to reset the state when the cross-process lock is dirty.
2025-11-25 15:41:27 +01:00
Ivan Enderlin 1cf0601ba3 refactor(sdk) Introduce RoomEventCacheStateLock and read/write guards.
This patch extracts fields from `RoomEventCacheState` and move them
into `RoomEventCacheStateLock`. This lock provides 2 methods: `read`
and `write`, respectively to acquire a read-only lock, and a write-only
lock, represented by the `RoomEventCacheStateLockReadGuard` and the
`RoomEventCacheStateLockWriteGuard` types.

All “public” methods on `RoomEventCacheState` now are facade to the read
and write guards.

This refactoring makes the code to compile with the last change in
`EventCacheStore::lock`, which now returns a `EventCacheStoreLockState`.
The next step is to re-load `RoomEventCacheStateLock` when the lock is
dirty! But before doing that, we need this new mechanism to centralise
the management of the store lock.
2025-11-25 15:41:27 +01:00
Ivan Enderlin e034a51b7b test(sdk): Update to use EventCacheStoreLockState. 2025-11-25 15:41:27 +01:00
Ivan Enderlin 9e6a6c0e71 fix(base): Use the EventCacheStoreLockState. 2025-11-25 15:41:27 +01:00
Ivan Enderlin d1633f2a78 feat(base): EventCacheStoreLockGuard can be cloned.
This patch implements `Clone` for `EventCacheStoreLockGuard`.
2025-11-25 15:41:27 +01:00
Ivan Enderlin 4dbee471ac feat(common): CrossProcessLockGuard can be cloned.
This patch implements `Clone` for `CrossProcessLockGuard`.
2025-11-25 15:41:27 +01:00
Ivan Enderlin 997f992d15 refactor(base): EventCacheStoreLockState owns a clone of the inner store.
This patch changes `EventCacheStoreLockState` to own a clone of
the inner store. It helps to remove the `'a` lifetime, and so it
“disconnects” from the lifetime of the store.
2025-11-25 15:41:27 +01:00
Ivan Enderlin 3d5b32494e feat(base): Add EventCacheStoreLockGuard::clear_dirty. 2025-11-25 15:41:27 +01:00
Ivan Enderlin c5893f882c feat(common): Add CrossProcessLockGuard::is_dirty and ::clear_dirty.
This patch replicates the `is_dirty` and `clear_dirty` methods from
`CrossProcessLock` to `CrossProcessLockGuard`. It allows to get an
access to this API from a guard when one doesn't have the cross-process
lock at hand.
2025-11-25 15:41:27 +01:00
Ivan Enderlin 68e8866bcf chore(sdk): Clean up imports. 2025-11-25 15:41:27 +01:00
Ivan Enderlin c98d9db185 feat(base) Create the EventCacheStoreLockState type.
This patch updates `EventCacheStoreLock::lock()` to return an
`EventCacheStoreLockState` instead of an `EventCacheStoreLockGuard`, so
that the caller has to handle dirty locks.
2025-11-25 15:41:27 +01:00
Ivan Enderlin cee2b1bebf feat(common): Add CrossProcessLockState::map.
This patch adds the `CrossProcessLockState::map` method along with its
companion `MappedCrossProcessLockState` type. The idea is to facilitate
the creation of custom `CrossProcessLockState`-like type in various
usage of the cross-process lock.
2025-11-25 15:41:27 +01:00
Ivan Enderlin 19a96b41df feat(common) Add #[must_use] on CrossProcessLockGuard and *State.
This patch adds a `#[must_use]` attribute on `CrossProcessLockGuard` and
`CrossProcessLockState` to avoid a misuse.
2025-11-25 15:41:27 +01:00
Ivan Enderlin 80decaebf4 chore(common) Rename CrossProcessLockKind to CrossProcessLockState.
This patch renames the `CrossProcessLockKind` type to
`CrossProcessLockState`.
2025-11-25 15:41:27 +01:00
Jorge Martín 20ee85bd0f fix: Remove unnecessary options from sentry::ClientOptions 2025-11-25 14:19:49 +01:00
Jorge Martín 813c5fc9f9 misc: Bump Sentry SDK to v0.46.0 2025-11-25 14:19:49 +01:00
Jorge Martín a349b8e753 misc: Add support for bridge spans
These will use `bridge_trace_id` to map an exising client transaction/span to this one so they'll be displayed as a single one in Sentry.

This is done through the `sentry.trace` field, which will be used by `sentry-tracing` to differentiate these kinds of special spans.

The special fields need to be added on the Span creation, that's why we do it in the constructor instead of just using `span.record(...)` later.
2025-11-25 14:19:49 +01:00
Damir Jelić ec44c74d53 ci: Generate and upload junit files for the integration tests 2025-11-25 11:22:55 +01:00
dependabot[bot] 7475f03b13 chore(deps): bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-25 10:48:12 +01:00
Ivan Enderlin f13dc4b070 doc: Add AI policy.
This patch adds AI policy. For the moment, it's a copy-paste (modulo
emphasises) from Forgejo's.
2025-11-25 10:34:11 +01:00
Ivan Enderlin 9757ff54ba doc: Format and fix the CONTRIBUTING.md document.
This patch formats the `CONTRIBUTING.md` file, plus it fixes some links,
lists, block of code etc.
2025-11-25 10:34:11 +01:00
Doug aa79e34794 chore: Add forgotten tests for removing space child.
Make sure to also check the Option inside the Result when looking for the event.
2025-11-24 17:37:33 +02:00
Doug d5f09dffaa spaces: Fix an incorrect early return introduced at the last minute. 2025-11-24 17:37:33 +02:00
Johannes Marbach ae9070815c fix(oauth): use ruma::time::instant for wasm compatibility
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-11-24 14:11:57 +01:00
Doug f49c588ade spaces: Add a method to get the joined parents of a given child. 2025-11-24 14:57:21 +02:00
Doug 8e0dba641d spaces: Add methods to add/remove space children. 2025-11-24 14:57:21 +02:00
Damir Jelić 2f7d2b3b9b chore: Bump our sentry-tracing deps 2025-11-21 17:00:44 +01:00
Ivan Enderlin d228bde8ef doc(ui): Merge a duplicated Refactor Section. 2025-11-21 16:17:09 +01:00
Ivan Enderlin 83a7d591bd doc(ui,ffi): Update CHANGELOG.md. 2025-11-21 16:17:09 +01:00
Ivan Enderlin 247bb4960e feat(ui,ffi): Add LatestEventValue::Local { sender, .. }.
This patch adds a `sender: OwnedUserId` field to
`LatestEventValue::Local` in `matrix_sdk_ui::timeline` (and the
corresponding `matrix_sdk_ffi` type).
2025-11-21 16:17:09 +01:00
Ivan Enderlin 83f9d74626 feat(ui,ffi): Add LatestEventValue::Local { profile, .. }.
This patch adds a `profile: TimelineDetails<Profile>` field to
`LatestEventValue::Local` in `matrix_sdk_ui::timeline` (and the
corresponding `matrix_sdk_ffi` type).
2025-11-21 16:17:09 +01:00
Damir Jelić eed5f11f26 Merge pull request #5881 from matrix-org/poljar/event-cache/fix-race-condition
Fix a race condition in the redecryptor leading to missed decryption attempts
2025-11-21 15:22:00 +01:00
Damir Jelić 75a977cc47 ci: Free up disk space for the benchmark jobs as well 2025-11-21 14:18:22 +01:00
Damir Jelić 5b396d0b0d chore: Add a link to the github issue for why async-stream isn't bumped 2025-11-21 14:18:22 +01:00
Damir Jelić 7de210a88f chore: Update the deny.toml file
The adler crate is no longer in our tree, it has been replaced by the
adler2 crate.
2025-11-21 14:18:22 +01:00
Damir Jelić 127154fcfa chore: Bump our deps and update the Cargo.lock file 2025-11-21 14:18:22 +01:00
Kévin Commaille 0e46732ede Add changelog
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-11-20 16:44:54 +00:00
Kévin Commaille 1352bd74d6 Upgrade Ruma
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-11-20 16:44:54 +00:00
Ivan Enderlin 762135ba22 doc(ui): Improve documentation of RoomListLoadingState.
This patch adds intra-links and clarifies a sentence about the room
type.
2025-11-20 14:40:48 +01:00
Damir Jelić 7a1fadddc3 doc(redecryptor): Document that we're listening to the event cache as well 2025-11-20 13:31:58 +01:00
Damir Jelić f343c98b63 fix(redecryptor): Fix race a condition where events might not be redecrypted
This patch fixes a race condition where events won't get decrypted
because a room key arrives after the initial decryption attempt but
before the UTD has been persisted in the event cache.

The fix is relatively straightforward, we'd need a synchronization point
for the two different tasks, the event cache which adds events and the
redecryptor which listens to room keys to decrypt events.

A lock could have been used, so the storing and redecrypting of events
becomes synchronized via the storage layer. This approach could have
degraded performance since the event cache needs to handle a lot of
events.

The approach that was chosen here is to let the redecryptor listen to
updates coming from the event cache itself. If the event cache tells us
that it persisted a UTD, we will attempt to decrypt. Upon a successful
decryption we will replace the event in the cache as well.
2025-11-20 13:27:58 +01:00
Damir Jelić fd4821c3ec refactor(redecryptor): Make the filter closure a function 2025-11-20 13:27:13 +01:00
Damir Jelić 5dea64b0ef feat(linked-chunk): Add method to get the items of an Update
This patch adds a convenience function for the Update enum. If one only
cares about the items contained in the Update, then they can chose to
use this method to extract them out of the enum.
2025-11-20 13:26:23 +01:00
Damir Jelić 2388acaf33 test(redecryptor): Add a test confirming a race condition in the redecryptor
This patch adds a test confirming that the redecryptor has a race
condition.

Namely, events and room keys are received over two different sync
streams from the homeserver. When events are received over the sync, we
first try to decrypt them, this might fail because the room key hasn't
yet arrived over the other sync stream. The event cache will then
persist the event as a UTD.

At the same time, the redecryptor will listen to room keys that arrive
on the other sync stream. Once the redecryptor gets notified about a
room key, it will attempt to fetch the event from the event cache to
decrypt the event and replace it.

Crucially if the key arrives before the event gets persisted but after
the initial decryption attempt we might never attempt to redecrypt such
an event.
2025-11-20 13:20:12 +01:00
Damir Jelić 91bc1ef28f test(redecryptor): Factor out common code in the redecryptor tests 2025-11-20 13:19:49 +01:00
Richard van der Hoff b1eaa5edca sdk: improve logging for received history bundles
We had an instance where a user joined a room on Element X but did not download
the key bundle, so let's add some logging to help figure out what was going on.
2025-11-19 11:38:58 +01:00
Doug 0b5e1fb9c5 xtask: Add support for building watchOS targets. 2025-11-19 11:36:53 +01:00
Ivan Enderlin 2eb4323fe1 test(ui): Test long-polling in RoomListService.
This patch tests whether long-polling is used for Sliding Sync requests
made by `RoomListService`.
2025-11-19 10:39:11 +01:00
Ivan Enderlin db806f6b8d test(ui): Simplify macro usage.
This patch declares the type of the expected value for `assert pos`.
2025-11-19 10:39:11 +01:00
Ivan Enderlin 64a51af18d feat(ui): Manually define when to do long-polling in the RoomListService.
This patch uses the newly introduced
`SlidingSyncListBuilder::requires_timeout` to define when the
`RoomListService` must apply a long-polling depending on its state
machine.
2025-11-19 10:39:11 +01:00
Ivan Enderlin da52532b60 feat(sdk): Add SlidingSyncListBuilder::requires_timeout.
This patch adds a new `SlidingSyncListBuilder::requires_timeout` method
that takes a function deciding whether the list requires a timeout, i.e.
if the list should trigger a `http::Request::timeout`, i.e. if it
deserves a long-polling or not.

The default behaviour is kept for compatibility purposes.
2025-11-19 10:39:11 +01:00
Ivan Enderlin f846eea7a3 doc(sdk): Update outdated documentation of SlidingSyncList::set_sync_mode.
This patch updates the documentation of `SlidingSyncList::set_sync_mode`
to remove an outdated reference to a `reset` method that no longer
exists.
2025-11-19 10:39:11 +01:00
Ivan Enderlin 475db3e640 refactor(sdk) Change RwLock<Observable> to SharedObservable.
This patch updates `SlidingSyncListInner::state` from a
`RwLock<Observable>` to a `SharedObservable`. It is semantically and
programmatically identical, but the API is simpler.
2025-11-19 10:39:11 +01:00
Damir Jelić efe511e5e8 Merge pull request #5869 from matrix-org/poljar/event-cache/remove-timeline-redecrypion-logic 2025-11-19 10:31:32 +01:00
Damir Jelić 4ae82dd634 feat(bindings): Allow user identities to only be fetched from storage 2025-11-19 09:42:26 +01:00
Jorge Martin Espinosa d860749f95 Revert "doc: Add warnings about overriding the server URL"
This reverts commit 95d8ba94e1.
2025-11-18 15:58:19 +01:00
Jorge Martin Espinosa 012a9825a4 Revert "refactor(ffi): Remove unused Session::homeserver_url value"
This reverts commit 4eb3cc9812.
2025-11-18 15:58:19 +01:00
Jorge Martín 1c22d0b25b doc: add changelogs 2025-11-18 12:26:30 +01:00
Jorge Martín be86fe4aa9 doc: Improve doc comments
Also move `EventMeta::thread_root_id` next to `event_id`
2025-11-18 12:26:30 +01:00
Jorge Martín 385f7aa86d doc: Fix docs for ffi::Timeline::latest_event_id 2025-11-18 12:26:30 +01:00
Jorge Martín 5f996f77c6 reafactor(ffi): Have ffi::Timeline::latest_event_id use ui::Timeline::latest_event_id, instead of ui::Timeline::latest_event
This is important because `latest_event` would also return local events, which won't have an event id.
2025-11-18 12:26:30 +01:00
Jorge Martín 02491fc6ec test: Add test for TimelineController::latest_event_id 2025-11-18 12:26:30 +01:00
Jorge Martín 0f62ff991d fix clippy 2025-11-18 12:26:30 +01:00
Jorge Martín 6b245264e1 fix(test): Fix broken test locally: it was using a previous cached value before 2025-11-18 12:26:30 +01:00
Jorge Martín f7b92c84e7 fix(ui): Sending read receipt in live timeline when latest event is in a thread
Previously, this used the latest event in the thread as the event to mark as read, while this is not right if we're in a context that hides thread events
2025-11-18 12:26:30 +01:00
Jorge Martín 4eb3cc9812 refactor(ffi): Remove unused Session::homeserver_url value 2025-11-18 12:16:28 +01:00
Jorge Martín 95d8ba94e1 doc: Add warnings about overriding the server URL
This may be dangerous when done while restoring an existing session.
2025-11-18 12:16:28 +01:00
Andy Balaam ca436016b4 base: Bump ruma to 91424b1fc
And update to reflect the new feature name unstable-msc4362, which
provides the new unstable prefix io.element.msc4362.encrypt_state_events
2025-11-18 11:10:55 +00:00
Andy Balaam 5b82550199 crypto: Wait for a stream in state encryption test
This was sometimes failing for me locally, so use a macro that expects a
value from a stream soon, rather than immediately.
2025-11-18 11:10:55 +00:00
Andy Balaam 5d396e4795 crypto: Refer to MSC4362 when we are talking about encrypted state 2025-11-17 09:40:47 +02:00
Damir Jelić e9c8f101d6 chore: Remove the various redecrytion tasks 2025-11-14 12:54:00 +01:00
Damir Jelić 6e97607c2d refactor(timeline): Replace the various decryption tasks with one R2D2 task 2025-11-14 12:54:00 +01:00
Damir Jelić 4e71b7c351 feat(ui): Create a task to listen to redecryptor reports in the timeline
This task is still necessary because the redecryptor in the event cache
might miss some room keys.

In this case the timeline can tell the redecryptor which events it
should retry to decrypt.

We're collecting all the UTDs in the timeline and telling the
redecryptor to do its best.
2025-11-14 12:52:44 +01:00
Richard van der Hoff 9ab886fa2b crypto: Merge inbound Megolm sessions [#5865]
When we receive two copies of the same inbound Megolm session from two sources, merge them together intelligently.

Fixes: #5108, #4698
2025-11-13 19:06:44 +00:00
Richard van der Hoff 60072b3456 Integ test for merging megolm sessions with history sharing
Add an integration test that checks that, when we receive a copy of a megolm
session directly after having previously received it via history sharing, we
get the best bits of both.
2025-11-13 18:37:18 +00:00
Richard van der Hoff 822b1c9787 crypto: replace uses of compare_group_session
... with `merge_received_group_session`.

`merge_received_group_session` expands the logic of `compare_group_session` to
handle the fact that there is more than one axis of "better" or "worse" and we
may need to take the best bits of two copies of the session.
2025-11-13 18:37:18 +00:00
Richard van der Hoff 52344fad77 crypto: Add new method Store::merge_received_group_session
Add a method which can be used to merge a received `InboundGroupSession` into
whatever we find in the store.
2025-11-13 18:37:18 +00:00
Damir Jelić e0427767aa refactor(timeline): Use the event cache to request redecryption 2025-11-13 16:57:36 +01:00
Damir Jelić 927c82f97a refactor(timeilne): Add a method to compute redecryption candidates 2025-11-13 16:56:36 +01:00
Richard van der Hoff 97ba0b1bbb crypto: factor out InboundGroupSession.compare_ratchet
In order to correctly merge sessions, we need more granular comparisons between
two sessions than just "Better" or "Worse", so factor out a method that *just*
looks at the ratchet states.
2025-11-13 13:51:25 +00:00
JoFrost 17df3f84d0 feat(ffi): expose join_rules in OtherState::RoomJoinRules (#5863)
Expose the room join rules in the `OtherState::RoomJoinRules` event 
for the FFI timeline.

It reuses the existing `JoinRules` type from the client module and
converts the event content accordingly. This allows clients to inspect
the room’s current join rule directly from the event. Like `m.federate`,
this field was previously unavailable in the FFI variant of the SDK.

---------

Signed-off-by: JoFrost <20685007+JoFrost@users.noreply.github.com>
2025-11-13 15:20:17 +02:00
Damir Jelić 4fbc83af44 Merge pull request #5746 from matrix-org/poljar/event-cache/redecryptor 2025-11-13 12:19:51 +01:00
Damir Jelić 9508675aca fix(redecryptor): Early return if we don't have any events to process 2025-11-13 11:59:21 +01:00
Damir Jelić 0d08ed0758 refactor(redecryptor): Add some type aliases for the event ID/event tuples 2025-11-13 11:59:21 +01:00
Damir Jelić 913ebe9fa9 docs(redecryptor): Clarify that we're talking about the UI timeline in the r2d2 docs 2025-11-13 11:59:05 +01:00
Jorge Martin Espinosa f702364fe9 feat(sdk): Add a power level value field for StateEventType::SpaceChild (#5857)
Closes https://github.com/matrix-org/matrix-rust-sdk/issues/5839

Co-authored-by: Stefan Ceriu <stefanc@matrix.org>
2025-11-13 07:17:57 +00:00
Jonas Platte 1db4a4cb9a Use MSRV-aware resolver 2025-11-12 14:58:30 +01:00
Damir Jelić 2e9e9aedd7 chore(redecryptor): Ensure the upgrade_event_cache method is inlined 2025-11-12 13:31:37 +01:00
Damir Jelić 38df621b8a chore(event-cache): Limit the visibility of post_process_new_events 2025-11-12 13:31:37 +01:00
Damir Jelić f9c23b3612 refactor(redecryptor): Use an abort handle to manage the redecryption task 2025-11-12 13:31:37 +01:00
Damir Jelić 952c5af07c chore(redecryptor): Time how long it takes to replace UTDs 2025-11-12 12:56:21 +01:00
Damir Jelić 717f016f21 docs(redecryptor): Add some docs to the Redecryptor struct itself 2025-11-12 12:55:58 +01:00
Damir Jelić 3ad70623bb chore(redecryptor): Use relative imports more often 2025-11-12 12:55:19 +01:00
Damir Jelić 84a21a42d0 fix(event-cache): Don't hold on to the event cache locks as long when fetching events 2025-11-12 12:54:32 +01:00
Damir Jelić d2eab603c1 fix(event-cache): Limit the visibility of room_linked_chunk_mut a bit better 2025-11-12 12:51:35 +01:00
Andy Balaam 8883b9db5a Improve the wording of error messages when redecryption fails
The previous message implied that we had received a session for this
message, but that is only one of the several reasons we might encounter
this situation. If redecryption failed, it is more likely we got here
because we'd been asked to attempt redecryption for all UTDs e.g. when
we build a new timeline.

Additionally, having similar wording for the error case and the unable
to decrypt case could also cause confusion, so I adjusted the wording to
make clear which situation is happening.
2025-11-12 11:47:55 +00:00
Ivan Enderlin a3424a7c4a feat(base): Explicitly handle the CrossProcessLockKind::Dirty case in MediaStore.
This patch replaces the `into_guard()` call by a `match` over
`CrossProcessLockKind` so that the `Dirty` case is explicitly handled.

The mid-term idea is to remove the `into_guard()` method because it
is “dangerous” as it hides the `Dirty` case.
2025-11-11 15:12:27 +01:00
Ivan Enderlin fa3ca980e9 doc(common): Explain how to clear a dirty cross-process lock. 2025-11-11 15:12:27 +01:00
Jorge Martín cbd4722dcb doc: Add changelog entry 2025-11-11 14:50:00 +01:00
Jorge Martín a22caa32c0 misc: Add better default target-feature values for Android in ARM64 devices 2025-11-11 14:50:00 +01:00
Damir Jelić f61ba4f47c fix(ui): Don't do a authenticated /versions call when building the roomlist service 2025-11-11 14:37:10 +01:00
Damir Jelić 9a3857d3a7 feat(client): Add a method to only get the server versions from the cache 2025-11-11 14:37:10 +01:00
Ivan Enderlin e79f832160 fix(ui): Undo an optimisation to start at SettingUp.
This patch undo an optimisation that was initialising the
`RoomListService` at the `SettingUp` state if a `pos` value was
recovered successfully (see bbf9bf2c0b).
The problem is that it starts with a range of 0..99 instead of 0..19,
which can slow things done in particular cases. Whilst a good idea on
paper, it's not in practise. So let's continue to recover the `pos`, but
let's keep starting at the `Init` state.
2025-11-11 13:12:02 +01:00
Ivan Enderlin 46d05d877b fix(base): Remove a panic in a log.
We must not panic if the event has no event ID.
2025-11-11 11:15:59 +01:00
Ivan Enderlin 610f82aeb2 chore(sqlite): Remove connection::Config.
This patch removes the `connection::Config` type. It was “inspired”
from `deadpool_sqlite`, but we can clearly remove it by using our own
`SqliteStoreConfig` type. It simplifies the way we open a database.
2025-11-11 11:09:18 +01:00
Ivan Enderlin 60490f4eff doc(sqlite): Add documentation to connection.
This patch explains why we create our own implementation of `deadpool`
for `rusqlite`.
2025-11-11 11:09:18 +01:00
Ivan Enderlin 6a828e31dd feat(sqlite): Replace deadpool-sqlite by our own implementation.
This patch replaces `deadpool-sqlite` by our own implementation in
`crate::connection`. It still uses `deadpool` but the object manager has
a different implementation.
2025-11-11 11:09:18 +01:00
dependabot[bot] fff270d997 chore(deps): bump CodSpeedHQ/action from 4.3.1 to 4.3.3
Bumps [CodSpeedHQ/action](https://github.com/codspeedhq/action) from 4.3.1 to 4.3.3.
- [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/4348f634fa7309fe23aac9502e88b999ec90a164...bb005fe1c1eea036d3894f02c049cb6b154a1c27)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-11 09:19:04 +01:00
Johannes Marbach a50ecb5b18 refactor(oauth): remove superfluous join in QR login tests
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-11-11 09:18:16 +01:00
Ivan Enderlin 18654444b6 doc(sdk): Remove a dead reference in the doc.
This patch removes the reference to `Update`, that is no longer required.
2025-11-11 09:17:34 +01:00
dependabot[bot] 10ff5d0cc6 chore(deps): bump bnjbvr/cargo-machete
Bumps [bnjbvr/cargo-machete](https://github.com/bnjbvr/cargo-machete) from e7d460faa33cbba452e69e8b1700e6a75e8a72b8 to 04b9adbd8c1c00963289b5628510dd907b27dc60.
- [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/e7d460faa33cbba452e69e8b1700e6a75e8a72b8...04b9adbd8c1c00963289b5628510dd907b27dc60)

---
updated-dependencies:
- dependency-name: bnjbvr/cargo-machete
  dependency-version: 04b9adbd8c1c00963289b5628510dd907b27dc60
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-10 18:26:12 +01:00
Johannes Marbach f9584f5b2a feat(ffi): add sender and room information to sync notifications
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-11-08 09:30:11 +01:00
Damir Jelić 66619e9d1d test(oauth): Pass the rendezvous server to the bob task as well
This avoids the scenario where the mock server gets deallocated before
the rendezvous server and thus the rendezvous specific mock guards.

Dropping those in the wrong order will result in a panic.
2025-11-08 09:29:30 +01:00
Damir Jelić 2ea1c42a1a test(oauth): No need to use join in the qrcode login granting tests 2025-11-08 09:29:30 +01:00
Damir Jelić f6ef5fbfd1 chore: Remove a stale TODO item 2025-11-08 09:29:30 +01:00
JoFrost a6062a6cfd feat(ffi): expose m.federate and history visibility in their events (#5830)
Hello, I'm writing on behalf of the Citadel product developed by ERCOM.
This PR expose `m.federate` and `history_visibility` in timeline diffs.
These fields are available in the Matrix SDK but were previously omitted
from the FFI variant.

Signed-off-by: JoFrost <20685007+JoFrost@users.noreply.github.com>
2025-11-07 16:16:17 +01:00
Ivan Enderlin 3f3f6c2fc6 refactor(common): Revisit CrossProcessLock::try_lock_once and spin_lock's outputs.
This patch changes the signature of `CrossProcessLock::try_lock_once`.
It was returning a:

```rust
Result<CrossProcessLockResult, CrossProcessLockError>
```

Now it returns a:

```rust
Result<Result<CrossProcessLockKind, CrossProcessLockUnobtained>, L::LockError>
```

We will explain these new types in a moment.

This patch also changes the signature of `CrossProcessLock::spin_lock`.
It was returning a:

```rust
Result<CrossProcessLockGuard, CrossProcessLockError>
```

Now it returns a:

```rust
Result<Result<CrossProcessLockKind, CrossProcessLockUnobtained>, L::LockError>
```

First off, we notice that the returned types are now unified. The
`CrossProcessLockResult` type has been renamed `CrossProcessLockKind`
and lives in a `Result::Ok`. The `CrossProcessLockResult::Unobtained`
variant has been removed, but `CrossProcessLockUnobtainedReason`
has been renamed to `CrossProcessLockUnobtained` and lives in a
`Result::Err`.

Second, the `CrossProcessLockError` now is a union type between
`CrossProcessLockUnobtained` and `TryLock::LockError`. It's not used
by `try_lock_once` or `spin_lock`, but only by the code using the
cross-process lock to provide a unified error type.

The ideas behind these changes are:

- it's easy to forward an error from the `TryLock`,
- it's difficult to ignore the `Clean` vs. `Dirty` state of the lock
  guard,
- unified API with clearly separated responsibility (the first `Result`
  vs. the second `Result`).

Note: the `CrossProcessLockKind::into_guard` method aims at being
removed. It's useful now to maintain compatibility but it's “dangerous”
as it makes trivial to skip `Clean` vs. `Dirty` states. We ultimately
don't want that.
2025-11-07 16:13:03 +01:00
Damir Jelić d7d4730b21 docs(redecryptor): Document the redecryptor a bit more 2025-11-07 15:38:35 +01:00
Damir Jelić 4c4cd41457 test(timeline): Workarounds to get the timeline tests passing
This is necessary because both the timeline and the event cache attempt
to redecrypt events currently.

This will change once only the event cache handles this task.
2025-11-07 15:38:35 +01:00
Damir Jelić 4a519bd547 test(redecryptor): More tests for the redecryptor 2025-11-07 15:38:35 +01:00
Damir Jelić 4109fddc97 feat(redecryptor): Post-process the events once they are replaced 2025-11-07 15:38:35 +01:00
Damir Jelić 7e98858815 feat(redecryptor): More precise logs for the redecryption attempts 2025-11-07 15:38:35 +01:00
Damir Jelić 3a0a5b9888 feat(redecryptor): Use the room to redecrypt events
This allows us to properly calculate the push actions.
2025-11-07 15:38:35 +01:00
Damir Jelić 621d936b4c feat(redecryptor): Let the redecryptor listen to room key withheld updates 2025-11-07 15:38:35 +01:00
Damir Jelić a2f89e85b9 feat: Redecryptor start to send out redecryptor reports 2025-11-07 15:38:35 +01:00
Damir Jelić 4ed239351a feat(event cache): Enable the redecryptor in the event cache 2025-11-07 15:38:35 +01:00
Damir Jelić 5c3bca86a4 doc(event cache): Document the redecryptor 2025-11-07 15:38:35 +01:00
Damir Jelić e934235045 feat(redecryptor): Rejigger things so we can relisten to the room key stream 2025-11-07 15:38:35 +01:00
Damir Jelić f2cc6c650a test(redecryptor): Add a test to show that the redecryptor works 2025-11-07 15:38:35 +01:00
Damir Jelić 8103b9cc23 feat(event cache): Create the redecryptor 2025-11-07 15:29:07 +01:00
Damir Jelić d3c839a2d0 feat(event cache): Add a method to access the linked chunk mutably 2025-11-07 15:14:42 +01:00
Ivan Enderlin 3b1418463b doc(common): Fix a link in the CHANGELOG.md. 2025-11-07 11:26:09 +01:00
Ivan Enderlin 9f248affa9 doc(common): Update CHANGELOG.md. 2025-11-07 11:26:09 +01:00
Ivan Enderlin edf7604d30 feat(common): Detect when the cross-process lock has been dirtied.
This patch detects when the cross-process lock has been dirtied.

A new `CrossProcessLockResult` enum is introduced to simplify the
returned value of `try_lock_once` and `spin_lock`. It flattens the
previous `Result<Option<_>>` by providing 3 variants: `Clean`, `Dirty`
and `Unobtained`.
2025-11-07 11:26:09 +01:00
Ivan Enderlin f7a767ce97 feat(indexeddb): Add Lease::generation in crypto, media, and event cache stores.
This patch adds `Lease::generation` support in the crypto, media and
event cache stores.

For the crypto store, we add the new `lease_locks` object store/table.
Previously, `Lease` was stored in `core`, but without any prefix, it's
easy to overwrite another records, it's dangerous. The sad thing is
that it's hard to delete the existing leases in `core` because the keys
aren't known. See the comment in the code explaining the tradeoff.

For media and event cache stores, the already existing `leases` object
store/table is cleared so that we can change the format of `Lease`
easily.
2025-11-07 11:26:09 +01:00
Ivan Enderlin 6c922e69d0 feat(common): Add a cross-process lock generation.
This patch adds `CrossProcessLockGeneration`. A lock generation is an
integer incremented each time the lock is taken by another holder. If
the generation changes, it means the lock is _dirtied_. This _dirtying_
aspect is going to be expanded in the next patches. This patch focuses
on the introduction of this _generation_.

The `CrossProcessLock::try_lock_once` method, and
the `TryLock::try_lock` method, both returns a
`Option<CrossProcessLockGeneration>` instead of a `bool`: `true` is
replaced by `Some(_)`, `false` by `None`.
2025-11-07 11:26:09 +01:00
Ivan Enderlin 01d75e939c chore(indexeddb): Run rustfmt. 2025-11-07 11:26:09 +01:00
Jorge Martín 8b805b1ea5 refactor: Try to avoid filtering all event items before finding one with the wanted id 2025-11-07 10:42:53 +01:00
Jorge Martín a1768ea518 refactor: Add profile cache for handle_remote_event 2025-11-07 10:42:53 +01:00
Johannes Marbach 0b66019632 feat(ffi): add bindings for granting login with a QR code
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-11-06 15:15:59 +01:00
Richard van der Hoff c064ca8b18 Merge pull request #5834 from matrix-org/rav/history_sharing/fix_withheld_utd_cause
crypto: correct UtdCause for unshared historical messages
2025-11-06 11:45:06 +00:00
Jorge Martín fa6d18b55f refactor(sdk): Make the deserialization of the ignored users happen in parallel too 2025-11-06 11:13:23 +01:00
Jorge Martín 17de97e98e refactor(sdk): Fetch member data concurrently
Creating a `RoomMember` takes a lot of store queries, and previously all of them were done sequentially. I've tried to make this process run as much in parallel as I can.
2025-11-06 11:13:23 +01:00
Richard van der Hoff c60f92a917 crypto: correct UtdCause for unshared historical messages
Per https://github.com/element-hq/element-meta/issues/2876, we want messages
where the history was not shared to appear the same as regular "historical"
messages.
2025-11-05 15:08:10 +00:00
Richard van der Hoff 0865e96f08 refactor(crypto): simplify UtdCause logic
I find a single match statement easier to reason about than one nested in another.

Also, import `UnableToDecryptReason::*`, to shorten the match lines.
2025-11-05 15:08:10 +00:00
Richard van der Hoff 8f726e4fb9 test: use a Timeline for shared_history integ tests
I want to be able to test that the correct `UtdCause` is presented for withheld
historical messages. That means we need to use `/sync` rather than `/event` to
obtain the message (since the MSC4115 `membership` field is missing on `/event`
(https://github.com/element-hq/synapse/issues/17486)). So then the most
realistic way to get hold of the actual UtdCause is to use a Timeline.

Of course, the thing I actually want to test doesn't actually work correctly,
so it's left as a FIXME in this commit.
2025-11-05 15:08:10 +00:00
Johannes Marbach b4ebc8bc25 feat(oauth): add flow for granting login by scanning a QR code
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-11-05 13:27:28 +01:00
Johannes Marbach da1369b9c2 refactor(oauth): rename request_login to request_login_with_scanned_qr_code to avoid future name clashes for the opposite flow
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-11-05 13:27:28 +01:00
Johannes Marbach d122f10147 fix(oauth): fix doc comment for GrantLoginWithGeneratedQrCode::subscribe_to_progress
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-11-05 13:27:28 +01:00
Johannes Marbach bcf81c89e9 refactor(oauth): make device creation timeout configurable and use a lower value for tests to speed them up
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-11-05 13:27:28 +01:00
Johannes Marbach d3dd9d28c8 refactor(oauth): extend doc comment of GrantLoginWithQrCodeBuilder::generate for better usability and to match the login flow
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-11-05 13:27:28 +01:00
Johannes Marbach dcd08e8d3b refactor(oauth): move QrProgress to module file for later reuse
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-11-05 13:27:28 +01:00
Johannes Marbach 82c583b5bc feat(ffi): expose Client::register_notification_handler
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-11-05 10:09:31 +01:00
Ivan Enderlin 81ff96d569 fix(sqlite): Fix the database version.
The database has been updated but the version hasn't been bumped.
2025-11-04 14:59:15 +01:00
Damir Jelić 49db60d951 feat: Allow events to be fetched by event type 2025-11-04 13:58:49 +01:00
Damir Jelić 8f4267332a test: Allow to create encrypted events in the event factory 2025-11-04 13:58:49 +01:00
Damir Jelić 950c42742d refactor(sqlite): Save the event type of an event in the SQLite event cache 2025-11-04 13:58:49 +01:00
Damir Jelić f91ffb4c31 feat: Add a method to get the event type of a TimelineEventKind 2025-11-04 13:58:49 +01:00
Richard van der Hoff 301ca5e2b8 Fix up changelogs incorrectly updated since 0.14.0 (#5828)
All of these entries have been incorrectly added to the changelogs
*since* 0.14.0 was released :(
2025-11-04 12:50:49 +00:00
Doug 1a384f0049 xtask: Workaround UniFFI's noHandle generation for Swift.
https://github.com/mozilla/uniffi-rs/issues/2717
2025-11-04 14:11:14 +02:00
Damir Jelić 781df5526d Revert "fix: Allow /versions requests to refresh the token"
This reverts commit 05b40af2c1.
2025-11-04 09:50:37 +01:00
dependabot[bot] ea07d0199a chore(deps): bump crate-ci/typos from 1.38.1 to 1.39.0
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.38.1 to 1.39.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.38.1...v1.39.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-03 15:46:41 +01:00
dependabot[bot] ddfd2fb570 chore(deps): bump bnjbvr/cargo-machete
Bumps [bnjbvr/cargo-machete](https://github.com/bnjbvr/cargo-machete) from 53dce01c203a6a857c9544ebec630a370d596d65 to e7d460faa33cbba452e69e8b1700e6a75e8a72b8.
- [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/53dce01c203a6a857c9544ebec630a370d596d65...e7d460faa33cbba452e69e8b1700e6a75e8a72b8)

---
updated-dependencies:
- dependency-name: bnjbvr/cargo-machete
  dependency-version: e7d460faa33cbba452e69e8b1700e6a75e8a72b8
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-03 15:41:54 +01:00
Richard van der Hoff 3b5f1eee27 Merge pull request #5820 from matrix-org/rav/history_sharing/transitive_withheld_code
crypto: pass on "history_not_shared" withheld notifications
2025-11-03 12:58:04 +00:00
Richard van der Hoff 99ae08ebfe Merge remote-tracking branch 'origin/main' into rav/history_sharing/transitive_withheld_code 2025-11-03 12:15:52 +00:00
Richard van der Hoff 9efb0de4d7 Merge pull request #5819 from matrix-org/rav/cryptostore_withheld_sessions_by_room_id
crypto: add new `CryptoStore` method `get_withheld_sessions_by_room_id`
2025-11-03 12:13:48 +00:00
Damir Jelić 05b40af2c1 fix: Allow /versions requests to refresh the token 2025-10-31 15:58:44 +01:00
Damir Jelić 09ee1375cd fix: Skip authorization headers when doing a /versions while doing a token refresh 2025-10-31 15:58:44 +01:00
Damir Jelić a96485c07a test: Test that we don't end up in a deadlock when fetching the version 2025-10-31 15:58:44 +01:00
Damir Jelić 9680fc3a0f test: Test that the skip_auth option works correctly 2025-10-31 15:58:44 +01:00
Damir Jelić 422f925033 feat: Allow authorization headers to be skipped with the RequestConfig 2025-10-31 15:58:44 +01:00
Richard van der Hoff 3695d76dec crypto: pass on "history_not_shared" withheld notifications
When constructing a key bundle, if we had received a key bundle ourselves, in
which one or more sessions was marked as "history not shared", pass that on to
the new user.
2025-10-31 12:00:06 +00:00
Richard van der Hoff 0faf3eecea update changelogs 2025-10-31 12:00:06 +00:00
Richard van der Hoff 13a30f7b7a crypto: test for CryptoStore::get_withheld_sessions_by_room_id
integration test for the new method
2025-10-31 12:00:06 +00:00
Richard van der Hoff 444fcfa098 stores: new method CryptoStore::get_withheld_sessions_by_room_id
Implement this across all the store implementations
2025-10-30 23:10:05 +00:00
Richard van der Hoff cadbd33957 sqlite: add room_id index on direct_withheld_info table 2025-10-30 18:50:24 +00:00
Richard van der Hoff 8189010d58 indexeddb: invert key order for withheld sessions
... in preparation for extracting all withheld sessions for a given room.
2025-10-30 18:50:24 +00:00
Richard van der Hoff ee828614fb Merge pull request #5807 from matrix-org/rav/history_sharing/not_shared_code
crypto: use a new withheld code when history is marked as "not shareable"
2025-10-30 15:12:57 +01:00
Richard van der Hoff ef3c6719cf test: integ test for withhelds in history sharing
Add an integration test that ensures that the correct withheld code is sent
when history is marked as "not shareable"
2025-10-30 13:58:38 +00:00
Richard van der Hoff 55ef066eb4 crypto: use new withheldcode when we encounter unshareable sessions 2025-10-30 13:58:38 +00:00
Richard van der Hoff e3105bfca8 crypto: define new WithheldCode for MSC4268 2025-10-30 13:58:38 +00:00
Johannes Marbach 9fff07dfbb feat(oauth): add flow for reciprocating a login using a QR code generated on the existing device
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-10-29 20:37:22 +01:00
Johannes Marbach ce7f2fb24f refactor(secure_channel): rename SecureChannel::new to SecureChannel::reciprocate and make it available outside of tests
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-10-29 20:37:22 +01:00
Johannes Marbach b60b042cfe feat(testing): add mock for get device endpoint
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-10-29 20:37:22 +01:00
Damir Jelić 046d8ebdd1 test: Allow to omit the timeout for assert_recv_with_timeout (#5814)
Add some documentation to it while we're at it as well.
2025-10-29 15:19:37 +00:00
Damir Jelić 896f4114a2 chore(sqlite): Don't log the room ID twice when saving events
The room ID is already logged as part of the span due to the instrument
attribute.
2025-10-29 15:58:41 +01:00
Damir Jelić e2d42cef67 test: Add some spans to distinguish which user is mocking up the encryption 2025-10-29 15:54:47 +01:00
Ivan Enderlin 12e39f5ef1 chore(ffi): Restore ClientBuilder::session_paths as #[deprecated].
This method restores and marks `ClientBuilder::session_paths` as
deprecated.
2025-10-29 15:28:20 +01:00
Ivan Enderlin 38875b021d chore(ffi): Allow clippy::result_large_err.
These two methods are used only once, it's fine to get a large error
here.
2025-10-29 15:28:20 +01:00
Ivan Enderlin 0bbfc3ce41 doc(ffi): Update CHANGELOG.md and README.md. 2025-10-29 15:28:20 +01:00
Ivan Enderlin 7c6ff517d5 feat(ffi): Add IndexedDB and in-memory session stores support.
This patch introduces the `sqlite` and `indexeddb` feature flag,
enabling the use of SQLite or IndexedDB for the stores. This patch also
introduces the ability to use non-persistent, in-memory stores.

The new `ClientBuilder::in_memory_store`, `ClientBuilder::sqlite_store`
and `ClientBuilder::indexeddb_store` methods are introduced to
configure the stores. This patch adds new `SqliteStoreBuilder` and
`IndexedDbStoreBuilder` structure.
2025-10-29 15:28:20 +01:00
Kévin Commaille 8e25c36289 Upgrade Ruma (#5815)
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-29 15:02:27 +01:00
Damir Jelić 200fde8850 chore: Convert a Note to a NOTE
All uppercase is the correct convention and some editors even highlight
things if the correct convention is used.
2025-10-29 15:01:48 +01:00
Damir Jelić d4e0ec302a chore: Fix a comment 2025-10-29 15:00:38 +01:00
Jorge Martín c3e01a6902 doc: Add changelog 2025-10-29 10:08:03 +01:00
Jorge Martín 25b1c85998 feat(ffi): Upgrade UniFFI to v0.30.0 2025-10-29 10:08:03 +01:00
Richard van der Hoff 5a5b8afd4a crypto: add logging for withheld data in key bundles 2025-10-28 12:37:08 +00:00
Jorge Martín 9af8fad880 doc: Add changelogs 2025-10-28 10:57:31 +01:00
Jorge Martín b748148d36 fix(ui): Make Timeline::latest_event always return the latest event, not the latest item if it's an event
This matches the usages of `latest_event_id` in other parts of the SDK.
2025-10-28 10:57:31 +01:00
Jorge Martín 513a69c547 feat(ffi): Add Timeline::latest_event_id
It will allow us to fetch the latest event id coming from the SDK instead of deciding which one to use in the clients, which could be altered by filters, post-processing, etc.
2025-10-28 10:57:31 +01:00
Jorge Martín 2f58109853 feat(ffi): Add Room::mark_as_fully_read_unchecked
This method shouldn't be widely used, but it's useful when we want to mark the room as fully read when leaving it and at the same time we have to destroy the room and timeline instances immediately so their in-memory cache is cleared
2025-10-28 10:57:31 +01:00
dependabot[bot] deda2ec75a chore(deps): bump bnjbvr/cargo-machete
Bumps [bnjbvr/cargo-machete](https://github.com/bnjbvr/cargo-machete) from 026132adc2b95c4f16b8c2943d14aedb731daadc to 53dce01c203a6a857c9544ebec630a370d596d65.
- [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/026132adc2b95c4f16b8c2943d14aedb731daadc...53dce01c203a6a857c9544ebec630a370d596d65)

---
updated-dependencies:
- dependency-name: bnjbvr/cargo-machete
  dependency-version: 53dce01c203a6a857c9544ebec630a370d596d65
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 17:02:13 +01:00
dependabot[bot] 01a0e136dc chore(deps): bump CodSpeedHQ/action from 4.2.1 to 4.3.1
Bumps [CodSpeedHQ/action](https://github.com/codspeedhq/action) from 4.2.1 to 4.3.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/c6574d0c2a990bca2842ce9af71549c5bfd7fbe0...4348f634fa7309fe23aac9502e88b999ec90a164)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 17:01:41 +01:00
dependabot[bot] 5ab792e68e chore(deps): bump actions/upload-artifact from 4 to 5
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 17:01:16 +01:00
Michael Goldenberg 89d46cd342 doc(indexeddb): add changelog entry for separating media content and metadata in IndexedDB
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg 155a7b481b refactor(indexeddb): use UUID instead of u64 as media content id
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg 0ac943b4c4 refactor(indexeddb): rename MediaContent::id -> MediaContent::content_id
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg 18fe2b20e6 doc(indexeddb): fix typos in documentation
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg 64a0f62631 refactor(indexeddb): remove (de)serialization functionality from top-level media type
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg c169cab3b0 refactor(indexeddb): remove media object store and associated types
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg 91858e0913 refactor(indexeddb): simplify error type for media metadata impl of indexed
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg f70c036ff9 refactor(indexeddb): re-implement media-related fns in terms of media metadata and media content stores
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg 70d6d557ca refactor(indexeddb): implement specialized fn for getting media metadata keys via generalized fn
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg a52df18740 refactor(indexeddb): add transaction fns for getting media metadata keys by index
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg ce6ef90f74 refactor(indexeddb): rename transaction fn for getting all media metadata keys
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg 5f00e71f5f refactor(indexeddb): add content id to media metadata keys
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg 539bd9c79a refactor(indexeddb): add constants for media content id bounds
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg 23785a3023 refactor(indexeddb): remove unused type synonym
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg 3d31b81abf refactor(indexeddb): add type synonym for content id in indexed media content key
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg 3b2ef02749 refactor(indexeddb): add fn for prefixed key ranges from existing key ranges
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg c887819809 refactor(indexeddb): return indexed type from Transaction::{put_item,put_item_if} and its derivatives
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg 780b782660 refactor(indexeddb): return indexed type from Transaction::add_item and its derivatives
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg 4d4ae79b7a refactor(indexeddb): return indexed type and js value from indexed type serializer
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg af0a3aa91b refactor(indexeddb): add transaction fns for deleting media metadata
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg 84e5ce0a98 refactor(indexeddb): add transaction fns for add/putting media metadata
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg 574df8951e refactor(indexeddb): add transaction fns for getting media metadata
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg 6ff186b744 refactor(indexeddb): add indexed types and keys for media metadata
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg 6036f19af6 refactor(indexeddb): add migrations for media metadata store
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg f78bac2fc6 refactor(indexeddb): add content id and content size to media metadata
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg 1ae3b79c08 refactor(indexeddb): flatten nested media metadata into media type
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg b4d702f1ef refactor(indexeddb): add transaction fns for getting the next available media content id
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg c258368925 refactor(indexeddb): add key bounds for media content id key
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg 43f19e411a refactor(indexeddb): add constant for representing safe bounds of u64
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg 4e8ddde2f2 refactor(indexeddb): add transaction fn for getting max key in range
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg dfb3713f1e refactor(indexeddb): add transaction fns for basic media content operations
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg 105fa53a4c refactor(indexeddb): add indexed types for media content
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg 7238d3ca23 refactor(indexeddb): remove indexed media content type synonym
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg 3c522f9505 refactor(indexeddb): add type for tracking media content and associated id
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Michael Goldenberg 0796b71bd3 refactor(indexeddb): add migrations for media content store
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-27 16:59:00 +01:00
Kévin Commaille 547ab31b82 bonus(sdk): Add more profile tests
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-27 10:51:45 +01:00
Kévin Commaille 3f5d51a203 Add changelog for extended profile fields
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-27 10:51:45 +01:00
Kévin Commaille 4ea0b7d984 refactor(sdk): Prefer DELETE HTTP method for profile fields
When it is supported by the homeserver.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-27 10:51:45 +01:00
Kévin Commaille d2faa1be1a feat(sdk): Add support for deleting custom profile fields
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-27 10:51:45 +01:00
Kévin Commaille ca0929876f feat(sdk): Add support for setting custom profile fields
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-27 10:51:45 +01:00
Kévin Commaille c9d3088701 feat(sdk): Add support for fetching custom profile fields
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-27 10:51:45 +01:00
Johannes Marbach 68b902e4bc feat(ffi): add bindings for listening to global send queue updates
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-10-24 17:43:29 +02:00
Richard van der Hoff 8bb8bbae9c Merge pull request #5737 from matrix-org/kaylendog/shared-history/store
When we receive a key bundle, add any `withheld` data to the crypto store.
2025-10-24 17:31:46 +02:00
Damir Jelić 3733ee8534 chore: Remove the matrix-sdk-crypto re-export in the matrix-sdk crate 2025-10-24 16:37:15 +02:00
kaylendog b045462f76 feat: Append withheld info from room key bundle to store. 2025-10-24 14:29:35 +01:00
Richard van der Hoff 8bb5e501a4 test(crypto): use MegolmV2 in tests where experimental-algorithms are enabled 2025-10-24 13:19:37 +01:00
Richard van der Hoff 7aead98863 refactor(crypto): Split receive_room_key_bundle to helper methods
Split out session import logic to `import_room_key_bundle_sessions`.
2025-10-24 13:09:20 +01:00
kaylendog 7607c4ef82 tests: Test deserializing m.room_key.withheld to withheld entry.
Tests that a to-device `m.room_key.withheld` event can be
serialized (using JSON), then deserialized as a RoomKeyWithheldEntry.
Ensures compatibility with exisiting store data.
2025-10-24 13:09:17 +01:00
Skye Elliot 02fe0c9f53 feat: Add RoomKeyWithheldEntry to wrap to-device and bundle payloads. 2025-10-24 12:33:36 +01:00
Stefan Ceriu d117532fae feat(spaces): add support for MSC3230 and top level space order (#5799)
This is an unstable feature but as per
[MSC3230](https://github.com/matrix-org/matrix-spec-proposals/pull/3230)
each space room might have an optional
`m.space_order`/`org.matrix.msc3230.space_order` string field in its
room account data defining the lexicographical order in which the spaces
should be displayed, with spaces missing this field shown at the bottom
and ordered by their room id.
2025-10-24 12:18:29 +03:00
dependabot[bot] 34c5e24b72 chore(deps): bump actions/setup-node from 5 to 6
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 5 to 6.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v5...v6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-24 09:41:23 +02:00
dependabot[bot] 5bdb7ae732 chore(deps): bump CodSpeedHQ/action from 4.1.1 to 4.2.1
Bumps [CodSpeedHQ/action](https://github.com/codspeedhq/action) from 4.1.1 to 4.2.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/6b43a0cd438f6ca5ad26f9ed03ed159ed2df7da9...c6574d0c2a990bca2842ce9af71549c5bfd7fbe0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-24 09:40:54 +02:00
dependabot[bot] 5729ad4dd5 chore(deps): bump bnjbvr/cargo-machete
Bumps [bnjbvr/cargo-machete](https://github.com/bnjbvr/cargo-machete) from 744a6d5e0db5d189ad36edb08c5f77107cc42310 to 026132adc2b95c4f16b8c2943d14aedb731daadc.
- [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/744a6d5e0db5d189ad36edb08c5f77107cc42310...026132adc2b95c4f16b8c2943d14aedb731daadc)

---
updated-dependencies:
- dependency-name: bnjbvr/cargo-machete
  dependency-version: 026132adc2b95c4f16b8c2943d14aedb731daadc
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-24 09:40:31 +02:00
Stefan Ceriu d36b68b7d1 fix(spaces): have space children with an order field set come before the others in room lists 2025-10-22 13:51:48 +03:00
Johannes Marbach 34d71b0392 feat(composer): add support for attachments in drafts
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-10-21 11:00:24 +01:00
Damir Jelić 430304f392 chore: Rewrite timeline redecryption tests to use HTTP mocking
This is important since we want to move the redecryption logic out of
the timeline into the main crate. This in turn means that we don't have
such low level access to the redecryption logic.

Not all tests were rewritten:
    - `test_retry_edit_and_more` Is proving to be difficult to rewrite,
      may come in a separate commit.
    - `test_retry_fetching_encryption_info` Needs verification state
      changes. Will be rewritten on the event cache layer
2025-10-20 16:16:41 +02:00
Johannes Marbach 5f54237f4f feat(ffi): add bindings for logging in by generating a QR code on the new device
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-10-20 13:47:41 +02:00
Kévin Commaille f78f1795eb Upgrade Ruma
A new batch of breaking changes, allowing to stop providing dummy
`SupportedVersions` where they are not necessary.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-20 12:39:58 +02:00
Jorge Martin Espinosa dcd8aa13f0 fix: NotificationSettings::unmute_room didn't clear the cached notification mode 2025-10-17 12:02:38 +02:00
Ginger a4e68ba885 feat: Move Client::get_dm_room into the main impl Client block
This patch moves the `Client::get_dm_room` helper function and its tests
from `src/encryption/mod.rs` to `src/client/mod.rs`, so it may be used
without the `e2e-encryption` crate feature enabled.

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

Signed-off-by: Ginger <ginger@gingershaped.computer>
2025-10-16 14:03:09 +00:00
Johannes Marbach e8fb133cbf feat(oauth): Enable new devices to generate a QR code for login
This patch adds the complementary login flow for the already existing QR code login support.
Namely, previously it was only possible for the new device to scan a QR code to log in. Now
it's possible for the new device to create the QR code and let the existing device scan it.

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

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-10-15 16:04:50 +02:00
Damir Jelić a11daf24e5 test(ui): Test that the recency comparison function implements a total order 2025-10-14 17:12:41 +02:00
Damir Jelić b012512a21 chore(ui): Fix a copy/paste issue and add a note explaining a sort implementation
Co-authored-by: Benjamin Bouvier <benjamin@bouvier.cc>
Signed-off-by: Damir Jelić <poljar@termina.org.uk>
2025-10-14 17:12:41 +02:00
Benjamin Bouvier 818b1b6000 test(event cache): rewrite the test_redact_touches_thread to make it resilient to races
In the previous version of the thread, the following sequence of events
could happen:

- we subscribe to the thread linked chunk changes
- *then*, the events are being added to the thread linked chunk

This is an edge case where, since we've subscribed to the thread linked
chunk, the thread root will be "known" to be part of a thread, and will
be appended to the thread linked chunk.

If the events happen in the other order (first the events are added to
the thread linked chunk, then we subscribe to the changes), then the
thread root will not be part of the thread linked chunk (because when it
arrived, we didn't know it would be a thread root). As such, the thread
linked chunk state would end up being different in this case.

The solution is to make it so that the thread linked chunk is always
subscribed to *before* any events are added to it. This way, we make
sure that we'll always have the thread root in the thread linked chunk.
2025-10-14 15:37:04 +02:00
Benjamin Bouvier 5b523d21e4 review: rename try_remove_event to remove_if_present 2025-10-14 15:37:04 +02:00
Benjamin Bouvier fcab05e44b chore: make clippy happy 2025-10-14 15:37:04 +02:00
Benjamin Bouvier 70fb53612d test(timeline): ensure that a thread summary being removed is properly propagated to the main timeline 2025-10-14 15:37:04 +02:00
Benjamin Bouvier 059b5e7c1f feat(timeline): correctly mark a replied-to event as redacted, in threads 2025-10-14 15:37:04 +02:00
Benjamin Bouvier 9d7c21f508 refactor(timeline): make it possible to pass an EmbeddedEvent to maybe_update_responses 2025-10-14 15:37:04 +02:00
Benjamin Bouvier 3705b73256 feat(event cache): when a thread has only redacted replies, remove the thread summary 2025-10-14 15:37:04 +02:00
Benjamin Bouvier 3fb874f901 fix(event cache): also update the thread summary when the redacted event is not the latest one 2025-10-14 15:37:04 +02:00
Benjamin Bouvier 215087f2c1 feat(event cache): have redaction affect thread chunks and summaries
In this initial version, a redaction will:

- *remove* the event from the thread chunk, as does Element Web,
- update the thread summary to reflect the new number of messages in the
  thread, and let us have a thread summary with 0 replies.

A next commit will adapt the code so that a thread summary with 0
replies is removed.
2025-10-14 15:37:04 +02:00
Kévin Commaille 1e2bf39a7c Update Ruma
Brings changes to the requests metadata. It was changed from a struct to a trait, and the authentication scheme is now an associated type.

This allows to forbid at compile time requests that use an unsupported authentication scheme.
2025-10-14 15:32:32 +02:00
vaw c1bc814ac2 feat(timeline): Use read receipt as fallback for read marker
Signed-off-by: vaw <git@nlih.de>
2025-10-14 09:07:50 +01:00
Richard van der Hoff 7185fcbac8 Merge pull request #5763 from matrix-org/rav/history_sharing_exclude_insecure_devices
crypto: Fix bugs in processing incoming encrypted to-device messages
2025-10-13 17:02:14 +01:00
Richard van der Hoff 01e2e4877c test(crypto): Regresion test for #5613
Add a test to ensure that history-sharing still works when "exclude insecure
devices" is enabled.
2025-10-13 16:41:56 +01:00
Richard van der Hoff 3622355a08 test(crypto): add regression test for #5768 2025-10-13 16:41:56 +01:00
Richard van der Hoff c388332e47 crypto: fall back to sender_device_keys for encrypted to-device messages
When receiving an encrypted to-device message, if the sender device is not in
the store, but the event includes `sender_device_keys`, use
`sender_device_keys` to do the verification checks etc.

Fixes: https://github.com/matrix-org/matrix-rust-sdk/issues/5768
2025-10-13 16:41:56 +01:00
dependabot[bot] 6042a9e9b0 chore(deps): bump crate-ci/typos from 1.37.2 to 1.38.1
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.37.2 to 1.38.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.37.2...v1.38.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-13 17:07:56 +02:00
dependabot[bot] 04260458ef chore(deps): bump qmaru/wasm-pack-action from 0.5.1 to 0.5.2
Bumps [qmaru/wasm-pack-action](https://github.com/qmaru/wasm-pack-action) from 0.5.1 to 0.5.2.
- [Release notes](https://github.com/qmaru/wasm-pack-action/releases)
- [Commits](https://github.com/qmaru/wasm-pack-action/compare/v0.5.1...v0.5.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-13 17:04:11 +02:00
Benjamin Bouvier 7c3a8b335a doc(timeline): tweak wording of TimelineBuilder::with_focus
It was incorrect to say that the timeline focus can be changed after the
timeline has been created, since it is *not* the case. Also explained
what the default value is.
2025-10-13 16:42:45 +02:00
Kévin Commaille a8aa8761d8 Add changelog for waveform changes
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-13 14:10:14 +02:00
Kévin Commaille bfc96181dd refactor(sdk): Change waveform to be a list of values between 0 and 1
Most clients will probably work with values between 0 and 1 and need to
convert it just to send it, so we can move that conversion into the SDK.

This is also more forwards-compatible, because MSC3246 now has a
different max value for the amplitude, so when this becomes stable, the
only change needed will be in the SDK.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-13 14:10:14 +02:00
Kévin Commaille eb1ee434b3 refactor(sdk): Allow to send waveform for any audio message
By moving the waveform declaration into `BaseAudioInfo`.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-13 14:10:14 +02:00
Richard van der Hoff e7fa8a429a test(crypto): simplify send_and_receive_encrypted_to_device_test_helper
No need to convert the event content to a to-device request, and then convert
back again.
2025-10-10 15:47:10 +01:00
Richard van der Hoff 8b6572bb23 test(crypto): Factor out test helper for encrypting to-device content
I'm going to need to suppress `sender_device_keys` for more tests, so pull out
a test helper to help with this.
2025-10-10 15:47:10 +01:00
Richard van der Hoff 43e94bcfb4 crypto: look up sender device for key bundles
Currently, when we receive a room key bundle to-device event, we don't look up
the sender device at all, meaning that the message is then marked as "from
missing device", which means that if you turn on "exclude insecure devices",
the message is dropped.

This patch changes the logic so that room key bundle to-device events are
treated the same way as most other to-device events (except room keys, which
continue to be special).

Fixes: https://github.com/matrix-org/matrix-rust-sdk/issues/5613, although the
integration test now fails because instead we hit https://github.com/matrix-org/matrix-rust-sdk/issues/5768.
2025-10-10 15:47:10 +01:00
Michael Goldenberg 588d604653 refactor(indexeddb): remove extraneous log message
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-10 10:09:52 +02:00
Michael Goldenberg 90cf669f94 refactor(indexeddb): import transaction mode from indexed_db_futures rather than web_sys
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-10 10:09:52 +02:00
Michael Goldenberg 70a608b3b5 refactor(indexeddb): remove nested memory store from MediaStore impl
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-10 10:09:52 +02:00
Michael Goldenberg bcd4337985 test(indexeddb): add integration tests for MediaStoreInner
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-10 10:09:52 +02:00
Michael Goldenberg 58972ca9d9 fix(indexeddb): ensure media that ignore retention policy is always put into IndexedDB
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-10 10:09:52 +02:00
Michael Goldenberg ef672271c2 fix(indexeddb): ensure tx is committed in MediaStore::set_media_retention_policy_inner
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-10 10:09:52 +02:00
Michael Goldenberg 1bc417956c feat(indexeddb): add IndexedDB-backed impl for MediaStoreInner::set_ignore_media_retention_policy_inner
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-10 10:09:52 +02:00
Michael Goldenberg 1127184db2 refactor(indexeddb): add transaction fn for putting media into IndexedDB
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-10 10:09:52 +02:00
Michael Goldenberg 63a3c2d51a refactor(indexeddb): remove base64 encoding of unencrypted media content
This makes unencrypted content sizes consistent and testable.

Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-10 10:09:52 +02:00
Michael Goldenberg 3f55c217c1 feat(indexeddb): add IndexedDB-backed impl for MediaStoreInner::clean_inner
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-10 10:09:52 +02:00
Michael Goldenberg 19632683f7 refactor(indexeddb): make getters for media content size key consistent with those for other media keys
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-10 10:09:52 +02:00
Michael Goldenberg 03f964a5a3 refactor(indexeddb): add fns to get field components of indexed media keys
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-10 10:09:52 +02:00
Michael Goldenberg 752beadb83 fix(indexeddb): add associated index to IndexedKey<Media> where missing
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-10 10:09:52 +02:00
Michael Goldenberg 6244721ab4 refactor(indexeddb): add transaction fns for deleting media by content size and access time
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-10 10:09:52 +02:00
Michael Goldenberg 6e034b0d7b refactor(indexeddb): add transaction fn for getting the size of the media cache
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-10 10:09:52 +02:00
Michael Goldenberg e4a717dff5 refactor(indexeddb): add media-specific transaction fns for getting and operating on keys
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-10 10:09:52 +02:00
Michael Goldenberg 6acf628fc5 refactor(indexeddb): import cursor direction enum from indexed_db_futures rather than web_sys
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-10 10:09:52 +02:00
Michael Goldenberg db91bb35ee refactor(indexeddb): add transaction fns for getting and operating on keys
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-10 10:09:52 +02:00
Michael Goldenberg 3b7dbf5c04 feat(indexeddb): add IndexedDB-backed impl for MediaStoreInner::last_media_cleanup_time_inner
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-10 10:09:52 +02:00
Michael Goldenberg 22bfe8fbd3 refactor(indexeddb): add fns to get and put media cleanup time
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-10 10:09:52 +02:00
Michael Goldenberg e4243b7af3 refactor(indexeddb): add indexed type and traits for media cleanup time
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-10 10:09:52 +02:00
Michael Goldenberg f891c1ca06 refactor(indexeddb): add type for representing media cleanup time
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-10 10:09:52 +02:00
Michael Goldenberg c2839d7594 refactor(indexeddb): add conversions and operations for UnixTime
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-10 10:09:52 +02:00
Richard van der Hoff c45ede972e crypto: factor out Account::get_event_sender_device
`Account::parse_decrypted_to_device_event` is getting a bit big and unwieldy,
so factor out the bit that attempts to find the sending device.

(Also, remove an outdated TODO.)
2025-10-09 18:26:46 +01:00
Johannes Marbach 9b485013e1 feat(ffi): add bindings for listening to room send queue updates
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-10-09 15:18:04 +01:00
Kévin Commaille cb3d281f8f Upgrade Ruma after removal of legacy mention push rules
The legacy mention push rules were removed, and the
`contains_display_name` condition was deprecated.

Some tests check for backwards-compatibility with legacy mentions, so we
need to add them back for those tests.

A test with an encrypted event was relying on the legacy mentions, so
the encrypted event was replaced with another one with an intentional
mention.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-09 15:08:38 +01:00
Kévin Commaille a72c19a240 test(ui): Allow to set own user id of TestRoomDataProvider
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-09 15:08:38 +01:00
Kévin Commaille cf4a1dee4b Upgrade Ruma after StringEnum changes
StringEnum now also implements Ord, PartialOrd, Eq and PartialEq so it
is not necessary to derive them. Also the ordering used is comparing the
string representation of the variants.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-09 15:08:38 +01:00
Kévin Commaille 487470be8f Upgrade Ruma after extended profile field stabilization
Extended profile fields were stabilized so the old endpoints are now
deprecated, and there are a few other changes.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-09 15:08:38 +01:00
Richard van der Hoff b94823216d Merge pull request #5766 from matrix-org/rav/device_keys_self_signature
crypto: give `DeviceKeys` ability to check their own signature
2025-10-09 12:26:41 +01:00
Richard van der Hoff 232119cf57 crypto: avoid redundant conversion to DeviceData
There is (now) no need to turn the `sender_device_keys` into a `DeviceData`.
2025-10-09 11:32:09 +01:00
Richard van der Hoff 64818e2ef9 crypto: docs on Account::check_sender_device_keys (#5765)
I had to do some thinking about this, so wrote down my conclusions.
2025-10-09 11:31:30 +01:00
Richard van der Hoff a2ded93234 crypto: give DeviceKeys ability to check their own signature
Adds `DeviceKeys::has_signed` and `DeviceKeys::check_self_signature`, and
removes `DeviceData::has_signed` and `DeviceData::verify_device_keys`.

I just found this easier to grok, and it means we can avoid needlessly turning
a `DeviceKeys` into a `DeviceData` sometimes.
2025-10-09 11:14:42 +01:00
Stefan Ceriu fc892564d8 fix(spaces): handle empty string room names when computing the display names
Fixes #5762
2025-10-09 12:59:06 +03:00
Johannes Marbach 358803783f feat(oauth): add LoginProgress::SyncingSecrets
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-10-08 11:00:54 +02:00
Benjamin Bouvier ef440eed2b chore(base): don't log the same missing room info log line on every single sync
This should only happen when a room has been forgotten and was a room
DMs before. Ideally, we'd clean up the room DM event data, but since
this is slightly more involved, we don't do that here just quite yet.
2025-10-07 21:03:09 +02:00
Johannes Marbach 79e1930b22 Make LoginProgres::EstablishingSecureChannel generic in order to reuse it for the other QR login flow
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-10-07 21:00:42 +02:00
Kévin Commaille 6191e2c24e fix(sdk): Make impl Stream return type not use any lifetime
With Rust 2024, by default `impl` return types use any generic that is
in scope, so in these cases the lifetime of `self`.

But since the return type is actually owned, the returned impl shouldn't
use any lifetime, which is what `use<>` does.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-07 15:22:52 +02:00
Kévin Commaille ba2fe1d387 fix(crypto): Make impl Stream return type not use any lifetime
With Rust 2024, by default `impl` return types use any generic that is
in scope, so in these cases the lifetime of `self`.

But since the return type is actually owned, the returned impl shouldn't
use any lifetime, which is what `use<>` does.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-07 15:22:52 +02:00
Stefan Ceriu c7b4b5dc05 chore(ffi): expose the computed SpaceRooms display name
This reuses the same naming scheme used in the FFI Room and RoomInfo
2025-10-07 15:33:36 +03:00
Stefan Ceriu 87d9bd14e3 feat(spaces): reuse existing room display name computation logic for spaces 2025-10-07 13:05:59 +03:00
Benjamin Bouvier 44a4ca94be chore: fix new typos 2025-10-06 17:39:23 +02:00
dependabot[bot] 3b43a7e5e8 chore(deps): bump crate-ci/typos from 1.36.3 to 1.37.2
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.36.3 to 1.37.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.36.3...v1.37.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-06 17:39:23 +02:00
dependabot[bot] 773d304f9e chore(deps): bump CodSpeedHQ/action from 4.0.1 to 4.1.1
Bumps [CodSpeedHQ/action](https://github.com/codspeedhq/action) from 4.0.1 to 4.1.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/653fdc30e6c40ffd9739e40c8a0576f4f4523ca1...6b43a0cd438f6ca5ad26f9ed03ed159ed2df7da9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-06 17:19:31 +02:00
dependabot[bot] b47174e394 chore(deps): bump bnjbvr/cargo-machete
Bumps [bnjbvr/cargo-machete](https://github.com/bnjbvr/cargo-machete) from 7c2dc36a6fe4a75848d9397e34c95474f38c82ef to 744a6d5e0db5d189ad36edb08c5f77107cc42310.
- [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/7c2dc36a6fe4a75848d9397e34c95474f38c82ef...744a6d5e0db5d189ad36edb08c5f77107cc42310)

---
updated-dependencies:
- dependency-name: bnjbvr/cargo-machete
  dependency-version: 744a6d5e0db5d189ad36edb08c5f77107cc42310
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-06 17:11:40 +02:00
Damir Jelić b1e0339159 ci: Add the crypto team as CODEOWNERS for the indexeddb crypto store implementation 2025-10-06 14:58:37 +02:00
Benjamin Bouvier 7fbc4144b1 feat(timeline): allow a poll edit to be an embedded event
This will properly show edited polls as the latest thread id, as a nice
benefit.
2025-10-06 14:05:34 +02:00
Benjamin Bouvier a2a26ae45e refactor(timeline): pass directly the poll start and fallback text to PollState ctor 2025-10-06 14:05:34 +02:00
Benjamin Bouvier 06301bc2f8 refactor(timeline): store fewer datum a in PollState
We don't really need the poll start event entirely, since we're only
interested in the start block and the fallback text. With this, it'll be
simpler to create embedded polls from poll edit events.
2025-10-06 14:05:34 +02:00
Benjamin Bouvier 85abe76121 feat(timeline): also support poll edits as embedded events 2025-10-06 14:05:34 +02:00
Benjamin Bouvier 97ff61081a feat(timeline): support edits in embedded events 2025-10-06 14:05:34 +02:00
Benjamin Bouvier 0017ccb0c1 feat(event cache): update a thread summary if an edit related to the latest thread reply 2025-10-06 14:05:34 +02:00
Benjamin Bouvier 350fdd8ad4 feat(common): add support for extracting only an edited target event id from an edit event 2025-10-06 14:05:34 +02:00
Benjamin Bouvier f937bf60e2 refactor(event cache): make naming more consistent around latest thread event 2025-10-06 14:05:34 +02:00
Benjamin Bouvier da394f5015 refactor(event cache): update the thread summary in a separate function 2025-10-06 14:05:34 +02:00
Benjamin Bouvier 158e3925b7 refactor(event cache): don't collect back-paginated events that are going to be filtered out immediately 2025-10-06 14:05:34 +02:00
Benjamin Bouvier 4828f4c555 refactor(event cache): rename save_event to save_events as it's involving multiple events 2025-10-06 14:05:34 +02:00
Benjamin Bouvier 92e7cb3af2 refactor(event cache): save the content of threaded events in the store 2025-10-06 14:05:34 +02:00
Benjamin Bouvier a6a590aa1f test(timeline): add a test that an edit to a thread's latest event updates the thread summary 2025-10-06 14:05:34 +02:00
Kévin Commaille d01a28c9b2 Upgrade Ruma
Brings a breaking change with event structs being non-exhaustive now,
so they need to be constructed with methods rather than with a struct
declaration.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-06 10:21:00 +02:00
Johannes Marbach 68075b65fb refactor(auth): make auxiliary functions reusable outside LoginWithQrCode
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-10-04 09:41:16 +02:00
Kévin Commaille d4d40945e8 refactor(tests): Use EventFactory to build events
There is a breaking change in Ruma and those types are now
non-exhaustive so they can't be built with the struct declaration
anymore.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-03 17:01:58 +02:00
Kévin Commaille 2b69a7f741 feat(sdk-test): Add conversions to deserialized types for EventBuilder
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-03 17:01:58 +02:00
Stefan Ceriu d85b45ed64 feat(spaces): sort space room list rooms as defined in the spec
The ordering criteria is defined at https://spec.matrix.org/latest/client-server-api/#ordering-of-children-within-a-space. The gist is that `order` comes first, then `timestamp` and finally the `room_id`

This is not available for top level spaces, but there is an MSC that addresses it at https://github.com/matrix-org/matrix-spec-proposals/pull/3230 and Ruma support has been added in https://github.com/ruma/ruma/pull/2231. The SDK side implementation for that will come in a later PR.
2025-10-03 15:22:52 +02:00
Stefan Ceriu b43237536d chore(spaces): store the full children_state data when fetching /hierarchy 2025-10-03 15:22:52 +02:00
Kévin Commaille fbafae42bb refactor(tests): Replace uses of EventBuilder::into_raw
We actually want other event formats in those cases, and in most cases
just using `.into()` is enough to generate the proper format.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-03 15:22:25 +02:00
Kévin Commaille 08563d4096 refactor(sdk-test): Use enum to represent possible event formats of EventBuilder
And use the proper fields for these formats. We also add more conversion
implementations for the types associated with these formats.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-03 15:22:25 +02:00
Andy Balaam 32f3670aeb task(crypto): Warn API users to fetch device info before processing verification requests 2025-10-03 14:21:44 +01:00
Ivan Enderlin 8a23aae9dc perf(common): Compute the message in TracingTimer when required (#5662)
This patch moves the creation of the `message` in `TracingTimer` if and
only if the log is enabled. Computing it every time is useless, and can
even slow things down (because of the time calculation).
2025-10-03 15:19:47 +02:00
Jorge Martín 2822815384 feat(ffi): Add NotificationSettings::get_raw_push_rules
This allows clients to get the raw push rules so they can be added to bug reports if needed
2025-10-03 15:16:07 +02:00
Ivan Enderlin a7cb094aaf feat(sqlite): Add a write-only connection in SqliteStateStore.
This patch introduces a write-only connection in `SqliteStateStore`
_à la_ `SqliteEventCacheStore`. The idea is to get many read-only
connections, and a single write-only connections behind a lock, so that
there is a single writer at a time.

This patch renames the `acquire` method to `read`, and it introduces a
new `write` connection.
2025-10-03 15:00:37 +02:00
Ivan Enderlin 764a8a4c77 doc(sqlite): Fix // to ///.
This patch transforms an inline comment into a doc comment.
2025-10-03 15:00:37 +02:00
mgoldenberg 2e6790d0a5 IndexedDB: upgrade indexed_db_futures dependency (#5722)
**NOTE:** _this should not be merged until matrix-org/rust-indexed-db#1
is merged! The `[patch]` in this branch should point to the official
`matrix-org` fork of `rust-indexed_db`, but is currently pointed at my
personal fork._

## Background

This pull request makes updates
[`indexed_db_futures`](https://docs.rs/indexed_db_futures/latest/indexed_db_futures/index.html)
in the `matrix-sdk-indexeddb` crate. The reason we'd like to update this
dependency is because the version currently used does not fully support
the Chrome browser (see #5420).

The latest version of `indexed_db_futures` has significant changes. Many
of these changes can be integrated without issue. There is, however, a
single change which is incompatible with the `matrix-sdk-indexeddb`
crate. Namely, one cannot access the active transaction in the callback
to update the database (for details, see Alorel/rust-indexed-db#66).

### An Updated Proposal

Originally, new migrations were implemented in order to work around this
issue (see #5467). However, the proposal was ultimately rejected (see
@andybalaam's
[comment](https://github.com/matrix-org/matrix-rust-sdk/pull/5467#issuecomment-3149550617)).

For this reason, the dependency has instead been `[patch]`ed in the
top-level `Cargo.toml` with a modified version of `indexed_db_futures`
(see matrix-org/rust-indexed-db#1). Furthermore, these changes have been
proposed to the maintainer and are awaiting feedback (see
Alorel/rust-indexed-db#72).

### Why do we need the active transaction in our migrations?

The `crypto_store` module provides access to the active transaction to
its migrations (see
[here](https://github.com/matrix-org/matrix-rust-sdk/blob/ca89700dfe9f29dcd823bb10861807f9d75e0634/crates/matrix-sdk-indexeddb/src/crypto_store/migrations/mod.rs#L211)).
Furthermore, there is a single migration (`v11_to_v12`) in the
`crypto_store` module which actually makes use of the active transaction
(see
[here](https://github.com/matrix-org/matrix-rust-sdk/blob/ca89700dfe9f29dcd823bb10861807f9d75e0634/crates/matrix-sdk-indexeddb/src/crypto_store/migrations/v11_to_v12.rs#L23)).

For clarity, the reason `v11_to_v12` is problematic in the latest
versions of `indexed_db_futures` is because it is simply adding an index
to an object store which was created in a different migration and this
requires access to the active transaction. All the other migrations
create object stores and indices in the same migration, which does not
suffer from the same issue.

## Changes

- Move `indexed_db_futures` to the workspace `Cargo.toml` and add a
`[patch]` so that it points to a modified version.
- Add `GenericError` type and conversions in order to more easily map
`indexed_db_futures` errors into `matrix-sdk-*` errors.
- Update all IndexedDB interactions so that they use the upgraded
interface provided by `indexed_db_futures`
- Add functionality for running `wasm-pack` tests against Chrome


---
Closes #5420.

---

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


Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>

---------

Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-10-03 14:10:10 +02:00
Stefan Ceriu 67d8db3d93 fix(spaces): filter out non-joined rooms from the space leaving process and handle 2025-10-03 13:43:14 +03:00
Ivan Enderlin 52518e0e2e fix(sdk): Use RoomPowerLevels::user_can_kick_user in filter_any_sync_state_event.
This patch replaces `user_can_kick` by `user_can_kick`: it performs an
extra check to make sure the acting user has at least the same power
level as the target user.
2025-10-03 12:37:37 +02:00
Kévin Commaille b8b54246c4 Silence unused-imports lint
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-03 12:27:50 +03:00
Kévin Commaille 95e93ca00b fix(ui): Fix broken links
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-03 12:27:50 +03:00
Kévin Commaille bb6ba08dfb fix: Remove newly detected unused imports
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-03 12:27:50 +03:00
Kévin Commaille 8c515b0c12 fix(docs): Replace doc_auto_cfg with doc_cfg feature
The former has been merge in the latter, and it errors when generating
the docs in a recent version of nightly, like the one used on docs.rs.

This also requires to bump the version of nightly used in CI, otherwise
it would break the docs generation.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-03 12:27:50 +03:00
Ivan Enderlin 81a69f82d2 feat(sdk): Accept invite for latest event if it targets the current user. 2025-10-03 11:26:45 +02:00
Ivan Enderlin f4a6d12979 refactor(sdk): Split power_levels in latest_event.
This patch splits `power_levels: &Option<(&UserId, RoomPowerLevels)>`
into 2 variables: `own_user_id: Option<&UserId>` and `power_levels:
Option<&RoomPowerLevels>`. The idea is to be able to get the
`own_user_id` even if the power levels are `None`.
2025-10-03 11:26:45 +02:00
Ivan Enderlin 203a3783ae feat(sdk): Support m.room.membership with membership: "invite" as latest event. 2025-10-03 11:26:45 +02:00
Ivan Enderlin 8eb7264e5d test(sdk): Test that m.room.member for an invite can be a latest event candidate. 2025-10-03 11:26:45 +02:00
Kévin Commaille a4bd36cbe8 fix(ci): Fix cargo-codspeed command
A new release occurred which has a breaking change in the syntax used to
select a benchmark.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-02 21:33:24 +02:00
Stefan Ceriu 8e8ad0167a change(spaces): return a reference to the rooms vector from the leave handle
well now
2025-10-02 12:41:42 +03:00
Stefan Ceriu 0f78959c9a change(spaces): compute LeaveSpaceRooms for the LeaveSpaceHandle asynchronously in its constructor 2025-10-02 12:41:42 +03:00
Stefan Ceriu 7a431a3afd change(spaces): have the leave space rooms interface take a filter
This helps make sure the rooms to be left were actually part of the space graph as they are stored inside the `LeaveRoomHandle` and filtered from there. On the FFI layer on the other hand, we still take plain strings as working around the limitations would've significantly complicated things.
2025-10-02 12:41:42 +03:00
Stefan Ceriu a6d033ea4c chore(spaces): move joined_rooms and the SpaceGraph underneath the same Arc Mutex 2025-10-02 12:41:42 +03:00
Stefan Ceriu ad41cbc368 chore(spaces): remove unused Unknown Space Error variant 2025-10-02 12:41:42 +03:00
Stefan Ceriu cf0c3e7009 chore(spaces): move the LeaveSpaceRoom struct to the top of the file 2025-10-02 12:41:42 +03:00
Stefan Ceriu 3a60d34f3f feat(ffi): expose the space service leaving interfaces
fix newline ffi
2025-10-02 12:41:42 +03:00
Stefan Ceriu 9114c22b70 feat(spaces): add mechanism for _ordely_ leaving a space and its children
When leaving a space the user should be informed of which rooms are DMs (already part of the `SpaceRoom`)
and in which they might be the last admin, where leaving would prevent anybody else for taking control.
2025-10-02 12:41:42 +03:00
Stefan Ceriu 8655afd117 chore(spaces): store the built space graph in between the various updates so it can be used for leaving spaces 2025-10-02 12:41:42 +03:00
Stefan Ceriu f5ec9b6427 feat(spaces): add graph method for retrieving a flat list of nodes belonging to a subtree ordered in bottom up dfs visiting order. 2025-10-02 12:41:42 +03:00
Ivan Enderlin 2eab7cf818 fix(ui): RoomListItem refreshes its cache_is_space. 2025-10-02 09:45:14 +02:00
Kévin Commaille 6072618e85 Add changelog for caption change
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-02 08:15:56 +02:00
Kévin Commaille 70b19cc907 refactor(sdk): Use TextMessageEventContent to send a caption
It doesn't make sense to send a formatted caption without a plain text
caption so using TextMessageEventContent forces the latter to be present.

This also allows to use the helpful constructors of
TextMessageEventContent.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-10-02 08:15:56 +02:00
Hubert Chathi 57d21ccdf6 Create a separate error variant to indicate a failure importing a secret. (#5647)
Part of the fix to
https://github.com/element-hq/element-x-android/issues/5099

Allows applications to distinguish between errors that occur when
unlocking Secret Storage, or errors that occur when importing a secret,
so that they can display appropriate feedback (or not) to the user.

- [ ] 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:

---------

Signed-off-by: Hubert Chathi <hubertc@matrix.org>
2025-10-02 08:14:46 +02:00
Benjamin Bouvier 37ee5d5075 refactor(stores): get rid of the temporary compute_filter_strings now that Ruma has been updated
This was a local fix for a bug in Ruma, that has been fixed upstream since then, so we can get rid of the workaround now.
2025-10-01 16:50:23 +00:00
Benjamin Bouvier 681b22142f refactor(timeline): add more logs when we couldn't create an embedded event
This should help figuring out why some thread's latest replies are
marked as "unsupported events".
2025-10-01 10:54:00 +02:00
Benjamin Bouvier 248d77a4d9 refactor(ffi): add debug logging when a latest event is not a standalone content item 2025-10-01 10:54:00 +02:00
Benjamin Bouvier f4451b5c82 refactor(timeline): TimelineAction::from_content always returns Something now 2025-10-01 10:54:00 +02:00
Mathieu Velten 59b7da247c Add some doc to add_event_handler for invites and stripped state (#5705)
Signed-off-by: Mathieu Velten <mathieu@velten.xyz>
Co-authored-by: Damir Jelić <poljar@termina.org.uk>
2025-09-30 14:25:17 +00:00
Benjamin Bouvier be5bd449b5 test(timeline): ensure unthreaded receipts are loaded in the main timeline view mode 2025-09-30 15:09:57 +02:00
Benjamin Bouvier 2eb29518dc refactor(timeline): no need to look at the receipt timestamp
I was wrong in a previous commit: both receipts are on the same event
anyways, so we can safely override the keys in the read receipts map
(overriding would mean both receipts point to the same event, which is
fine, as we're displaying only one of those).
2025-09-30 15:09:57 +02:00
Benjamin Bouvier 16d0840115 refactor(timeline): move code around for loading initial main|unthreaded receipts 2025-09-30 15:09:57 +02:00
Benjamin Bouvier d90576bf0d fix(timeline): when loading initial receipts for main|unthreaded, load both kinds
This is a fix, because some other code elsewhere will use both kinds of
receipts whenever they're received over sync. The code that's modified
in this patch is called for the initial load of receipts, that happens
whenever we see a new event. Since the two code paths were not doing the
same thing, this would affect the displayed receipts, depending on
whether we received them during sync, or after loading the timeline for
the first time.
2025-09-30 15:09:57 +02:00
Benjamin Bouvier bbeb2d21b1 refactor(timeline): slightly rearrange code so as to remove a dubious comment 2025-09-30 14:29:27 +02:00
Benjamin Bouvier 187b646c07 refactor(event cache): have the room pagination handle waiting for the previous pagination token from sync
This case is very specific to the room pagination, and will not apply to
the thread pagination; by removing it from the generic pagination logic,
we'll be able to use the generic pagination logic for threads.
2025-09-30 14:29:27 +02:00
Benjamin Bouvier 8a47e3cd1c refactor(event cache): inline conclude_load_more_for_fully_loaded_chunk into its only caller
And that's one overlong function name less!
2025-09-30 14:29:27 +02:00
Benjamin Bouvier 973d71f54e docs(event cache): add comments to clarify when None can be returned from internal pagination methods 2025-09-30 14:29:27 +02:00
dependabot[bot] 943b048fa0 chore(deps): bump bnjbvr/cargo-machete
Bumps [bnjbvr/cargo-machete](https://github.com/bnjbvr/cargo-machete) from cb0995971182a3babbea3f086bf306d5509cac47 to 7c2dc36a6fe4a75848d9397e34c95474f38c82ef.
- [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/cb0995971182a3babbea3f086bf306d5509cac47...7c2dc36a6fe4a75848d9397e34c95474f38c82ef)

---
updated-dependencies:
- dependency-name: bnjbvr/cargo-machete
  dependency-version: 7c2dc36a6fe4a75848d9397e34c95474f38c82ef
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-30 09:36:57 +01:00
dependabot[bot] 2c70c31c56 chore(deps): bump crate-ci/typos from 1.36.2 to 1.36.3
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.36.2 to 1.36.3.
- [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.36.2...v1.36.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-30 07:36:48 +03:00
Ivan Enderlin be7129bacc chore(ui): Remove an unnecessary Arc in SpaceRoomList.
`SharedObservable` is already shareable, no need for an `Arc` here.
2025-09-29 09:54:54 +03:00
Ivan Enderlin 2ec33183c4 doc(sqlite): Fix formatting and typo. 2025-09-26 16:07:20 +02:00
Doug d6d720c015 ffi: Expose a room list filter for spaces. 2025-09-26 10:22:53 +01:00
Stefan Ceriu 5b52c729a5 feat(spaces): automatically subscribe to SpaceRoomList "parent" space room info updates (#5712)
… when known to the client and forward updates through the existing
`subscribe_to_space_updates` mechanisms.

This allows clients to listen to updates without having to resort to a
separate room info subscription on their side.
2025-09-25 13:05:29 +03:00
Benjamin Bouvier 7d649e92d4 fix(timeline): don't listen to live thread events when the timeline is focused on a thread permalink 2025-09-25 11:27:18 +02:00
Benjamin Bouvier 021d3fb5d7 test(timeline): add a test to show that threaded permalink timeline receives thread live updates 2025-09-25 11:27:18 +02:00
Jorge Martín 5f02212312 fix(ffi): ffi::Room::load_or_fetch_event fails with missing room_id
The raw event was being deserialized in a wrong way and it could have a missing room id in some cases, returning an error even when the event was found
2025-09-25 10:57:48 +02:00
Benjamin Bouvier e158e8abc0 refactor(timeline): in thread permalinks, avoid back-paginating if the root event is part of the /context response
For thread permalinks, we start with a /context query that will load the
focused event, and maybe a few other in-thread events. In fact, it can
also include the thread root event, which was excluded before. Instead,
we would get a previous-token for back-paginations, which would be used
in /relations. When the request to /relations returns an empty previous
token, that means we've reached the start of the thread, and in this
case we would manually load the root with /event.

We can do better, if the root event is part of the initial /context
response: skip the back-paginations altogether, and make sure to include
the root event in `init_focus()`.
2025-09-25 10:21:19 +02:00
Damir Jelić e2ec8bcbd6 ci: Install clippy for the test-crypto CI run 2025-09-25 10:14:21 +02:00
Benjamin Bouvier 36e0d4bfb8 ci: don't compile the benchmarks in a separate CI step
They're built as part of the codspeed run these days, so this is
duplicated wasteful work.
2025-09-25 10:05:27 +02:00
Benjamin Bouvier 05362be89a ci: use the latest bnjbvr/cargo-machete action
It's much faster now as it downloads the latest version of the
precompiled binary from Github, and it will use the latest tagged
version of cargo-machete by default.
2025-09-25 10:05:27 +02:00
Ivan Enderlin 03fc5dacbe feat(ui): The recency sorter handles recency stamp _and_ latest event's timestamp.
This patch revisits a feature we have disabled a couple of days ago:
the `recency` sorter was initially only supporting the recency stamp,
then later the recency stamp _and_ the latest event's timestamp. It was
however buggy and we had to revert it. Now it's time to re-introduce it
but with a different approach.

The previous rules were:

1. if two rooms have a latest event, use their latest event's timestamps
   as their _scores,
2. if one of room has a latest event, use the recency stamp as their
   _scores_ for both rooms.

Rule 2 was buggy because one room was sometimes using its latest
event's timestamp, and sometimes its recency stamp, based on what it was
compared to. It was an error!

The new rules are the following:

1. unchanged
2. if one room has a latest event, use its latest event's timestamp as
   its _score_, and use no _score_ for the other room,
3. if two rooms have NO latest event, use the recency stamp as their
   _scores_.

It means that a room with no latest event will always be sorted _after_
a room with a latest event. It can feel cruel, but it should be an edge
case. When a room is synchronised, it should receive events, which
should trigger the computation of a latest event.

Note that this patch also renames _rank_ to _score_, as I consider it's
a better vocabulary. It could be confusing to use _rank_ as one can
expect all rooms to be indexed and get a rank, but it's not the case.
_Score_ sounds better.
2025-09-24 17:51:38 +02:00
Alexis Loiseau 0a0e31af83 feat(ui): add custom events to timeline when explicitly filtered
This allows custom message-like events (created by the `EventContent` macro from ruma) to be added to the timeline if they are explicitly allowed when building the timeline with a custom `event_filter`.

The custom event content is not available directly to the consumer, but it can still fetch it from the matrix-sdk client with its `event_id`, or display a "this type of event is not supported". 

Signed-off-by: Itess <me@aloiseau.com>

Fixes #5598.
2025-09-24 15:50:50 +00:00
Ivan Enderlin 290f27a343 doc(base): Update CHANGELOG.md. 2025-09-24 16:12:09 +02:00
Ivan Enderlin 0033de1f49 refactor(base): Rename sync_lock to state_store_lock.
This patch renames the `sync_lock` to `state_store_lock` because it is
what it is. It's not about the sync, it's about the state store.
2025-09-24 16:12:09 +02:00
Ivan Enderlin 688eb6880d refactor(sdk): Move the RoomLatestEvents* types in their own module.
This patch moves the `RoomLatestEvents*` types in their own new
`room_latest_events` module.
2025-09-24 14:26:14 +02:00
Ivan Enderlin 07704c7835 feat(sdk): Put locks around RoomLatestEvents.
This patch tries to solve a problem raised by Complement Crypto. In
`compute_latest_events`, in two places, `RegisteredRooms::rooms` was
locked with an exclusive write access. Then, during the updates of
the latest events (via `RoomLatestEvents::update_with_event_cache` and
`RoomLatestEvents::update_with_send_queue`), the state store lock is
acquired to update the state store. At the same time, in the sync, the
state store lock can **already** be taken to store the new updates, and
the function `subscribe_to_room_latest_events` is called, which waits
on the lock around `RegisteredRooms::rooms` to be available. We have a
dead lock:

- `compute_latest_events` waits on the state store lock while the lock
  on `RegisteredRooms::rooms` is taken with an exclusive access,
- the sync has acquired the state store lock while it waits on the lock
  on `RegisteredRooms::rooms`

This patch introduces a lock inside `RoomLatestEvents`. A new
`RoomLatestEventsState` type is introduced to be the lockable value. A
new `RoomLatestEvents::read()` and `RoomLatestEvents::write()` methods
are introduced to respectively return a `RoomLatestEventsReadGuard` and
a `RoomLatestEventsWriteGuard` type. The idea is to abstract a bit the
owned lock guard and to distribute the methods that were previously on
`RoomLatestEvents` in the new guard types, so that we keep the `&self`
and `&mut self` semantics, plus we take a lock for multiple operations.

The deadlock is fixed because of all the following reasons combined:

- only `RegisteredRooms::room_latest_event`,
  `RegisteredRooms::forget_room` and `RegisteredRooms::forget_thread`
  take a write lock over `RegisteredRooms::rooms`,
- `compute_latest_events` no longer takes a write lock over
  `RegisteredRoooms::rooms`, but it also has a short-lived lock:
  its read lock is dropped as soon as the **owned** write lock over
  `RoomLatestEvents` is taken.

Now, `subscribe_to_room_latest_events` can acquire a write lock over
`RegisteredRooms::rooms` while `compute_latest_events` is doing the
updates. If the state store lock is acquired here, it will just wait
its availability: the sync flow can finish and release the lock without
being blocked by `subscribe_to_room_latest_events`.
2025-09-24 14:26:14 +02:00
Ivan Enderlin 60c3b3dd43 chore(base): Scope the lock guard to a block.
This patch ensures that the state store lock guard (`_sync_lock`) is
scoped to a block so that it cannot live too long.
2025-09-24 14:26:14 +02:00
Ivan Enderlin d00cfb0ba8 fix(base): Take the state store lock before updating it.
This patch updates
`BaseClient::receive_sync_response_with_requested_required_states` to
take the state store lock before applying any change onto it.
2025-09-24 14:26:14 +02:00
Ivan Enderlin f7e1866bda feat(sdk,ui): Automatically subscribe to LatestEvents if EventCache has subscribed. 2025-09-24 14:26:14 +02:00
Ivan Enderlin b316c534ea feat(sdk): Call LatestEvents::listen_to_room for rooms in sync response.
This patch updates `SlidingSyncResponseProcessor::handle_room_response`
to automatically call `LatestEvents::listen_to_room` based on
`http::Response`'s `rooms`.

Why? Because when a sync is received, we want its `LatestEventValue`
to be computed, so that it can trigger a `RoomInfoNotableUpdate`,
which will update the `RoomList`, which will re-sort the
rooms. So far, `LatestEvents::listen_to_room` was called by
`RoomListService::subscribe_to_rooms`, but it's possible to receive a
room update via the sync for a room that is not subscribed, i.e. out
of the viewport of the room list in Matrix clients (it's recommended
to subscribe to rooms that “enter” the viewport). Without this
patch, rooms are receiving updates but the room list is not entirely
refreshed/recalculated.
2025-09-24 14:26:14 +02:00
multi prise fa7fd5df42 Remove unusual import 2025-09-24 12:35:17 +02:00
multi prise adc8276162 Add key opening logic to the media store 2025-09-24 12:35:17 +02:00
multi prise abecb33e34 Lint code 2025-09-24 12:35:17 +02:00
multi prise b26ce417f0 Add comments documenting the new structure 2025-09-24 12:35:17 +02:00
multi prise 5a1bd54bb1 Implement use of Zeroizing struct for string 2025-09-24 12:35:17 +02:00
multi prise 0d0e2aa472 Add ZeroiseOnDrop trait to secret and make the key a Box 2025-09-24 12:35:17 +02:00
multi prise 88ed0afcb3 Replace missing line 2025-09-24 12:35:17 +02:00
multi prise 9938ab8b1f Reimplement previous tests for the store and on top of the one testing the opening with a key 2025-09-24 12:35:17 +02:00
multi prise eed7384934 Remove some superfluous change 2025-09-24 12:35:17 +02:00
multi prise 32255cd178 Update changelog 2025-09-24 12:35:17 +02:00
multi prise c51536a054 reformat 2025-09-24 12:35:17 +02:00
multi prise 6099928b40 Remove conditional logic for running tests 2025-09-24 12:35:17 +02:00
multi prise fac1f295b2 Correct wrong borrow 2025-09-24 12:35:17 +02:00
multi prise 24d02a72e3 implement zeroizing of secrets after use 2025-09-24 12:35:17 +02:00
multi prise 2a073043fd Revert "Use of lifetime in order to not clone/copy the data"
This reverts commit 009ee3a0e5fdff1332aaf0b1e62ab2577d728b82.
2025-09-24 12:35:17 +02:00
multi prise c5b35209b3 Revert "Update matrix-sdk with new lifetimes"
This reverts commit 3a8f5f110cd755158e3a5605a282556da6060417.
2025-09-24 12:35:17 +02:00
multi prise 8e759befd3 Refactorize tests config to correspond with the new api 2025-09-24 12:35:17 +02:00
multi prise 9faffa5b10 Temporary comment insecure function 2025-09-24 12:35:17 +02:00
multi prise 31200357a0 Update matrix-sdk with new lifetimes 2025-09-24 12:35:17 +02:00
multi prise 75b8c9fe93 Use of lifetime in order to not clone/copy the data 2025-09-24 12:35:17 +02:00
multi prise 004d98230c Uncomment the config directives and allows test to run faster by usinf an insecure export function 2025-09-24 12:35:17 +02:00
multi prise eb37a0d2e1 Update some expect text to make sure they reflect the use of secret instead of a only a key to encrypt a store 2025-09-24 12:35:17 +02:00
multi prise 8a0e61e95b correct comment on the state store file 2025-09-24 12:35:17 +02:00
multi prise a2e2765298 correct comment on the crypto store file 2025-09-24 12:35:17 +02:00
multi prise 6e1e0981b1 remove typo 2025-09-24 12:35:17 +02:00
multi prise b2120a8f3d Update changelog to represent changes 2025-09-24 12:35:17 +02:00
multi prise 5d169ae765 Comment the use 2025-09-24 12:35:17 +02:00
multi prise 34dd7ea3cd Revert get_or_create_store_cypher to use 2025-09-24 12:35:17 +02:00
multi prise 4930c589a8 Refactorize SqliteStoreConfig::key and SqliteStoreConfig::passphrase method 2025-09-24 12:35:17 +02:00
multi prise f6d2e73cab More reformarting of files 2025-09-24 12:35:17 +02:00
multi prise 2bd5ec30d1 Correct some tests 2025-09-24 12:35:17 +02:00
multi prise 425b502977 Format files 2025-09-24 12:35:17 +02:00
multi prise 0dfecd78d6 Updated the store encryption to use a enum Secret instead of passphrase 2025-09-24 12:35:17 +02:00
multi prise 4754ac2cbf Updated changelog 2025-09-24 12:35:17 +02:00
multi prise 72bb452b5b Remove all passphrase mention 2025-09-24 12:35:17 +02:00
multi prise c17dbf9ebe Replace the passphrase logic with a key logic in the implementation of encrypted store 2025-09-24 12:35:17 +02:00
multi prise 5fd7c9e179 Add the key logic to the SqliteStoreConfig struct 2025-09-24 12:35:17 +02:00
Hubert Chathi 840ce43fed Add function to check if the user has another device to verify against (#5699)
Part of the fix for
https://github.com/element-hq/element-x-android/issues/4864 and
https://github.com/element-hq/element-x-ios/issues/4190

Allows applications to determine whether the user can verify against
another device in order to cross-sign their new device.
2025-09-24 11:31:42 +01:00
Benjamin Bouvier e71d565346 test: add a test for is_threaded when the focused timeline points to an in-thread event 2025-09-23 17:43:22 +02:00
Benjamin Bouvier 5a06f5f351 test: use a RoomContextResponseTemplate builder pattern to create responses to /context 2025-09-23 17:43:22 +02:00
Benjamin Bouvier 1e01e3fc62 refactor(timeline): don't allocate a vector for the in-thread events when initially loading a thread permalink 2025-09-23 17:43:22 +02:00
Jorge Martín bcba5f4571 chore(doc): Add changelogs 2025-09-23 14:13:34 +02:00
Jorge Martín 7736b50c04 fix(ui): Fix tests again.
The wrong pair of 'from' tokens were used for the forward paginations in the unit test
2025-09-23 14:13:34 +02:00
Jorge Martín 6e1dc121a5 fix(sdk+ui): Expose TimelineController::focus so the right backwards pagination case is used for focused thread pagination
Before, the same pagination as for the live thread timeline was used by mistake.

Fix the tests and check the right tokens are used for `/relations`.
2025-09-23 14:13:34 +02:00
Jorge Martín 08f0200174 refactor(sdk): Use .expect to unwrap the AnyPaginator, remove PaginationError::NotInstantiated
Also, improve the legibility of some usages
2025-09-23 14:13:34 +02:00
Jorge Martín eda561e00e refactor(ffi): Replace derefs and add doc comment to thread_root_event_id 2025-09-23 14:13:34 +02:00
Jorge Martín 578320cefc fix(sdk): Make sure we only include the events received from the /context request that are part of the thread in the case where the event focus is for a threaded event
Modify the tests and mocks so this filtering is checked.
2025-09-23 14:13:34 +02:00
Jorge Martín 59ed28d3f8 refactor(sdk): add several helper functions to the AnyPaginator wrapper, use them where needed
Move `hide_threaded_events` from `TimelineEventFocusKind::Event` to `AnyPaginator::Unthreaded`
2025-09-23 14:13:34 +02:00
Jorge Martín a0eecac8e0 chore(doc): Fix ThreadedEventsLoader::paginate_forwards docs 2025-09-23 14:13:34 +02:00
Jorge Martín 27ba6d070b feat(ffi): Expose TimelineEvent::thread_root_event_id 2025-09-23 14:13:34 +02:00
Jorge Martín 14ca34b09b feat(ffi): Add Room::load_or_fetch_event to the FFI layer
This way we can retrieve random events in a room and check their properties - this is needed to decide whether a permalink for an event should open in a thread or not
2025-09-23 14:13:34 +02:00
Jorge Martín 2166de7b0d refactor(sdk+ui): Make TimelineFocus::Event { paginator } generic
This way we can have the same focus handling both the focused event pagination in the main timeline with the `Paginator` and the focused event pagination in a thread with `EventThreadsLoader`.

The actual paginator is populated in `TimelineController::init_focus` after we call `/context` and can check if the event is part of a thread.
2025-09-23 14:13:34 +02:00
Jorge Martín fd66ae9226 feat(sdk): Add forwards pagination to ThreadEventsLoader
Make the existing `token` field a new `tokens` one with `PaginationTokens` type.

# Conflicts:
#	crates/matrix-sdk/src/paginators/thread.rs
2025-09-23 14:13:34 +02:00
Shrey Patel 56100dfa00 chore(search): Update README. 2025-09-23 11:27:55 +02:00
Benjamin Bouvier 2b567e18bc refactor(timeline): don't require an ExactSizeIterator on replace_with_initial_remote_events
This is only used to know if the new events list is empty or not, which
we can figure thanks to a peekable iterator.
2025-09-22 15:31:02 +02:00
Benjamin Bouvier a5e84230c7 chore(ffi): rejigger recent emoji code around so as to work with default features disabled
For some reason, running `cargo xtask ci clippy` locally would now fail,
complaining that the recent emoji functions didn't exist, in the FFI
layer. I suspect it's because some of the uniffi derive macro to export
functions incorrectly propagates the `cfg` guards; so a solution is to
move all this code under a new mod, that's enabled if and only if the
feature's enabled.
2025-09-22 11:42:10 +02:00
Benjamin Bouvier bf4a46e8de chore: rename a few badly named variables in the sql event cache store 2025-09-22 11:20:49 +02:00
Ivan Enderlin 659ae57218 fix(ui): room_list::sorters::recency is no longer based on 2 data.
This patch fixes an issue where the `recency` sorter is based on either
the latest event's timestamp, or the room recency stamp. This cannot
work with a sort algorithm as the position of a particular room can
be different based on what it is compared to (i.e. if the rooms have a
latest event value or not).

This patch updates the `recency` sorter to only use the recency stamp
for now, as the latest event is not yet computed for all rooms.
2025-09-19 17:39:05 +02:00
Shrey Patel dff6cb4414 refactor(search): Move RoomIndexBuilder into a submodule of index. 2025-09-19 16:37:09 +02:00
Shrey Patel 76348977d4 feat(search): Add encrypted search index support. 2025-09-19 16:37:09 +02:00
Shrey Patel a66e6822ed refactor(search): Add RoomIndexBuilder to create RoomIndex. 2025-09-19 16:37:09 +02:00
Shrey Patel b494303c07 feat(search): Implement an encrypted wrapper for a tantivy::directory::MmapDirectory. 2025-09-19 16:37:09 +02:00
Ivan Enderlin 0f0e37b677 chore: Update eyeball-im, eyeball-im-util and imbl.
This patch updates `eyeball-im` to 0.8.0, `eyeball-im-util` to 0.10.0
and `imbl` to 6.1.0.

The idea is to fix this bug https://github.com/jplatte/eyeball/pull/80.
2025-09-19 16:17:31 +02:00
Ivan Enderlin fc12a7340f chore(ui): Add a temporary entries_with_dynamic_adapters_with.
This patch adds a temporary
`RoomList::entries_with_dynamic_adapters_with` method to help debug an
issue in Element X.
2025-09-19 15:55:57 +02:00
Shrey Patel 80390346b1 feat(multiverse): Add search indexing at startup. 2025-09-19 14:25:02 +02:00
Shrey Patel 4b87dfea0b feat(sdk): Lazily create RoomIndex on search. 2025-09-19 14:25:02 +02:00
Shrey Patel 79aa0ab60d feat(search): Add bulk processing. 2025-09-19 14:25:02 +02:00
Shrey Patel a8ef44306a refactor(sdk): Move search index related code into its own module. 2025-09-19 14:25:02 +02:00
Shrey Patel b929f3e569 fix(search): Remove unused IndexError variants. 2025-09-19 14:25:02 +02:00
Ivan Enderlin 1c737e6569 bench: Run the room_list benchmark in the CI. 2025-09-19 11:06:45 +02:00
Benjamin Bouvier 8ae88e1e45 refactor(sdk): make the update_in_memory_caches method infallible 2025-09-18 18:00:27 +02:00
Benjamin Bouvier 768f9bfdb6 doc: fix a typo in a doc comment of invite_acceptance_details 2025-09-18 18:00:27 +02:00
Benjamin Bouvier 864d6c1a43 perf: avoid recomputing room notification modes on every sync
Some background knowledge: the room notification modes are functions of
the push rules events, and they will only change when the push rules
event changes. As a result, there are only two cases where we need to
recompute them:

- when the push rules event changed, we need to recompute all the room
notification modes, in case one has changed;
- when we run into a new room, we need to compute an initial value for
its room notification modes.

Based on these observations, this improves the code to avoid recomputing
the room notification mode on every single sync response. Instead,
they're computed if and only if the push rules event has changed, or for
new rooms only.

Also, this avoids reconstructing one `NotificationSettings` object per
room, since this would load from the database each time. Instead, a
single object is created (at most), and its `Rules` object is directly
accessed, to avoid repeatedly taking the lock on its internal `rules`
field.

This makes it so that the time spent under this method from tens of
milliseconds to less than 1 millisecond, in testing. See the pull
request initial post for more numbers.
2025-09-18 18:00:27 +02:00
Benjamin Bouvier da70aea5b0 feat(multiverse): allow not sharing pos at start
This is helpful to reproduce an initial sync response, and observe how
long it takes to process.
2025-09-18 18:00:27 +02:00
Benjamin Bouvier 1a9c7d5e2f test: add regression test that even if a room isn't in a sync response, its notification mode may be updated 2025-09-18 18:00:27 +02:00
Doug 9cd7760858 ffi: Expose is_direct on SpaceRoom. 2025-09-18 18:56:03 +03:00
Benjamin Bouvier 8575ed3f64 chore: define FrozenSlidingSyncPos only if the e2e-encryption feature is enabled
This caused compilation errors in other PRs, since the introduction of
Rust 1.90, which was able to detect this was unused otherwise.
2025-09-18 17:27:25 +02:00
Ivan Enderlin 1834f36136 doc(ui): Update the CHANGELOG.md file. 2025-09-18 15:38:16 +02:00
Ivan Enderlin 0bbefa000b chore(ui): Rename room_list_service::Room to RoomListItem.
This patch renames the `Room` type in `room_list_service` to
`RoomListItem` to avoid confusion with `matrix_sdk::Room`.
2025-09-18 15:38:16 +02:00
Ivan Enderlin a84c97b292 feat(ui): Introduce room_list_service::Room to cache data.
This patch improves throughput by +710% in `room_list_service` sorters
and filters. It introduces a new `room_list_service::Room` type that
derefs to `matrix_sdk::Room`. However, it **caches** some data from
`matrix_sdk::Room`. Why doing so? Because filters, but more specifically
sorters!, are calling methods on `matrix_sdk::Room`, so likely on
`matrix_sdk::RoomInfo`, quite intensively. `RoomInfo` is behind a
`SharedObservable`, which means it's behind a lock. Each time a sorter
sorts 2 rooms, the lock on `RoomInfo` can be called twice or more.

By caching the data, `RoomInfo` is reached once per refresh data, but
not during the filtering nor the sorting. It greatly reduces contention
on the `RoomInfo` lock, which improves the throughput by +710%, and the
time by -87%.

The cached data are refreshed in `merge_stream_and_receiver` when
(i) the stream of `Room` is updated, or when (ii) the stream of
`RoomInfoNotableUpdate` is updated. It's a central place it happens,
which isolates the behaviour.
2025-09-18 15:38:16 +02:00
Ivan Enderlin 94267d9597 chore(base): Rename Room::inner to Room::info.
This patch renames the `Room::inner` containing the `RoomInfo` to
`Room::info`.
2025-09-18 15:38:16 +02:00
Ivan Enderlin 62eb1996d9 feat(base): Add new_latest_event_timestamp and new_latest_event_is_local.
This patch adds 2 methods on `RoomInfo`: `new_latest_event_timestamp`
and `new_latest_event_is_local`, which respectively returns
`LatestEventValue::timestamp` and `LatestEventValue::is_local`. The goal
is to avoid cloning a `LatestEventValue` when it's useless. For example,
in the room list sorters!

This patch also updates the room list sorters `recency` and
`latest_event` to use these new methods. It improves the speed and
throughput by 18%.
2025-09-18 15:38:16 +02:00
Ivan Enderlin ec5c31a19d chore(bench): Add the room_list benchmark.
This patch adds the `room_list` benchmark. The goal is to measure
the time it takes to create a room list, to sort it, to filter and to
“display” it.
2025-09-18 15:38:16 +02:00
Jorge Martín 7e474c3a52 refactor(sdk): Remove UpdateGlobalAccountDataEndpoint and GlobalAccountDataEndpoint
These are meant to be replaced with specific endpoints for each case.

Added the `global_account_data_mock_builder` helper function to make building these mock endpoints a bit easier.
2025-09-18 13:12:49 +02:00
Jorge Martín 8706ad74b3 refactor(sdk): Immediately truncate the recent emoji list
This way we'll always work with a list of at most `MAX_RECENT_EMOJI_COUNT` items, which should ensure performance is good enough.
2025-09-18 13:12:49 +02:00
Jorge Martín 3cc88e5008 test(sdk): Redo matching the request body for the update recent emoji endpoint 2025-09-18 13:12:49 +02:00
Jorge Martín a937780623 fix(sdk): Use a max recent emoji count of 100
This prevents the account data from growing indefinitely and makes sure values that aren't recently used can be forgotten.
2025-09-18 13:12:49 +02:00
Benjamin Bouvier 9dc27698dd chore: address new clippy recommendations 2025-09-18 12:01:48 +02:00
Benjamin Bouvier 49d72cd992 chore: run rustfmt after moving to the next edition of matrix-sdk 2025-09-18 12:01:48 +02:00
Benjamin Bouvier 98e799da80 chore: remove unnecessary bindings modifiers
match ergonomics ftw! in the 2024 edition, these are necessary only when
the capturing mode is `move`.
2025-09-18 12:01:48 +02:00
Benjamin Bouvier ea386c9e64 chore: specify explicitly that some stream/futures don't capture any lifetime 2025-09-18 12:01:48 +02:00
Benjamin Bouvier bba2af9882 refactor: don't use the reserved keyword gen in tests
I *think* these compare a generated URL against an expected one, but the
naming is a bit strange in those tests. It's just tests, after all.
2025-09-18 12:01:48 +02:00
Benjamin Bouvier 51934dd249 test: stop using the unit never type fallback
By making it explicit that the async closures return a unit type, we can
get rid of these two allow lines, and prepare for migrating this code to
the 2024 edition.
2025-09-18 12:01:48 +02:00
Benjamin Bouvier 07924ad4e4 chore: bump matrix-sdk to edition 2024 2025-09-18 12:01:48 +02:00
Michael Goldenberg 1312a27597 feat(indexeddb): add IndexedDB-backed impls for getting, adding, replacing, and removing media
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 17:03:39 +02:00
Michael Goldenberg 038207870a refactor(indexeddb): add media-related functions to media store transaction type
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 17:03:39 +02:00
Michael Goldenberg eb62ac9fad refactor(indexeddb): use UnixTime type to represent Media::last_access
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 17:03:39 +02:00
Michael Goldenberg 79154bd03d refactor(indexeddb): add type for representing time relative to unix epoch
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 17:03:39 +02:00
Michael Goldenberg c7990e6e33 refactor(indexeddb): replace media source index with media uri index
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 17:03:39 +02:00
Michael Goldenberg c839c01205 refactor(indexeddb): add fns for putting an item into IndexedDB if the serialized value satisfies predicate
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 17:03:39 +02:00
Michael Goldenberg 62d2d0ff94 fix(indexeddb): fix import in state store migration tests
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 14:00:43 +02:00
Michael Goldenberg bcae429062 refactor(indexeddb): tweak features and imports to ensure types and traits are only available when they are needed
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 14:00:43 +02:00
Michael Goldenberg b0e9f3c666 refactor(indexeddb): expose safe encode trait even when e2e-encryption feature is not enabled
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 14:00:43 +02:00
Michael Goldenberg a325105190 feat(indexeddb): add experimental encrypted state events feature to quiet warning about using non-existent features in crypto store
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 14:00:43 +02:00
Michael Goldenberg 3835a7ff94 refactor(indexeddb): remove unused imports from transaction module
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 14:00:43 +02:00
Michael Goldenberg ead9400702 refactor(indexeddb): allow dead code in transaction and indexed type serializer modules while media store under development
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 14:00:43 +02:00
Michael Goldenberg 78172bb7b6 refactor(indexeddb): remove unused imports and dead code from event cache store module
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 14:00:43 +02:00
Michael Goldenberg bbf2164ab2 refactor(indexeddb): allow dead code in event cache store builder until it is publicly exposed
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 14:00:43 +02:00
Michael Goldenberg 46a2ee6177 fix(indexeddb): handle result in event cache store migrations
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 14:00:43 +02:00
Michael Goldenberg f1caf8f27f refactor(indexeddb): remove unused imports in media store module
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 14:00:43 +02:00
Michael Goldenberg 60bfc48b6b refactor(indexeddb): allow dead code in media store module as it is still under development
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 14:00:43 +02:00
Michael Goldenberg afa339c02b refactor(indexeddb): remove extraneous core object store from event cache store migrations
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 14:00:43 +02:00
Michael Goldenberg c9b7fc7007 refactor(indexeddb): rename serializer types modules to indexed_types
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 14:00:43 +02:00
Michael Goldenberg 2f6bb3a1eb refactor(indexeddb): move module-specific constants into their own modules
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 14:00:43 +02:00
Michael Goldenberg a259860221 refactor(indexeddb): deduplicate constants for types from std
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 14:00:43 +02:00
Michael Goldenberg 2765c18e61 refactor(indexeddb): move custom bool serializer into serializer module
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 14:00:43 +02:00
Michael Goldenberg b44b6478c0 refactor(indexeddb): nest generalized transaction in event cache store transaction
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 14:00:43 +02:00
Michael Goldenberg 0bebf144d1 refactor(indexeddb): nest generalized transaction in media store transaction
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 14:00:43 +02:00
Michael Goldenberg 975b08c019 refactor(indexeddb): add generalized transaction type and error
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 14:00:43 +02:00
Michael Goldenberg 453613c13f refactor(indexeddb): deduplicate async error deps trait
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 14:00:43 +02:00
Michael Goldenberg cb94969e2a refactor(indexeddb): deduplicate serializer traits and types
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 14:00:43 +02:00
Michael Goldenberg 0e4e4eae2b refactor(indexeddb): rename IndexeddbMediaStoreSerializer{Error} to IndexedTypeSerializer{Error}
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 14:00:43 +02:00
Michael Goldenberg b9410dff61 refactor(indexeddb): rename IndexeddbEventCacheStoreSerializer{Error} to IndexedTypeSerializer{Error}
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 14:00:43 +02:00
Michael Goldenberg 013bb9a5ac refactor(indexeddb): rename IndexeddbSerializer to SafeEncodeSerializer
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 14:00:43 +02:00
Michael Goldenberg 891ed0efff refactor(indexeddb): move SafeEncode-related traits and types into their own module
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-09-17 14:00:43 +02:00
Ivan Enderlin 2e3be13b4d fix(ui): The recency room list sorter stops using the latest event's timestamp.
This patch updates the `recency` room list sorter to no longer
use the `LatestEventValue::timestamp` method. It keeps using the
`Room::recency_stamp` for the moment, as it was the case before. This
patch is a test to try finding the problem in some Matrix clients where
the room list becomes unusable. We suspect it's because of this patch
sorter.
2025-09-17 13:32:42 +02:00
Ivan Enderlin efcb7125ad chore(ffi): Define new log target for deserialized_responses.
This patch defines a new log target,
`MatrixSdkCommonDeserializedResponses`. It is enabled by the
`SyncProfiling`, `EventCache` or `Timeline` log packs.

This patch also changes the level of the log in
`TimelineEvent::timestamp` from `trace` to `warn`.
2025-09-17 11:33:14 +02:00
Valere Fedronic 681863423c feat(rtc): Remove deprecated CallNotify in favour of RtcNotification
`CallNotify` event has been deprecated in favour of `RtcNotification` event https://github.com/ruma/ruma/pull/2199
2025-09-16 16:06:39 +02:00
Stefan Ceriu 8c6922d5a9 feat(spaces): use the space children_state received from /hierarchy to populate children via parameters and expose them on SpaceRooms 2025-09-16 12:55:58 +03:00
Benjamin Bouvier 8c60ef2635 refactor(timeline): more refactorings around timeline focus
This includes a new code location where we'd need to handle
permalink-in-thread differently, and reduces the number of matches on
the focus kind.
2025-09-16 11:51:10 +02:00
Benjamin Bouvier 3e9e74a888 feat(timeline): add a public is_threaded() method to know if a timeline is focused on a thread or not
And use fewer `matches!` statements to figure whether a timeline is
threaded or not, or what its thread root is.
2025-09-16 11:51:10 +02:00
Benjamin Bouvier a06403c12f refactor(timeline): use a getter to figure if a focus is on a thread
This will pave the way for permalink targets which are for events in a
thread, by making it possible to add a future condition in the
`TimelineFocusKind::Event` case (if the pagination used under the hood
is using /relations, then it's a thread).
2025-09-16 11:51:10 +02:00
Ivan Enderlin d3a7d26c7d chore(common): Add log in TimelineEvent::timestamp.
This patch adds a log in `TimelineEvent::timestamp` when the `timestamp`
has to be extracted. It can be a performance problem depending on when
it's called.
2025-09-16 10:54:05 +02:00
Benjamin Bouvier e83f37e68b test: add test for the previous commit 2025-09-15 18:02:34 +02:00
Benjamin Bouvier efda12058f fix(room service): enable the thread subscriptions extension iff the server advertises support for it 2025-09-15 18:02:34 +02:00
dependabot[bot] f12ee861b0 chore(deps): bump tj-actions/changed-files from 46.0.5 to 47.0.0
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 46.0.5 to 47.0.0.
- [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/v46.0.5...v47.0.0)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-version: 47.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-15 17:22:23 +02:00
dependabot[bot] 48cc68c466 chore(deps): bump CodSpeedHQ/action from 4.0.0 to 4.0.1
Bumps [CodSpeedHQ/action](https://github.com/codspeedhq/action) from 4.0.0 to 4.0.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/6eeb021fd0f305388292348b775d96d95253adf4...653fdc30e6c40ffd9739e40c8a0576f4f4523ca1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-15 17:15:01 +02:00
Skye Elliot acaff39594 fix(crypto): Report inner-outer state key differences as invalid.
Signed-off-by: Skye Elliot <actuallyori@gmail.com>
2025-09-15 15:13:25 +02:00
Stefan Ceriu bdc564bb55 chore(spaces): rewrite /hierarchy room list filtering and parent space extraction to avoid mixing responsibilities. 2025-09-15 15:31:49 +03:00
Stefan Ceriu ac68c4a47d chore(ffi): expose the new SpaceRoomList space and its respective updates publisher. 2025-09-15 15:31:49 +03:00
Stefan Ceriu 75a5c19f91 chore(spaces): have SpaceRoom::new_from_known work with a reference and reduce the number of clones required. 2025-09-15 15:31:49 +03:00
Stefan Ceriu 7a53615d80 feat(spaces): expose an is_direct flag on SpaceRooms 2025-09-15 15:31:49 +03:00
Stefan Ceriu 802e137ae5 chore(spaces): align on calling the owner of the room list a space and not a parent_space 2025-09-15 15:31:49 +03:00
Stefan Ceriu 68cb3fb6a4 feat(spaces): expose the parent space on the SpaceRoomList 2025-09-15 15:31:49 +03:00
Jorge Martín cc1fbf9882 refactor(ffi): Make EC optional parameters default to None
This is done so we can just use:

```kotlin
VirtualElementCallWidgetConfig(intent = ...)
```

Instead of:

```kotlin
VirtualElementCallWidgetConfig(
    intent = ...,
    skipLobby = null,
    header = null,
    hideHeader = null,
    preload = null,
    ...
)
```
2025-09-15 11:21:49 +02:00
Richard van der Hoff 89c1c8e4fa changelog 2025-09-12 17:13:08 +01:00
Richard van der Hoff 423f15a125 test(crypto): add a test for verification_request_content 2025-09-12 17:13:08 +01:00
Richard van der Hoff 4f2cd1c5ec crypto(test): utility function for creating VerificationMachines 2025-09-12 17:13:08 +01:00
Andy Balaam 8a6c4fdcb4 Return a MessageType from verification_request_content 2025-09-12 17:13:08 +01:00
Richard van der Hoff b8cbd6c448 Merge pull request #5654 from matrix-org/rav/identity_test_cleanups
crypto: Simplify `PrivateCrossSigningIdentity::with_account`
2025-09-12 14:21:19 +01:00
Ivan Enderlin 5ccbc1c378 fix(sqlite): Empty the cache after the introduction of TimelineEvent::timestamp.
After the merge of
https://github.com/matrix-org/matrix-rust-sdk/pull/5648, we want
all events to get a `TimelineEvent::timestamp` value (extracted from
`origin_server_ts`).

To accomplish that, we are emptying the event cache. New synced events
will be built correctly, with a valid `TimelineEvent::timestamp`,
allowing a clear, stable situation.
2025-09-12 15:06:46 +02:00
Richard van der Hoff 0002ea46ab crypto: inline PrivateCrossSigningIdentity::with_account
This was now only used in one place, and I think it makes more sense to inline
it into olm::Account than leave it in `PrivateCrossSigningIdentity`.
2025-09-12 14:04:02 +01:00
Richard van der Hoff bb46dc74d0 crypto: Factor out PrivateCrossSigningIdentity::for_account
It turns out that creating a cross-signing identity *without* the upload
requests is a very common thing to do, especially in tests. We can simplify
some code by factoring it out as a new helper.
2025-09-12 14:04:02 +01:00
Shrey Patel c2bc465c06 feat(base): Add get_room_events to EventCacheStore trait and impls. 2025-09-12 12:59:22 +02:00
Ivan Enderlin b788ba0d73 feat(base) LatestEventValue::timestamp uses the new TimelineEvent::timestamp method.
This patch updates `LatestEventValue::timestamp` to use
the new `TimelineEvent::timestamp` method in case of a
`LatestEventValue::Remote`.
2025-09-12 11:43:49 +02:00
Ivan Enderlin 53a74f3949 feat(common): Add TimelineEvent::timestamp.
This patch adds the `timestamp` field to `TimelineEvent`.
It's a copy of the `origin_server_ts` value, parsed as an
`Option<MilliSecondsSinceUnixEpoch>`. It's `None` if the parsing failed,
or if the `TimelineEvent` was deserialised from a version before this
new field was added.

A new `extract_timestamp` function is added for this purpose. It
protects against malicious `origin_server_ts` where the value can be
set to year 2100 for example. The only protection we are adding here is
to take the `min(origin_server_ts, now())`, so that the event can never
been “in the future”.

It doesn't protect against a malicious value like 0. It's non-trivial to
define a minimum timestamp for an event.

When a `TimelineEvent` is mapped from one kind to another kind, the
`timestamp` is carried over. To achieve that, new `to_decrypted` and
`to_utd` methods are added.

The rest of the code is updated accordingly.
2025-09-12 11:43:49 +02:00
Damir Jelić 215ca3d798 Merge pull request #5641 from matrix-org/feat/element-recent-emojis
feat: Add Element recent emojis for shared emoji reactions
2025-09-12 11:32:12 +02:00
Jorge Martín 87032a36bd fix: use the same ordering as in Element Web: first, sort by count descending, then by recency for items with equal count 2025-09-12 08:21:14 +02:00
Jorge Martín 4f881b55f9 refactor: link to the Element Web implementation 2025-09-12 08:21:14 +02:00
Jorge Martín bb9bdee4a7 refactor: fix doc comment issues 2025-09-12 08:21:14 +02:00
Jorge Martín 4216ec6113 test: Add serialization and deserialization tests for recent emojis 2025-09-12 08:21:14 +02:00
Jorge Martín b2df2742bd refactor: Move the recent emoji functions from client to account.
Also, make sure updating the emojis first fetches the most up-to-date data.
2025-09-12 08:21:14 +02:00
Jorge Martín 163ed929fe refactor: Add docs to test helper 2025-09-12 08:21:14 +02:00
Jorge Martín 9776ae6acd refactor: rename feature to experimental-element-recent-emoji 2025-09-12 08:21:14 +02:00
Jorge Martín 68c2b89bf5 fix: Fix clippy 2025-09-12 08:21:14 +02:00
Jorge Martín b744e5789a fix: Fix test in doc comment 2025-09-12 08:21:14 +02:00
Jorge Martín 231840f6ae refactor: Make the ci task also use the element-recent-emojis feature for different tasks 2025-09-12 08:21:14 +02:00
Jorge Martín fc224b17c7 refactor(ui): Make toggle_reaction return a bool so we know if the emoji was added or removed 2025-09-12 08:21:14 +02:00
Jorge Martín 5522509e6b feat(ffi): Add bindings for the recent emojis 2025-09-12 08:21:14 +02:00
Jorge Martín 89fd0b5e53 feat(sdk): Allow adding and retrieving recent emojis
Include some tests.
2025-09-12 08:21:14 +02:00
Jorge Martín eee1fa2b71 feat(sdk-base): Add Element recent emojis event
Add a feature for it too
2025-09-12 08:21:14 +02:00
Timo K 62763ca000 fix element call url "intent" serialization
Signed-off-by: Timo K <toger5@hotmail.de>
2025-09-12 07:52:37 +03:00
Johannes Marbach 5e573417cb fix(timeline): avoid replacing timeline items when the encryption info is unchanged
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-09-11 15:48:39 +01:00
Damir Jelić c3621f2bd1 Merge branch 'release-0.14' 2025-09-11 13:34:56 +02:00
Damir Jelić 0eac2a099f chore(base): Add the CVE ID for the power level panic to the changelog 2025-09-11 13:33:33 +02:00
Kévin Commaille 90eb403c18 sqlite: Drop media table from event cache store
Since the media store was split into a separate database.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-09-11 11:43:04 +01:00
Ivan Enderlin 878e02b652 doc(common): Rephrase the documentation of cross_process_lock a little bit. 2025-09-10 20:35:45 +02:00
Ivan Enderlin bbe8f17b1a refactor(common): Rename LockStoreError to CrossProcessLockError.
This patch renames the `LockStoreError` enum to `CrossProcessLockError`
to be consistent with the other types in the same module.

The `BackingStoreError` variant is also renamed to `TryLockError`.
2025-09-10 20:35:45 +02:00
Ivan Enderlin f65bb6016c refactor(common): Rename store_locks to cross_process_lock. 2025-09-10 20:35:45 +02:00
Ivan Enderlin 976eacb624 refactor(common): Rename BackingStore to TryLock.
This patch renames the `BackingStore` trait to `TryLock`. It also
renames the `CrossProcessLock::store` field to `locker`. It's not
necessarily a store, it can be anything.
2025-09-10 20:35:45 +02:00
Ivan Enderlin 5fe5cfd85f refactor(common): Rename CrossProcessStoreLock* to CrossProcessLock*.
This patch renames `CrossProcessStoreLock` and
`CrossProcessStoreLockGuard` to `CrossProcessLock` and
`CrossProcessLockGuard`.
2025-09-10 20:35:45 +02:00
Ivan Enderlin 0233ac906e chore(common): Simplify code.
This patch simplifies a code that does a surprising thing. The `#[cfg]`
isn't necessary here.
2025-09-10 20:35:45 +02:00
Damir Jelić 4cc1cd1913 chore(sdk): Better logs for the duplicate one-time key error 2025-09-10 13:09:35 +00:00
Valere 2248bbf6ab fix adding both final and dev id doesn't work with ruma alias 2025-09-10 13:07:48 +02:00
dragonfly1033 2afbdfae0b Split media store from event cache store. (#5568)
This PR is a start to the process of splitting the media store from the
event cache store. #5410

It contains:
* Split `MediaStore` trait from `EventCacheStore`. 
* Rename `EventCacheStoreMedia` to `MediaStoreInner`. 
* Move relevant tests into `MediaStoreIntegrationTests`.

This will be done over 3 PR's (reviewing 1, 2, 3 then merging 3 into 2
into 1).

A reminder comment for my own sanity:
This PR will not pass tests until after merging.

Current state of this PR:
- [x] Step 1 reviewed #5568
- [x] Step 2 reviewed #5569 
- [x] Step 3 reviewed #5571 
- [x] Step 3 merged into Step 2
- [x] Step 2 merged into Step 1
- [ ] Add changes to changelog.
- [ ] Ready to merge 🎉 

Note, may also want to: 
* Re-organize file structure
* Split/refactor benchmarks namely `benchmarks/benches/event_cache.rs`

<!-- 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: Shrey Patel shreyp@element.io

---------

Co-authored-by: Shrey Patel <shreyp@element.io>
2025-09-10 12:03:02 +02:00
Timo d2ca0262ae feat(element-call url params): split url params into configuration and properties (#5560)
This PR is part of an onging effort to move responsiblity to the EC app
and out of the EX apps.

4 intends (f.ex `join_existing` `start_new_dm`... ) (as url paramters)
are introduced in recent element call versions. Those intends behave
like defaults. If an intend is set a set of url parameters are
predefined.
Not all params can be covered by the intend (for insteance the
`widget_id` or the `host_url`).
This PR splits the url parameters into configuration (things that can be
configured by the intent) and properties (things that still need to be
passed one by one)


The goal with this change is that EX only needs to configre the intent
once and the EC codebase can update the behavior in those 4 specific
scenarios in case new features come along (auto hangup when other
participants leave, send call ring notification...)


Signed-off-by: Timo K <toger5@hotmail.de>

<!-- 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:

---------

Signed-off-by: Timo K <toger5@hotmail.de>
2025-09-10 11:46:21 +02:00
Damir Jelić f1064425bd Merge branch 'release-0.14' into poljar/merge-0.14-back-to-main 2025-09-10 11:20:39 +02:00
Damir Jelić 5ef3ecac8c chore: Allow the adler crate despite it being unmaintained 2025-09-10 10:41:18 +02:00
Damir Jelić 6c537d74de chore: Release matrix-sdk-base version 0.14.1 2025-09-10 10:41:18 +02:00
Damir Jelić 476fe5f9d2 fix(base): Fix a panic when we encounter a power level at Int::Min 2025-09-10 10:41:18 +02:00
Damir Jelić 186132c248 test(base): Test how we normalize power levels at the int limits 2025-09-10 10:41:18 +02:00
Damir Jelić 0f90631d4a test(base): Add a proptest to validate our normalize_power_level method 2025-09-10 10:41:18 +02:00
Damir Jelić 80262f2f36 chore: Put the power level normalization logic into a separate function 2025-09-10 10:41:18 +02:00
Damir Jelić 91c5f8a01a chore: Add some missing PR links in our changelog 2025-09-10 10:41:18 +02:00
Valere 502d6d3095 widget capabilities rtc decline test 2025-09-09 18:22:49 +02:00
Valere db4ce0bea5 widget-driver: Add read/send capabilities for rtc decline event 2025-09-09 18:22:49 +02:00
Shrey Patel b0c0e0e0c4 feat(search): Add paginated search. 2025-09-09 16:24:25 +02:00
Ivan Enderlin 5c78ddec13 refactor(ui): Remove intermediate structs.
This patch removes intermediate structs and uses a function directly.
2025-09-09 15:48:39 +02:00
Ivan Enderlin 59a62550e6 refactor(ui): Remove intermediate structs.
This patch removes intermediate structs and uses a function directly.
2025-09-09 15:48:39 +02:00
Ivan Enderlin fd356a9e17 chore(ui): Introduce the notion of _rank_ in the recency sorter.
This patch adds the notion of _rank_ in the `recency` sorter to avoid
confusion around `u64`: is it a timestamp or a recency stamp? It's
purely semantics, but I hope it clarify the code.
2025-09-09 15:48:39 +02:00
Ivan Enderlin fc69b2683f refactor(ui): Rename *Matcher to *Sorter in room_list_service::sorters.
This patch renames all the structure `*Matcher` to `*Sorter`.
And their `matches` method become `cmp`. It was copy-pasted from
`room_list_service::filters` probably, but the semantics here are not
_matcher_ but _sorter_. It's more consistent that `cmp` returns an
`Ordering`.
2025-09-09 15:48:39 +02:00
Ivan Enderlin 5c94177581 doc(base): Fix documentation of recency_stamp. 2025-09-09 15:48:39 +02:00
Ivan Enderlin 0335785e67 feat(base): Introduce the RoomRecencyStamp type.
This patch adds the `RoomRecencyStamp` type to avoid confusion with other
`u64` values.
2025-09-09 15:48:39 +02:00
Ivan Enderlin c860be4969 feat(ui): Use the new latest_event sorter in the room list.
This patch installs the `new_sorter_latest_event` in the room list.
2025-09-09 15:48:39 +02:00
Ivan Enderlin 8ff7e58bc0 doc(ui): Fix a typo. 2025-09-09 15:48:39 +02:00
Ivan Enderlin 01c0775e59 feat(ui): Update the recency sorter to include the LatestEventValue.
This patch updates the `recency` sorter of the room list to rely on the
`LatestEventValue`'s timestamp, or on the `bump_stamp` returned by the
sync. Using the `LatestEventValue`'s timestamp is more reliable as we
don't rely on the server. However, we must be careful to compare values
of the same nature because the timetamp from the `LatestEventValue` and
the `bump_stamp` doesn't represent the same thing! The `bump_stamp` is
only used when the value for the `LatestEventValue` is `None`.

It's a compromise to get a more accurate listing. Though,
`LatestEventValue::timestamp` returns the `origin_server_ts` value,
which can be forged by a malicious user (then a room could be _sticked_
at the top or at the bottom of the room list). Note that this problem
already existed in the past before the server computed a `bump_stamp`.
Also note that some homeservers use the `origin_server_ts` as the
`bump_stamp` value. Anyway, it's not a security risk as far as I know.
2025-09-09 15:48:39 +02:00
Ivan Enderlin bd3ddc19e9 feat(base): Implement LatestEventValue::timestamp.
This patch implements a `LatestEventValue::timestamp` method to fetch
the timestamp of a latest event value.
2025-09-09 15:48:39 +02:00
Ivan Enderlin 8156bc25f8 feat(ui): Add the new latest_event sorter for the room list.
This patch implements the new `latest_event` sorter for the room list
which puts the local latest events before the other kinds (like `Remote`
or `None`).
2025-09-09 15:48:39 +02:00
Damir Jelić 441b006c5f ci: Bump the codspeed action and define our benchmark mode 2025-09-09 14:45:49 +02:00
Damir Jelić ce3b67f801 Update bindings/matrix-sdk-ffi/CHANGELOG.md
Co-authored-by: Ivan Enderlin <ivan@mnt.io>
Signed-off-by: Damir Jelić <poljar@termina.org.uk>
2025-09-09 10:06:21 +02:00
Damir Jelić 260037c4c7 Remove the normalized power level from the bindings
The field is reportedly unused so there's no need to spend time to
calculate the value and pass it over the FFI.
2025-09-09 10:06:21 +02:00
Damir Jelić a93274de36 fix(base): Fix a panic when we encounter a power level at Int::Min 2025-09-09 10:06:21 +02:00
Damir Jelić 77b426e1aa test(base): Test how we normalize power levels at the int limits 2025-09-09 10:06:21 +02:00
Damir Jelić 251530b6f4 test(base): Add a proptest to validate our normalize_power_level method 2025-09-09 10:06:21 +02:00
Damir Jelić 46c7338509 chore: Put the power level normalization logic into a separate function 2025-09-09 10:06:21 +02:00
Damir Jelić 9e2f2b3534 chore: Add some missing PR links in our changelog 2025-09-09 09:47:49 +02:00
dependabot[bot] 03c6dd9bfc chore(deps): bump crate-ci/typos from 1.35.7 to 1.36.2
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.35.7 to 1.36.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.35.7...v1.36.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-08 16:52:16 +02:00
dependabot[bot] 3b7a626b8f chore(deps): bump actions/github-script from 7 to 8
Bumps [actions/github-script](https://github.com/actions/github-script) from 7 to 8.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v7...v8)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-08 16:51:53 +02:00
dependabot[bot] 2e7bea9253 chore(deps): bump actions/setup-node from 4 to 5
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 5.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4...v5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-08 16:50:02 +02:00
dependabot[bot] c7de40b54d chore(deps): bump CodSpeedHQ/action from 3.8.1 to 4.0.0
Bumps [CodSpeedHQ/action](https://github.com/codspeedhq/action) from 3.8.1 to 4.0.0.
- [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/76578c2a7ddd928664caa737f0e962e3085d4e7c...6eeb021fd0f305388292348b775d96d95253adf4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-08 15:49:20 +01:00
Shrey Patel 086233ad5f feat(multiverse): Show messages in search results. 2025-09-08 14:05:28 +02:00
Valere 412b7bbc7b add changelog 2025-09-06 11:15:49 +02:00
Valere 0617c88c1c fix example compilation 2025-09-06 11:15:49 +02:00
Valere 83b390204d Add an example for subscribe_to_call_decline_events 2025-09-06 11:15:49 +02:00
Valere 4ef249dc6e review: quick new lines for clarity 2025-09-06 11:15:49 +02:00
Valere 0aef2559bd review: Remove unneeded msc4310 feature (consistent with msc4075) 2025-09-06 11:15:49 +02:00
Valere 653a00351c review: Remove unneeded generic and remove early returns 2025-09-06 11:15:49 +02:00
Valere 8606ac3dfb fix typo 2025-09-06 11:15:49 +02:00
Valere db5503e30e tests: add decline call tests 2025-09-06 11:15:49 +02:00
Valere c51b4f03a2 misc: clippy fixes 2025-09-06 11:15:49 +02:00
Valere 0fc0a5514d guard test behing feature flag 2025-09-06 11:15:49 +02:00
Valere e83af1aae2 tests: subscribe_to_call_decline_events 2025-09-06 11:15:49 +02:00
Valere ab58c376dd bindings: MSC4310 call decline and subscribe to decline events 2025-09-06 11:15:49 +02:00
Damir Jelić 6c8fb507a2 chore: Allow the adler crate despite it being unmaintained 2025-09-06 11:15:08 +02:00
Ivan Enderlin b1c28f4bc1 feat(ui): sync_service::State::Error contains the cause error.
This patch updates the `State::Error` variant to contain the error that
led to this state.
2025-09-05 22:31:53 +02:00
Ivan Enderlin 6dbdffd36e refactor(ui): sync_service::State no longer implements PartialEq.
This patch removes the `PartialEq` implementation on
`sync_service::State`. It was only used for test purposes. Outside that,
it doesn't make sense.
2025-09-05 22:31:53 +02:00
Ivan Enderlin e45387b65b refactor(ui): Encode more states in type systems.
This patch updates the signature of `TerminationReport`'s
constructors so that it's impossible to create invalid states, like
an `origin` of `TerminationOrigin::RoomList` with an error of type
`encryption_sync_service::Error`. The constructors force the error to
match the origin.
2025-09-05 22:31:53 +02:00
Ivan Enderlin b8803cb465 refactor(ui): Remove TerminationReport::has_expired.
This patch moves `SyncTaskSupervisor::check_if_expired` to
`TerminationReport::has_expired`. Because `TerminationReport` now holds
the error, we can remove the `has_expired` field and get a `has_expired`
method!
2025-09-05 22:31:53 +02:00
Ivan Enderlin 3c88b46c54 feat(ui): TerminationReport contains the error if any.
This patch changes the `TerminationReport::is_error` field to become
`error: Option<Error>`. This patch also creates new constructor on
`TerminationReport` to simplify the code.
2025-09-05 22:31:53 +02:00
Damir Jelić d25632507d Merge pull request #5628 from matrix-org/release-0.14
Merge back release branch for 0.14
2025-09-04 19:19:59 +02:00
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
838 changed files with 136158 additions and 42756 deletions
+7
View File
@@ -7,3 +7,10 @@ crates-io = "https://docs.rs/"
[unstable]
rustdoc-map = true
[target.aarch64-linux-android]
# These rust flags improve the performance on Android on arm64
rustflags = ["-C", "target-feature=+neon,+aes,+sha2,+sha3,+pmuv3"]
[env]
IPHONEOS_DEPLOYMENT_TARGET = "16.0"
+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
+5 -15
View File
@@ -10,6 +10,7 @@ exclude = [
version = 2
ignore = [
{ id = "RUSTSEC-2024-0436", reason = "Unmaintained paste crate, not critical." },
{ id = "RUSTSEC-2024-0388", reason = "Unmaintained derivative crate, not a direct dependency" },
]
[licenses]
@@ -17,6 +18,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",
@@ -26,18 +28,6 @@ allow = [
"Unicode-3.0",
"Zlib",
]
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]
# We should disallow this, but it's currently a PITA.
@@ -51,9 +41,7 @@ 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",
# Same 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
@@ -63,4 +51,6 @@ allow-git = [
# We can release vodozemac whenever we need but let's not block development
# on releases.
"https://github.com/matrix-org/vodozemac",
# A patch override for the bindings: https://github.com/Alorel/rust-indexed-db/pull/72
"https://github.com/matrix-org/rust-indexed-db"
]
+1
View File
@@ -1,2 +1,3 @@
* @matrix-org/rust
/crates/matrix-sdk-crypto @matrix-org/rust @matrix-org/rust-crypto-reviewers
/crates/matrix-sdk-indexeddb/src/crypto_store @matrix-org/rust @matrix-org/rust-crypto-reviewers
+2
View File
@@ -5,3 +5,5 @@ updates:
schedule:
# Check for updates to GitHub Actions every week
interval: "weekly"
cooldown:
default-days: 7
+2 -1
View File
@@ -1,6 +1,7 @@
<!-- description of the changes in this PR -->
- [ ] Public API changes documented in changelogs (optional)
- [ ] I've documented the public API Changes in the appropriate `CHANGELOG.md` files.
- [ ] This PR was made with the help of AI.
<!-- Sign-off, if not part of the commits -->
<!-- See CONTRIBUTING.md if you don't know what this is -->
+88 -39
View File
@@ -1,54 +1,103 @@
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'
permissions:
contents: read
id-token: write
strategy:
matrix:
benchmark:
- crypto_bench
- event_cache
- linked_chunk
- store_bench
- timeline
- room_list
steps:
- name: Checkout the repo
uses: actions/checkout@v4
# 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
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2025-02-20
components: rustfmt
cd /opt
find . -maxdepth 1 -mindepth 1 '!' -path ./containerd '!' -path ./actionarchivecache '!' -path ./runner '!' -path ./runner-cache -exec rm -rf '{}' ';'
rm -rf /opt/hostedtoolcache
- name: Run Benchmarks
run: cargo bench | tee benchmark-output.txt
# Get rid of binaries and libs we're not interested in.
sudo rm -rf \
/usr/local/julia* \
/usr/local/aws*
- 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
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
- 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'
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
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
persist-credentials: false
- name: Setup rust toolchain, cache and cargo-codspeed binary
uses: moonrepo/setup-rust@abb2d32350334249b178c401e5ec5836e0cd88d3
with:
channel: stable
cache-target: release
bins: cargo-codspeed
- name: Build the benchmark target(s)
run: cargo codspeed build -p benchmarks --bench ${{ matrix.benchmark }} --features codspeed
- name: Run the benchmarks
uses: CodSpeedHQ/action@db35df748deb45fdef0960669f57d627c1956c30
with:
run: cargo codspeed run
mode: simulation
+43 -24
View File
@@ -12,6 +12,8 @@ on:
- synchronize
- ready_for_review
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
@@ -31,15 +33,19 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Install protoc
uses: taiki-e/install-action@v2
uses: taiki-e/install-action@85b24a67ef0c632dfefad70b9d5ce8fddb040754 # v2.75.10
with:
tool: protoc@3.20.3
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
with:
toolchain: stable
# Cargo config can screw with caching and is only used for alias config
# and extra lints, which we don't care about here
@@ -47,12 +53,12 @@ jobs:
run: rm .cargo/config.toml
- name: Load cache
uses: Swatinem/rust-cache@v2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Get xtask
uses: actions/cache/restore@v4
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: target/debug/xtask
key: "${{ needs.xtask.outputs.cachekey-linux }}"
@@ -69,32 +75,37 @@ jobs:
steps:
- name: Checkout Rust SDK
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Checkout Kotlin Rust Components project
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
repository: matrix-org/matrix-rust-components-kotlin
path: rust-components-kotlin
ref: main
persist-credentials: false
- name: Use JDK 17
uses: actions/setup-java@v4
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # 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.7
uses: malinskiy/action-android/install-sdk@fa103ef30331e95f266418a6a97e98f61f626887 # release/0.1.7
- name: Install android ndk
uses: nttld/setup-ndk@v1
uses: nttld/setup-ndk@ed92fe6cadad69be94a966a7ee3271275e62f779 # v1
id: install-ndk
with:
ndk-version: r27
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
with:
toolchain: stable
# Cargo config can screw with caching and is only used for alias config
# and extra lints, which we don't care about here
@@ -102,12 +113,12 @@ jobs:
run: rm .cargo/config.toml
- name: Load cache
uses: Swatinem/rust-cache@v2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Get xtask
uses: actions/cache/restore@v4
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: target/debug/xtask
key: "${{ needs.xtask.outputs.cachekey-linux }}"
@@ -136,16 +147,20 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
# install protoc in case we end up rebuilding opentelemetry-proto
- name: Install protoc
uses: taiki-e/install-action@v2
uses: taiki-e/install-action@85b24a67ef0c632dfefad70b9d5ce8fddb040754 # v2.75.10
with:
tool: protoc@3.20.3
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
with:
toolchain: stable
- name: Install aarch64-apple-ios target
run: rustup target install aarch64-apple-ios
@@ -156,12 +171,12 @@ jobs:
run: rm .cargo/config.toml
- name: Load cache
uses: Swatinem/rust-cache@v2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Get xtask
uses: actions/cache/restore@v4
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: target/debug/xtask
key: "${{ needs.xtask.outputs.cachekey-macos }}"
@@ -175,11 +190,11 @@ 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"
uses: matrix-org/complement-crypto/.github/workflows/single_sdk_tests.yml@main
uses: matrix-org/complement-crypto/.github/workflows/single_sdk_tests.yml@399a1deeab0d7e4fa9604cbe83b1df6058c40193 # main
with:
use_rust_sdk: "." # use local checkout
use_complement_crypto: "MATCHING_BRANCH"
@@ -191,16 +206,20 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
# install protoc in case we end up rebuilding opentelemetry-proto
- name: Install protoc
uses: taiki-e/install-action@v2
uses: taiki-e/install-action@85b24a67ef0c632dfefad70b9d5ce8fddb040754 # v2.75.10
with:
tool: protoc@3.20.3
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
with:
toolchain: stable
- name: Add rust targets
run: |
@@ -212,7 +231,7 @@ jobs:
run: rm .cargo/config.toml
- name: Load cache
uses: Swatinem/rust-cache@v2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
+95 -62
View File
@@ -7,6 +7,8 @@ on:
pull_request:
branches: [main]
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
@@ -34,17 +36,22 @@ jobs:
- no-sqlite
- no-encryption-and-sqlite
- sqlite-cryptostore
- rustls-tls
- experimental-encrypted-state-events
- markdown
- socks
- sso-login
- search
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
with:
toolchain: stable
- name: Install libsqlite
run: |
@@ -52,7 +59,7 @@ jobs:
sudo apt-get install libsqlite3-dev
- name: Load cache
uses: Swatinem/rust-cache@v2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
# use a separate cache for each job to work around
# https://github.com/Swatinem/rust-cache/issues/124
@@ -63,10 +70,12 @@ jobs:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install nextest
uses: taiki-e/install-action@nextest
uses: taiki-e/install-action@85b24a67ef0c632dfefad70b9d5ce8fddb040754 # v 2.75.10
with:
tool: nextest
- name: Get xtask
uses: actions/cache/restore@v4
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: target/debug/xtask
key: "${{ needs.xtask.outputs.cachekey-linux }}"
@@ -83,21 +92,27 @@ jobs:
steps:
- name: Checkout the repo
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
with:
toolchain: stable
- name: Load cache
uses: Swatinem/rust-cache@v2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install nextest
uses: taiki-e/install-action@nextest
uses: taiki-e/install-action@85b24a67ef0c632dfefad70b9d5ce8fddb040754 # v 2.75.10
with:
tool: nextest
- name: Get xtask
uses: actions/cache/restore@v4
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: target/debug/xtask
key: "${{ needs.xtask.outputs.cachekey-linux }}"
@@ -114,7 +129,9 @@ jobs:
steps:
- name: Checkout the repo
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Install libsqlite
run: |
@@ -122,18 +139,23 @@ jobs:
sudo apt-get install libsqlite3-dev
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
with:
toolchain: stable
components: clippy
- name: Load cache
uses: Swatinem/rust-cache@v2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install nextest
uses: taiki-e/install-action@nextest
uses: taiki-e/install-action@85b24a67ef0c632dfefad70b9d5ce8fddb040754 # v 2.75.10
with:
tool: nextest
- name: Get xtask
uses: actions/cache/restore@v4
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: target/debug/xtask
key: "${{ needs.xtask.outputs.cachekey-linux }}"
@@ -165,10 +187,12 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Install protoc
uses: taiki-e/install-action@v2
uses: taiki-e/install-action@85b24a67ef0c632dfefad70b9d5ce8fddb040754 # v2.75.10
with:
tool: protoc@3.20.3
@@ -179,17 +203,19 @@ jobs:
sudo apt-get install libsqlite3-dev
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
with:
toolchain: ${{ matrix.rust }}
- name: Load cache
uses: Swatinem/rust-cache@v2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install nextest
uses: taiki-e/install-action@nextest
uses: taiki-e/install-action@85b24a67ef0c632dfefad70b9d5ce8fddb040754 # v 2.75.10
with:
tool: nextest
- name: Test
run: |
@@ -237,22 +263,25 @@ jobs:
steps:
- name: Checkout the repo
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
with:
toolchain: stable
targets: wasm32-unknown-unknown
components: clippy
- name: Install wasm-pack
uses: qmaru/wasm-pack-action@v0.5.0
uses: qmaru/wasm-pack-action@785fe709cd17eb6a97607eda9b6f5dbebed2b89c # v0.5.3
if: '!matrix.check_only'
with:
version: v0.10.3
version: v0.13.1
- name: Load cache
uses: Swatinem/rust-cache@v2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
# use a separate cache for each job to work around
# https://github.com/Swatinem/rust-cache/issues/124
@@ -263,10 +292,12 @@ jobs:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install nextest
uses: taiki-e/install-action@nextest
uses: taiki-e/install-action@85b24a67ef0c632dfefad70b9d5ce8fddb040754 # v 2.75.10
with:
tool: nextest
- name: Get xtask
uses: actions/cache/restore@v4
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: target/debug/xtask
key: "${{ needs.xtask.outputs.cachekey-linux }}"
@@ -287,10 +318,12 @@ jobs:
steps:
- name: Checkout Actions Repository
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Check the spelling of the files in our repo
uses: crate-ci/typos@v1.33.1
uses: crate-ci/typos@cf5f1c29a8ac336af8568821ec41919923b05a83 # v1.45.1
lint:
name: Lint
@@ -299,26 +332,28 @@ jobs:
steps:
- name: Checkout the repo
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Install protoc
uses: taiki-e/install-action@v2
uses: taiki-e/install-action@85b24a67ef0c632dfefad70b9d5ce8fddb040754 # v2.75.10
with:
tool: protoc@3.20.3
- name: Install Rust
uses: dtolnay/rust-toolchain@master
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
with:
toolchain: nightly-2025-02-20
toolchain: nightly-2026-02-26
components: clippy, rustfmt
- name: Load cache
uses: Swatinem/rust-cache@v2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Get xtask
uses: actions/cache/restore@v4
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: target/debug/xtask
key: "${{ needs.xtask.outputs.cachekey-linux }}"
@@ -333,8 +368,7 @@ jobs:
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 'synapse'
@@ -350,9 +384,18 @@ 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Install libsqlite
run: |
@@ -360,15 +403,19 @@ jobs:
sudo apt-get install libsqlite3-dev
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
with:
toolchain: stable
- name: Load cache
uses: Swatinem/rust-cache@v2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install nextest
uses: taiki-e/install-action@nextest
uses: taiki-e/install-action@85b24a67ef0c632dfefad70b9d5ce8fddb040754 # v 2.75.10
with:
tool: nextest
- name: Test
env:
@@ -376,25 +423,11 @@ jobs:
HOMESERVER_URL: "http://localhost:8008"
HOMESERVER_DOMAIN: "synapse"
run: |
cargo nextest run -p matrix-sdk-integration-testing
cargo nextest run --profile ci -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@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Load cache
uses: Swatinem/rust-cache@v2
- name: Upload test results to Codecov
if: ${{ !cancelled() }}
uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Compile benchmarks (no run)
run: |
cargo bench --profile dev --no-run
files: ./target/nextest/ci/junit.xml
token: ${{ secrets.CODECOV_TOKEN }}
+98 -37
View File
@@ -6,6 +6,8 @@ on:
pull_request:
branches: [main]
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
@@ -17,9 +19,16 @@ 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"
permissions:
contents: read
id-token: write
# run several docker containers with the same networking stack so the hostname 'synapse'
# maps to the synapse container, etc.
@@ -35,18 +44,80 @@ 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ github.event.pull_request.head.sha }}
persist-credentials: false
- name: Install libsqlite
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
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
with:
toolchain: stable
# Cargo config can screw with caching and is only used for alias config
# and extra lints, which we don't care about here
@@ -54,54 +125,44 @@ jobs:
run: rm .cargo/config.toml
- name: Load cache
uses: Swatinem/rust-cache@v2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
prefix-key: "coverage"
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install tarpaulin
uses: taiki-e/install-action@v2
- name: Install nextest and llvm-cov
uses: taiki-e/install-action@85b24a67ef0c632dfefad70b9d5ce8fddb040754 # v 2.75.10
with:
tool: cargo-tarpaulin
tool: nextest,cargo-llvm-cov
# set up backend for integration tests
- uses: actions/setup-python@v5
- name: Get xtask
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
python-version: 3.8
path: target/debug/xtask
key: "${{ needs.xtask.outputs.cachekey-linux }}"
fail-on-cache-miss: true
- name: Run tarpaulin
- 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"
# Copied with minimal adjustments, source:
# https://github.com/google/mdbook-i18n-helpers/blob/2168b9cea1f4f76b55426591a9bcc308a620194f/.github/workflows/test.yml
- name: Get PR number and commit SHA
run: |
echo "Storing PR number ${{ github.event.number }}"
echo "${{ github.event.number }}" > pr_number.txt
echo "Storing commit SHA ${{ github.event.pull_request.head.sha }}"
echo "${{ github.event.pull_request.head.sha }}" > commit_sha.txt
# 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.
- name: Store coverage report in artifacts
uses: actions/upload-artifact@v4
- name: Upload coverage to Codecov
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2
with:
name: codecov_report
path: |
cobertura.xml
pr_number.txt
commit_sha.txt
if-no-files-found: error
use_oidc: true
- run: |
echo 'The coverage report was stored in Github artifacts.'
echo 'It will be uploaded to Codecov using `upload_coverage.yml` workflow shortly.'
- name: Upload test results to Codecov
if: ${{ !cancelled() }}
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2
with:
use_oidc: true
report_type: "test_results"
+7 -2
View File
@@ -6,9 +6,14 @@ on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
permissions: {}
jobs:
cargo-deny:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: EmbarkStudios/cargo-deny-action@v2
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- uses: EmbarkStudios/cargo-deny-action@3fd3802e88374d3fe9159b834c7714ec57d6c979 # v2.0.15
+6 -2
View File
@@ -8,6 +8,8 @@ on:
pull_request: # focus on the changed files in current PR
branches: [main]
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
@@ -17,10 +19,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Check for changed files
id: changed-files
uses: tj-actions/changed-files@115870536a85eaf050e369291c7895748ff12aea
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
- name: Detect long path
env:
ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} # ignore the deleted files
@@ -2,11 +2,15 @@ name: Detects unused dependencies
on:
pull_request: { branches: "*" }
permissions: {}
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Machete
uses: bnjbvr/cargo-machete@v0.8.0
uses: bnjbvr/cargo-machete@ac30a525c0a8d163a92d727b3ff079ee3f6ecb08
+10 -8
View File
@@ -21,25 +21,27 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Install protoc
uses: taiki-e/install-action@v2
uses: taiki-e/install-action@85b24a67ef0c632dfefad70b9d5ce8fddb040754 # v2.75.10
with:
tool: protoc@3.20.3
- name: Install Rust
uses: dtolnay/rust-toolchain@master
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
with:
toolchain: nightly-2025-02-20
toolchain: nightly-2026-02-26
- name: Install Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version: 20
- name: Load cache
uses: Swatinem/rust-cache@v2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
@@ -52,11 +54,11 @@ 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@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: './target/doc/'
- name: Deploy to GitHub Pages
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
+6 -2
View File
@@ -2,11 +2,15 @@ name: Git Checks
on: [pull_request]
permissions: {}
jobs:
block-fixup:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Block Fixup Commit Merge
uses: 13rac1/block-fixup-merge-action@v2.0.0
uses: 13rac1/block-fixup-merge-action@bd5504fb9ca0253e109d98eb86b7debc01970cdc # v2.0.0
+9 -3
View File
@@ -7,10 +7,16 @@ on:
pull_request:
branches: [main]
permissions: {}
jobs:
msrv:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: taiki-e/install-action@cargo-hack
- run: cargo hack check --rust-version --workspace --all-targets --ignore-private
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- uses: taiki-e/install-action@85b24a67ef0c632dfefad70b9d5ce8fddb040754 # cargo-hack
with:
tool: cargo-hack
- run: cargo hack check --rust-version --workspace --all-targets
-79
View File
@@ -1,79 +0,0 @@
# Copied with minimal adjustments, source:
# https://github.com/google/mdbook-i18n-helpers/blob/2168b9cea1f4f76b55426591a9bcc308a620194f/.github/workflows/coverage-report.yml
name: Upload code coverage
on:
# This workflow is triggered after every successful execution
# of `coverage` workflow.
workflow_run:
workflows: ["Code Coverage"]
types:
- completed
jobs:
coverage:
name: Upload coverage report
runs-on: ubuntu-latest
if: github.event.workflow_run.conclusion == 'success'
steps:
- name: 'Fetch coverage report from artifacts'
id: prepare_report
uses: actions/github-script@v7
with:
script: |
var fs = require('fs');
// List artifacts of the workflow run that triggered this workflow
var artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
let codecovReport = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == "codecov_report";
});
if (codecovReport.length != 1) {
throw new Error("Unexpected number of {codecov_report} artifacts: " + codecovReport.length);
}
var download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: codecovReport[0].id,
archive_format: 'zip',
});
fs.writeFileSync('codecov_report.zip', Buffer.from(download.data));
- id: parse_previous_artifacts
run: |
unzip codecov_report.zip
echo "Detected PR is: $(<pr_number.txt)"
echo "Detected commit_sha is: $(<commit_sha.txt)"
# Make the params available as step output
echo "override_pr=$(<pr_number.txt)" >> "$GITHUB_OUTPUT"
echo "override_commit=$(<commit_sha.txt)" >> "$GITHUB_OUTPUT"
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ steps.parse_previous_artifacts.outputs.override_commit || '' }}
path: repo_root
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
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 }}
+9 -3
View File
@@ -20,6 +20,8 @@ on:
description: "The cache key for the macos build artifact"
value: "${{ jobs.xtask.outputs.cachekey-macos }}"
permissions: {}
env:
CARGO_TERM_COLOR: always
@@ -43,7 +45,9 @@ jobs:
steps:
- name: Checkout repo
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Calculate cache key
id: cachekey
@@ -53,7 +57,7 @@ jobs:
echo "cachekey-${{ matrix.cachekey-id }}=xtask-${{ matrix.cachekey-id }}-${{ hashFiles('Cargo.toml', 'xtask/**') }}" >> $GITHUB_OUTPUT
- name: Check xtask cache
uses: actions/cache@v4
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
id: xtask-cache
with:
path: target/debug/xtask
@@ -64,7 +68,9 @@ jobs:
- name: Install Rust stable toolchain
if: steps.xtask-cache.outputs.cache-hit != 'true'
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
with:
toolchain: stable
- name: Build
if: steps.xtask-cache.outputs.cache-hit != 'true'
+24
View File
@@ -0,0 +1,24 @@
name: Lint GHA workflows with zizmor
on:
push:
branches: ["main"]
pull_request:
branches: ["**"]
permissions: {}
jobs:
zizmor:
name: Run zizmor
runs-on: ubuntu-latest
permissions:
security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files.
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Run zizmor
uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
+1
View File
@@ -4,6 +4,7 @@ master.zip
emsdk-*
.idea/
.env
.envrc
.build
.swiftpm
/Package.swift
+1 -1
View File
@@ -7,4 +7,4 @@ group_imports = "StdExternalCrate"
format_code_in_doc_comments = true
doc_comment_code_block_width = 80
# Workaround for https://github.com/rust-lang/rust.vim/issues/464
edition = "2021"
edition = "2024"
+6 -1
View File
@@ -16,12 +16,17 @@ extend-ignore-re = [
[default.extend-identifiers]
WeeChat = "WeeChat"
# all of these are valid words, but should never appear in this repo
[default.extend-words]
# all of these are valid words, but should never appear in this repo
bellow = "below"
stat = "state"
sing = "sign"
singed = "signed"
singing = "signing"
# crate name
ratatui = "ratatui"
# path name
consts = "consts"
# base64 false positives
Nd = "Nd"
Abl = "Abl"
+3
View File
@@ -0,0 +1,3 @@
rules:
unpinned-uses:
severity: high
+115 -64
View File
@@ -1,4 +1,4 @@
# Contributing to matrix-rust-sdk
# Contributing to `matrix-rust-sdk`
## Chat rooms
@@ -29,57 +29,62 @@ integration tests that need a running synapse instance. These tests reside in
[README](./testing/matrix-sdk-integration-testing/README.md) to easily set up a
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.
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:
And for an improved review experience it's recommended (but not necessary) to
install the `cargo-insta` tool:
Unix:
```
```shell
curl -LsSf https://insta.rs/install.sh | sh
```
Windows:
```
```shell
powershell -c "irm https://insta.rs/install.ps1 | iex"
```
Usual flow is to first run the test, then review them.
```
```shell
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.
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
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.
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
each commit should have a *good commit message*.
Ideally, a PR should have a _proper title_, with _atomic logical commits_, and
each commit should have a _good commit message_.
A *proper PR title* would be a one-liner summary of the changes in the PR,
A _proper PR title_ would be a one-liner summary of the changes in the PR,
following the same guidelines of a good commit message, including the
area/feature prefix. Something like `FFI: Allow logs files to be pruned.` would
be a good PR title.
@@ -87,7 +92,7 @@ be a good PR title.
(An additional bad example of a bad PR title would be `mynickname/branch name`,
that is, just the branch name.)
# Writing changelog entries
## Writing changelog entries
Our goal is to maintain clear, concise, and informative changelogs that
accurately document changes in the project. Changelog entries should be written
@@ -121,13 +126,18 @@ A good example of a changelog entry could look like the following:
For security-related changelog entries, please include the following additional
details alongside the pull request number:
* Impact: Clearly describe the issue's potential impact on users or systems.
* CVE Number: If available, include the CVE (Common Vulnerabilities and Exposures) identifier.
* GitHub Advisory Link: Provide a link to the corresponding GitHub security advisory for further context.
- Impact: Clearly describe the issue's potential impact on users or systems.
- CVE Number: If available, include the CVE (Common Vulnerabilities and
Exposures) identifier.
- GitHub Advisory Link: Provide a link to the corresponding GitHub security
advisory for further context.
```markdown
- Use a constant-time Base64 encoder for secret key material to mitigate
side-channel attacks leaking secret key material ([#156](https://github.com/matrix-org/vodozemac/pull/156)) (Low, [CVE-2024-40640](https://www.cve.org/CVERecord?id=CVE-2024-40640), [GHSA-j8cm-g7r6-hfpq](https://github.com/matrix-org/vodozemac/security/advisories/GHSA-j8cm-g7r6-hfpq)).
side-channel attacks leaking secret key material
([#156](https://github.com/matrix-org/vodozemac/pull/156)) (Low,
[CVE-2024-40640](https://www.cve.org/CVERecord?id=CVE-2024-40640),
[GHSA-j8cm-g7r6-hfpq](https://github.com/matrix-org/vodozemac/security/advisories/GHSA-j8cm-g7r6-hfpq)).
```
## Commit message format
@@ -139,18 +149,19 @@ git trailers are supported and have special meaning (see below).
Conventional Commits are structured as follows:
```
```text
<type>(<scope>): <short summary>
```
The type of changes which will be included in changelogs is one of the following:
The type of changes which will be included in changelogs is one of the
following:
* `feat`: A new feature
* `fix`: A bug fix
* `doc`: Documentation changes
* `refactor`: Code refactoring
* `perf`: Performance improvements
* `ci`: Changes to CI configuration files and scripts
- `feat`: A new feature
- `fix`: A bugfix
- `doc`: Documentation changes
- `refactor`: Code refactoring
- `perf`: Performance improvements
- `ci`: Changes to CI configuration files and scripts
The scope is optional and can specify the area of the codebase affected (e.g.,
olm, cipher).
@@ -163,15 +174,16 @@ changelog entry.
The metadata must be included in the following git-trailers:
* `Security-Impact`: The magnitude of harm that can be expected, i.e. low/moderate/high/critical.
* `CVE`: The CVE that was assigned to this issue.
* `GitHub-Advisory`: The GitHub advisory identifier.
- `Security-Impact`: The magnitude of harm that can be expected, i.e.
low/moderate/high/critical.
- `CVE`: The CVE that was assigned to this issue.
- `GitHub-Advisory`: The GitHub advisory identifier.
Please include all of the fields that are available.
Please include all the fields that are available.
Example:
```
```text
fix(crypto): Use a constant-time Base64 encoder for secret key material
This patch fixes a security issue around a side-channel vulnerability[1]
@@ -213,9 +225,9 @@ your contributions, follow these basic rules:
5. Keep PRs on topic and small. Large PRs are harder to review and more prone to
delays. Create small, focused commits that address a single topic. Use a
combination of [git add] -p or git checkout -p to split changes into logical
units. This makes your work easier to review and reduces the chance of
introducing unrelated changes.
combination of [git add] -p or [git checkout] -p to split changes into
logical units. This makes your work easier to review and reduces the chance
of introducing unrelated changes.
[git add]: https://git-scm.com/docs/git-add#Documentation/git-add.txt---patch
[git checkout]: https://git-scm.com/docs/git-checkout#Documentation/git-checkout.txt---patch
@@ -227,12 +239,12 @@ guidelines to make the maintainers life easier and increase the chances that
your PR will be reviewed swiftly.
1. Use [fixup] commits. When addressing reviewer feedback, you can create fixup
commits. These commits mark your changes as corrections of specific previous
commits in the PR.
commits. These commits mark your changes as corrections of specific previous
commits in the PR.
Example:
```bash
```shell
git commit --fixup=<commit-hash>
```
@@ -247,7 +259,7 @@ requested.
3. Once the PR has been approved, rebase your PR to squash all the fixup
commits, the [autosquash] option can help with this.
```bash
```shell
git rebase main --interactive --autosquash
```
@@ -257,14 +269,16 @@ git rebase main --interactive --autosquash
## Sign off
In order to have a concrete record that your contribution is intentional
and you agree to license it under the same terms as the project's license, we've
adopted the same lightweight approach that the [Linux Kernel](https://www.kernel.org/doc/Documentation/SubmittingPatches),
[Docker](https://github.com/docker/docker/blob/master/CONTRIBUTING.md), and many other
projects use: the DCO ([Developer Certificate of Origin](http://developercertificate.org/)).
This is a simple declaration that you wrote the contribution or otherwise have the right
to contribute it to Matrix:
and you agree to license it under the same terms as the project's
license, we've adopted the same lightweight approach that the [Linux
Kernel](https://www.kernel.org/doc/Documentation/SubmittingPatches),
[Docker](https://github.com/docker/docker/blob/master/CONTRIBUTING.md),
and many other projects use: the DCO ([Developer Certificate of
Origin](http://developercertificate.org/)). This is a simple declaration that
you wrote the contribution or otherwise have the right to contribute it to
Matrix:
```
```text
Developer Certificate of Origin
Version 1.1
@@ -305,7 +319,7 @@ By making a contribution to this project, I certify that:
If you agree to this for your contribution, then all that's needed is to
include the line in your commit or pull request comment:
```
```text
Signed-off-by: Your Name <your@email.example.org>
```
@@ -316,16 +330,53 @@ Git allows you to add this signoff automatically when using the `-s` flag to
If you forgot to sign off your commits before making your pull request and are
on Git 2.17+ you can mass signoff using rebase:
```
```text
git rebase --signoff origin/main
```
## Tips for working on the `matrix-rust-sdk` with specific IDEs
## AI policy
* [RustRover](https://www.jetbrains.com/rust/) will attempt to sync the project
with all features enabled, causing an error in `matrix-sdk` ("only one of the
features 'native-tls' or 'rustls-tls' can be enabled"). To work around this,
open `crates/matrix-sdk/Cargo.toml` in RustRover and uncheck one of the
`native-tls` or `rustls-tls` feature definitions:
This policy is a copy of the [Forgejo's AI agreement][Forgejo].
![Screenshot of RustRover](.img/rustrover-disable-feature.png)
### Terminology
This does not necessarily reflect the official or commonly used terminology.
Software and services that heavily rely on large language model technology to
generate their outcomes are referred to as _Artificial Intelligence_ (AI).
Examples of products that fit this definition: GitHub Copilot, ChatGPT, Claude
Sonnet, DeepSeek, Llama and Gemini.
There is a distinction between _general_ and _narrow_ AI, all the aforementioned
examples fall under general AI as they were not trained to execute a specific
well-defined task. Narrow AI is trained to be used for specific well-defined
tasks where the problem space is known in advance.
_Vibe coding_ is the practice where AI creates a code change (feature, bugfix,
tests, refactor) with a human that describes what needs to be implemented.
_AI agents_ are AIs that are configured to perform interactions or make changes
with little to no human supervision.
### Agreement
1. If content was made with the help of AI, you **must** convey that this is
the case. This includes content that you authored but was motivated by a
suggestion of AI.
2. If at any point you used AI's work in your contribution you should make
an effort to **verify** that you can submit this under the license of the
repository.
3. The **accountability** of using AI in a contribution lies with the person
that makes that contribution.
4. All communication, that includes: commit messages, pull request messages,
documentation, code comments and issues (and comments on issues/pull
requests), that is intended to be read by people to understand your thoughts
and work **must not** have been generated with AI. We exclude machine
translation and tooling that helps with grammar and spelling check.
5. Using general AI for review is **forbidden**. If the change contains changes
to the user experience it has to be approved by a human reviewer.
6. It is **not allowed** to use AI in an autonomous-looking way to contribute to
the Matrix Rust SDK. This also applies when someone engages in _vibe coding_
or uses so-called _agent mode_.
[Forgejo]: https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md
Generated
+2363 -1300
View File
File diff suppressed because it is too large Load Diff
+144 -93
View File
@@ -11,111 +11,134 @@ members = [
"xtask",
]
exclude = ["testing/data"]
# xtask, testing and the bindings should only be built when invoked explicitly.
default-members = ["benchmarks", "crates/*", "labs/*"]
resolver = "2"
# xtask, multiverse, testing and the bindings should only be built when invoked explicitly.
default-members = ["benchmarks", "crates/*"]
resolver = "3"
[workspace.package]
rust-version = "1.85"
rust-version = "1.93"
[workspace.dependencies]
anyhow = "1.0.95"
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.4"
async-rx = "0.1.3"
async-stream = "0.3.5"
async-trait = "0.1.85"
base64 = "0.22.1"
bitflags = "2.8.0"
byteorder = "1.5.0"
chrono = "0.4.39"
eyeball = { version = "0.8.8", features = ["tracing"] }
eyeball-im = { version = "0.7.0", features = ["tracing"] }
eyeball-im-util = "0.9.0"
futures-core = "0.3.31"
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.2.0"
imbl = "5.0.0"
indexmap = "2.7.1"
insta = { version = "1.42.1", features = ["json", "redactions"] }
itertools = "0.14.0"
js-sys = "0.3.69"
mime = "0.3.17"
once_cell = "1.20.2"
pbkdf2 = { version = "0.12.2" }
pin-project-lite = "0.2.16"
proptest = { version = "1.6.0", default-features = false, features = ["std"] }
rand = "0.8.5"
reqwest = { version = "0.12.12", default-features = false }
rmp-serde = "1.3.0"
# Be careful to use commits from the https://github.com/ruma/ruma/tree/ruma-0.12
# branch until a proper release with breaking changes happens.
ruma = { version = "0.12.3", features = [
anyhow = { version = "1.0.100", default-features = false }
aquamarine = { version = "0.6.0", default-features = false }
as_variant = { version = "1.3.0", default-features = false }
assert-json-diff = { version = "2.0.2", default-features = false }
assert_matches = { version = "1.5.0", default-features = false }
assert_matches2 = { version = "0.1.2", default-features = false }
async_cell = { version = "0.2.3", default-features = false }
async-compat = { version = "0.2.5", default-features = false }
async-once-cell = { version = "0.5.4", default-features = false }
async-rx = { version = "0.2.0", default-features = false }
# Bumping this to 0.3.6 produces a test failure because the semantic between the
# versions changed subtly: https://github.com/matrix-org/matrix-rust-sdk/issues/4599
async-stream = { version = "0.3.6", default-features = false }
async-trait = { version = "0.1.89", default-features = false }
base64 = { version = "0.22.1", default-features = false, features = ["std"] }
bitflags = { version = "2.10.0", default-features = false }
byteorder = { version = "1.5.0", default-features = false, features = ["std"] }
cfg-if = { version = "1.0.4", default-features = false }
clap = { version = "4.5.53", default-features = false, features = ["std", "help", "usage"] }
chrono = { version = "0.4.42", default-features = false, features = ["clock", "std", "oldtime", "wasmbind"] }
dirs = { version = "6.0.0", default-features = false }
eyeball = { version = "0.8.8", default-features = false, features = ["tracing"] }
eyeball-im = { version = "0.8.0", default-features = false, features = ["tracing"] }
eyeball-im-util = { version = "0.10.0", default-features = false }
futures-core = { version = "0.3.31", default-features = false, features = ["std"] }
futures-executor = { version = "0.3.31", default-features = false, features = ["std"] }
futures-util = { version = "0.3.31", default-features = false, features = ["std"] }
getrandom = { version = "0.4.2", default-features = false }
gloo-timers = { version = "0.3.0", default-features = false }
gloo-utils = { version = "0.2.0", default-features = false, features = ["serde"] }
growable-bloom-filter = { version = "2.1.1", default-features = false }
hkdf = { version = "0.12.4", default-features = false }
hmac = { version = "0.12.1", default-features = false }
http = { version = "1.3.1", default-features = false }
imbl = { version = "6.1.0", default-features = false }
indexed_db_futures = { version = "0.7.0", package = "matrix_indexed_db_futures", default-features = false }
indexmap = { version = "2.12.1", default-features = false }
insta = { version = "1.44.1", features = ["json", "redactions"] }
itertools = { version = "0.14.0", default-features = false, features = ["use_std"] }
js-sys = { version = "0.3.82", default-features = false, features = ["std"] }
mime = { version = "0.3.17", default-features = false }
oauth2 = { version = "5.0.0", default-features = false, features = ["timing-resistant-secret-traits"] }
oauth2-reqwest = { version = "0.1.0-alpha.3", default-features = false }
pbkdf2 = { version = "0.12.2", default-features = false }
pin-project-lite = { version = "0.2.16", default-features = false }
proc-macro2 = { version = "1.0.106", default-features = false }
proptest = { version = "1.9.0", default-features = false, features = ["std"] }
quote = { version = "1.0.37", default-features = false }
rand = { version = "0.10.1", default-features = false, features = ["std", "std_rng", "thread_rng"] }
regex = { version = "1.12.2", default-features = false }
reqwest = { version = "0.13.1", default-features = false }
rmp-serde = { version = "1.3.0", default-features = false }
ruma = { git = "https://github.com/ruma/ruma", rev = "7680eebd9586669e1a4e5b1fd1c2c691221369d4", features = [
"client-api-c",
"compat-unset-avatar",
"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-msc3230",
"unstable-msc3401",
"unstable-msc3266",
"unstable-msc3417",
"unstable-msc3488",
"unstable-msc3489",
"unstable-msc4075",
"unstable-msc4140",
"unstable-msc4143",
"unstable-msc4171",
"unstable-msc4278",
"unstable-msc4286",
] }
ruma-common = "0.15.2"
sentry = "0.36.0"
sentry-tracing = "0.36.0"
serde = { version = "1.0.217", features = ["rc"] }
serde_html_form = "0.2.7"
serde_json = "1.0.138"
sha2 = "0.10.8"
similar-asserts = "1.6.1"
stream_assert = "0.1.1"
tempfile = "3.16.0"
thiserror = "2.0.11"
tokio = { version = "1.43.1", default-features = false, features = ["sync"] }
tokio-stream = "0.1.17"
tracing = { version = "0.1.40", default-features = false, features = ["std"] }
tracing-core = "0.1.32"
tracing-subscriber = "0.3.18"
unicode-normalization = "0.1.24"
uniffi = { version = "0.28.0" }
uniffi_bindgen = { version = "0.28.0" }
url = "2.5.4"
uuid = "1.12.1"
vodozemac = { version = "0.9.0", features = ["insecure-pk-encryption"] }
wasm-bindgen = "0.2.84"
wasm-bindgen-test = "0.3.50"
web-sys = "0.3.69"
wiremock = "0.6.2"
zeroize = "1.8.1"
"unstable-msc4306",
"unstable-msc4308",
"unstable-msc4310",
] }
sentry = { version = "0.47.0", default-features = false }
sentry-tracing = { version = "0.47.0", default-features = false }
serde = { version = "1.0.228", default-features = false, features = ["std", "rc", "derive"] }
serde_html_form = { version = "0.2.8", default-features = false }
serde_json = { version = "1.0.145", default-features = false, features = ["std"] }
sha2 = { version = "0.10.9", default-features = false }
similar-asserts = { version = "1.7.0", default-features = false }
stream_assert = { version = "0.1.1", default-features = false }
syn = { version = "2.0.43", default-features = false, features = ["derive", "parsing", "printing", "clone-impls"] }
tempfile = { version = "3.23.0", default-features = false }
thiserror = { version = "2.0.17", default-features = false }
tokio = { version = "1.48.0", default-features = false, features = ["sync"] }
tokio-stream = { version = "0.1.17", default-features = false }
tracing = { version = "0.1.41", default-features = false, features = ["std"] }
tracing-appender = { version = "0.2.3", default-features = false }
tracing-core = { version = "0.1.34", default-features = false }
tracing-subscriber = { version = "0.3.20", default-features = false, features = ["std", "smallvec", "fmt"] }
unicode-normalization = { version = "0.1.25", default-features = false }
unicode-segmentation = { version = "1.12.0", default-features = false }
uniffi = { version = "0.31.0", default-features = false, features = ["cargo-metadata"] }
uniffi_bindgen = { version = "0.31.0", default-features = false, features = ["cargo-metadata"] }
url = { version = "2.5.7", default-features = false }
uuid = { version = "1.18.1", default-features = false }
vergen-gitcl = { version = "1.0.8", default-features = false }
vodozemac = { version = "0.10.0", default-features = false, features = ["libolm-compat", "insecure-pk-encryption", "experimental-session-config"] }
wasm-bindgen = { version = "0.2.105", default-features = false }
wasm-bindgen-test = { version = "0.3.55", default-features = false, features = ["std"] }
web-sys = { version = "0.3.82", default-features = false }
wiremock = { version = "0.6.5", default-features = false }
zeroize = { version = "1.8.2", default-features = false }
matrix-sdk = { path = "crates/matrix-sdk", version = "0.12.0", default-features = false }
matrix-sdk-base = { path = "crates/matrix-sdk-base", version = "0.12.0" }
matrix-sdk-common = { path = "crates/matrix-sdk-common", version = "0.12.0" }
matrix-sdk-crypto = { path = "crates/matrix-sdk-crypto", version = "0.12.0" }
matrix-sdk = { path = "crates/matrix-sdk", version = "0.16.0", default-features = false }
matrix-sdk-base = { path = "crates/matrix-sdk-base", version = "0.16.0" }
matrix-sdk-common = { path = "crates/matrix-sdk-common", version = "0.16.0" }
matrix-sdk-crypto = { path = "crates/matrix-sdk-crypto", version = "0.16.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.12.0", default-features = false }
matrix-sdk-qrcode = { path = "crates/matrix-sdk-qrcode", version = "0.12.0" }
matrix-sdk-sqlite = { path = "crates/matrix-sdk-sqlite", version = "0.12.0", default-features = false }
matrix-sdk-store-encryption = { path = "crates/matrix-sdk-store-encryption", version = "0.12.0" }
matrix-sdk-test = { path = "testing/matrix-sdk-test", version = "0.12.0" }
matrix-sdk-ui = { path = "crates/matrix-sdk-ui", version = "0.12.0", default-features = false }
matrix-sdk-indexeddb = { path = "crates/matrix-sdk-indexeddb", version = "0.16.0", default-features = false }
matrix-sdk-qrcode = { path = "crates/matrix-sdk-qrcode", version = "0.16.0" }
matrix-sdk-sqlite = { path = "crates/matrix-sdk-sqlite", version = "0.16.0", default-features = false }
matrix-sdk-store-encryption = { path = "crates/matrix-sdk-store-encryption", version = "0.16.0" }
matrix-sdk-test = { path = "testing/matrix-sdk-test", version = "0.16.0" }
matrix-sdk-test-utils = { path = "testing/matrix-sdk-test-utils", version = "0.16.0" }
matrix-sdk-ui = { path = "crates/matrix-sdk-ui", version = "0.16.0", default-features = false }
matrix-sdk-search = { path = "crates/matrix-sdk-search", version = "0.16.0" }
[workspace.lints.rust]
rust_2018_idioms = "warn"
@@ -137,13 +160,15 @@ 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`
@@ -151,6 +176,19 @@ redundant_clone = "warn"
# Saves a lot of disk space. If symbols are needed, use the dbg profile.
debug = 0
# Profile for debug builds with full optimization and minimal debug symbols.
# This should be just enough to have proper backtraces, having way smaller binaries
# (10% of the size with full debug symbols profile, like `reldbg`).
# This profile differs from `reldbg` in not containing the debug symbols needed for
# debugging with LLDB/GDB, trading that for binary size, allowing quick iterations
# of building the bindings, installing in a real device, testing your changes, repeat.
# It's also different from `dev` in having enough debug symbols to display backtraces.
[profile.reldev]
inherits = "dev"
opt-level = 3
debug = "line-tables-only"
strip = "debuginfo"
[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.
@@ -171,6 +209,16 @@ debug = 2
inherits = "dbg"
opt-level = 3
[profile.dist]
# Use release profile as a base
inherits = "release"
# Strip the minimal debug info, while still allowing us to have proper backtraces, but it will affect debuggers
strip = "debuginfo"
# Use link time optimizations
lto = true
# Use binary size optimization, since this is intended for distributed copies of the SDK
opt-level = "s"
[profile.profiling]
inherits = "release"
# LTO is too slow to compile.
@@ -178,12 +226,15 @@ 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" }
const_panic = { git = "https://github.com/jplatte/const_panic", rev = "e0b317a9a7bde2d48a7d15b6a60d70e4a41d3b5f" }
# 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" }
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" }
+48 -27
View File
@@ -1,38 +1,55 @@
<h1 align="center">Matrix Rust SDK</h1>
<div align="center">
<i>Your all-in-one toolkit for creating Matrix clients with Rust, from simple bots to full-featured apps.</i>
<br/><br/>
<img src="contrib/logo.svg">
<br>
<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>
<br>
<br>
<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>
<div align="center">
The Matrix Rust SDK is a collection of libraries that make it easier to build
[Matrix] clients in [Rust]. It takes care of the low-level details like encryption,
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.
[Matrix]: https://matrix.org/
[Rust]: https://www.rust-lang.org/
## Project structure
The Matrix Rust SDK is made up of several crates that build on top of each other. Here are the key ones:
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-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,
@@ -45,6 +62,9 @@ The Matrix Rust SDK is made up of several crates that build on top of each other
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 considered production ready and backs multiple client
@@ -54,9 +74,6 @@ implementations such as Element X
[Fractal](https://gitlab.gnome.org/World/fractal) and [iamb](https://github.com/ulyssa/iamb). Client developers should feel
confident to build upon it.
Development of the SDK has been primarily sponsored by Element though accepts
contributions from all.
## Bindings
The higher-level crates of the Matrix Rust SDK can be embedded in other
@@ -67,3 +84,7 @@ 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 -8
View File
@@ -1,7 +1,7 @@
[package]
name = "benchmarks"
description = "Matrix SDK benchmarks"
edition = "2021"
edition = "2024"
license = "Apache-2.0"
rust-version.workspace = true
version = "1.0.0"
@@ -10,24 +10,27 @@ publish = false
[package.metadata.release]
release = false
[features]
codspeed = []
[dependencies]
criterion = { version = "0.5.1", features = ["async", "async_tokio", "html_reports"] }
matrix-sdk = { workspace = true, features = ["native-tls", "e2e-encryption", "sqlite", "testing"] }
assert_matches.workspace = true
criterion = { version = "4.2.1", features = ["async", "async_tokio", "html_reports"], package = "codspeed-criterion-compat" }
futures-util.workspace = true
matrix-sdk = { workspace = true, features = ["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
rand.workspace = true
ruma.workspace = true
serde.workspace = true
serde_json.workspace = true
tempfile = "3.3.0"
tokio = { workspace = true, default-features = false, features = ["rt-multi-thread"] }
tempfile.workspace = true
tokio = { workspace = true, features = ["rt-multi-thread"] }
wiremock.workspace = true
[target.'cfg(target_os = "linux")'.dependencies]
pprof = { version = "0.14.0", features = ["flamegraph", "criterion"] }
[[bench]]
name = "crypto_bench"
harness = false
@@ -47,3 +50,11 @@ harness = false
[[bench]]
name = "timeline"
harness = false
[[bench]]
name = "event_cache"
harness = false
[[bench]]
name = "room_list"
harness = false
+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);
+361
View File
@@ -0,0 +1,361 @@
use std::{pin::Pin, sync::Arc};
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use matrix_sdk::{
RoomInfo, RoomState, SqliteEventCacheStore, StateStore,
cross_process_lock::CrossProcessLockConfig,
store::StoreConfig,
sync::{JoinedRoomUpdate, RoomUpdates},
test_utils::client::MockClientBuilder,
};
use matrix_sdk_base::event_cache::store::{DynEventCacheStore, IntoEventCacheStore, MemoryStore};
use matrix_sdk_test::{ALICE, base64_sha256_hash, event_factory::EventFactory};
use ruma::{
OwnedRoomId, RoomId,
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 {
// Synapse's room IDs for rooms v1 to v11 have an 18 characters localpart.
let raw_room_id = format!("!firstbatchroom{i:04}:example.com");
let room_id = if i % 10 == 9 {
// Make 1 in 10 rooms use a room v12 ID, which is a base64 hash similar to an
// event ID.
RoomId::new_v2(&base64_sha256_hash(raw_room_id.as_bytes())).unwrap()
} else {
OwnedRoomId::try_from(raw_room_id).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 = event_factory.text_msg(format!("Message {j}")).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(CrossProcessLockConfig::multi_process(
"cross-process-store-locks-holder-name",
))
.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);
// Room v1-v11 ID.
let room_id = room_id!("!initialtestingroom:ben.ch");
// Room v12 ID.
let other_room_id = room_id!("!ICMDdumUm6RRX_eWYY2wMb2w0CY0Z_5OvlY2gBR6ELc");
// 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 = event_factory.text_msg("hello world").into_event();
let target_event_id =
{ &target_event.event_id().expect("generated event has an event ID") };
joined_room_update.timeline.events.push(target_event);
// Add the numerous edits.
for i in 0..num_related_events {
let event = event_factory
.text_msg(format!("* edit {i}"))
.edit(
target_event_id,
RoomMessageEventContentWithoutRelation::text_plain(format!("edit {i}")),
)
.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 = event_factory.text_msg(format!("unrelated message {i}")).into();
joined_room_update.timeline.events.push(event);
}
// Add other events, in the same room, related to other events.
let other_target_event = event_factory.text_msg("hello world").into_event();
let other_target_event_id =
other_target_event.event_id().expect("generated event has an event ID");
joined_room_update.timeline.events.push(other_target_event);
for _i in 0..NUM_OTHER_EVENTS {
let event = event_factory.reaction(&other_target_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 = event_factory.text_msg(format!("hi {i}")).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(CrossProcessLockConfig::multi_process(
"cross-process-store-locks-holder-name",
))
.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()
.unwrap();
assert_eq!(target.event_id().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);
+65 -63
View File
@@ -1,16 +1,16 @@
use std::{sync::Arc, time::Duration};
use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion, Throughput};
use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use matrix_sdk::{
linked_chunk::{lazy_loader, LinkedChunk, LinkedChunkId, Update},
SqliteEventCacheStore,
linked_chunk::{LinkedChunk, LinkedChunkId, Update, lazy_loader},
};
use matrix_sdk_base::event_cache::{
store::{DynEventCacheStore, IntoEventCacheStore, MemoryStore, DEFAULT_CHUNK_CAPACITY},
Event, Gap,
store::{DEFAULT_CHUNK_CAPACITY, DynEventCacheStore, IntoEventCacheStore, MemoryStore},
};
use matrix_sdk_test::{event_factory::EventFactory, ALICE};
use ruma::{room_id, EventId};
use matrix_sdk_test::{ALICE, event_factory::EventFactory};
use ruma::room_id;
use tempfile::tempdir;
use tokio::runtime::Builder;
@@ -20,6 +20,11 @@ enum Operation {
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()
@@ -28,14 +33,14 @@ fn writing(c: &mut Criterion) {
.build()
.expect("Failed to create an asynchronous runtime");
let room_id = room_id!("!foo:bar.baz");
let room_id = room_id!("!fabricandofitfaber: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("writing");
let mut group = c.benchmark_group("Linked chunk writing");
group.sample_size(10).measurement_time(Duration::from_secs(30));
for number_of_events in [10, 100, 1000, 10_000, 100_000] {
for &number_of_events in NUMBER_OF_EVENTS {
let sqlite_temp_dir = tempdir().unwrap();
// Declare new stores for this set of events.
@@ -61,12 +66,7 @@ fn writing(c: &mut Criterion) {
{
let mut events = (0..number_of_events)
.map(|nth| {
event_factory
.text_msg("foo")
.event_id(&EventId::parse(format!("$ev{nth}")).unwrap())
.into_event()
})
.map(|nth| event_factory.text_msg(format!("foo {nth}")).into_event())
.peekable();
let mut gap_nth = 0;
@@ -83,9 +83,8 @@ fn writing(c: &mut Criterion) {
}
{
operations.push(Operation::PushGapBack(Gap {
prev_token: format!("gap{gap_nth}"),
}));
operations
.push(Operation::PushGapBack(Gap { token: format!("gap{gap_nth}") }));
gap_nth += 1;
}
}
@@ -96,7 +95,7 @@ fn writing(c: &mut Criterion) {
// Get a bencher.
group.bench_with_input(
BenchmarkId::new(store_name, number_of_events),
BenchmarkId::new(format!("Linked chunk writing [{store_name}]"), number_of_events),
&operations,
|bencher, operations| {
// Bench the routine.
@@ -145,14 +144,14 @@ fn reading(c: &mut Criterion) {
.build()
.expect("Failed to create an asynchronous runtime");
let room_id = room_id!("!foo:bar.baz");
let room_id = room_id!("!fabricandofitfaber: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("reading");
let mut group = c.benchmark_group("Linked chunk reading");
group.sample_size(10);
for num_events in [10, 100, 1000, 10_000, 100_000] {
for &num_events in NUMBER_OF_EVENTS {
let sqlite_temp_dir = tempdir().unwrap();
// Declare new stores for this set of events.
@@ -173,12 +172,7 @@ fn reading(c: &mut Criterion) {
// 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()
})
.map(|nth| event_factory.text_msg(format!("foo {nth}")).into_event())
.peekable();
let mut lc =
@@ -187,11 +181,14 @@ fn reading(c: &mut Criterion) {
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}") });
lc.push_gap_back(Gap { token: format!("gap{num_gaps}") });
num_gaps += 1;
}
@@ -205,30 +202,47 @@ fn reading(c: &mut Criterion) {
// Define the throughput.
group.throughput(Throughput::Elements(num_events));
// Get a bencher.
group.bench_function(BenchmarkId::new(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();
// 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");
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");
}
})
});
// 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();
@@ -240,21 +254,9 @@ fn reading(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
}
criterion_group! {
name = event_cache;
config = criterion();
config = Criterion::default();
targets = writing, reading,
}
+33 -54
View File
@@ -1,22 +1,24 @@
use std::time::Duration;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use matrix_sdk::{store::RoomLoadSettings, test_utils::mocks::MatrixMockServer};
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use matrix_sdk::{
cross_process_lock::CrossProcessLockConfig, 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, JoinedRoomBuilder, StateTestEvent};
use matrix_sdk_test::{JoinedRoomBuilder, base64_sha256_hash, 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::{MembershipState, RoomMemberEvent},
mxc_uri, owned_room_id, owned_user_id,
mxc_uri, owned_device_id, owned_room_id, owned_user_id,
serde::Raw,
user_id, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId,
};
use serde_json::json;
use tokio::runtime::Builder;
use wiremock::{Request, ResponseTemplate};
@@ -24,7 +26,7 @@ pub fn receive_all_members_benchmark(c: &mut Criterion) {
const MEMBERS_IN_ROOM: usize = 100000;
let runtime = Builder::new_multi_thread().build().expect("Can't create runtime");
let room_id = owned_room_id!("!room:example.com");
let room_id = owned_room_id!("!homohominilupusest:example.com");
let f = EventFactory::new().room(&room_id);
let mut member_events: Vec<Raw<RoomMemberEvent>> = Vec::with_capacity(MEMBERS_IN_ROOM);
@@ -56,15 +58,18 @@ pub fn receive_all_members_benchmark(c: &mut Criterion) {
.expect("initial filling of sqlite failed");
let base_client = BaseClient::new(
StoreConfig::new("cross-process-store-locks-holder-name".to_owned())
.state_store(sqlite_store),
StoreConfig::new(CrossProcessLockConfig::multi_process(
"cross-process-store-locks-holder-name",
))
.state_store(sqlite_store),
ThreadingSupport::Disabled,
);
runtime
.block_on(base_client.activate(
SessionMeta {
user_id: user_id!("@somebody:example.com").to_owned(),
device_id: device_id!("DEVICE_ID").to_owned(),
user_id: owned_user_id!("@somebody:example.com"),
device_id: owned_device_id!("DEVICE_ID"),
},
RoomLoadSettings::default(),
None,
@@ -82,7 +87,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();
});
@@ -100,32 +105,22 @@ pub fn load_pinned_events_benchmark(c: &mut Criterion) {
const PINNED_EVENTS_COUNT: usize = 100;
let runtime = Builder::new_multi_thread().enable_all().build().expect("Can't create runtime");
let room_id = owned_room_id!("!room:example.com");
let room_id = owned_room_id!("!homohominilupusest:example.com");
let sender_id = owned_user_id!("@sender:example.com");
let f = EventFactory::new().room(&room_id).sender(&sender_id);
let mut joined_room_builder =
JoinedRoomBuilder::new(&room_id).add_state_event(StateTestEvent::Encryption);
JoinedRoomBuilder::new(&room_id).add_state_event(f.room_encryption());
let pinned_event_ids: Vec<OwnedEventId> = (0..PINNED_EVENTS_COUNT)
.map(|i| EventId::parse(format!("${i}")).expect("Invalid event id"))
.map(|i| {
EventId::new_v2_or_v3(&base64_sha256_hash(format!("${i}").as_bytes()))
.expect("Invalid event id")
})
.collect();
joined_room_builder = joined_room_builder.add_state_event(StateTestEvent::Custom(json!(
{
"content": {
"pinned": pinned_event_ids
},
"event_id": "$15139375513VdeRF:localhost",
"origin_server_ts": 151393755,
"sender": "@example:localhost",
"state_key": "",
"type": "m.room.pinned_events",
"unsigned": {
"age": 703422
}
}
)));
joined_room_builder =
joined_room_builder.add_state_bulk(vec![f.room_pinned_events(pinned_event_ids).into()]);
let (server, client, room) = runtime.block_on(async move {
let server = MatrixMockServer::new().await;
@@ -162,11 +157,11 @@ pub fn load_pinned_events_benchmark(c: &mut Criterion) {
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);
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());
@@ -178,15 +173,14 @@ pub fn load_pinned_events_benchmark(c: &mut Criterion) {
.lock()
.await
.unwrap()
.as_clean()
.unwrap()
.clear_all_linked_chunks()
.await
.unwrap();
let timeline = TimelineBuilder::new(&room)
.with_focus(TimelineFocus::PinnedEvents {
max_events_to_load: 100,
max_concurrent_requests: 10,
})
.with_focus(TimelineFocus::PinnedEvents)
.build()
.await
.expect("Could not create timeline");
@@ -205,24 +199,9 @@ pub fn load_pinned_events_benchmark(c: &mut Criterion) {
group.finish();
}
fn criterion() -> Criterion {
#[cfg(target_os = "linux")]
{
Criterion::default().with_profiler(pprof::criterion::PProfProfiler::new(
100,
pprof::criterion::Output::Flamegraph(None),
))
}
#[cfg(not(target_os = "linux"))]
{
Criterion::default()
}
}
criterion_group! {
name = room;
config = criterion();
config = Criterion::default();
targets = receive_all_members_benchmark, load_pinned_events_benchmark,
}
criterion_main!(room);
+101
View File
@@ -0,0 +1,101 @@
use assert_matches::assert_matches;
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use futures_util::pin_mut;
use matrix_sdk::{stream::StreamExt, test_utils::mocks::MatrixMockServer};
use matrix_sdk_test::{JoinedRoomBuilder, base64_sha256_hash, event_factory::EventFactory};
use matrix_sdk_ui::{
RoomListService, eyeball_im::VectorDiff, room_list_service::filters::new_filter_non_left,
};
use rand::{distr::Uniform, prelude::Distribution};
use ruma::{OwnedRoomId, RoomId, owned_user_id};
use tokio::runtime::Builder;
/// Benchmark the time it takes to create a room list.
pub fn create(c: &mut Criterion) {
const NUMBER_OF_ROOMS: usize = 1000;
const NUMBER_OF_EVENTS_PER_ROOM: usize = 1000;
let runtime = Builder::new_multi_thread().enable_all().build().expect("Can't create runtime");
let (server, client) = runtime.block_on(async {
let server = MatrixMockServer::new().await;
let client = server.client_builder().build().await;
client.event_cache().subscribe().unwrap();
(server, client)
});
let sender_id = owned_user_id!("@mnt_io:matrix.org");
let mut rand = rand::rng();
let server_ts_range = Uniform::try_from(100..1000).unwrap();
for room_nth in 0..NUMBER_OF_ROOMS {
// Synapse's room IDs for rooms v1 to v11 have an 18 characters localpart.
let raw_room_id = format!("!arsgratiaartis{room_nth:04}:example.com");
let room_id = if room_nth % 10 == 9 {
// Make 1 in 10 rooms use a room v12 ID, which is a base64 hash similar to an
// event ID.
RoomId::new_v2(&base64_sha256_hash(raw_room_id.as_bytes())).unwrap()
} else {
OwnedRoomId::try_from(raw_room_id).unwrap()
};
let first_server_ts = server_ts_range.sample(&mut rand);
let event_factory = EventFactory::new().room(&room_id).server_ts(first_server_ts);
let events = (0..NUMBER_OF_EVENTS_PER_ROOM)
.map(|event_nth| {
event_factory
.text_msg(format!("a {room_nth}_{event_nth}"))
.sender(&sender_id)
.into_raw_sync()
})
.collect::<Vec<_>>();
let _room = runtime.block_on(async {
server
.sync_room(&client, JoinedRoomBuilder::new(&room_id).add_timeline_bulk(events))
.await
});
}
let mut group = c.benchmark_group("RoomList");
group.throughput(Throughput::Elements(NUMBER_OF_ROOMS.try_into().unwrap()));
group.bench_function(
BenchmarkId::new(
"Create",
format!("{NUMBER_OF_ROOMS} rooms × {NUMBER_OF_EVENTS_PER_ROOM} events"),
),
|bencher| {
bencher.to_async(&runtime).iter(|| async {
let room_list_service = RoomListService::new(client.clone())
.await
.expect("build the room list service");
let room_list = room_list_service.all_rooms().await.expect("fetch `all_rooms`");
let (entries_stream, entries_controller) =
room_list.entries_with_dynamic_adapters(20);
// Setting the filter will trigger the entries stream computation.
entries_controller.set_filter(Box::new(new_filter_non_left()));
pin_mut!(entries_stream);
let update = entries_stream.next().await.expect("receiving the reset update");
assert_eq!(update.len(), 1);
assert_matches!(&update[0], VectorDiff::Reset { values } => {
assert_eq!(values.len(), 20);
});
});
},
);
group.finish();
}
criterion_group! {
name = room_list;
config = Criterion::default();
targets = create
}
criterion_main!(room_list);
+41 -32
View File
@@ -1,28 +1,17 @@
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::{
authentication::matrix::MatrixSession, config::StoreConfig, Client, RoomInfo, RoomState,
SessionTokens, StateChanges,
Client, RoomInfo, RoomState, SessionTokens, StateChanges,
authentication::matrix::MatrixSession, config::StoreConfig,
cross_process_lock::CrossProcessLockConfig,
};
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 matrix_sdk_test::base64_sha256_hash;
use ruma::{OwnedRoomId, RoomId, owned_device_id, owned_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;
@@ -30,25 +19,43 @@ 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();
for i in 0..NUM_JOINED_ROOMS {
let room_id = RoomId::parse(format!("!room{i}:example.com")).unwrap().to_owned();
// Synapse's room IDs for rooms v1 to v11 have an 18 characters localpart.
let raw_room_id = format!("!joinedchamber{i:05}:example.com");
let room_id = if i % 20 == 19 {
// Make 1 in 20 rooms use a room v12 ID, which is a base64 hash similar to an
// event ID.
RoomId::new_v2(&base64_sha256_hash(raw_room_id.as_bytes())).unwrap()
} else {
OwnedRoomId::try_from(raw_room_id).unwrap()
};
changes.add_room(RoomInfo::new(&room_id, RoomState::Joined));
}
for i in 0..NUM_STRIPPED_JOINED_ROOMS {
let room_id = RoomId::parse(format!("!strippedroom{i}:example.com")).unwrap().to_owned();
// Synapse's room IDs for rooms v1 to v11 have an 18 characters localpart.
let raw_room_id = format!("!strippedlodge{i:05}:example.com");
let room_id = if i % 20 == 19 {
// Make 1 in 20 rooms use a room v12 ID, which is a base64 hash similar to an
// event ID.
RoomId::new_v2(&base64_sha256_hash(raw_room_id.as_bytes())).unwrap()
} else {
OwnedRoomId::try_from(raw_room_id).unwrap()
};
changes.add_room(RoomInfo::new(&room_id, RoomState::Invited));
}
let session = MatrixSession {
meta: SessionMeta {
user_id: user_id!("@somebody:example.com").to_owned(),
device_id: device_id!("DEVICE_ID").to_owned(),
user_id: owned_user_id!("@somebody:example.com"),
device_id: owned_device_id!("DEVICE_ID"),
},
tokens: SessionTokens { access_token: "OHEY".to_owned(), refresh_token: None },
};
@@ -58,19 +65,19 @@ 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")
.store_config(
StoreConfig::new("cross-process-store-locks-holder-name".to_owned())
.state_store(store.clone()),
StoreConfig::new(CrossProcessLockConfig::multi_process(
"cross-process-store-locks-holder-name",
))
.state_store(store.clone()),
)
.build()
.await
@@ -92,15 +99,17 @@ 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 {
let client = Client::builder()
.homeserver_url("https://matrix.example.com")
.store_config(
StoreConfig::new("cross-process-store-locks-holder-name".to_owned())
.state_store(store.clone()),
StoreConfig::new(CrossProcessLockConfig::multi_process(
"cross-process-store-locks-holder-name",
))
.state_store(store.clone()),
)
.build()
.await
@@ -124,7 +133,7 @@ pub fn restore_session(c: &mut Criterion) {
criterion_group! {
name = benches;
config = criterion();
config = Criterion::default();
targets = restore_session
}
criterion_main!(benches);
+27 -44
View File
@@ -1,10 +1,10 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use matrix_sdk::test_utils::mocks::MatrixMockServer;
use matrix_sdk_test::{event_factory::EventFactory, JoinedRoomBuilder, StateTestEvent};
use matrix_sdk_ui::timeline::TimelineBuilder;
use matrix_sdk_test::{JoinedRoomBuilder, event_factory::EventFactory};
use matrix_sdk_ui::timeline::{TimelineBuilder, TimelineReadReceiptTracking};
use ruma::{
events::room::message::RoomMessageEventContentWithoutRelation, owned_room_id, owned_user_id,
EventId,
OwnedEventId, events::room::message::RoomMessageEventContentWithoutRelation, owned_room_id,
owned_user_id,
};
use tokio::runtime::Builder;
@@ -18,7 +18,7 @@ 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 room_id = owned_room_id!("!fortesfortunajuvat:example.com");
let sender_id = owned_user_id!("@sender:example.com");
let other_sender_id = owned_user_id!("@other_sender:example.com");
@@ -35,51 +35,49 @@ pub fn create_timeline_with_initial_events(c: &mut Criterion) {
_ => 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(),
);
events.push(f.text_msg(format!("Message {i}")).sender(sender).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(),
);
let prev_event_id = events[i - 2]
.get_field::<OwnedEventId>("event_id")
.expect("invalid event ID")
.expect("missing event ID");
events.push(f.reaction(&prev_event_id, "👍").sender(sender).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();
let prev_event_id = events[i - 3]
.get_field::<OwnedEventId>("event_id")
.expect("invalid event ID")
.expect("missing event ID");
events.push(
f.text_msg(format!("* Message {}v2", i - 3))
.edit(
&prev_event,
&prev_event_id,
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 prev_event_id = events[i - 6]
.get_field::<OwnedEventId>("event_id")
.expect("invalid event ID")
.expect("missing event ID");
events.push(f.redaction(&prev_event_id).sender(sender).into_raw_sync());
}
}
let builder = JoinedRoomBuilder::new(&room_id)
.add_state_event(StateTestEvent::Encryption)
.add_state_event(f.room_encryption().sender(&sender_id))
.add_timeline_bulk(events);
let room = runtime.block_on(async move {
@@ -94,16 +92,16 @@ pub fn create_timeline_with_initial_events(c: &mut Criterion) {
room
});
let mut group = c.benchmark_group("Test");
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_timeline_with_initial_events", format!("{NUM_EVENTS} events")),
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()
.track_read_marker_and_receipts(TimelineReadReceiptTracking::AllEvents)
.build()
.await
.expect("Could not create timeline");
@@ -117,24 +115,9 @@ pub fn create_timeline_with_initial_events(c: &mut Criterion) {
group.finish();
}
fn criterion() -> Criterion {
#[cfg(target_os = "linux")]
{
Criterion::default().with_profiler(pprof::criterion::PProfProfiler::new(
100,
pprof::criterion::Output::Flamegraph(None),
))
}
#[cfg(not(target_os = "linux"))]
{
Criterion::default()
}
}
criterion_group! {
name = room;
config = criterion();
config = Criterion::default();
targets = create_timeline_with_initial_events
}
criterion_main!(room);
+2 -2
View File
@@ -1,4 +1,4 @@
// swift-tools-version:5.6
// swift-tools-version:5.7
// A package manifest for local development. This file will be copied
// into the root of the repo when generating an XCFramework.
@@ -8,7 +8,7 @@ import PackageDescription
let package = Package(
name: "MatrixRustSDK",
platforms: [
.iOS(.v15),
.iOS(.v16),
.macOS(.v12)
],
products: [
+2 -2
View File
@@ -1,4 +1,4 @@
// swift-tools-version: 5.6
// swift-tools-version: 5.7
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
@@ -6,7 +6,7 @@ import PackageDescription
let package = Package(
name: "MatrixRustSDK",
platforms: [
.iOS(.v15),
.iOS(.v16),
.macOS(.v12)
],
products: [
+14 -8
View File
@@ -2,7 +2,7 @@
name = "matrix-sdk-crypto-ffi"
version = "0.1.0"
authors = ["Damir Jelić <poljar@termina.org.uk>"]
edition = "2021"
edition = "2024"
rust-version.workspace = true
description = "Uniffi based bindings for the Rust SDK crypto crate"
repository = "https://github.com/matrix-org/matrix-rust-sdk"
@@ -23,14 +23,20 @@ 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"
hmac.workspace = true
http.workspace = true
matrix-sdk-common = { workspace = true, features = ["uniffi"] }
matrix-sdk-ffi-macros.workspace = true
pbkdf2 = "0.12.2"
pbkdf2.workspace = true
rand.workspace = true
ruma.workspace = true
serde.workspace = true
@@ -45,6 +51,7 @@ zeroize = { workspace = true, features = ["zeroize_derive"] }
[dependencies.js_int]
version = "0.2.2"
default-features = false
features = ["lax_deserialize"]
[dependencies.matrix-sdk-crypto]
@@ -56,17 +63,16 @@ workspace = true
features = ["crypto-store"]
[dependencies.tokio]
version = "1.43.1"
default-features = false
workspace = true
features = ["rt-multi-thread"]
[build-dependencies]
uniffi = { workspace = true, features = ["build"] }
vergen = { version = "8.2.5", features = ["build", "git", "gitcl"] }
uniffi = { workspace = true, default-features = false, features = ["build"] }
vergen-gitcl = { workspace = true, default-features = false, features = ["build"] }
[dev-dependencies]
assert_matches2.workspace = true
tempfile = "3.8.0"
tempfile.workspace = true
[lints]
workspace = true
+24 -10
View File
@@ -33,15 +33,22 @@ Rust supports many different [targets], you'll have to make sure to pick the
right one for your device or emulator.
After this is done, we'll have to configure [Cargo] to use the correct linker
for our target. Cargo is configured using a TOML file that will be found in
`%USERPROFILE%\.cargo\config.toml` on Windows or `$HOME/.cargo/config` on Unix
platforms. More details and configuration options for Cargo can be found in the
official docs over [here](https://doc.rust-lang.org/cargo/reference/config.html).
for our target, by providing the Cargo setting of
[target.<triple>.linker](https://doc.rust-lang.org/cargo/reference/config.html#targettriplelinker)
with a value of the path to an appropriate linker in your NDK installation.
This may be set through an environment variable:
```
$ export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="<path-to-ndk-installation>/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android30-clang"
```
Alternatively, it may be set in the `.cargo/config.toml` file in the current directory,
any parent directory, or your home directory:
```
[target.aarch64-linux-android]
ar = "NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/ar"
linker = "NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android30-clang"
linker = "<path-to-ndk-installation>/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android30-clang"
```
## Building
@@ -51,7 +58,13 @@ we'll need to set the `ANDROID_NDK` environment variable to the location of our
Android NDK installation.
```
$ export ANDROID_NDK=$HOME/Android/Sdk/ndk/22.0.7026061/
$ export ANDROID_NDK=$HOME/Android/Sdk/ndk/<some-installed-version>
```
Also, include the NDK tools directory in your `PATH`:
```
$ export PATH="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH"
```
### Building for a target
@@ -62,12 +75,13 @@ The bindings can built for the `aarch64` target with:
$ cargo build --target aarch64-linux-android
```
After that, a dynamic library can be found in the `target/aarch64-linux-android/debug` directory.
The library will be called `libmatrix_crypto.so` and needs to be renamed and
After that, a dynamic library can be found in the `target/aarch64-linux-android/debug` directory,
under the repository root directory.
The library will be called `libmatrix_sdk_crypto_ffi.so` and needs to be renamed and
copied into the `jniLibs` directory of your Android project, for Element Android:
```
$ cp ../../target/aarch64-linux-android/debug/libmatrix_crypto.so \
$ cp ../../target/aarch64-linux-android/debug/libmatrix_sdk_crypto_ffi.so \
/home/example/matrix-sdk-android/src/main/jniLibs/aarch64/libuniffi_olm.so
```
+3 -2
View File
@@ -5,7 +5,7 @@ use std::{
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.
@@ -59,7 +59,8 @@ fn get_clang_major_version(clang_path: &Path) -> 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,10 +3,10 @@ use std::{collections::HashMap, iter, ops::DerefMut, sync::Arc};
use hmac::Hmac;
use matrix_sdk_crypto::{
backups::DecryptionError,
store::{BackupDecryptionKey, CryptoStoreError as InnerStoreError},
store::{CryptoStoreError as InnerStoreError, types::BackupDecryptionKey},
};
use pbkdf2::pbkdf2;
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use rand::{RngExt, distr::Alphanumeric, rng};
use sha2::Sha512;
use thiserror::Error;
use zeroize::Zeroize;
@@ -75,11 +75,7 @@ impl BackupRecoveryKey {
#[allow(clippy::new_without_default)]
#[uniffi::constructor]
pub fn new() -> Arc<Self> {
Arc::new(Self {
inner: BackupDecryptionKey::new()
.expect("Can't gather enough randomness to create a recovery key"),
passphrase_info: None,
})
Arc::new(Self { inner: BackupDecryptionKey::new(), passphrase_info: None })
}
/// Try to create a [`BackupRecoveryKey`] from a base 64 encoded string.
@@ -97,7 +93,7 @@ impl BackupRecoveryKey {
/// Create a new [`BackupRecoveryKey`] from the given passphrase.
#[uniffi::constructor]
pub fn new_from_passphrase(passphrase: String) -> Arc<Self> {
let mut rng = thread_rng();
let mut rng = rng();
let salt: String = iter::repeat(())
.map(|()| rng.sample(Alphanumeric))
.map(char::from)
@@ -1,15 +1,16 @@
use std::{mem::ManuallyDrop, sync::Arc};
use matrix_sdk_common::executor::Handle;
use matrix_sdk_crypto::{
DecryptionSettings,
dehydrated_devices::{
DehydratedDevice as InnerDehydratedDevice, DehydratedDevices as InnerDehydratedDevices,
RehydratedDevice as InnerRehydratedDevice,
},
store::DehydratedDeviceKey as InnerDehydratedDeviceKey,
store::types::DehydratedDeviceKey as InnerDehydratedDeviceKey,
};
use ruma::{api::client::dehydrated_device, events::AnyToDeviceEvent, serde::Raw, OwnedDeviceId};
use ruma::{OwnedDeviceId, api::client::dehydrated_device, events::AnyToDeviceEvent, serde::Raw};
use serde_json::json;
use tokio::runtime::Handle;
use crate::{CryptoStoreError, DehydratedDeviceKey};
@@ -28,8 +29,6 @@ pub enum DehydrationError {
Store(#[from] matrix_sdk_crypto::CryptoStoreError),
#[error("The pickle key has an invalid length, expected 32 bytes, got {0}")]
PickleKeyLength(usize),
#[error(transparent)]
Rand(#[from] rand::Error),
}
impl From<matrix_sdk_crypto::dehydrated_devices::DehydrationError> for DehydrationError {
@@ -154,9 +153,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(())
}
@@ -222,13 +225,11 @@ impl From<dehydrated_device::put_dehydrated_device::unstable::Request>
#[cfg(test)]
mod tests {
use crate::{dehydrated_devices::DehydrationError, DehydratedDeviceKey};
use crate::{DehydratedDeviceKey, dehydrated_devices::DehydrationError};
#[test]
fn test_creating_dehydrated_key() {
let result = DehydratedDeviceKey::new();
assert!(result.is_ok());
let dehydrated_device_key = result.unwrap();
let dehydrated_device_key = DehydratedDeviceKey::new();
let base_64 = dehydrated_device_key.to_base64();
let inner_bytes = dehydrated_device_key.inner;
+44 -1
View File
@@ -1,9 +1,9 @@
#![allow(missing_docs)]
use matrix_sdk_crypto::{
store::{CryptoStoreError as InnerStoreError, DehydrationError as InnerDehydrationError},
KeyExportError, MegolmError, OlmError, SecretImportError as RustSecretImportError,
SignatureError as InnerSignatureError,
store::{CryptoStoreError as InnerStoreError, DehydrationError as InnerDehydrationError},
};
use matrix_sdk_sqlite::OpenStoreError;
use ruma::{IdParseError, OwnedUserId};
@@ -76,6 +76,49 @@ pub enum DecryptionError {
Store { error: String },
}
/// Error describing what went wrong when exporting a [`SecretsBundle`].
///
/// The [`SecretsBundle`] can only be exported if we have all cross-signing
/// private keys in the store.
#[derive(Debug, thiserror::Error, uniffi::Error)]
pub enum SecretsBundleExportError {
/// The store itself had an error.
#[error(transparent)]
CryptoStore(CryptoStoreError),
/// We're missing one or more cross-signing keys.
#[error("The store doesn't contain all the cross-signing keys")]
MissingCrossSigningKeys,
/// We have a backup key stored, but we don't know the version of the
/// backup.
#[error("The store contains a backup key, but no backup version")]
MissingBackupVersion,
#[error("serialization error: {error}")]
Serialization { error: String },
}
impl From<matrix_sdk_crypto::store::SecretsBundleExportError> for SecretsBundleExportError {
fn from(value: matrix_sdk_crypto::store::SecretsBundleExportError) -> Self {
match value {
matrix_sdk_crypto::store::SecretsBundleExportError::Store(e) => {
Self::CryptoStore(e.into())
}
matrix_sdk_crypto::store::SecretsBundleExportError::MissingCrossSigningKey(_)
| matrix_sdk_crypto::store::SecretsBundleExportError::MissingCrossSigningKeys => {
Self::MissingCrossSigningKeys
}
matrix_sdk_crypto::store::SecretsBundleExportError::MissingBackupVersion => {
Self::MissingBackupVersion
}
}
}
}
impl From<serde_json::Error> for SecretsBundleExportError {
fn from(err: serde_json::Error) -> Self {
Self::Serialization { error: err.to_string() }
}
}
impl From<MegolmError> for DecryptionError {
fn from(value: MegolmError) -> Self {
match &value {
+42 -20
View File
@@ -31,19 +31,22 @@ pub use error::{
CryptoStoreError, DecryptionError, KeyImportError, SecretImportError, SignatureError,
};
use js_int::UInt;
pub use logger::{set_logger, Logger};
pub use logger::{Logger, set_logger};
pub use machine::{KeyRequestPair, OlmMachine, SignatureVerification};
use matrix_sdk_common::deserialized_responses::{ShieldState as RustShieldState, ShieldStateCode};
use matrix_sdk_crypto::{
CollectStrategy, EncryptionSettings as RustEncryptionSettings,
olm::{IdentityKeys, InboundGroupSession, SenderData, Session},
store::{
Changes, CryptoStore, DehydratedDeviceKey as InnerDehydratedDeviceKey, PendingChanges,
RoomSettings as RustRoomSettings,
CryptoStore,
types::{
Changes, DehydratedDeviceKey as InnerDehydratedDeviceKey, PendingChanges,
RoomSettings as RustRoomSettings,
},
},
types::{
DeviceKey, DeviceKeys, EventEncryptionAlgorithm as RustEventEncryptionAlgorithm, SigningKey,
},
CollectStrategy, EncryptionSettings as RustEncryptionSettings,
};
use matrix_sdk_sqlite::SqliteCryptoStore;
pub use responses::{
@@ -51,9 +54,9 @@ pub use responses::{
Request, RequestType, SignatureUploadRequest, UploadSigningKeysRequest,
};
use ruma::{
events::room::history_visibility::HistoryVisibility as RustHistoryVisibility,
DeviceKeyAlgorithm, DeviceKeyId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedUserId,
RoomId, SecondsSinceUnixEpoch, UserId,
events::room::history_visibility::HistoryVisibility as RustHistoryVisibility,
};
use serde::{Deserialize, Serialize};
use tokio::runtime::Runtime;
@@ -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;
@@ -503,6 +506,7 @@ fn collect_sessions(
})
.collect::<anyhow::Result<_>>()?,
sender_data: SenderData::legacy(),
forwarder_data: None,
room_id: RoomId::parse(session.room_id)?,
imported: session.imported,
backed_up: session.backed_up,
@@ -662,6 +666,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,
@@ -691,6 +698,8 @@ impl From<EncryptionSettings> for RustEncryptionSettings {
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(),
@@ -818,10 +827,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(())?,
@@ -841,9 +850,9 @@ pub struct DehydratedDeviceKey {
impl DehydratedDeviceKey {
/// Generates a new random pickle key.
pub fn new() -> Result<Self, DehydrationError> {
let inner = InnerDehydratedDeviceKey::new()?;
Ok(inner.into())
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
InnerDehydratedDeviceKey::new().into()
}
/// Creates a new dehydration pickle key from the given slice.
@@ -866,8 +875,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 }
}
}
@@ -907,6 +916,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,
@@ -917,7 +930,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,
})
}
}
@@ -997,18 +1015,18 @@ impl PkEncryption {
}
/// Encrypt a message using this [`PkEncryption`] object.
pub fn encrypt(&self, plaintext: &str) -> PkMessage {
pub fn encrypt(&self, plaintext: &str) -> Option<PkMessage> {
use vodozemac::base64_encode;
let message = self.inner.encrypt(plaintext.as_ref());
let message = self.inner.encrypt(plaintext.as_ref()).ok()?;
let vodozemac::pk_encryption::Message { ciphertext, mac, ephemeral_key } = message;
PkMessage {
Some(PkMessage {
ciphertext: base64_encode(ciphertext),
mac: base64_encode(mac),
ephemeral_key: ephemeral_key.to_base64(),
}
})
}
}
@@ -1031,11 +1049,11 @@ uniffi::setup_scaffolding!();
#[cfg(test)]
mod tests {
use anyhow::Result;
use serde_json::{json, Value};
use serde_json::{Value, json};
use tempfile::tempdir;
use super::MigrationData;
use crate::{migrate, EventEncryptionAlgorithm, OlmMachine, RoomSettings};
use crate::{EventEncryptionAlgorithm, OlmMachine, RoomSettings, migrate};
#[test]
fn android_migration() -> Result<()> {
@@ -1170,6 +1188,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
@@ -1179,6 +1199,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
+1 -1
View File
@@ -3,7 +3,7 @@ use std::{
sync::{Arc, Mutex},
};
use tracing_subscriber::{fmt::MakeWriter, EnvFilter};
use tracing_subscriber::{EnvFilter, fmt::MakeWriter};
/// Trait that can be used to forward Rust logs over FFI to a language specific
/// logger.
+84 -41
View File
@@ -10,18 +10,22 @@ use std::{
use js_int::UInt;
use matrix_sdk_common::deserialized_responses::AlgorithmInfo;
use matrix_sdk_crypto::{
CollectStrategy, DecryptionSettings, LocalTrust, OlmMachine as InnerMachine,
UserIdentity as SdkUserIdentity,
backups::{
MegolmV1BackupKey as RustBackupKey, SignatureState,
SignatureVerification as RustSignatureCheckResult,
},
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,
};
use ruma::{
DeviceKeyAlgorithm, EventId, OneTimeKeyAlgorithm, OwnedTransactionId, OwnedUserId, RoomId,
UserId,
api::{
IncomingResponse,
client::{
backup::add_backup_keys::v3::Response as KeysBackupResponse,
keys::{
@@ -31,35 +35,35 @@ use ruma::{
upload_signatures::v3::Response as SignatureUploadResponse,
},
message::send_message_event::v3::Response as RoomMessageResponse,
sync::sync_events::{v3::ToDevice, DeviceLists as RumaDeviceLists},
sync::sync_events::{DeviceLists as RumaDeviceLists, v3::ToDevice},
to_device::send_event_to_device::v3::Response as ToDeviceResponse,
},
IncomingResponse,
},
events::{
key::verification::VerificationMethod, room::message::MessageType, AnyMessageLikeEvent,
AnySyncMessageLikeEvent, MessageLikeEvent,
AnyMessageLikeEvent, AnySyncMessageLikeEvent, AnyTimelineEvent, MessageLikeEvent,
key::verification::VerificationMethod, room::message::MessageType,
},
serde::Raw,
to_device::DeviceIdOrAllDevices,
DeviceKeyAlgorithm, EventId, OneTimeKeyAlgorithm, OwnedTransactionId, OwnedUserId, RoomId,
UserId,
};
use serde::{Deserialize, Serialize};
use serde_json::{value::RawValue, Value};
use serde_json::{Value, value::RawValue};
use tokio::runtime::Runtime;
use zeroize::Zeroize;
use crate::{
dehydrated_devices::DehydratedDevices,
error::{CryptoStoreError, DecryptionError, SecretImportError, SignatureError},
parse_user_id,
responses::{response_from_string, OwnedResponse},
BackupKeys, BackupRecoveryKey, BootstrapCrossSigningResult, CrossSigningKeyExport,
CrossSigningStatus, DecodeError, DecryptedEvent, Device, DeviceLists, EncryptionSettings,
EventEncryptionAlgorithm, KeyImportError, KeysImportResult, MegolmV1BackupKey,
ProgressListener, Request, RequestType, RequestVerificationResult, RoomKeyCounts, RoomSettings,
Sas, SignatureUploadRequest, StartSasResult, UserIdentity, Verification, VerificationRequest,
dehydrated_devices::DehydratedDevices,
error::{
CryptoStoreError, DecryptionError, SecretImportError, SecretsBundleExportError,
SignatureError,
},
parse_user_id,
responses::{OwnedResponse, response_from_string},
};
/// The return value for the [`OlmMachine::receive_sync_changes()`] method.
@@ -96,8 +100,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(),
@@ -526,6 +530,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,15 +549,17 @@ 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()
@@ -798,12 +805,12 @@ impl OlmMachine {
let room_id = RoomId::parse(room_id)?;
let content = serde_json::from_str(&content)?;
let encrypted_content = self
let result = self
.runtime
.block_on(self.inner.encrypt_room_event_raw(&room_id, &event_type, &content))
.expect("Encrypting an event produced an error");
Ok(serde_json::to_string(&encrypted_content)?)
Ok(serde_json::to_string(&result.content)?)
}
/// Encrypt the given event with the given type and content for the given
@@ -829,6 +836,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();
@@ -837,8 +845,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(),
@@ -861,9 +872,18 @@ impl OlmMachine {
///
/// * `room_id` - The unique id of the room where the event was sent to.
///
/// * `handle_verification_events` - if the supplied event is a verification
/// event, use it to update the verification state. **Note**: it is
/// recommended to avoid setting this flag to true and use the explicit
/// [`OlmMachine::receive_verification_event`] method instead:
/// verification events sometimes need preparation before we can handle
/// them: see the documentation for
/// [`OlmMachine::receive_verification_event`].
///
/// * `strict_shields` - If `true`, messages will be decorated with strict
/// warnings (use `false` to match legacy behaviour where unsafe keys have
/// lower severity warnings and unverified identities are not decorated).
///
/// * `decryption_settings` - The setting for decrypting messages.
pub fn decrypt_room_event(
&self,
@@ -893,22 +913,19 @@ impl OlmMachine {
&decryption_settings,
))?;
if handle_verification_events {
if let Ok(e) = decrypted.event.deserialize() {
match &e {
AnyMessageLikeEvent::RoomMessage(MessageLikeEvent::Original(
original_event,
)) => {
if let MessageType::VerificationRequest(_) = &original_event.content.msgtype
{
self.runtime.block_on(self.inner.receive_verification_event(&e))?;
}
}
_ if e.event_type().to_string().starts_with("m.key.verification") => {
if handle_verification_events
&& let Ok(AnyTimelineEvent::MessageLike(e)) = decrypted.event.deserialize()
{
match &e {
AnyMessageLikeEvent::RoomMessage(MessageLikeEvent::Original(original_event)) => {
if let MessageType::VerificationRequest(_) = &original_event.content.msgtype {
self.runtime.block_on(self.inner.receive_verification_event(&e))?;
}
_ => (),
}
_ if e.event_type().to_string().starts_with("m.key.verification") => {
self.runtime.block_on(self.inner.receive_verification_event(&e))?;
}
_ => (),
}
}
@@ -1092,6 +1109,14 @@ impl OlmMachine {
///
/// This method can be used to pass verification events that are happening
/// in rooms to the `OlmMachine`. The event should be in the decrypted form.
///
/// **Note**: If the supplied event is an `m.room.message` event with
/// `msgtype: m.key.verification.request`, then the device information for
/// the sending user must be up-to-date before calling this method
/// (otherwise, the request will be ignored). It is hard to guarantee this
/// is the case, but you can maximize your chances by explicitly making a
/// request to /keys/query for the user's device info, and processing the
/// response with [`OlmMachine::mark_request_as_sent`].
pub fn receive_verification_event(
&self,
event: String,
@@ -1384,6 +1409,24 @@ impl OlmMachine {
Ok(())
}
/// Export all the secrets we have in the store into a serialized
/// SecretsBundle.
///
/// This method will export all the private cross-signing keys and, if
/// available, the private part of a backup key and its accompanying
/// version.
///
/// The method will fail if we don't have all three private cross-signing
/// keys available.
///
/// **Warning**: Only export this and share it with a trusted recipient,
/// i.e. if an existing device is sharing this with a new device.
pub fn export_secrets_bundle(&self) -> Result<String, SecretsBundleExportError> {
let bundle = self.runtime.block_on(self.inner.store().export_secrets_bundle())?;
Ok(serde_json::to_string(&bundle)?)
}
/// Request missing local secrets from our devices (cross signing private
/// keys, megolm backup). This will ask the sdk to create outgoing
/// request to get the missing secrets.
@@ -4,14 +4,15 @@ use std::collections::HashMap;
use http::Response;
use matrix_sdk_crypto::{
CrossSigningBootstrapRequests,
types::requests::{
AnyIncomingResponse, KeysBackupRequest, OutgoingRequest,
OutgoingVerificationRequest as SdkVerificationRequest, RoomMessageRequest, ToDeviceRequest,
UploadSigningKeysRequest as RustUploadSigningKeysRequest,
},
CrossSigningBootstrapRequests,
};
use ruma::{
OwnedTransactionId, UserId,
api::client::{
backup::add_backup_keys::v3::Response as KeysBackupResponse,
keys::{
@@ -27,8 +28,7 @@ use ruma::{
to_device::send_event_to_device::v3::Response as ToDeviceResponse,
},
assign,
events::EventContent,
OwnedTransactionId, UserId,
events::MessageLikeEventContent,
};
use serde_json::json;
+1 -1
View File
@@ -1,4 +1,4 @@
use matrix_sdk_crypto::{types::CrossSigningKey, UserIdentity as SdkUserIdentity};
use matrix_sdk_crypto::{UserIdentity as SdkUserIdentity, types::CrossSigningKey};
use crate::CryptoStoreError;
@@ -1,14 +1,15 @@
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,
Verification as InnerVerification, VerificationRequest as InnerVerificationRequest,
CancelInfo as RustCancelInfo, QrVerification as InnerQr, QrVerificationState, Sas as InnerSas,
SasState as RustSasState, Verification as InnerVerification,
VerificationRequest as InnerVerificationRequest,
VerificationRequestState as RustVerificationRequestState,
matrix_sdk_qrcode::QrVerificationData,
};
use ruma::events::key::verification::VerificationMethod;
use tokio::runtime::Handle;
use vodozemac::{base64_decode, base64_encode};
use crate::{CryptoStoreError, OutgoingVerificationRequest, SignatureUploadRequest};
@@ -209,7 +210,7 @@ impl Sas {
///
/// # Flowchart
///
/// The flow of the verification process is pictured bellow. Please note
/// The flow of the verification process is pictured below. Please note
/// that the process can be cancelled at each step of the process.
/// Either side can cancel the process.
///
+4 -4
View File
@@ -1,6 +1,6 @@
[package]
description = "Helper macros to write FFI bindings"
edition = "2021"
edition = "2024"
homepage = "https://github.com/matrix-org/matrix-rust-sdk"
keywords = ["matrix", "chat", "messaging", "ruma"]
license = "Apache-2.0"
@@ -17,9 +17,9 @@ test = false
doctest = false
[dependencies]
proc-macro2 = "1.0.86"
quote = "1.0.18"
syn = { version = "2.0.43", features = ["full", "extra-traits"] }
proc-macro2 = { workspace = true , features = ["proc-macro"] }
quote.workspace = true
syn = { workspace = true, features = ["full", "extra-traits", "proc-macro"] }
[lints]
workspace = true
+8 -8
View File
@@ -27,18 +27,18 @@ pub fn export(attr: TokenStream, item: TokenStream) -> TokenStream {
}
} else if let Item::Impl(blk) = &item {
for item in &blk.items {
if let ImplItem::Fn(fun) = item {
if fun.sig.asyncness.is_some() {
return true;
}
if let ImplItem::Fn(fun) = item
&& fun.sig.asyncness.is_some()
{
return true;
}
}
} else if let Item::Trait(blk) = &item {
for item in &blk.items {
if let TraitItem::Fn(fun) = item {
if fun.sig.asyncness.is_some() {
return true;
}
if let TraitItem::Fn(fun) = item
&& fun.sig.asyncness.is_some()
{
return true;
}
}
}
+445 -8
View File
@@ -6,11 +6,444 @@ All notable changes to this project will be documented in this file.
## [Unreleased] - ReleaseDate
### Bug Fixes
- Add `Client::set_avatar_url` to manually set the avatar URL of the user to a provided MXC one.
- Allow setting a custom Sliding Sync connection ID and timeline limit on `RoomListService`.
([#6289](https://github.com/matrix-org/matrix-rust-sdk/pull/6289))
- Fix devices on Android 11 crashing because the SDK could not be initialized using `libloading`
to get a reference to the JVM. Replaced `libloading` with `jvm-getter`, which works like a
compatibility layer. ([#6370](https://github.com/matrix-org/matrix-rust-sdk/pull/6370))
- Added `android_platform.rs` for fixing the `rustls` integration on Android, which was broken.
([#6306](https://github.com/matrix-org/matrix-rust-sdk/pull/6306))
- [**breaking**] `OtherState` properly supports redacted events that still have fields in the
content. The following fields are no longer optional:
- `federate` in `OtherState::RoomCreate`.
- `history_visibility` in `OtherState::RoomHistoryVisibility`.
- `thresholds` in `OtherState::RoomPowerLevels`.
- `omit_checksums` option is now enabled for the Kotlin bindings in all FFI-exporting crates.
We enabled them because with JNA direct mapping enabled they result in invalid checks in
ARM 32bit devices, preventing the SDK from working altogether (see
[this issue](https://github.com/mozilla/uniffi-rs/issues/2740)).
([#6069](https://github.com/matrix-org/matrix-rust-sdk/pull/6069),
[#6112](https://github.com/matrix-org/matrix-rust-sdk/pull/6112),
[#6115](https://github.com/matrix-org/matrix-rust-sdk/pull/6115),
[#6116](https://github.com/matrix-org/matrix-rust-sdk/pull/6116)).
- `Client::create_room` now uses `RoomPowerLevelsContentOverride` under the hood instead of
`RoomPowerLevelsEventContent` to be able to explicitly set values which would previously be
ignored if they matched the default power level values specified by the spec: these may not be
the same in the homeserver and result in rooms with incorrect power levels being created.
([#6034](https://github.com/matrix-org/matrix-rust-sdk/pull/6034))
- Fix the `is_last_admin` check in `LeaveSpaceRoom` since it was not
accounting for the membership state.
[#6032](https://github.com/matrix-org/matrix-rust-sdk/pull/6032)
- [**breaking**] `LatestEventValue::Local { is_sending: bool }` is replaced
by [`state: LatestEventValueLocalState`] to represent 3 states: `IsSending`,
`HasBeenSent` and `CannotBeSent`.
([#5968](https://github.com/matrix-org/matrix-rust-sdk/pull/5968/))
### Features
- Add `Client::get_dm_rooms` function to get a list with the DMs for the provided user id.
([#6487](https://github.com/matrix-org/matrix-rust-sdk/pull/6487))
- Expose `ffi::NotificationRoomInfo::service_members` so clients can use the list of service
members to calculate if a room is a DM from the notification info.
([#6474](https://github.com/matrix-org/matrix-rust-sdk/pull/6474))
- Enable `experimental-push-secrets` feature by default.
([#6473](https://github.com/matrix-org/matrix-rust-sdk/pull/6394))
- Add new high-level search helpers `RoomSearchIterator` and `GlobalSearchIterator` to perform
searches for messages in a room or across all rooms.
([6394](https://github.com/matrix-org/matrix-rust-sdk/pull/6394))
- Added the `Client.request_openid_token()` method.
([#6458](https://github.com/matrix-org/matrix-rust-sdk/pull/6458))
- Added the `Client::import_secrets_bundle` method.
([#6212](https://github.com/matrix-org/matrix-rust-sdk/pull/6212))
- [**breaking**] Remove support for `native-tls` and remove all feature
flags for selecting TLS backend, as `rustls` is the now the only supported
TLS backend.
([#6409](https://github.com/matrix-org/matrix-rust-sdk/pull/6409))
- Expose `event_type_raw` and `latest_json()` on `EventTimelineItem`,
allowing clients to access the raw event type string and full event JSON for
custom event handling without pattern-matching through nested enums.
([#6387](https://github.com/matrix-org/matrix-rust-sdk/pull/6387))
([#6424](https://github.com/matrix-org/matrix-rust-sdk/pull/6424))
- Expose sync v2 API through FFI via `Client.sync_v2()` and
`Client.sync_once_v2()`, enabling mobile clients to sync without
requiring Sliding Sync support on the homeserver. `Client.sync_v2()`
accepts a `SyncListenerV2` callback that receives a `SyncResponseV2`
after each successful sync.
([#6359](https://github.com/matrix-org/matrix-rust-sdk/pull/6359))
- Added `HomeserverCapabilities` and `Client::homeserver_capabilities()` to get the capabilities
of the homeserver. ([#6371](https://github.com/matrix-org/matrix-rust-sdk/pull/6371))
- Expose `Room.send_state_event_raw()` for sending arbitrary state events
through the FFI layer.
([#6350](https://github.com/matrix-org/matrix-rust-sdk/pull/6350))
- Introduce a `ThreadListService` which offers reactive interfaces for rendering
and managing the list of threads from a particular room.
([6311](https://github.com/matrix-org/matrix-rust-sdk/pull/6311))
- [**breaking**] Move `LiveLocation` out of `TimelineItemContent` and into `MsgLikeKind`
so it has access to `MsgLikeContent` `reactions`.
([#6286](https://github.com/matrix-org/matrix-rust-sdk/pull/6286))
- Add `HumanQrLoginError::UnsupportedQrCodeType` for when a QR is parseable but cannot be used to
complete a login.
([#6141](https://github.com/matrix-org/matrix-rust-sdk/pull/6285)
- Add `HumanQrGrantLoginError::UnsupportedQrCodeType` for when a QR is parseable but cannot be used
to grant a login.
([#6141](https://github.com/matrix-org/matrix-rust-sdk/pull/6285)
- Add the `QrCodeData::base_url` and `QrCodeData::intent` methods.
([#6283](https://github.com/matrix-org/matrix-rust-sdk/pull/6283))
- Add `Encryption::recover_and_fix_backup` to automatically fix key storage backup if the
private backup decryption key is missing, invalid or inconsistent with the public key.
([#6252](https://github.com/matrix-org/matrix-rust-sdk/pull/6252))
- Add support for [MSC3489](https://github.com/matrix-org/matrix-spec-proposals/pull/3489)
live location sharing through a new `TimelineItemContent::LiveLocation` variant.
([#6232](https://github.com/matrix-org/matrix-rust-sdk/pull/6232))
- Add `HumanQrGrantLoginError::ConnectionInsecure` for errors establishing the secure channel
([#6141](https://github.com/matrix-org/matrix-rust-sdk/pull/6141)
- Add `HumanQrGrantLoginError::Expired` for when a timeout is encountered during the grant
([#6141](https://github.com/matrix-org/matrix-rust-sdk/pull/6141)
- Add `HumanQrGrantLoginError::Cancelled` for when the grant is cancelled
([#6141](https://github.com/matrix-org/matrix-rust-sdk/pull/6141)
- Add `HumanQrGrantLoginError::OtherDeviceAlreadySignedIn` for when the other device is already signed in
([#6141](https://github.com/matrix-org/matrix-rust-sdk/pull/6141)
- Add `HumanQrGrantLoginError::DeviceNotFound` for when the requested device was not returned by the homeserver
([#6141](https://github.com/matrix-org/matrix-rust-sdk/pull/6141)
- Add `RoomInfo::is_low_priority` for getting the room's `m.lowpriority` tag state
([#6183](https://github.com/matrix-org/matrix-rust-sdk/pull/6183))
- Add `Client::subscribe_to_duplicate_key_upload_errors` for listening to duplicate key
upload errors from `/keys/upload`.
([#6135](https://github.com/matrix-org/matrix-rust-sdk/pull/6135/))
- Add `NotificationItem::raw_event` to get the raw event content of the event that triggered the notification, which can be useful for debugging and to support clients that want to implement custom handling for certain notifications. ([#6122](https://github.com/matrix-org/matrix-rust-sdk/pull/6122))
- [**breaking**] Extend `TimelineFocus::Event` to allow marking the target
event as the root of a thread.
[#6050](https://github.com/matrix-org/matrix-rust-sdk/pull/6050)
- [**breaking**] Remove `TimelineFilter::EventTypeFilter` which has been replaced by
the more generic `TimelineFilter::EventFilter`. Users of `TimelineEventTypeFilter::include`
and `TimelineEventTypeFilter::exclude` can switch to `TimelineEventFilter::include_event_types`
and `TimelineEventFilter::exclude_event_types`.
([#6070](https://github.com/matrix-org/matrix-rust-sdk/pull/6070/))
- Add `TimelineFilter::EventFilter` for filtering events based on their type or
content. For content filtering, only membership and profile change filters
are available as of now.
([#6048](https://github.com/matrix-org/matrix-rust-sdk/pull/6048/))
- Introduce `SpaceFilter`s as a mechanism for narrowing down what's displayed in
the room list ([#6025](https://github.com/matrix-org/matrix-rust-sdk/pull/6025))
- Expose room power level thresholds in `OtherState::RoomPowerLevels` (ban, kick, invite, redact, state &
events defaults, per-event overrides, notifications), so clients can compute the required power level
for actions and compare with previous values. ([#5931](https://github.com/matrix-org/matrix-rust-sdk/pull/5931))
- Add `RoomCreationParameters::is_space` parameter to be able to create spaces. ([#6010](https://github.com/matrix-org/matrix-rust-sdk/pull/6010/))
- [**breaking**] `LazyTimelineItemProvider::get_shields` no longer returns an
an `Option`: the `ShieldState` type contains a `None` variant, so the
`Option` was redundant. The `message` field has also been removed: since there
was no way to localise the returned string, applications should not be using it.
([#5959](https://github.com/matrix-org/matrix-rust-sdk/pull/5959))
- Add `Room::list_threads` to list all the threads in a room.
([#5953](https://github.com/matrix-org/matrix-rust-sdk/pull/5953))
- Add `SpaceService::get_space_room` to get a space given its id from the space graph if available.
[#5944](https://github.com/matrix-org/matrix-rust-sdk/pull/5944)
- Add `QrCodeData::to_bytes()` to allow generation of a QR code.
([#5939](https://github.com/matrix-org/matrix-rust-sdk/pull/5939))
- [**breaking**]: The new Latest Event API replaces the old API.
`Room::new_latest_event` overwrites the `Room::latest_event` method. See the
documentation of `matrix_sdk::latest_event` to learn about the new API.
[#5624](https://github.com/matrix-org/matrix-rust-sdk/pull/5624/)
- Created `RoomPowerLevels::events` function which returns a `HashMap<TimelineEventType, i64>` with all the power
levels per event type. ([#5937](https://github.com/matrix-org/matrix-rust-sdk/pull/5937))
- Expose `EventTimelineItem::forwarder` and `forwarder_profile`, which, if present, provide the ID and profile of
the user who forwarded the keys used to decrypt the event as part of an [MSC4268](https://github.com/matrix-org/matrix-spec-proposals/pull/4268)
key bundle.
([#6000](https://github.com/matrix-org/matrix-rust-sdk/pull/6000))
- Add `NonFavorite` filter to the Room List API. ([#5991](https://github.com/matrix-org/matrix-rust-sdk/pull/5991)
- Add `call_intent` (either `RtcCallIntent::Audio` or `RtcCallIntent::Video`) field to `RtcNotification` event content. ([#6207](https://github.com/matrix-org/matrix-rust-sdk/pull/6207))
- Add `RoomInfo::active_room_call_consensus_intent` method to get the call intent for the current call,
based on what members are advertising.
([#6274](https://github.com/matrix-org/matrix-rust-sdk/pull/6274))
### Refactor
- [**breaking**] `Room::observe_live_location_shares` has been replaced by
`Room::live_location_shares`. Call [`LiveLocationShares::subscribe`] on it to
receive an initial snapshot and a stream of incremental updates.The stream is seeded from the event cache
on creation and includes the own user's shares (previously excluded). `LiveLocationShare.is_live`
has been removed; instead `ts` (start timestamp) and `timeout` (duration in milliseconds) are now
exposed so clients can compute liveness themselves via `current_time < ts + timeout`. Non-live
shares are automatically removed from the list. A new `LiveLocationShareListener` callback
interface must be implemented and passed to the method.
([#6385](https://github.com/matrix-org/matrix-rust-sdk/pull/6385))
- [**breaking**] The `RoomAliases` variants of `StateEventContent`, `StateEventType` and
`OtherState` was removed. This state event type was removed from the Matrix specification a while
ago, and support for it has been removed in Ruma.
([#6414](https://github.com/matrix-org/matrix-rust-sdk/pull/6414))
- `Client::new` no longer unnecessarily instantiates an `OAuth` component if `CrossProcessLockConfig::SingleProcess`
is used. ([#6293](https://github.com/matrix-org/matrix-rust-sdk/pull/6293))
- [**breaking**] `Room::report_content()` no longer takes a `score` argument, because it was
removed from the Matrix specification.
([#6256](https://github.com/matrix-org/matrix-rust-sdk/pull/6256))
- [**breaking**] The `current_version` field of `ErrorKind::WrongRoomKeysVersion`
is no longer optional.
([#6241](https://github.com/matrix-org/matrix-rust-sdk/pull/6241))
- [**breaking**] The following variants of `AccountManagementAction` were
renamed to match their new names after being merge in the Matrix specification:
- `SessionsList` is renamed to `DevicesList`
- `SessionView` is renamed to `DeviceView`
- `SessionEnd` is renamed to `DeviceDelete`
([#6217](https://github.com/matrix-org/matrix-rust-sdk/pull/6217))
- [**breaking**] `HumanQrGrantLoginError::UnableToCreateDevice` has been removed
([#6141](https://github.com/matrix-org/matrix-rust-sdk/pull/6141)
- [**breaking**] Removed `ClientBuilder::enable_oidc_refresh_lock` in favour of using `ClientBuilder::cross_process_lock_config`
to configure that lock when a `MultiProcess` configuration is supplied. ([#6204](https://github.com/matrix-org/matrix-rust-sdk/pull/6204))
- `RoomPaginationStatus` is renamed to `PaginationStatus`.
([#6174](https://github.com/matrix-org/matrix-rust-sdk/pull/6174/))
- [**breaking**] Replaced `ClientBuilder::cross_process_store_locks_holder_name` with `ClientBuilder::cross_process_lock_config`,
which accepts a `CrossProcessLockConfig` value to specify whether the resulting `Client` will be used in a single
process or multiple processes. ([#6160](https://github.com/matrix-org/matrix-rust-sdk/pull/6160))
- [**breaking**] Refactored `is_last_admin` to `is_last_owner` the check will now
account also for v12 rooms, where creators and users with PL 150 matter.
([#6036](https://github.com/matrix-org/matrix-rust-sdk/pull/6036))
- [**breaking**] The existing `TimelineEventType` was renamed to `TimelineEventContent`, because it contained the
actual contents of the event. Then, we created a new `TimelineEventType` enum that actually contains *just* the
event type. ([#5937](https://github.com/matrix-org/matrix-rust-sdk/pull/5937))
- [**breaking**] The function `TimelineEvent::event_type` is now `TimelineEvent::content`.
([#5937](https://github.com/matrix-org/matrix-rust-sdk/pull/5937))
- [**breaking**] The `SpaceService` will no longer auto-subscribe to required
client events when invoking the `subscribe_to_joined_spaces` but instead do it
through its, now async, constructor.
([#5972](https://github.com/matrix-org/matrix-rust-sdk/pull/5972))
- [**breaking**] The `SpaceService`'s `joined_spaces` method has been renamed
`top_level_joined_spaces` and `subscribe_to_joined_spaces` to `space_service.subscribe_to_top_level_joined_spaces`
([#5972](https://github.com/matrix-org/matrix-rust-sdk/pull/5972))
## [0.16.0] - 2025-12-04
### Breaking changes
- `TimelineConfiguration::track_read_receipts`'s type is now an enum to allow tracking to be enabled for all events
(like before) or only for message-like events (which prevents read receipts from being placed on state events).
([#5900](https://github.com/matrix-org/matrix-rust-sdk/pull/5900))
- `Client::reset_server_info()` has been split into `reset_supported_versions()`
and `reset_well_known()`.
([#5910](https://github.com/matrix-org/matrix-rust-sdk/pull/5910))
- Add `HumanQrLoginError::NotFound` for non-existing / expired rendezvous sessions
([#5898](https://github.com/matrix-org/matrix-rust-sdk/pull/5898))
- Add `HumanQrGrantLoginError::NotFound` for non-existing / expired rendezvous sessions
([#5898](https://github.com/matrix-org/matrix-rust-sdk/pull/5898))
- The `LatestEventValue::Local` type gains 2 new fields: `sender` and `profile`.
([#5885](https://github.com/matrix-org/matrix-rust-sdk/pull/5885))
- The `Encryption::user_identity()` method has received a new argument. The
`fallback_to_server` argument controls if we should attempt to fetch the user
identity from the homeserver if it wasn't found in the local storage.
([#5870](https://github.com/matrix-org/matrix-rust-sdk/pull/5870))
- Expose the power level required to modify `m.space.child` on
`room::power_levels::RoomPowerLevelsValues`.
- Rename `Client::login_with_qr_code` to `Client::new_login_with_qr_code_handler`.
([#5836](https://github.com/matrix-org/matrix-rust-sdk/pull/5836))
- Add the `sqlite` feature, along with the `indexeddb` feature, to enable either
the SQLite or IndexedDB store. The `session_paths`, `session_passphrase`,
`session_pool_max_size`, `session_cache_size` and `session_journal_size_limit`
methods on `ClientBuilder` have been removed. New methods are added:
`ClientBuilder::in_memory_store` if one wants non-persistent stores,
`ClientBuilder::sqlite_store` to configure and to use SQLite stores (if
the `sqlite` feature is enabled), and `ClientBuilder::indexeddb_store` to
configure and to use IndexedDB stores (if the `indexeddb` feature is enabled).
([#5811](https://github.com/matrix-org/matrix-rust-sdk/pull/5811))
The code:
```rust
client_builder
.session_paths("data_path", "cache_path")
.passphrase("foobar")
```
now becomes:
```rust
client_builder
.sqlite_store(
SqliteSessionStoreBuilder::new("data_path", "cache_path")
.passphrase("foobar")
)
```
- UniFFI was upgraded to `v0.30.0` ([#5808](https://github.com/matrix-org/matrix-rust-sdk/pull/5808)).
- The `waveform` parameter in `Timeline::send_voice_message` format changed to a list of `f32`
between 0 and 1.
([#5732](https://github.com/matrix-org/matrix-rust-sdk/pull/5732))
- The `normalized_power_level` field has been removed from the `RoomMember`
struct.
([#5635](https://github.com/matrix-org/matrix-rust-sdk/pull/5635))
- Remove the deprecated `CallNotify` event (`org.matrix.msc4075.call.notify`) in favor of the new
`RtcNotification` event (`org.matrix.msc4075.rtc.notification`).
([#5668](https://github.com/matrix-org/matrix-rust-sdk/pull/5668))
- Add `QrLoginProgress::SyncingSecrets` to indicate that secrets are being synced between the two
devices.
([#5760](https://github.com/matrix-org/matrix-rust-sdk/pull/5760))
- Add `Room::subscribe_to_send_queue_updates` to observe room send queue updates.
([#5761](https://github.com/matrix-org/matrix-rust-sdk/pull/5761))
- `Client::login_with_qr_code` now returns a handler that allows performing the flow with either the
current device scanning or generating the QR code. Additionally, new errors `HumanQrLoginError::CheckCodeAlreadySent`
and `HumanQrLoginError::CheckCodeCannotBeSent` were added.
([#5786](https://github.com/matrix-org/matrix-rust-sdk/pull/5786))
- `ComposerDraft` now includes attachments alongside the text message.
([#5794](https://github.com/matrix-org/matrix-rust-sdk/pull/5794))
- Add `Client::subscribe_to_send_queue_updates` to observe global send queue updates.
([#5784](https://github.com/matrix-org/matrix-rust-sdk/pull/5784))
### Features
- Add `Client::get_store_sizes()` so to query the size of the existing stores, if available. ([#5911](https://github.com/matrix-org/matrix-rust-sdk/pull/5911))
- Expose `is_space` in `NotificationRoomInfo`, allowing clients to determine if the room that triggered the notification is a space.
- Add push actions to `NotificationItem` and replace `SyncNotification` with `NotificationItem`.
([#5835](https://github.com/matrix-org/matrix-rust-sdk/pull/5835))
- Add `Client::new_grant_login_with_qr_code_handler` for granting login to a new device by way of
a QR code.
([#5836](https://github.com/matrix-org/matrix-rust-sdk/pull/5836))
- Add `Client::register_notification_handler` for observing notifications generated from sync responses.
([#5831](https://github.com/matrix-org/matrix-rust-sdk/pull/5831))
- Add `Room::mark_as_fully_read_unchecked` so clients can mark a room as read without needing a `Timeline` instance. Note this method is not recommended as it can potentially cause incorrect read receipts, but it can needed in certain cases.
- Add `Timeline::latest_event_id` to be able to fetch the event id of the latest event of the timeline.
- Add `Room::load_or_fetch_event` so we can get a `TimelineEvent` given its event id ([#5678](https://github.com/matrix-org/matrix-rust-sdk/pull/5678)).
- Add `TimelineEvent::thread_root_event_id` to expose the thread root event id for this type too ([#5678](https://github.com/matrix-org/matrix-rust-sdk/pull/5678)).
- Add `NotificationSettings::get_raw_push_rules` so clients can fetch the raw JSON content of the push rules of the current user and include it in bug reports ([#5706](https://github.com/matrix-org/matrix-rust-sdk/pull/5706)).
- Add new API to decline calls ([MSC4310](https://github.com/matrix-org/matrix-spec-proposals/pull/4310)): `Room::decline_call` and `Room::subscribe_to_call_decline_events`
([#5614](https://github.com/matrix-org/matrix-rust-sdk/pull/5614))
- Expose `m.federate` in `OtherState::RoomCreate` and `history_visibility` in `OtherState::RoomHistoryVisibility`, allowing clients to know whether a room federates and how its history is shared in the appropriate timeline events.
- Expose `join_rule` in `OtherState::RoomJoinRules`, allowing clients to know the join rules of a room from the appropriate timeline events.
### Changes
- `Timeline::latest_event_id` now uses its `ui::Timeline::latest_event_id` counterpart, instead of getting the latest event from the timeline and then its id.([#5864](https://github.com/matrix-org/matrix-rust-sdk/pull/5864))
- Build Android ARM64 bindings using better default RUSTFLAGS (the same used for iOS ARM64). This should improve performance. [(#5854)](https://github.com/matrix-org/matrix-rust-sdk/pull/5854)
## [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
- `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.
@@ -25,12 +458,13 @@ Breaking changes:
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,
- `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).
- 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.
@@ -46,7 +480,7 @@ Additions:
Breaking changes:
- `contacts` has been removed from `OidcConfiguration` (it was unused since the switch to OAuth).
- `contacts` has been removed from `OidcConfiguration` (it was unused since the switch to OAuth).
## [0.11.0] - 2025-04-11
@@ -123,7 +557,8 @@ Breaking changes:
- 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>`.
- `RoomPreview::own_membership_details` is now `RoomPreview::member_with_sender_info`, takes any user id and returns an
`Option<RoomMemberWithSenderInfo>`.
Additions:
@@ -138,9 +573,11 @@ Additions:
- 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
- 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.
- 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.
+98 -70
View File
@@ -1,7 +1,7 @@
[package]
name = "matrix-sdk-ffi"
version = "0.12.0"
edition = "2021"
version = "0.16.0"
edition = "2024"
homepage = "https://github.com/matrix-org/matrix-rust-sdk"
keywords = ["matrix", "chat", "messaging", "ffi"]
license = "Apache-2.0"
@@ -14,102 +14,130 @@ publish = false
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", "unstable-msc4274"]
bundled-sqlite = ["matrix-sdk/bundled-sqlite"]
default = ["bundled-sqlite", "unstable-msc4274", "experimental-element-recent-emojis", "experimental-push-secrets"]
# Use SQLite for the session storage.
sqlite = ["matrix-sdk/sqlite"]
# Use an embedded version of SQLite.
bundled-sqlite = ["sqlite", "matrix-sdk/bundled-sqlite"]
# Use IndexedDB for the session storage.
indexeddb = ["matrix-sdk/indexeddb"]
unstable-msc4274 = ["matrix-sdk-ui/unstable-msc4274"]
# Required when targeting a Javascript environment, like Wasm in a browser.
js = ["matrix-sdk-ui/js"]
# Enable sentry error monitoring, not compatible with Wasm platforms.
sentry = ["dep:sentry", "dep:sentry-tracing"]
experimental-element-recent-emojis = ["matrix-sdk/experimental-element-recent-emojis"]
experimental-push-secrets = ["matrix-sdk/experimental-push-secrets"]
[dependencies]
anyhow.workspace = true
as_variant.workspace = true
async-compat = "0.2.4"
extension-trait = "1.0.1"
chrono.workspace = true
extension-trait = "1.0.2"
eyeball-im.workspace = true
futures-util.workspace = true
language-tags = "0.3.2"
log-panics = { version = "2", features = ["with-backtrace"] }
log-panics = { version = "2.1.0", default-features = false, features = ["with-backtrace"] }
matrix-sdk = { workspace = true, features = [
"anyhow",
"e2e-encryption",
"experimental-widgets",
"markdown",
"socks",
"uniffi",
"federation-api",
"experimental-search"
] }
matrix-sdk-base.workspace = true
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", "unstable-msc4278"] }
sentry-tracing = "0.36.0"
mime = { version = "0.3.17", default-features = false }
ruma = { workspace = true, features = [
"html",
"unstable-msc3488",
"compat-unset-avatar",
"unstable-msc3245-v1-compat",
"unstable-msc4278",
"unstable-msc3230",
# Audio event type
"unstable-msc3927",
# File event type
"unstable-msc3551",
# Image event type
"unstable-msc3552",
# Video event type
"unstable-msc3553",
# Voice event type
"unstable-msc3245",
# Emote event type
"unstable-msc3954",
# Image pack event type
"unstable-msc2545",
# Room language event type
"unstable-msc4334",
] }
serde.workspace = true
serde_json.workspace = true
sentry = { workspace = true, optional = true, features = [
# Most default features enabled otherwise.
"backtrace",
"contexts",
"debug-images",
"panic",
"reqwest",
"sentry-debug-images",
] }
sentry-tracing = { workspace = true, optional = true }
thiserror.workspace = true
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
tracing.workspace = true
tracing-appender = { version = "0.2.2" }
tracing-appender.workspace = true
tracing-core.workspace = true
tracing-subscriber = { workspace = true, features = ["env-filter"] }
uniffi = { workspace = true, features = ["tokio"] }
url.workspace = true
uuid = { version = "1.4.1", features = ["v4"] }
uuid = { version = "1.4.1", default-features = false, features = ["std", "v4"] }
zeroize.workspace = true
oauth2.workspace = true
[target.'cfg(not(target_os = "android"))'.dependencies.matrix-sdk]
workspace = true
features = [
"anyhow",
"e2e-encryption",
"experimental-widgets",
"markdown",
# note: differ from block below
"native-tls",
"socks",
"sqlite",
"uniffi",
]
[target.'cfg(target_family = "wasm")'.dependencies]
console_error_panic_hook = { version = "0.1.7", default-features = false }
tokio = { workspace = true, features = ["sync", "macros"] }
uniffi = { workspace = true, features = ["wasm-unstable-single-threaded"] }
futures-executor.workspace = true
[target.'cfg(not(target_os = "android"))'.dependencies.sentry]
version = "0.36.0"
default-features = false
features = [
# TLS lib used on non-Android platforms.
"native-tls",
# Most default features enabled otherwise.
"backtrace",
"contexts",
"panic",
"reqwest",
]
[target.'cfg(not(target_family = "wasm"))'.dependencies]
async-compat.workspace = true
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
uniffi = { workspace = true, features = ["tokio"] }
[target.'cfg(target_os = "android")'.dependencies]
paranoid-android = "0.2.1"
paranoid-android = { version = "0.2.2", default-features = false }
# Needed for `rustls-platform-verifier`. Newer versions aren't compatible with it.
jni = "0.21.1"
# Used to access the credential storage on Android
rustls-platform-verifier = "0.6.2"
# Needed for intializing and keeping the JavaVM reference around
once_cell = "1.21.4"
# Gobley's jvm-getter is used to get a JVM pointer from all Android versions
jvm-getter = "0.1.0"
[target.'cfg(target_os = "android")'.dependencies.matrix-sdk]
workspace = true
features = [
"anyhow",
"e2e-encryption",
"experimental-widgets",
"markdown",
# note: differ from block above
"rustls-tls",
"socks",
"sqlite",
"uniffi",
]
[target.'cfg(target_os = "android")'.dependencies.sentry]
version = "0.36.0"
default-features = false
features = [
# TLS lib specific for Android.
"rustls",
# Most default features enabled otherwise.
"backtrace",
"contexts",
"panic",
"reqwest",
]
[dev-dependencies]
similar-asserts.workspace = true
tempfile.workspace = true
[build-dependencies]
uniffi = { workspace = true, features = ["build"] }
vergen = { version = "8.1.3", features = ["build", "git", "gitcl"] }
vergen-gitcl = { workspace = true, features = ["build"] }
[lints]
workspace = true
+21 -4
View File
@@ -2,12 +2,29 @@
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
### Functionality
- `sentry`: Enable error monitoring using Sentry, not supports on Wasm platforms.
- `sqlite`: Use SQLite for the session storage.
- `bundled-sqlite`: Use an embedded version of SQLite instead of the system provided one.
- `indexeddb`: Use IndexedDB for the session storage.
### Unstable specs
- `unstable-msc4274`: Adds support for gallery message types, which contain multiple media elements.
## Platforms
Each supported target should use features to build the relevant system. Here are some suggested feature flags for the major platforms:
- Android: `"bundled-sqlite,unstable-msc4274,sentry"`
- iOS: `"bundled-sqlite,unstable-msc4274,sentry"`
- JavaScript/Wasm: `"indexeddb,unstable-msc4274"`
### Swift/iOS sync
### Swift/iOS async
TBD
+37 -2
View File
@@ -1,3 +1,17 @@
// 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 that specific language governing permissions and
// limitations under the License.
use std::{
env,
error::Error,
@@ -5,7 +19,7 @@ use std::{
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.
@@ -43,6 +57,23 @@ fn setup_x86_64_android_workaround() {
}
}
/// Adds a workaround for watchOS simulator builds to manually link against the
/// CoreFoundation framework in order to avoid linker errors. Otherwise, errors
/// like the following may occur:
///
/// = note: Undefined symbols for architecture arm64:
/// "_CFArrayCreate", referenced from:
/// "_CFDataCreate", referenced from:
/// "_CFRelease", referenced from:
/// etc.
fn setup_watchos_simulator_workaround() {
let target = env::var("TARGET").expect("TARGET not set");
if target.ends_with("watchos-sim") {
println!("cargo:rustc-link-arg=-framework");
println!("cargo:rustc-link-arg=CoreFoundation");
}
}
/// Run the clang binary at `clang_path`, and return its major version number
fn get_clang_major_version(clang_path: &Path) -> String {
let clang_output =
@@ -58,7 +89,11 @@ fn get_clang_major_version(clang_path: &Path) -> String {
fn main() -> Result<(), Box<dyn Error>> {
setup_x86_64_android_workaround();
setup_watchos_simulator_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(())
}
+2
View File
@@ -1,10 +1,12 @@
namespace matrix_sdk_ffi {};
[Remote]
dictionary Mentions {
sequence<string> user_ids;
boolean room;
};
[Remote]
interface RoomMessageEventContentWithoutRelation {
RoomMessageEventContentWithoutRelation with_mentions(Mentions mentions);
};
+24 -3
View File
@@ -1,3 +1,17 @@
// 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 that specific language governing permissions and
// limitations under the License.
use std::{
collections::HashMap,
fmt::{self, Debug},
@@ -5,12 +19,12 @@ use std::{
};
use matrix_sdk::{
Error,
authentication::oauth::{
ClientId, ClientRegistrationData, OAuthError as SdkOAuthError,
error::OAuthAuthorizationCodeError,
registration::{ApplicationType, ClientMetadata, Localized, OAuthGrantType},
ClientId, ClientRegistrationData, OAuthError as SdkOAuthError,
},
Error,
};
use ruma::serde::Raw;
use url::Url;
@@ -23,6 +37,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,
}
@@ -43,6 +58,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> {
@@ -79,8 +99,9 @@ impl SsoHandler {
let auth = self.client.inner.matrix_auth();
let url = Url::parse(&callback_url).map_err(|_| SsoError::CallbackUrlInvalid)?;
let builder =
auth.login_with_sso_callback(url).map_err(|_| SsoError::CallbackUrlInvalid)?;
auth.login_with_sso_callback(url.into()).map_err(|_| SsoError::CallbackUrlInvalid)?;
builder.await.map_err(|_| SsoError::LoginWithTokenFailed)?;
Ok(())
}
}
@@ -1,3 +1,17 @@
// 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 that specific language governing permissions and
// limitations under the License.
//! A generic `ChunkIterator` that operates over a `Vec`.
//!
//! This type is not designed to work over FFI, but it can be embedded inside an
File diff suppressed because it is too large Load Diff
+326 -462
View File
@@ -1,32 +1,49 @@
use std::{fs, num::NonZeroUsize, path::Path, sync::Arc, time::Duration};
// 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 that specific language governing permissions and
// limitations under the License.
use futures_util::StreamExt;
// Allow UniFFI to use methods marked as `#[deprecated]`.
#![allow(deprecated)]
use std::{fs, num::NonZeroUsize, path::PathBuf, sync::Arc, time::Duration};
#[cfg(not(any(target_family = "wasm", target_os = "android")))]
use matrix_sdk::reqwest::Certificate;
use matrix_sdk::{
authentication::oauth::qrcode::{self, DeviceCodeErrorResponseType, LoginFailureReason},
crypto::{
types::qr_login::{LoginQrCodeDecodeError, QrCodeModeData},
CollectStrategy, TrustRequirement,
},
Client as MatrixClient, ClientBuildError as MatrixClientBuildError, HttpError, IdParseError,
RumaApiError, ThreadingSupport,
cross_process_lock::CrossProcessLockConfig as SdkCrossProcessLockConfig,
encryption::{BackupDownloadStrategy, EncryptionSettings},
event_cache::EventCacheError,
reqwest::Certificate,
ruma::{ServerName, UserId},
search_index::SearchIndexStoreKind,
sliding_sync::{
Error as MatrixSlidingSyncError, VersionBuilder as MatrixSlidingSyncVersionBuilder,
VersionBuilderError,
},
Client as MatrixClient, ClientBuildError as MatrixClientBuildError, HttpError, IdParseError,
RumaApiError, SqliteStoreConfig,
};
use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
use matrix_sdk_base::crypto::{CollectStrategy, DecryptionSettings, TrustRequirement};
use ruma::api::error::{DeserializationError, FromHttpResponseError};
use tracing::{debug, error};
use zeroize::Zeroizing;
use tracing::debug;
use super::client::Client;
#[cfg(any(feature = "sqlite", feature = "indexeddb"))]
use crate::store;
use crate::{
authentication::OidcConfiguration, client::ClientSessionDelegate, error::ClientError,
helpers::unwrap_or_clone_arc, runtime::get_runtime_handle, task_handle::TaskHandle,
client::ClientSessionDelegate,
error::ClientError,
helpers::unwrap_or_clone_arc,
store::{StoreBuilder, StoreBuilderOutcome},
};
/// A list of bytes containing a certificate in DER or PEM form.
@@ -39,164 +56,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())
}
/// 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,
}
}
}
#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi(flat_error)]
pub enum ClientBuildError {
@@ -266,53 +125,62 @@ 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>,
store: Option<StoreBuilder>,
system_is_memory_constrained: bool,
username: Option<String>,
homeserver_cfg: Option<HomeserverConfig>,
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,
cross_process_lock_config: CrossProcessLockConfig,
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>,
search_index_store: Option<SearchIndexStoreKind>,
#[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,
store: None,
system_is_memory_constrained: false,
username: None,
homeserver_cfg: None,
#[cfg(not(target_family = "wasm"))]
user_agent: None,
sliding_sync_version_builder: SlidingSyncVersionBuilder::None,
#[cfg(not(target_family = "wasm"))]
proxy: None,
#[cfg(not(target_family = "wasm"))]
disable_ssl_verification: false,
disable_automatic_token_refresh: false,
cross_process_store_locks_holder_name: None,
enable_oidc_refresh_lock: false,
cross_process_lock_config: CrossProcessLockConfig::SingleProcess,
session_delegate: None,
#[cfg(not(target_family = "wasm"))]
additional_root_certificates: Default::default(),
#[cfg(not(target_family = "wasm"))]
disable_built_in_root_certificates: false,
encryption_settings: EncryptionSettings {
auto_enable_cross_signing: false,
@@ -321,24 +189,22 @@ 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(),
threading_support: ThreadingSupport::Disabled,
search_index_store: None,
})
}
pub fn cross_process_store_locks_holder_name(
pub fn cross_process_lock_config(
self: Arc<Self>,
holder_name: String,
cross_process_lock_config: CrossProcessLockConfig,
) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.cross_process_store_locks_holder_name = Some(holder_name);
Arc::new(builder)
}
pub fn enable_oidc_refresh_lock(self: Arc<Self>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.enable_oidc_refresh_lock = true;
builder.cross_process_lock_config = cross_process_lock_config;
Arc::new(builder)
}
@@ -351,80 +217,13 @@ impl ClientBuilder {
Arc::new(builder)
}
/// Sets the paths that the client will use to store its data and caches.
/// Both paths **must** be unique per session as the SDK stores aren't
/// capable of handling multiple users, however it is valid to use the
/// same path for both stores on a single session.
///
/// Leaving this unset tells the client to use an in-memory data store.
pub fn session_paths(self: Arc<Self>, data_path: String, cache_path: String) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.session_paths = Some(SessionPaths { data_path, cache_path });
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`].
/// `matrix_sdk::SqliteStoreConfig` (if the `sqlite` feature is enabled).
/// Please check
/// `matrix_sdk::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;
@@ -455,12 +254,6 @@ impl ClientBuilder {
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,
@@ -470,43 +263,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,
@@ -545,12 +307,12 @@ 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)
}
@@ -574,60 +336,103 @@ 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)
}
/// Use in-memory session storage.
pub fn in_memory_store(self: Arc<Self>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.store = Some(StoreBuilder::InMemory);
Arc::new(builder)
}
/// Set up the search index store for this client, which is used to store
/// the message search index locally.
///
/// As soon as this is enabled, messages will start to be indexed, and can
/// be later queried for search.
///
/// `path` is the directory where the search index will be stored. It must
/// be unique per session.
///
/// `password` is an optional password to encrypt the search index at rest.
/// If `None`, the search index will be stored unencrypted.
pub fn with_search_index_store(
self: Arc<Self>,
path: String,
password: Option<String>,
) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
// Note: creation of the path is deferred to later.
let path = PathBuf::from(path);
let kind = if let Some(password) = password {
SearchIndexStoreKind::EncryptedDirectory(path, password)
} else {
SearchIndexStoreKind::UnencryptedDirectory(path)
};
builder.search_index_store = Some(kind);
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();
let mut inner_builder = MatrixClient::builder()
.cross_process_store_config(builder.cross_process_lock_config.into());
if let Some(holder_name) = &builder.cross_process_store_locks_holder_name {
inner_builder =
inner_builder.cross_process_store_locks_holder_name(holder_name.clone());
}
let store_path = if let Some(store) = &builder.store {
match store.build()? {
#[cfg(feature = "sqlite")]
StoreBuilderOutcome::Sqlite { config, cache_path, store_path: data_path } => {
inner_builder = inner_builder
.sqlite_store_with_config_and_cache_path(config, Some(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);
Some(data_path)
}
#[cfg(feature = "indexeddb")]
StoreBuilderOutcome::IndexedDb { name, passphrase } => {
inner_builder = inner_builder.indexeddb_store(&name, passphrase.as_deref());
debug!(
data_path = %data_path.to_string_lossy(),
event_cache_path = %cache_path.to_string_lossy(),
"Creating directories for data (state and crypto) and cache stores.",
);
None
}
fs::create_dir_all(data_path)?;
fs::create_dir_all(cache_path)?;
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);
StoreBuilderOutcome::InMemory => None,
}
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.");
debug!("Not using a session store");
None
};
if let Some(search_index_store) = builder.search_index_store {
// Create the search index directory.
match search_index_store {
SearchIndexStoreKind::UnencryptedDirectory(ref path)
| SearchIndexStoreKind::EncryptedDirectory(ref path, _) => {
fs::create_dir_all(path)?;
}
_ => {}
}
// Configure the inner builder to use the search index store.
inner_builder = inner_builder.search_index_store(search_index_store);
}
// Determine server either from URL, server name or user ID.
inner_builder = match builder.homeserver_cfg {
Some(HomeserverConfig::Url(url)) => inner_builder.homeserver_url(url),
@@ -650,52 +455,62 @@ impl ClientBuilder {
}
};
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);
}
#[cfg(not(target_family = "wasm"))]
{
#[cfg(target_os = "android")]
{
inner_builder =
inner_builder.add_raw_root_certificates(builder.additional_root_certificates)
}
}
#[cfg(not(target_os = "android"))]
{
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);
}
}
}
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 {
@@ -722,12 +537,12 @@ impl ClientBuilder {
if let Some(timeout) = config.timeout {
updated_config = updated_config.timeout(Duration::from_millis(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(
max_concurrent_requests as usize,
));
}
updated_config = updated_config.read_timeout(DEFAULT_READ_TIMEOUT);
if let Some(max_concurrent_requests) = config.max_concurrent_requests
&& max_concurrent_requests > 0
{
updated_config = updated_config
.max_concurrent_requests(NonZeroUsize::new(max_concurrent_requests as usize));
}
if let Some(max_retry_time) = config.max_retry_time {
updated_config =
@@ -736,86 +551,111 @@ impl ClientBuilder {
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?;
Ok(Arc::new(
Client::new(
sdk_client,
builder.enable_oidc_refresh_lock,
builder.session_delegate,
store_path,
)
.await?,
))
Ok(Arc::new(Client::new(sdk_client, builder.session_delegate, store_path).await?))
}
}
#[cfg(feature = "sqlite")]
#[matrix_sdk_ffi_macros::export]
impl ClientBuilder {
/// Use SQLite as the session storage.
pub fn sqlite_store(self: Arc<Self>, config: Arc<store::SqliteStoreBuilder>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.store = Some(StoreBuilder::Sqlite(unwrap_or_clone_arc(config)));
Arc::new(builder)
}
/// Finish the building of the client and attempt to log in using the
/// provided [`QrCodeData`].
/// Sets the paths that the client will use to store its data and caches
/// with SQLite.
///
/// 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 OAuth
/// 2.0 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(
/// Both paths **must** be unique per session as the SDK
/// stores aren't capable of handling multiple users, however it is
/// valid to use the same path for both stores on a single session.
#[deprecated = "Use `ClientBuilder::session_store_with_sqlite` instead"]
pub fn session_paths(self: Arc<Self>, data_path: String, cache_path: String) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.store =
Some(StoreBuilder::Sqlite(store::SqliteStoreBuilder::raw_new(data_path, cache_path)));
Arc::new(builder)
}
}
#[cfg(feature = "indexeddb")]
#[matrix_sdk_ffi_macros::export]
impl ClientBuilder {
/// Use IndexedDB as the session storage.
pub fn indexeddb_store(
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 registration_data = oidc_configuration
.registration_data()
.map_err(|_| HumanQrLoginError::OidcMetadataInvalid)?;
let oauth = client.inner.oauth();
let login = oauth.login_with_qr_code(&qr_code_data.inner, Some(&registration_data));
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(get_runtime_handle().spawn(async move {
while let Some(state) = progress.next().await {
progress_listener.on_update(state.into());
}
}));
login.await?;
Ok(client)
config: Arc<store::IndexedDbStoreBuilder>,
) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.store = Some(StoreBuilder::IndexedDb(unwrap_or_clone_arc(config)));
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,
/// The path that the client will use to store its caches. This path can be
/// the same as the data path if you prefer to keep everything in one place.
cache_path: String,
#[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);
#[cfg(not(target_family = "wasm"))]
{
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);
#[cfg(not(target_family = "wasm"))]
{
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);
#[cfg(not(target_family = "wasm"))]
{
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);
#[cfg(not(target_family = "wasm"))]
{
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);
#[cfg(not(target_family = "wasm"))]
{
builder.user_agent = Some(user_agent);
}
Arc::new(builder)
}
}
#[derive(Clone, uniffi::Record)]
/// The config to use for HTTP requests by default in this client.
#[derive(Clone, uniffi::Record)]
pub struct RequestConfig {
/// Max number of retries.
retry_limit: Option<u64>,
@@ -833,3 +673,27 @@ pub enum SlidingSyncVersionBuilder {
Native,
DiscoverNative,
}
#[derive(Clone, Debug, uniffi::Enum)]
/// The cross-process lock config to use.
pub enum CrossProcessLockConfig {
/// The client will run using multiple processes.
MultiProcess {
/// The holder name to use for the lock.
holder_name: String,
},
/// The client will run in a single process, there is no need for a
/// cross-process lock.
SingleProcess,
}
impl From<CrossProcessLockConfig> for SdkCrossProcessLockConfig {
fn from(lock_config: CrossProcessLockConfig) -> Self {
match lock_config {
CrossProcessLockConfig::MultiProcess { holder_name } => {
SdkCrossProcessLockConfig::MultiProcess { holder_name }
}
CrossProcessLockConfig::SingleProcess => SdkCrossProcessLockConfig::SingleProcess,
}
}
}
-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::from_err)
}
+323 -15
View File
@@ -1,11 +1,25 @@
use std::sync::Arc;
// 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 that specific language governing permissions and
// limitations under the License.
use std::{str::FromStr, sync::Arc};
use futures_util::StreamExt;
use matrix_sdk::{
encryption,
encryption::{backups, recovery},
};
use matrix_sdk::encryption::{self, backups, recovery};
use matrix_sdk_base::crypto::types::{BackupSecrets, RoomKeyBackupInfo};
use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
use ruma::OwnedUserId;
use serde::de::Error;
use thiserror::Error;
use tracing::{error, info};
use zeroize::Zeroize;
@@ -79,9 +93,14 @@ pub enum RecoveryError {
#[error(transparent)]
Client { source: crate::ClientError },
/// Error in the secret storage subsystem.
/// Error in the secret storage subsystem, except for when importing a
/// secret.
#[error("Error in the secret-storage subsystem: {error_message}")]
SecretStorage { error_message: String },
/// Error when importing a secret from secret storage.
#[error("Error importing a secret: {error_message}")]
Import { error_message: String },
}
impl From<matrix_sdk::encryption::recovery::RecoveryError> for RecoveryError {
@@ -89,6 +108,9 @@ impl From<matrix_sdk::encryption::recovery::RecoveryError> for RecoveryError {
match value {
recovery::RecoveryError::BackupExistsOnServer => Self::BackupExistsOnServer,
recovery::RecoveryError::Sdk(e) => Self::Client { source: ClientError::from(e) },
recovery::RecoveryError::SecretStorage(
matrix_sdk::encryption::secret_storage::SecretStorageError::ImportError { .. },
) => Self::Import { error_message: value.to_string() },
recovery::RecoveryError::SecretStorage(e) => {
Self::SecretStorage { error_message: e.to_string() }
}
@@ -216,6 +238,225 @@ impl From<encryption::VerificationState> for VerificationState {
}
}
/// Struct containing the bundle of secrets to fully activate a new device for
/// end-to-end encryption.
#[derive(uniffi::Object)]
pub struct SecretsBundleWithUserId {
user_id: OwnedUserId,
inner: matrix_sdk_base::crypto::types::SecretsBundle,
}
/// Result for the check if a store has a valid secrets bundle.
#[derive(uniffi::Enum)]
pub enum DetectedSecretsBundle {
/// The store doesn't contain a secrets bundle at all.
None,
/// The store contains a bundle without a backup.
WithoutBackup,
/// The store contains a bundle with an unused backup, the backup key in the
/// bundle isn't used on the homeserver.
UnusedBackup,
/// The store contains a complete secrets bundle.
Complete,
}
/// Error type describing failures that can happen while exporting a
/// [`SecretsBundle`] from a SQLite store.
#[derive(Debug, thiserror::Error, uniffi::Error)]
pub enum BundleExportError {
/// The SQLite store couldn't be opened.
#[error("the store couldn't be opened: {msg}")]
OpenStoreError { msg: String },
/// Data from the SQLite store couldn't be exported.
#[error("the bundle couldn't be exported due to a storage error: {msg}")]
StoreError { msg: String },
/// The store doesn't contain a secrets bundle or it couldn't be read from
/// the store.
#[error("the bundle couldn't be exported: {msg}")]
SecretError { msg: String },
/// The store is empty and doesn't contain a secrets bundle.
#[error("the store is completely empty")]
StoreEmpty,
/// A JSON object couldn't be deserialized while the secrets bundle was
/// exported.
#[error("Couldn't deserialize a JSON value: {msg}")]
Json { msg: String },
/// Error returned when the secrets bundle is missing a backup key or
/// includes one that doesnt match the key configured for the active backup
/// version.
#[error(
"The bundle is missing a backup key or has one that isn't the one that's currently used"
)]
InvalidBackup,
}
#[cfg(feature = "sqlite")]
impl From<matrix_sdk::encryption::BundleExportError> for BundleExportError {
fn from(value: matrix_sdk::encryption::BundleExportError) -> Self {
match value {
matrix_sdk::encryption::BundleExportError::OpenStoreError(e) => {
BundleExportError::OpenStoreError { msg: e.to_string() }
}
matrix_sdk::encryption::BundleExportError::StoreError(e) => {
BundleExportError::StoreError { msg: e.to_string() }
}
matrix_sdk::encryption::BundleExportError::SecretExport(e) => {
BundleExportError::SecretError { msg: e.to_string() }
}
}
}
}
impl From<serde_json::Error> for BundleExportError {
fn from(value: serde_json::Error) -> Self {
Self::Json { msg: value.to_string() }
}
}
#[cfg(feature = "sqlite")]
#[matrix_sdk_ffi_macros::export]
impl SecretsBundleWithUserId {
/// Attempt to export a [`SecretsBundle`] from a crypto store.
///
/// This method can be used to retrieve a [`SecretsBundle`] from an existing
/// `matrix-sdk`-based client in order to import the [`SecretsBundle`] in
/// another [`Client`] instance.
///
/// This can be useful for migration purposes or to allow existing client
/// instances create new ones that will be fully verified.
#[uniffi::constructor]
pub async fn from_database(
database_path: &str,
mut passphrase: Option<String>,
backup_info: &str,
) -> Result<Arc<Self>, BundleExportError> {
let backup_info = serde_json::from_str(backup_info)?;
let ret = if let Some((user_id, bundle)) =
matrix_sdk::encryption::export_secrets_bundle_from_store(
database_path,
passphrase.as_deref(),
)
.await?
{
let is_backup_ok =
bundle.backup.as_ref().is_some_and(|backup| is_valid_backup(backup, &backup_info));
if is_backup_ok {
Ok(SecretsBundleWithUserId { user_id, inner: bundle }.into())
} else {
Err(BundleExportError::InvalidBackup)
}
} else {
Err(BundleExportError::StoreEmpty)
};
passphrase.zeroize();
ret
}
}
#[matrix_sdk_ffi_macros::export]
impl SecretsBundleWithUserId {
/// Attempt to create a [`SecretsBundle`] from a previously JSON serialized
/// bundle.
#[uniffi::constructor]
pub fn from_str(
user_id: &str,
bundle: &str,
backup_info: &str,
) -> Result<Arc<Self>, BundleExportError> {
let user_id =
OwnedUserId::from_str(user_id).map_err(|e| serde_json::Error::custom(e.to_string()))?;
let bundle: matrix_sdk_base::crypto::types::SecretsBundle = serde_json::from_str(bundle)?;
let backup_info = serde_json::from_str(backup_info)?;
let is_backup_ok =
bundle.backup.as_ref().is_some_and(|backup| is_valid_backup(backup, &backup_info));
if is_backup_ok {
Ok(Self { user_id, inner: bundle }.into())
} else {
Err(BundleExportError::InvalidBackup)
}
}
/// Does the bundle contain a backup key.
///
/// Since enabling a backup is optional, the backup key might be missing
/// from the bundle. Returns `false` if the backup key is missing,
/// otherwise `true`.
pub fn contains_backup_key(&self) -> bool {
self.inner.backup.is_some()
}
}
fn is_valid_backup(secrets: &BackupSecrets, info: &RoomKeyBackupInfo) -> bool {
match secrets {
BackupSecrets::MegolmBackupV1Curve25519AesSha2(secrets) => {
secrets.key.backup_key_matches(info)
}
}
}
fn check_bundle_and_info(
bundle: &matrix_sdk_base::crypto::types::SecretsBundle,
info: Option<&RoomKeyBackupInfo>,
) -> DetectedSecretsBundle {
match (&bundle.backup, info) {
(None, None) => DetectedSecretsBundle::WithoutBackup,
(None, Some(_)) => DetectedSecretsBundle::WithoutBackup,
(Some(_), None) => DetectedSecretsBundle::UnusedBackup,
(Some(backup), Some(info)) => {
if is_valid_backup(backup, info) {
DetectedSecretsBundle::Complete
} else {
DetectedSecretsBundle::UnusedBackup
}
}
}
}
/// Check if a JSON encoded string contains a valid [`SecretsBundle`].
#[uniffi::export]
pub fn json_string_contains_secrets_bundle(
bundle: &str,
backup_info: Option<String>,
) -> Result<DetectedSecretsBundle, ClientError> {
let info: Option<RoomKeyBackupInfo> =
backup_info.map(|info| serde_json::from_str(&info)).transpose()?;
let bundle: matrix_sdk_base::crypto::types::SecretsBundle = serde_json::from_str(bundle)?;
Ok(check_bundle_and_info(&bundle, info.as_ref()))
}
/// Check if a crypto store contains a valid [`SecretsBundle`].
#[cfg(feature = "sqlite")]
#[matrix_sdk_ffi_macros::export]
pub async fn database_contains_secrets_bundle(
database_path: &str,
mut passphrase: Option<String>,
backup_info: Option<String>,
) -> Result<DetectedSecretsBundle, BundleExportError> {
let info: Option<RoomKeyBackupInfo> =
backup_info.map(|info| serde_json::from_str(&info)).transpose()?;
let maybe_bundle = matrix_sdk::encryption::export_secrets_bundle_from_store(
database_path,
passphrase.as_deref(),
)
.await?;
passphrase.zeroize();
Ok(match maybe_bundle {
Some((_, bundle)) => check_bundle_and_info(&bundle, info.as_ref()),
None => DetectedSecretsBundle::None,
})
}
#[matrix_sdk_ffi_macros::export]
impl Encryption {
/// Get the public ed25519 key of our own device. This is usually what is
@@ -287,6 +528,15 @@ impl Encryption {
Ok(self.inner.recovery().is_last_device().await?)
}
/// Does the user have other devices that the current device can verify
/// against?
///
/// The device must be signed by the user's cross-signing key, must have an
/// identity, and must not be a dehydrated device.
pub async fn has_devices_to_verify_against(&self) -> Result<bool, ClientError> {
Ok(self.inner.has_devices_to_verify_against().await?)
}
pub async fn wait_for_backup_upload_steady_state(
&self,
progress_listener: Option<Box<dyn BackupSteadyStateListener>>,
@@ -381,6 +631,7 @@ impl Encryption {
Ok(None)
}
/// Download identity and key backup information from Recovery
pub async fn recover(&self, mut recovery_key: String) -> Result<()> {
let result = self.inner.recovery().recover(&recovery_key).await;
@@ -389,6 +640,23 @@ impl Encryption {
Ok(result?)
}
/// Download identity and key backup information from Recovery, and, if the
/// key backup information is inconsistent, create a new key backup.
///
/// This will create a new key backup if:
///
/// * Key backup is enabled and the backup decryption key is missing from
/// Recovery, or
/// * Key backup is enabled and the backup decryption key does not match the
/// public key
pub async fn recover_and_fix_backup(&self, mut recovery_key: String) -> Result<()> {
let result = self.inner.recovery().recover_and_fix_backup(&recovery_key).await;
recovery_key.zeroize();
Ok(result?)
}
pub fn verification_state(&self) -> VerificationState {
self.inner.verification_state().get().into()
}
@@ -417,11 +685,13 @@ impl Encryption {
/// This method always tries to fetch the identity from the store, which we
/// only have if the user is tracked, meaning that we are both members
/// of the same encrypted room. If no user is found locally, a request will
/// be made to the homeserver.
/// be made to the homeserver unless `fallback_to_server` is set to `false`.
///
/// # Arguments
///
/// * `user_id` - The ID of the user that the identity belongs to.
/// * `fallback_to_server` - Should we request the user identity from the
/// homeserver if one isn't found locally.
///
/// Returns a `UserIdentity` if one is found. Returns an error if there
/// was an issue with the crypto store or with the request to the
@@ -431,6 +701,7 @@ impl Encryption {
pub async fn user_identity(
&self,
user_id: String,
fallback_to_server: bool,
) -> Result<Option<Arc<UserIdentity>>, ClientError> {
match self.inner.get_user_identity(user_id.as_str().try_into()?).await {
Ok(Some(identity)) => {
@@ -442,12 +713,53 @@ impl Encryption {
Err(error) => {
error!("Failed fetching identity from the store: {error}");
}
};
}
info!("Requesting identity from the server.");
let identity = self.inner.request_user_identity(user_id.as_str().try_into()?).await?;
Ok(identity.map(|identity| Arc::new(UserIdentity { inner: identity })))
if fallback_to_server {
let identity = self.inner.request_user_identity(user_id.as_str().try_into()?).await?;
Ok(identity.map(|identity| Arc::new(UserIdentity { inner: identity })))
} else {
Ok(None)
}
}
/// This method will import all the private cross-signing keys and
/// the private part of a backup key and its accompanying version into the
/// store.
///
/// Importing all the secrets will mark the device as verified and enable
/// backups.
///
/// **Warning**: Only import this from a trusted source, i.e. if an existing
/// device is sharing this with a new device.
///
/// **Warning*: Only call this method right after logging in and before the
/// initial sync has been started.
pub async fn import_secrets_bundle(
&self,
secrets_bundle: &SecretsBundleWithUserId,
) -> Result<(), ClientError> {
let user_id = self._client.inner.user_id().expect(
"We should have a user ID available now, this is only called once we're logged in",
);
if user_id == secrets_bundle.user_id {
self.inner
.import_secrets_bundle(&secrets_bundle.inner)
.await
.map_err(ClientError::from_err)?;
self.inner.wait_for_e2ee_initialization_tasks().await;
Ok(())
} else {
Err(ClientError::Generic {
msg: "Secrets bundle does not belong to the user which was logged in".to_owned(),
details: None,
})
}
}
}
@@ -539,11 +851,7 @@ impl IdentityResetHandle {
/// 3. Go through the cross-signing key reset flow
/// 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(ClientError::from_err)
} else {
self.inner.reset(None).await.map_err(ClientError::from_err)
}
self.inner.reset(auth.map(Into::into)).await.map_err(ClientError::from_err)
}
pub async fn cancel(&self) {
+118 -39
View File
@@ -1,14 +1,34 @@
use std::{collections::HashMap, error::Error, fmt, fmt::Display, time::SystemTime};
// 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 that specific language governing permissions and
// limitations under the License.
use std::{collections::HashMap, error::Error, fmt, fmt::Display};
use matrix_sdk::{
authentication::oauth::OAuthError, encryption::CryptoStoreError, event_cache::EventCacheError,
reqwest, room::edit::EditError, send_queue::RoomSendQueueError, HttpError, IdParseError,
NotificationSettingsError as SdkNotificationSettingsError,
HttpError, IdParseError, NotificationSettingsError as SdkNotificationSettingsError,
QueueWedgeError as SdkQueueWedgeError, StoreError,
authentication::oauth::OAuthError,
encryption::{CryptoStoreError, identities::RequestVerificationError},
event_cache::EventCacheError,
reqwest,
room::{calls::CallError, edit::EditError},
send_queue::RoomSendQueueError,
};
use matrix_sdk_ui::{encryption_sync_service, notification_client, spaces, sync_service, timeline};
use ruma::{
MilliSecondsSinceUnixEpoch,
api::error::{ErrorBody, ErrorKind as RumaApiErrorKind, RetryAfter, StandardErrorBody},
};
use matrix_sdk_ui::{encryption_sync_service, notification_client, sync_service, timeline};
use ruma::api::client::error::{ErrorBody, ErrorKind as RumaApiErrorKind, RetryAfter};
use tracing::warn;
use uniffi::UnexpectedUniFFICallbackError;
use crate::{room_list::RoomListError, timeline::FocusEventError};
@@ -23,7 +43,6 @@ pub enum ClientError {
impl ClientError {
pub(crate) fn from_str<E: Display>(error: E, details: Option<String>) -> Self {
warn!("Error: {error}");
Self::Generic { msg: error.to_string(), details }
}
@@ -56,20 +75,21 @@ impl From<matrix_sdk::Error> for ClientError {
fn from(e: matrix_sdk::Error) -> Self {
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:?}")),
};
}
if let Some(api_error) = http_error.as_client_api_error()
&& let ErrorBody::Standard(StandardErrorBody { 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()
}
@@ -180,6 +200,12 @@ impl From<EditError> for ClientError {
}
}
impl From<CallError> for ClientError {
fn from(e: CallError) -> Self {
Self::from_err(e)
}
}
impl From<RoomSendQueueError> for ClientError {
fn from(e: RoomSendQueueError) -> Self {
Self::from_err(e)
@@ -198,6 +224,18 @@ impl From<FocusEventError> for ClientError {
}
}
impl From<RequestVerificationError> for ClientError {
fn from(e: RequestVerificationError) -> Self {
Self::from_err(e)
}
}
impl From<spaces::Error> for ClientError {
fn from(e: spaces::Error) -> Self {
Self::from_err(e)
}
}
/// Bindings version of the sdk type replacing OwnedUserId/DeviceIds with simple
/// String.
///
@@ -307,6 +345,39 @@ pub enum RoomError {
FailedSendingAttachment,
}
#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi(flat_error)]
pub enum LiveLocationError {
#[error("Network error")]
Network,
#[error("Existing beacon information not found")]
NotFound,
#[error("Beacon event is redacted and cannot be processed")]
Redacted,
#[error("Must join the room to access beacon information")]
Stripped,
#[error("The beacon event has expired")]
NotLive,
#[error("Deserialization error")]
Deserialization,
#[error("Other error: {msg}")]
Other { msg: String },
}
impl From<matrix_sdk::BeaconError> for LiveLocationError {
fn from(value: matrix_sdk::BeaconError) -> Self {
match value {
matrix_sdk::BeaconError::Network(_) => Self::Network,
matrix_sdk::BeaconError::NotFound => Self::NotFound,
matrix_sdk::BeaconError::Redacted => Self::Redacted,
matrix_sdk::BeaconError::Stripped => Self::Stripped,
matrix_sdk::BeaconError::Deserialization(_) => Self::Deserialization,
matrix_sdk::BeaconError::NotLive => Self::NotLive,
matrix_sdk::BeaconError::Other(err) => Self::Other { msg: err.to_string() },
}
}
}
#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi(flat_error)]
pub enum MediaInfoError {
@@ -709,7 +780,7 @@ pub enum ErrorKind {
/// [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>,
current_version: String,
},
/// A custom API error.
@@ -723,9 +794,9 @@ impl TryFrom<RumaApiErrorKind> for ErrorKind {
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::BadStatus(bad_status) => Ok(ErrorKind::BadStatus {
status: bad_status.status.map(|code| code.as_u16()),
body: bad_status.body.clone(),
}),
RumaApiErrorKind::CannotLeaveServerNoticeRoom => {
Ok(ErrorKind::CannotLeaveServerNoticeRoom)
@@ -737,19 +808,23 @@ impl TryFrom<RumaApiErrorKind> for ErrorKind {
RumaApiErrorKind::ConnectionTimeout => Ok(ErrorKind::ConnectionTimeout),
RumaApiErrorKind::DuplicateAnnotation => Ok(ErrorKind::DuplicateAnnotation),
RumaApiErrorKind::Exclusive => Ok(ErrorKind::Exclusive),
RumaApiErrorKind::Forbidden { .. } => Ok(ErrorKind::Forbidden),
RumaApiErrorKind::Forbidden => Ok(ErrorKind::Forbidden),
RumaApiErrorKind::GuestAccessForbidden => Ok(ErrorKind::GuestAccessForbidden),
RumaApiErrorKind::IncompatibleRoomVersion { room_version } => {
Ok(ErrorKind::IncompatibleRoomVersion { room_version: room_version.to_string() })
RumaApiErrorKind::IncompatibleRoomVersion(incompatible_room_version) => {
Ok(ErrorKind::IncompatibleRoomVersion {
room_version: incompatible_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 {
RumaApiErrorKind::LimitExceeded(limit_exceeded) => {
let retry_after_ms = match &limit_exceeded.retry_after {
Some(RetryAfter::Delay(duration)) => Some(duration.as_millis() as u64),
Some(RetryAfter::DateTime(system_time)) => {
let duration = system_time.duration_since(SystemTime::now()).ok();
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,
@@ -761,8 +836,10 @@ impl TryFrom<RumaApiErrorKind> for ErrorKind {
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::ResourceLimitExceeded(resource_limit_exceeded) => {
Ok(ErrorKind::ResourceLimitExceeded {
admin_contact: resource_limit_exceeded.admin_contact.clone(),
})
}
RumaApiErrorKind::RoomInUse => Ok(ErrorKind::RoomInUse),
RumaApiErrorKind::ServerNotTrusted => Ok(ErrorKind::ServerNotTrusted),
@@ -778,8 +855,8 @@ impl TryFrom<RumaApiErrorKind> for ErrorKind {
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::UnknownToken(unknown_token) => {
Ok(ErrorKind::UnknownToken { soft_logout: unknown_token.soft_logout.to_owned() })
}
RumaApiErrorKind::Unrecognized => Ok(ErrorKind::Unrecognized),
RumaApiErrorKind::UnsupportedRoomVersion => Ok(ErrorKind::UnsupportedRoomVersion),
@@ -789,10 +866,12 @@ impl TryFrom<RumaApiErrorKind> for ErrorKind {
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::WrongRoomKeysVersion(wrong_version) => {
Ok(ErrorKind::WrongRoomKeysVersion {
current_version: wrong_version.current_version.clone(),
})
}
RumaApiErrorKind::_Custom { .. } => {
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() })
+324 -36
View File
@@ -1,26 +1,40 @@
use std::ops::Deref;
// 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 that specific language governing permissions and
// limitations under the License.
use anyhow::{bail, Context};
use anyhow::{Context, bail};
use matrix_sdk::IdParseError;
use matrix_sdk_ui::timeline::TimelineEventItemId;
use ruma::{
EventId,
events::{
room::{
message::{MessageType as RumaMessageType, Relation},
redaction::SyncRoomRedactionEvent,
},
AnySyncMessageLikeEvent, AnySyncStateEvent, AnySyncTimelineEvent, AnyTimelineEvent,
MessageLikeEventContent as RumaMessageLikeEventContent, RedactContent,
RedactedStateEventContent, StaticStateEventContent, SyncMessageLikeEvent, SyncStateEvent,
TimelineEventType as RumaTimelineEventType,
room::{
encrypted,
message::{MessageType as RumaMessageType, Relation},
redaction::SyncRoomRedactionEvent,
},
},
EventId,
};
use crate::{
room_member::MembershipState,
ruma::{MessageType, NotifyType},
utils::Timestamp,
ClientError,
room_member::MembershipState,
ruma::{MessageType, RtcCallIntent, RtcNotificationType},
utils::Timestamp,
};
#[derive(uniffi::Object)]
@@ -40,16 +54,30 @@ impl TimelineEvent {
self.0.origin_server_ts().into()
}
pub fn event_type(&self) -> Result<TimelineEventType, ClientError> {
let event_type = match self.0.deref() {
pub fn content(&self) -> Result<TimelineEventContent, ClientError> {
let content = match &*self.0 {
AnySyncTimelineEvent::MessageLike(event) => {
TimelineEventType::MessageLike { content: event.clone().try_into()? }
TimelineEventContent::MessageLike { content: event.clone().try_into()? }
}
AnySyncTimelineEvent::State(event) => {
TimelineEventType::State { content: event.clone().try_into()? }
TimelineEventContent::State { content: event.clone().try_into()? }
}
};
Ok(event_type)
Ok(content)
}
/// Returns the thread root event id for the event, if it's part of a
/// thread.
pub fn thread_root_event_id(&self) -> Option<String> {
match &*self.0 {
AnySyncTimelineEvent::MessageLike(event) => {
match event.original_content().and_then(|content| content.relation()) {
Some(encrypted::Relation::Thread(thread)) => Some(thread.event_id.to_string()),
_ => None,
}
}
AnySyncTimelineEvent::State(_) => None,
}
}
}
@@ -59,16 +87,214 @@ impl From<AnyTimelineEvent> for TimelineEvent {
}
}
/// The timeline event type.
#[derive(Clone, uniffi::Enum, PartialEq, Eq, Hash)]
#[uniffi::export(Eq, Hash)]
pub enum TimelineEventType {
/// The event is a message-like one and should be displayed as such.
MessageLike { value: MessageLikeEventType },
/// The event is a state event, and may or may not be displayed in the
/// timeline.
State { value: StateEventType },
}
impl From<RumaTimelineEventType> for TimelineEventType {
fn from(value: RumaTimelineEventType) -> Self {
match value {
RumaTimelineEventType::Audio => {
Self::MessageLike { value: MessageLikeEventType::Audio }
}
RumaTimelineEventType::File => Self::MessageLike { value: MessageLikeEventType::File },
RumaTimelineEventType::Image => {
Self::MessageLike { value: MessageLikeEventType::Image }
}
RumaTimelineEventType::Video => {
Self::MessageLike { value: MessageLikeEventType::Video }
}
RumaTimelineEventType::Voice => {
Self::MessageLike { value: MessageLikeEventType::Voice }
}
RumaTimelineEventType::Emote => {
Self::MessageLike { value: MessageLikeEventType::Emote }
}
RumaTimelineEventType::Encrypted => {
Self::MessageLike { value: MessageLikeEventType::Encrypted }
}
RumaTimelineEventType::RoomMessage => {
Self::MessageLike { value: MessageLikeEventType::RoomMessage }
}
RumaTimelineEventType::CallAnswer => {
Self::MessageLike { value: MessageLikeEventType::CallAnswer }
}
RumaTimelineEventType::CallInvite => {
Self::MessageLike { value: MessageLikeEventType::CallInvite }
}
RumaTimelineEventType::CallHangup => {
Self::MessageLike { value: MessageLikeEventType::CallHangup }
}
RumaTimelineEventType::CallCandidates => {
Self::MessageLike { value: MessageLikeEventType::CallCandidates }
}
RumaTimelineEventType::CallNegotiate => {
Self::MessageLike { value: MessageLikeEventType::CallNegotiate }
}
RumaTimelineEventType::CallReject => {
Self::MessageLike { value: MessageLikeEventType::CallReject }
}
RumaTimelineEventType::CallSdpStreamMetadataChanged => {
Self::MessageLike { value: MessageLikeEventType::CallSdpStreamMetadataChanged }
}
RumaTimelineEventType::CallSelectAnswer => {
Self::MessageLike { value: MessageLikeEventType::CallSelectAnswer }
}
RumaTimelineEventType::KeyVerificationReady => {
Self::MessageLike { value: MessageLikeEventType::KeyVerificationReady }
}
RumaTimelineEventType::KeyVerificationStart => {
Self::MessageLike { value: MessageLikeEventType::KeyVerificationStart }
}
RumaTimelineEventType::KeyVerificationCancel => {
Self::MessageLike { value: MessageLikeEventType::KeyVerificationCancel }
}
RumaTimelineEventType::KeyVerificationAccept => {
Self::MessageLike { value: MessageLikeEventType::KeyVerificationAccept }
}
RumaTimelineEventType::KeyVerificationKey => {
Self::MessageLike { value: MessageLikeEventType::KeyVerificationKey }
}
RumaTimelineEventType::KeyVerificationMac => {
Self::MessageLike { value: MessageLikeEventType::KeyVerificationMac }
}
RumaTimelineEventType::KeyVerificationDone => {
Self::MessageLike { value: MessageLikeEventType::KeyVerificationDone }
}
RumaTimelineEventType::Location => {
Self::MessageLike { value: MessageLikeEventType::Location }
}
RumaTimelineEventType::Message => {
Self::MessageLike { value: MessageLikeEventType::Message }
}
RumaTimelineEventType::PollStart => {
Self::MessageLike { value: MessageLikeEventType::PollStart }
}
RumaTimelineEventType::UnstablePollStart => {
Self::MessageLike { value: MessageLikeEventType::UnstablePollStart }
}
RumaTimelineEventType::PollResponse => {
Self::MessageLike { value: MessageLikeEventType::PollResponse }
}
RumaTimelineEventType::UnstablePollResponse => {
Self::MessageLike { value: MessageLikeEventType::UnstablePollResponse }
}
RumaTimelineEventType::PollEnd => {
Self::MessageLike { value: MessageLikeEventType::PollEnd }
}
RumaTimelineEventType::UnstablePollEnd => {
Self::MessageLike { value: MessageLikeEventType::UnstablePollEnd }
}
RumaTimelineEventType::Beacon => {
Self::MessageLike { value: MessageLikeEventType::Beacon }
}
RumaTimelineEventType::Reaction => {
Self::MessageLike { value: MessageLikeEventType::Reaction }
}
RumaTimelineEventType::RoomEncrypted => {
Self::MessageLike { value: MessageLikeEventType::RoomEncrypted }
}
RumaTimelineEventType::RoomRedaction => {
Self::MessageLike { value: MessageLikeEventType::RoomRedaction }
}
RumaTimelineEventType::Sticker => {
Self::MessageLike { value: MessageLikeEventType::Sticker }
}
RumaTimelineEventType::CallNotify => {
Self::MessageLike { value: MessageLikeEventType::CallNotify }
}
RumaTimelineEventType::RtcNotification => {
Self::MessageLike { value: MessageLikeEventType::RtcNotification }
}
RumaTimelineEventType::RtcDecline => {
Self::MessageLike { value: MessageLikeEventType::RtcDecline }
}
RumaTimelineEventType::PolicyRuleRoom => {
Self::State { value: StateEventType::PolicyRuleRoom }
}
RumaTimelineEventType::PolicyRuleServer => {
Self::State { value: StateEventType::PolicyRuleServer }
}
RumaTimelineEventType::PolicyRuleUser => {
Self::State { value: StateEventType::PolicyRuleUser }
}
RumaTimelineEventType::RoomAvatar => Self::State { value: StateEventType::RoomAvatar },
RumaTimelineEventType::RoomCanonicalAlias => {
Self::State { value: StateEventType::RoomCanonicalAlias }
}
RumaTimelineEventType::RoomCreate => Self::State { value: StateEventType::RoomCreate },
RumaTimelineEventType::RoomEncryption => {
Self::State { value: StateEventType::RoomEncryption }
}
RumaTimelineEventType::RoomGuestAccess => {
Self::State { value: StateEventType::RoomGuestAccess }
}
RumaTimelineEventType::RoomHistoryVisibility => {
Self::State { value: StateEventType::RoomHistoryVisibility }
}
RumaTimelineEventType::RoomJoinRules => {
Self::State { value: StateEventType::RoomJoinRules }
}
RumaTimelineEventType::RoomMember => {
Self::State { value: StateEventType::RoomMemberEvent }
}
RumaTimelineEventType::RoomLanguage => {
Self::State { value: StateEventType::RoomLanguage }
}
RumaTimelineEventType::RoomName => Self::State { value: StateEventType::RoomName },
RumaTimelineEventType::RoomImagePack => {
Self::State { value: StateEventType::RoomImagePack }
}
RumaTimelineEventType::RoomPinnedEvents => {
Self::State { value: StateEventType::RoomPinnedEvents }
}
RumaTimelineEventType::RoomPowerLevels => {
Self::State { value: StateEventType::RoomPowerLevels }
}
RumaTimelineEventType::RoomServerAcl => {
Self::State { value: StateEventType::RoomServerAcl }
}
RumaTimelineEventType::RoomThirdPartyInvite => {
Self::State { value: StateEventType::RoomThirdPartyInvite }
}
RumaTimelineEventType::RoomTombstone => {
Self::State { value: StateEventType::RoomTombstone }
}
RumaTimelineEventType::RoomTopic => Self::State { value: StateEventType::RoomTopic },
RumaTimelineEventType::SpaceChild => Self::State { value: StateEventType::SpaceChild },
RumaTimelineEventType::SpaceParent => {
Self::State { value: StateEventType::SpaceParent }
}
RumaTimelineEventType::BeaconInfo => Self::State { value: StateEventType::BeaconInfo },
RumaTimelineEventType::CallMember => Self::State { value: StateEventType::CallMember },
RumaTimelineEventType::MemberHints => {
Self::State { value: StateEventType::MemberHints }
}
RumaTimelineEventType::_Custom(_) => {
Self::State { value: StateEventType::Custom { value: value.to_string() } }
}
_ => Self::MessageLike { value: MessageLikeEventType::Other(value.to_string()) },
}
}
}
#[derive(uniffi::Enum)]
// A note about this `allow(clippy::large_enum_variant)`.
// In order to reduce the size of `TimelineEventType`, we would need to
// In order to reduce the size of `TimelineEventContent`, 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 {
pub enum TimelineEventContent {
MessageLike { content: MessageLikeEventContent },
State { content: StateEventContent },
}
@@ -78,7 +304,6 @@ pub enum StateEventContent {
PolicyRuleRoom,
PolicyRuleServer,
PolicyRuleUser,
RoomAliases,
RoomAvatar,
RoomCanonicalAlias,
RoomCreate,
@@ -106,7 +331,6 @@ impl TryFrom<AnySyncStateEvent> for StateEventContent {
AnySyncStateEvent::PolicyRuleRoom(_) => StateEventContent::PolicyRuleRoom,
AnySyncStateEvent::PolicyRuleServer(_) => StateEventContent::PolicyRuleServer,
AnySyncStateEvent::PolicyRuleUser(_) => StateEventContent::PolicyRuleUser,
AnySyncStateEvent::RoomAliases(_) => StateEventContent::RoomAliases,
AnySyncStateEvent::RoomAvatar(_) => StateEventContent::RoomAvatar,
AnySyncStateEvent::RoomCanonicalAlias(_) => StateEventContent::RoomCanonicalAlias,
AnySyncStateEvent::RoomCreate(_) => StateEventContent::RoomCreate,
@@ -153,7 +377,13 @@ impl TryFrom<AnySyncStateEvent> for StateEventContent {
pub enum MessageLikeEventContent {
CallAnswer,
CallInvite,
CallNotify { notify_type: NotifyType },
RtcNotification {
notification_type: RtcNotificationType,
/// The timestamp at which this notification is considered invalid.
expiration_ts: Timestamp,
/// Soft indication of whether it is an audio or video call.
call_intent: Option<RtcCallIntent>,
},
CallHangup,
CallCandidates,
KeyVerificationReady,
@@ -163,11 +393,21 @@ pub enum MessageLikeEventContent {
KeyVerificationKey,
KeyVerificationMac,
KeyVerificationDone,
Poll { question: String },
ReactionContent { related_event_id: String },
Poll {
question: String,
},
ReactionContent {
related_event_id: String,
},
RoomEncrypted,
RoomMessage { message_type: MessageType, in_reply_to_event_id: Option<String> },
RoomRedaction { redacted_event_id: Option<String>, reason: Option<String> },
RoomMessage {
message_type: MessageType,
in_reply_to_event_id: Option<String>,
},
RoomRedaction {
redacted_event_id: Option<String>,
reason: Option<String>,
},
Sticker,
}
@@ -178,10 +418,14 @@ impl TryFrom<AnySyncMessageLikeEvent> for MessageLikeEventContent {
let content = match value {
AnySyncMessageLikeEvent::CallAnswer(_) => MessageLikeEventContent::CallAnswer,
AnySyncMessageLikeEvent::CallInvite(_) => MessageLikeEventContent::CallInvite,
AnySyncMessageLikeEvent::CallNotify(content) => {
let original_content = get_message_like_event_original_content(content)?;
MessageLikeEventContent::CallNotify {
notify_type: original_content.notify_type.into(),
AnySyncMessageLikeEvent::RtcNotification(event) => {
let origin_server_ts = event.origin_server_ts();
let original_content = get_message_like_event_original_content(event)?;
let expiration_ts = original_content.expiration_ts(origin_server_ts, None).into();
MessageLikeEventContent::RtcNotification {
notification_type: original_content.notification_type.into(),
expiration_ts,
call_intent: original_content.call_intent.map(|intent| intent.into()),
}
}
AnySyncMessageLikeEvent::CallHangup(_) => MessageLikeEventContent::CallHangup,
@@ -224,7 +468,7 @@ impl TryFrom<AnySyncMessageLikeEvent> for MessageLikeEventContent {
let original_content = get_message_like_event_original_content(content)?;
let in_reply_to_event_id =
original_content.relates_to.and_then(|relation| match relation {
Relation::Reply { in_reply_to } => Some(in_reply_to.event_id.to_string()),
Relation::Reply(reply) => Some(reply.in_reply_to.event_id.to_string()),
_ => None,
});
MessageLikeEventContent::RoomMessage {
@@ -270,21 +514,24 @@ where
Ok(original_content)
}
#[derive(Clone, uniffi::Enum)]
#[derive(Clone, uniffi::Enum, PartialEq, Eq, Hash)]
pub enum StateEventType {
BeaconInfo,
CallMember,
MemberHints,
PolicyRuleRoom,
PolicyRuleServer,
PolicyRuleUser,
RoomAliases,
RoomAvatar,
RoomCanonicalAlias,
RoomCreate,
RoomEncryption,
RoomGuestAccess,
RoomHistoryVisibility,
RoomImagePack,
RoomJoinRules,
RoomMemberEvent,
RoomLanguage,
RoomName,
RoomPinnedEvents,
RoomPowerLevels,
@@ -294,23 +541,27 @@ pub enum StateEventType {
RoomTopic,
SpaceChild,
SpaceParent,
Custom { value: String },
}
impl From<StateEventType> for ruma::events::StateEventType {
fn from(val: StateEventType) -> Self {
match val {
StateEventType::BeaconInfo => Self::BeaconInfo,
StateEventType::CallMember => Self::CallMember,
StateEventType::MemberHints => Self::MemberHints,
StateEventType::PolicyRuleRoom => Self::PolicyRuleRoom,
StateEventType::PolicyRuleServer => Self::PolicyRuleServer,
StateEventType::PolicyRuleUser => Self::PolicyRuleUser,
StateEventType::RoomAliases => Self::RoomAliases,
StateEventType::RoomAvatar => Self::RoomAvatar,
StateEventType::RoomCanonicalAlias => Self::RoomCanonicalAlias,
StateEventType::RoomCreate => Self::RoomCreate,
StateEventType::RoomEncryption => Self::RoomEncryption,
StateEventType::RoomGuestAccess => Self::RoomGuestAccess,
StateEventType::RoomHistoryVisibility => Self::RoomHistoryVisibility,
StateEventType::RoomImagePack => Self::RoomImagePack,
StateEventType::RoomJoinRules => Self::RoomJoinRules,
StateEventType::RoomLanguage => Self::RoomLanguage,
StateEventType::RoomMemberEvent => Self::RoomMember,
StateEventType::RoomName => Self::RoomName,
StateEventType::RoomPinnedEvents => Self::RoomPinnedEvents,
@@ -321,17 +572,28 @@ impl From<StateEventType> for ruma::events::StateEventType {
StateEventType::RoomTopic => Self::RoomTopic,
StateEventType::SpaceChild => Self::SpaceChild,
StateEventType::SpaceParent => Self::SpaceParent,
StateEventType::Custom { value } => value.into(),
}
}
}
#[derive(Clone, uniffi::Enum)]
#[derive(Clone, uniffi::Enum, PartialEq, Eq, Hash)]
pub enum MessageLikeEventType {
Audio,
Beacon,
CallAnswer,
CallCandidates,
CallHangup,
CallInvite,
CallNegotiate,
CallNotify,
CallReject,
CallSdpStreamMetadataChanged,
CallSelectAnswer,
Emote,
Encrypted,
File,
Image,
KeyVerificationAccept,
KeyVerificationCancel,
KeyVerificationDone,
@@ -339,6 +601,8 @@ pub enum MessageLikeEventType {
KeyVerificationMac,
KeyVerificationReady,
KeyVerificationStart,
Location,
Message,
PollEnd,
PollResponse,
PollStart,
@@ -346,20 +610,39 @@ pub enum MessageLikeEventType {
RoomEncrypted,
RoomMessage,
RoomRedaction,
RtcDecline,
RtcNotification,
Sticker,
UnstablePollEnd,
UnstablePollResponse,
UnstablePollStart,
Video,
Voice,
Other(String),
}
impl From<MessageLikeEventType> for ruma::events::MessageLikeEventType {
fn from(val: MessageLikeEventType) -> Self {
match val {
MessageLikeEventType::Audio => Self::Audio,
MessageLikeEventType::File => Self::File,
MessageLikeEventType::Image => Self::Image,
MessageLikeEventType::Video => Self::Video,
MessageLikeEventType::Voice => Self::Voice,
MessageLikeEventType::Beacon => Self::Beacon,
MessageLikeEventType::CallAnswer => Self::CallAnswer,
MessageLikeEventType::CallInvite => Self::CallInvite,
MessageLikeEventType::CallNotify => Self::CallNotify,
MessageLikeEventType::CallHangup => Self::CallHangup,
MessageLikeEventType::CallCandidates => Self::CallCandidates,
MessageLikeEventType::CallInvite => Self::CallInvite,
MessageLikeEventType::CallHangup => Self::CallHangup,
MessageLikeEventType::CallNegotiate => Self::CallNegotiate,
MessageLikeEventType::CallNotify => Self::CallNotify,
MessageLikeEventType::CallReject => Self::CallReject,
MessageLikeEventType::CallSdpStreamMetadataChanged => {
Self::CallSdpStreamMetadataChanged
}
MessageLikeEventType::CallSelectAnswer => Self::CallSelectAnswer,
MessageLikeEventType::Emote => Self::Emote,
MessageLikeEventType::Encrypted => Self::Encrypted,
MessageLikeEventType::KeyVerificationReady => Self::KeyVerificationReady,
MessageLikeEventType::KeyVerificationStart => Self::KeyVerificationStart,
MessageLikeEventType::KeyVerificationCancel => Self::KeyVerificationCancel,
@@ -367,17 +650,22 @@ impl From<MessageLikeEventType> for ruma::events::MessageLikeEventType {
MessageLikeEventType::KeyVerificationKey => Self::KeyVerificationKey,
MessageLikeEventType::KeyVerificationMac => Self::KeyVerificationMac,
MessageLikeEventType::KeyVerificationDone => Self::KeyVerificationDone,
MessageLikeEventType::Location => Self::Location,
MessageLikeEventType::Message => Self::Message,
MessageLikeEventType::Reaction => Self::Reaction,
MessageLikeEventType::RoomEncrypted => Self::RoomEncrypted,
MessageLikeEventType::RoomMessage => Self::RoomMessage,
MessageLikeEventType::RoomRedaction => Self::RoomRedaction,
MessageLikeEventType::RtcDecline => Self::RtcDecline,
MessageLikeEventType::Sticker => Self::Sticker,
MessageLikeEventType::PollEnd => Self::PollEnd,
MessageLikeEventType::PollResponse => Self::PollResponse,
MessageLikeEventType::PollStart => Self::PollStart,
MessageLikeEventType::RtcNotification => Self::RtcNotification,
MessageLikeEventType::UnstablePollEnd => Self::UnstablePollEnd,
MessageLikeEventType::UnstablePollResponse => Self::UnstablePollResponse,
MessageLikeEventType::UnstablePollStart => Self::UnstablePollStart,
MessageLikeEventType::Other(msgtype) => Self::from(msgtype),
}
}
}
+14
View File
@@ -1,3 +1,17 @@
// 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 that specific language governing permissions and
// limitations under the License.
use std::sync::Arc;
pub(crate) fn unwrap_or_clone_arc<T: Clone>(arc: Arc<T>) -> T {
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use matrix_sdk::crypto::IdentityState;
use matrix_sdk_base::crypto::IdentityState;
#[derive(uniffi::Record)]
pub struct IdentityStatusChange {
+7 -7
View File
@@ -1,14 +1,11 @@
// 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;
@@ -18,20 +15,23 @@ 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 search;
mod session_verification;
mod spaces;
mod store;
mod sync_service;
mod sync_v2;
mod task_handle;
mod timeline;
mod tracing;
mod utd;
mod utils;
mod widget;
@@ -9,24 +9,156 @@
// 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
// See the License for that specific language governing permissions and
// limitations under the License.
use crate::ruma::LocationContent;
use std::{fmt::Debug, sync::Arc};
use eyeball_im::VectorDiff;
use futures_util::StreamExt as _;
use matrix_sdk::live_location_share::{
LiveLocationShare as SdkLiveLocationShare, LiveLocationShares as SdkLiveLocationShares,
};
use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
use crate::{ruma::LocationContent, runtime::get_runtime_handle, task_handle::TaskHandle};
/// Details of the last known location beacon.
#[derive(uniffi::Record)]
pub struct LastLocation {
/// The most recent location content of the user.
/// The most recent location content shared for this asset.
pub location: LocationContent,
/// A timestamp in milliseconds since Unix Epoch on that day in local
/// time.
/// The timestamp of when the location was updated.
pub ts: u64,
}
/// Details of a users live location share.
/// Details of a user's 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 asset's last known location.
pub last_location: Option<LastLocation>,
/// The user ID of the person sharing their live location.
pub user_id: String,
/// The time when location sharing started.
pub start_ts: u64,
/// The duration that the location sharing will be live.
/// Meaning that the location will stop being shared at ts + timeout.
pub timeout: u64,
}
/// An update to the list of active live location shares.
///
/// Corresponds to a [`VectorDiff`] on the underlying [`ObservableVector`].
///
/// [`ObservableVector`]: eyeball_im::ObservableVector
#[derive(uniffi::Enum)]
pub enum LiveLocationShareUpdate {
Append { values: Vec<LiveLocationShare> },
Clear,
PushFront { value: LiveLocationShare },
PushBack { value: LiveLocationShare },
PopFront,
PopBack,
Insert { index: u32, value: LiveLocationShare },
Set { index: u32, value: LiveLocationShare },
Remove { index: u32 },
Truncate { length: u32 },
Reset { values: Vec<LiveLocationShare> },
}
/// Listener for live location share updates.
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait LiveLocationShareListener: SendOutsideWasm + SyncOutsideWasm + Debug {
/// Called with a batch of [`LiveLocationShareUpdate`]s whenever the list
/// of active shares changes.
fn on_update(&self, updates: Vec<LiveLocationShareUpdate>);
}
/// Tracks active live location shares in a room.
///
/// Holds the SDK [`SdkLiveLocationShares`] which keeps the beacon and
/// beacon_info event handlers registered for as long as this object is alive.
/// Call [`LiveLocationShares::subscribe`] to start receiving updates.
#[derive(uniffi::Object)]
pub struct LiveLocationShares {
inner: SdkLiveLocationShares,
}
impl LiveLocationShares {
pub fn new(inner: SdkLiveLocationShares) -> Self {
Self { inner }
}
}
#[matrix_sdk_ffi_macros::export]
impl LiveLocationShares {
/// Subscribe to changes in the list of active live location shares.
///
/// Immediately calls `listener` with a `Reset` update containing the
/// current snapshot (if non-empty), then calls it again for every
/// subsequent change that arrives from sync.
///
/// Returns a [`TaskHandle`] that, when dropped, stops the listener.
/// The event handlers remain registered for as long as this
/// [`LiveLocationShares`] object is alive.
pub fn subscribe(&self, listener: Box<dyn LiveLocationShareListener>) -> Arc<TaskHandle> {
let (initial_values, mut stream) = self.inner.subscribe();
if !initial_values.is_empty() {
listener.on_update(vec![LiveLocationShareUpdate::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());
}
})))
}
}
impl From<SdkLiveLocationShare> for LiveLocationShare {
fn from(share: SdkLiveLocationShare) -> Self {
let start_ts = share.beacon_info.ts.0.into();
let timeout = share.beacon_info.timeout.as_millis() as u64;
let asset = share.beacon_info.asset.type_.into();
let last_location = share.last_location.map(|l| LastLocation {
location: LocationContent {
body: "".to_owned(),
geo_uri: l.location.uri.to_string(),
description: None,
zoom_level: None,
asset,
},
ts: l.ts.0.into(),
});
LiveLocationShare { user_id: share.user_id.to_string(), last_location, start_ts, timeout }
}
}
impl From<VectorDiff<SdkLiveLocationShare>> for LiveLocationShareUpdate {
fn from(diff: VectorDiff<SdkLiveLocationShare>) -> Self {
match diff {
VectorDiff::Append { values } => {
Self::Append { values: values.into_iter().map(Into::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(Into::into).collect() }
}
}
}
}
+108 -34
View File
@@ -1,10 +1,25 @@
// 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 that specific language governing permissions and
// limitations under the License.
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,
RawNotificationEvent as SdkRawNotificationEvent,
};
use ruma::{EventId, OwnedEventId, OwnedRoomId, RoomId};
use tracing::error;
use crate::{
client::{Client, JoinRule},
@@ -31,17 +46,22 @@ 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 service_members: Vec<String>,
pub is_encrypted: Option<bool>,
pub is_direct: bool,
pub is_public: bool,
pub is_space: bool,
}
#[derive(uniffi::Record)]
pub struct NotificationItem {
pub event: NotificationEvent,
/// The raw JSON of the underlying event.
pub raw_event: String,
pub sender_info: NotificationSenderInfo,
pub room_info: NotificationRoomInfo,
@@ -51,20 +71,30 @@ pub struct NotificationItem {
pub is_noisy: Option<bool>,
pub has_mention: Option<bool>,
pub thread_id: Option<String>,
/// The push actions for this notification (notify, sound, highlight, etc.).
pub actions: Option<Vec<crate::notification_settings::Action>>,
}
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() }
}
};
let raw_event = match &item.raw_event {
SdkRawNotificationEvent::Timeline(raw) => raw.json().get().to_owned(),
SdkRawNotificationEvent::Invite(raw) => raw.json().get().to_owned(),
};
Self {
event,
raw_event,
sender_info: NotificationSenderInfo {
display_name: item.sender_display_name,
avatar_url: item.sender_avatar_url,
@@ -74,22 +104,67 @@ impl NotificationItem {
display_name: item.room_computed_display_name,
avatar_url: item.room_avatar_url,
canonical_alias: item.room_canonical_alias,
join_rule: item.room_join_rule.try_into().ok(),
topic: item.room_topic,
join_rule: item.room_join_rule.map(TryInto::try_into).transpose().ok().flatten(),
joined_members_count: item.joined_members_count,
service_members: item.service_members,
is_encrypted: item.is_room_encrypted,
is_direct: item.is_direct_message_room,
is_public: item.is_room_public,
is_space: item.is_space,
},
is_noisy: item.is_noisy,
has_mention: item.has_mention,
thread_id: item.thread_id.map(|t| t.to_string()),
actions: item
.actions
.map(|a| a.into_iter().filter_map(|action| action.try_into().ok()).collect()),
}
}
}
#[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,
/// The event has been redacted.
EventRedacted,
}
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,
SdkNotificationStatus::EventRedacted => NotificationStatus::EventRedacted,
}
}
}
#[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.
///
@@ -113,55 +188,54 @@ impl NotificationClient {
Ok(room)
}
/// See also documentation of
/// `MatrixNotificationClient::get_notification`.
/// 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
/// [`NotificationItem`] or no entry for it if it failed to fetch a
/// notification for the provided [`EventId`].
/// [`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, NotificationItem>, ClientError> {
) -> 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 result = HashMap::new();
let mut batch_result = HashMap::new();
for (key, value) in items.into_iter() {
match value {
Ok(item) => {
result.insert(key.to_string(), NotificationItem::from_inner(item));
}
Err(error) => {
// TODO This error should actually be returned so the clients can handle the
// error as they see fit, but it's failing when creating
// bindings for Go, i.e.
// (https://github.com/NordSecurity/uniffi-bindgen-go/issues/62)
error!("Could not fetch notification {key}, an error happened: {error}");
}
}
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(result)
Ok(batch_result)
}
}
@@ -1,26 +1,44 @@
// 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 that specific language governing permissions and
// limitations under the License.
use std::sync::{Arc, RwLock};
use matrix_sdk::{
Client as MatrixClient,
event_handler::EventHandlerHandle,
notification_settings::{
NotificationSettings as SdkNotificationSettings,
RoomNotificationMode as SdkRoomNotificationMode,
},
ruma::events::push_rules::PushRulesEvent,
Client as MatrixClient,
};
use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
use ruma::{
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,
events::push_rules::PushRulesEventContent,
push::{
Action as SdkAction, ComparisonOperator as SdkComparisonOperator, EventMatchConditionData,
EventPropertyContainsConditionData, EventPropertyIsConditionData, HighlightTweakValue,
PredefinedOverrideRuleId, PredefinedUnderrideRuleId, PushCondition as SdkPushCondition,
RoomMemberCountConditionData, RoomMemberCountIs, RuleKind as SdkRuleKind,
ScalarJsonValue as SdkJsonValue, SenderNotificationPermissionConditionData,
Tweak as SdkTweak,
},
};
use tokio::sync::RwLock as AsyncRwLock;
use crate::error::NotificationSettingsError;
use crate::error::{ClientError, NotificationSettingsError};
#[derive(Clone, Default, uniffi::Enum)]
pub enum ComparisonOperator {
@@ -166,19 +184,22 @@ impl TryFrom<SdkPushCondition> for PushCondition {
fn try_from(value: SdkPushCondition) -> Result<Self, Self::Error> {
Ok(match value {
SdkPushCondition::EventMatch { key, pattern } => Self::EventMatch { key, pattern },
SdkPushCondition::EventMatch(data) => {
Self::EventMatch { key: data.key, pattern: data.pattern }
}
#[allow(deprecated)]
SdkPushCondition::ContainsDisplayName => Self::ContainsDisplayName,
SdkPushCondition::RoomMemberCount { is } => {
Self::RoomMemberCount { prefix: is.prefix.into(), count: is.count.into() }
SdkPushCondition::RoomMemberCount(data) => {
Self::RoomMemberCount { prefix: data.is.prefix.into(), count: data.is.count.into() }
}
SdkPushCondition::SenderNotificationPermission { key } => {
Self::SenderNotificationPermission { key }
SdkPushCondition::SenderNotificationPermission(data) => {
Self::SenderNotificationPermission { key: data.key.to_string() }
}
SdkPushCondition::EventPropertyIs { key, value } => {
Self::EventPropertyIs { key, value: value.into() }
SdkPushCondition::EventPropertyIs(data) => {
Self::EventPropertyIs { key: data.key, value: data.value.into() }
}
SdkPushCondition::EventPropertyContains { key, value } => {
Self::EventPropertyContains { key, value: value.into() }
SdkPushCondition::EventPropertyContains(data) => {
Self::EventPropertyContains { key: data.key, value: data.value.into() }
}
_ => return Err("Unsupported condition type".to_owned()),
})
@@ -188,23 +209,28 @@ impl TryFrom<SdkPushCondition> for PushCondition {
impl From<PushCondition> for SdkPushCondition {
fn from(value: PushCondition) -> Self {
match value {
PushCondition::EventMatch { key, pattern } => Self::EventMatch { key, pattern },
PushCondition::EventMatch { key, pattern } => {
Self::EventMatch(EventMatchConditionData::new(key, pattern))
}
#[allow(deprecated)]
PushCondition::ContainsDisplayName => Self::ContainsDisplayName,
PushCondition::RoomMemberCount { prefix, count } => Self::RoomMemberCount {
is: RoomMemberCountIs {
PushCondition::RoomMemberCount { prefix, count } => {
Self::RoomMemberCount(RoomMemberCountConditionData::new(RoomMemberCountIs {
prefix: prefix.into(),
count: UInt::new(count).unwrap_or_default(),
},
},
}))
}
PushCondition::SenderNotificationPermission { key } => {
Self::SenderNotificationPermission { key }
Self::SenderNotificationPermission(SenderNotificationPermissionConditionData::new(
key.into(),
))
}
PushCondition::EventPropertyIs { key, value } => {
Self::EventPropertyIs { key, value: value.into() }
}
PushCondition::EventPropertyContains { key, value } => {
Self::EventPropertyContains { key, value: value.into() }
Self::EventPropertyIs(EventPropertyIsConditionData::new(key, value.into()))
}
PushCondition::EventPropertyContains { key, value } => Self::EventPropertyContains(
EventPropertyContainsConditionData::new(key, value.into()),
),
}
}
}
@@ -286,16 +312,19 @@ 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 }
Ok(match &value {
SdkTweak::Sound(sound) => Self::Sound { value: sound.to_string() },
SdkTweak::Highlight(highlight) => {
Self::Highlight { value: matches!(highlight, HighlightTweakValue::Yes) }
}
_ => {
let json_string = value
.custom_value()
.ok_or_else(|| "Unsupported tweak type".to_owned())?
.to_string();
Self::Custom { name: value.set_tweak().to_owned(), value: json_string }
}
_ => return Err("Unsupported tweak type".to_owned()),
})
}
}
@@ -305,16 +334,16 @@ impl TryFrom<Tweak> for SdkTweak {
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 }
}
Tweak::Sound { value } => Self::Sound(value.into()),
Tweak::Highlight { value } => Self::Highlight(value.into()),
Tweak::Custom { name, value } => Self::new(
name,
Some(
serde_json::value::RawValue::from_string(value)
.map_err(|e| format!("Failed to convert JSON value: {e}"))?,
),
)
.map_err(|e| format!("Failed to convert custom tweak: {e}"))?,
})
}
}
@@ -770,4 +799,11 @@ impl NotificationSettings {
.await?;
Ok(())
}
/// Returns the raw push rules in JSON format.
pub async fn get_raw_push_rules(&self) -> Result<Option<String>, ClientError> {
let raw_push_rules =
self.sdk_client.account().account_data::<PushRulesEventContent>().await?;
Ok(raw_push_rules.map(|raw| serde_json::to_string(&raw)).transpose()?)
}
}
-697
View File
@@ -1,697 +0,0 @@
use std::sync::{atomic::AtomicBool, Arc, OnceLock};
use tracing::warn;
use tracing_appender::rolling::{RollingFileAppender, Rotation};
use tracing_core::Subscriber;
use tracing_subscriber::{
field::RecordFields,
fmt::{
self,
format::{DefaultFields, Writer},
time::FormatTime,
FormatEvent, FormatFields, FormattedFields,
},
layer::SubscriberExt as _,
registry::LookupSpan,
util::SubscriberInitExt as _,
Layer,
};
use crate::{error::ClientError, tracing::LogLevel};
fn text_layers<S>(config: TracingConfiguration) -> impl Layer<S>
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
// Adjusted version of tracing_subscriber::fmt::Format
struct EventFormatter {
display_timestamp: bool,
display_level: bool,
}
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,
{
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 self.display_timestamp {
self.format_timestamp(&mut writer)?;
writer.write_char(' ')?;
}
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}")?;
}
}
if let Some(scope) = ctx.event_scope() {
writer.write_str(" | spans: ")?;
let mut first = true;
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}}}")?;
}
}
}
}
writeln!(writer)
}
}
let file_layer = config.write_to_files.map(|c| {
let mut builder = RollingFileAppender::builder()
.rotation(Rotation::HOURLY)
.filename_prefix(&c.file_prefix);
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)
}
let writer = builder.build(&c.path).expect("Failed to create a rolling file appender.");
// 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);
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(
file_layer,
config.write_to_stdout_or_system.then(|| {
// 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 FieldsFormatterFormStdoutOrSystem(DefaultFields);
impl<'writer> FormatFields<'writer> for FieldsFormatterFormStdoutOrSystem {
fn format_fields<R: RecordFields>(
&self,
writer: Writer<'writer>,
fields: R,
) -> std::fmt::Result {
self.0.format_fields(writer, fields)
}
}
#[cfg(not(target_os = "android"))]
return fmt::layer()
.fmt_fields(FieldsFormatterFormStdoutOrSystem::default())
.event_format(EventFormatter::new())
// See comment above.
.with_ansi(false)
.with_writer(std::io::stderr);
#[cfg(target_os = "android")]
return fmt::layer()
.fmt_fields(FieldsFormatterFormStdoutOrSystem::default())
.event_format(EventFormatter::for_logcat())
// See comment above.
.with_ansi(false)
.with_writer(paranoid_android::AndroidLogMakeWriter::new(
"org.matrix.rust.sdk".to_owned(),
));
}),
)
}
/// Configuration to save logs to (rotated) log-files.
#[derive(uniffi::Record)]
pub struct TracingFileConfiguration {
/// Base location for all the log files.
path: String,
/// Prefix for the log files' names.
file_prefix: String,
/// Optional suffix for the log file's names.
file_suffix: Option<String>,
/// Maximum number of rotated files.
///
/// If not set, there's no max limit, i.e. the number of log files is
/// unlimited.
max_files: Option<u64>,
}
#[derive(PartialEq, PartialOrd)]
enum LogTarget {
// External crates.
Hyper,
// FFI modules.
MatrixSdkFfi,
// SDK base modules.
MatrixSdkBaseEventCache,
MatrixSdkBaseSlidingSync,
MatrixSdkBaseStoreAmbiguityMap,
// SDK common modules.
MatrixSdkCommonStoreLocks,
// SDK modules.
MatrixSdk,
MatrixSdkClient,
MatrixSdkCrypto,
MatrixSdkCryptoAccount,
MatrixSdkEventCache,
MatrixSdkEventCacheStore,
MatrixSdkHttpClient,
MatrixSdkOidc,
MatrixSdkSendQueue,
MatrixSdkSlidingSync,
// SDK UI modules.
MatrixSdkUiTimeline,
}
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::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",
}
}
}
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),
];
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,
}
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],
}
}
}
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 {
sentry: Option<SentryLoggingCtx>,
}
static LOGGING: OnceLock<LoggingCtx> = OnceLock::new();
#[derive(uniffi::Record)]
pub struct TracingConfiguration {
/// 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.
///
/// 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.
sentry_dsn: Option<String>,
}
impl TracingConfiguration {
/// Sets up the tracing configuration and return a [`Logger`] instance
/// holding onto it.
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();
// 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,
..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 env_filter = build_tracing_filter(&self);
tracing_subscriber::registry()
.with(tracing_subscriber::EnvFilter::new(&env_filter))
.with(crate::platform::text_layers(self))
.with(sentry_layer)
.init();
// Log the log levels 🧠.
tracing::info!(env_filter, "Logging has been set up");
LoggingCtx { sentry: sentry_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 init_platform(
config: TracingConfiguration,
use_lightweight_tokio_runtime: bool,
) -> Result<(), ClientError> {
LOGGING.set(config.build()).map_err(|_| ClientError::Generic {
msg: "logger already initialized".to_owned(),
details: None,
})?;
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]
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");
};
}
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
}));
}
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 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,
sentry_dsn: None,
};
let filter = build_tracing_filter(&config);
assert_eq!(
filter,
"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,\
super_duper_app=error"
);
}
#[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,
sentry_dsn: None,
};
let filter = build_tracing_filter(&config);
assert_eq!(
filter,
"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,\
super_duper_app=trace,\
some_other_span=trace"
);
}
#[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,
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,
super_duper_app=info"#
.split('\n')
.map(|s| s.trim())
.collect::<Vec<_>>()
.join("")
);
}
}
@@ -0,0 +1,79 @@
use std::{error::Error, mem::MaybeUninit};
use jni::{
errors::JniError,
sys::{JNI_OK, JavaVM as RawJavaVM},
};
use tracing::debug;
static ANDROID_JVM: once_cell::sync::OnceCell<jni::JavaVM> = once_cell::sync::OnceCell::new();
/// Initialize the platform support for Android targets.
///
/// This includes setting up `rustls-platform-verifier`.
pub(crate) fn init() {
debug!("Initializing Android platform support");
ANDROID_JVM.get_or_init(|| {
match get_java_vm() {
Ok(jvm) => {
// Initialize rustls platform verifier
let mut env =
jvm.attach_current_thread_permanently().expect("Failed to attach thread");
init_rustls_platform_verifier(&mut env)
.expect("Failed to initialize rustls platform verifier");
debug!("Android platform support initialized successfully");
jvm
}
Err(e) => {
panic!("Failed to initialize Android platform support: {}", e);
}
}
});
}
fn get_java_vm() -> Result<jni::JavaVM, Box<dyn Error>> {
debug!("Getting a JVM pointer");
#[allow(non_snake_case)]
let JNI_GetCreatedJavaVMs = unsafe {
jvm_getter::find_jni_get_created_java_vms().expect("Failed to find JNI_GetCreatedJavaVMs")
};
let mut vm: MaybeUninit<*mut RawJavaVM> = MaybeUninit::uninit();
let status = unsafe { JNI_GetCreatedJavaVMs(vm.as_mut_ptr(), 1, &mut 0) };
if status != JNI_OK {
panic!("no JavaVM was found by JNI_GetCreatedJavaVMs");
}
unsafe { jni::JavaVM::from_raw(vm.assume_init()).map_err(|e| e.into()) }
}
fn init_rustls_platform_verifier(env: &mut jni::JNIEnv<'_>) -> jni::errors::Result<()> {
// Get the current activity thread
let activity_thread = env
.call_static_method(
"android/app/ActivityThread",
"currentActivityThread",
"()Landroid/app/ActivityThread;",
&[],
)?
.l()?;
// Then get the application context
let context = env
.call_method(activity_thread, "getApplication", "()Landroid/app/Application;", &[])?
.l()?;
Ok(rustls_platform_verifier::android::init_hosted(env, context)?)
}
/// Attach the current thread to a JVM one.
pub(crate) fn android_attach_current_thread_permanently()
-> jni::errors::Result<jni::JNIEnv<'static>> {
ANDROID_JVM
.get()
.ok_or_else(|| jni::errors::Error::JniCall(JniError::Unknown))?
.attach_current_thread_permanently()
}
+931
View File
@@ -0,0 +1,931 @@
// 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 that specific language governing permissions and
// limitations under the License.
use std::sync::OnceLock;
#[cfg(feature = "sentry")]
use std::sync::{Arc, atomic::AtomicBool};
use ::tracing::info;
#[cfg(feature = "sentry")]
use ::tracing::warn;
use tracing_appender::rolling::Rotation;
#[cfg(feature = "sentry")]
use tracing_core::Level;
use tracing_core::Subscriber;
use tracing_subscriber::{
EnvFilter, Layer, Registry,
field::RecordFields,
fmt::{
self, FormatEvent, FormatFields, FormattedFields,
format::{DefaultFields, Writer},
time::FormatTime,
},
layer::{Layered, SubscriberExt as _},
registry::LookupSpan,
reload::{self, Handle},
util::SubscriberInitExt as _,
};
use crate::error::ClientError;
/// Default maximum total size of all log files combined (10MB).
const DEFAULT_MAX_TOTAL_SIZE_BYTES: u64 = 10 * 1024 * 1024;
/// Default maximum age of log files in seconds (1 week).
const DEFAULT_MAX_AGE_SECONDS: u64 = 7 * 24 * 60 * 60;
mod rolling_writer;
pub mod tracing;
#[cfg(target_os = "android")]
mod android_platform;
use rolling_writer::SizeAndDateRollingWriter;
#[cfg(feature = "sentry")]
use self::tracing::BRIDGE_SPAN_NAME;
use self::tracing::LogLevel;
// Adjusted version of tracing_subscriber::fmt::Format
struct EventFormatter {
display_timestamp: bool,
display_level: bool,
}
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,
{
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 self.display_timestamp {
self.format_timestamp(&mut writer)?;
writer.write_char(' ')?;
}
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}")?;
}
}
if let Some(scope) = ctx.event_scope() {
writer.write_str(" | spans: ")?;
let mut first = true;
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>>()
&& !fields.is_empty()
{
write!(writer, "{{{fields}}}")?;
}
}
}
writeln!(writer)
}
}
// 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);
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)
}
}
type ReloadHandle = Handle<
tracing_subscriber::fmt::Layer<
Layered<EnvFilter, Registry>,
FieldsFormatterForFiles,
EventFormatter,
SizeAndDateRollingWriter,
>,
Layered<EnvFilter, Registry>,
>;
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();
let layers = Layer::and_then(
file_layer,
config.write_to_stdout_or_system.then(|| {
// 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 FieldsFormatterFormStdoutOrSystem(DefaultFields);
impl<'writer> FormatFields<'writer> for FieldsFormatterFormStdoutOrSystem {
fn format_fields<R: RecordFields>(
&self,
writer: Writer<'writer>,
fields: R,
) -> std::fmt::Result {
self.0.format_fields(writer, fields)
}
}
#[cfg(not(target_os = "android"))]
return fmt::layer()
.fmt_fields(FieldsFormatterFormStdoutOrSystem::default())
.event_format(EventFormatter::new())
// See comment above.
.with_ansi(false)
.with_writer(std::io::stderr);
#[cfg(target_os = "android")]
return fmt::layer()
.fmt_fields(FieldsFormatterFormStdoutOrSystem::default())
.event_format(EventFormatter::for_logcat())
// See comment above.
.with_ansi(false)
.with_writer(paranoid_android::AndroidLogMakeWriter::new(
"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,
SizeAndDateRollingWriter,
> {
let writer = SizeAndDateRollingWriter::new(
&file_configuration.path,
file_configuration.file_prefix,
file_configuration.file_suffix.unwrap_or_else(|| String::from(".log")),
Rotation::HOURLY,
file_configuration.max_total_size_bytes.unwrap_or(DEFAULT_MAX_TOTAL_SIZE_BYTES),
file_configuration.max_age_seconds.unwrap_or(DEFAULT_MAX_AGE_SECONDS),
)
.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.
#[derive(uniffi::Record)]
pub struct TracingFileConfiguration {
/// Base location for all the log files.
path: String,
/// Prefix for the log files' names.
file_prefix: String,
/// Optional suffix for the log file's names.
///
/// Default is ".log" if not specified.
file_suffix: Option<String>,
/// Maximum total size of all log files combined in bytes.
///
/// When the total size of all log files with the configured prefix and
/// suffix exceeds this limit, the oldest files will be removed until
/// the total is below the limit.
///
/// This is useful to prevent log files from consuming too much disk space
/// over time, even with multiple rotated files.
///
/// Default: 10MB (10 * 1024 * 1024 bytes) if not specified.
max_total_size_bytes: Option<u64>,
/// Maximum age of log files in seconds.
///
/// Log files older than this age will be automatically removed during
/// cleanup. This is checked when the writer is created and during
/// rotation operations.
///
/// Default: 1 week (7 * 24 * 60 * 60 seconds) if not specified.
max_age_seconds: Option<u64>,
}
#[derive(PartialEq, PartialOrd)]
enum LogTarget {
// External crates.
Hyper,
// FFI modules.
MatrixSdkFfi,
// SDK base modules.
MatrixSdkBaseEventCache,
MatrixSdkBaseSlidingSync,
MatrixSdkBaseStoreAmbiguityMap,
MatrixSdkBaseResponseProcessors,
// SDK common modules.
MatrixSdkCommonCrossProcessLock,
MatrixSdkCommonDeserializedResponses,
// SDK modules.
MatrixSdk,
MatrixSdkClient,
MatrixSdkCrypto,
MatrixSdkCryptoAccount,
MatrixSdkEventCache,
MatrixSdkEventCacheStore,
MatrixSdkHttpClient,
MatrixSdkLatestEvents,
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::MatrixSdkCommonCrossProcessLock => "matrix_sdk_common::cross_process_lock",
LogTarget::MatrixSdkCommonDeserializedResponses => {
"matrix_sdk_common::deserialized_responses"
}
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::MatrixSdkLatestEvents => "matrix_sdk::latest_events",
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::MatrixSdkLatestEvents, LogLevel::Info),
(LogTarget::MatrixSdkBaseEventCache, LogLevel::Info),
(LogTarget::MatrixSdkEventCacheStore, LogLevel::Info),
(LogTarget::MatrixSdkCommonCrossProcessLock, LogLevel::Warn),
(LogTarget::MatrixSdkCommonDeserializedResponses, 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::MatrixSdkCommonCrossProcessLock, // 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,
/// Enables all the logs relevant to the latest events.
LatestEvents,
}
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,
LogTarget::MatrixSdkCommonCrossProcessLock,
LogTarget::MatrixSdkCommonDeserializedResponses,
],
TraceLogPacks::SendQueue => &[LogTarget::MatrixSdkSendQueue],
TraceLogPacks::Timeline => {
&[LogTarget::MatrixSdkUiTimeline, LogTarget::MatrixSdkCommonDeserializedResponses]
}
TraceLogPacks::NotificationClient => &[LogTarget::MatrixSdkUiNotificationClient],
TraceLogPacks::SyncProfiling => &[
LogTarget::MatrixSdkSlidingSync,
LogTarget::MatrixSdkBaseSlidingSync,
LogTarget::MatrixSdkBaseResponseProcessors,
LogTarget::MatrixSdkCrypto,
LogTarget::MatrixSdkCommonCrossProcessLock,
LogTarget::MatrixSdkCommonDeserializedResponses,
],
TraceLogPacks::LatestEvents => &[
LogTarget::MatrixSdkLatestEvents,
LogTarget::MatrixSdkSendQueue,
LogTarget::MatrixSdkEventCache,
],
}
}
}
#[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 {
/// 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.
///
/// 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 configuration to use for error reporting.
#[cfg(feature = "sentry")]
sentry_config: Option<SentryConfig>,
}
#[cfg(feature = "sentry")]
#[derive(uniffi::Record)]
pub struct SentryConfig {
dsn: String,
app_version: String,
app_platform: 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.
//
// FIXME: Use safe API for this once stable. Tracking issue:
// https://github.com/rust-lang/rust/issues/93346
unsafe {
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_config) = self.sentry_config.take() {
// Initialize the Sentry client with the given options.
let sentry_guard = sentry::init((
sentry_config.dsn,
sentry::ClientOptions {
traces_sampler: Some(Arc::new(|ctx| {
// Make sure bridge spans are always uploaded
if ctx.name() == BRIDGE_SPAN_NAME { 1.0 } else { 0.0 }
})),
attach_stacktrace: true,
release: Some(env!("VERGEN_GIT_SHA").into()),
..sentry::ClientOptions::default()
},
));
sentry::configure_scope(|scope| {
scope.set_tag("app_version", sentry_config.app_version);
scope.set_tag("app_platform", sentry_config.app_platform);
});
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) {
matches!(
metadata.level(),
&Level::ERROR | &Level::WARN | &Level::INFO | &Level::DEBUG
)
} 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 🧠.
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 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,
})?;
if use_lightweight_tokio_runtime {
setup_lightweight_tokio_runtime();
} else {
setup_multithreaded_tokio_runtime();
}
}
#[cfg(target_os = "android")]
android_platform::init();
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();
#[cfg(target_os = "android")]
builder.on_thread_start(|| {
_ = android_platform::android_attach_current_thread_permanently();
});
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_config: 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::latest_events=info,
matrix_sdk_base::event_cache=info,
matrix_sdk_sqlite::event_cache_store=info,
matrix_sdk_common::cross_process_lock=warn,
matrix_sdk_common::deserialized_responses=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_config: 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::latest_events=trace,
matrix_sdk_base::event_cache=trace,
matrix_sdk_sqlite::event_cache_store=trace,
matrix_sdk_common::cross_process_lock=warn,
matrix_sdk_common::deserialized_responses=trace,
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_config: 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::latest_events=info,
matrix_sdk_base::event_cache=trace,
matrix_sdk_sqlite::event_cache_store=trace,
matrix_sdk_common::cross_process_lock=warn,
matrix_sdk_common::deserialized_responses=trace,
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("")
);
}
}
@@ -0,0 +1,914 @@
// Copyright 2026 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 that specific language governing permissions and
// limitations under the License.
use std::{
fs::{self, File, OpenOptions},
io::{self, Write},
path::{Path, PathBuf},
sync::Mutex,
time::SystemTime,
};
use tracing_appender::rolling::Rotation;
/// Custom rolling file appender that supports time-based rotation and
/// size-based cleanup.
///
/// This writer automatically manages log files with the following behavior:
///
/// # File Naming
///
/// Log files are named using the pattern: `{prefix}.{timestamp}.{suffix}`
/// where the timestamp format depends on the rotation period:
/// - `MINUTELY`: `YYYY-MM-DD-HH-MM`
/// - `HOURLY`: `YYYY-MM-DD-HH`
/// - `DAILY`: `YYYY-MM-DD`
/// - `WEEKLY` or `NEVER`: `YYYY-Www` (ISO week number, e.g., `2024-W03`)
///
/// # Automatic Rotation
///
/// Files are rotated (a new file is created) when the configured time period
/// changes. For example, with hourly rotation, a new file is created when the
/// hour changes. Rotation is checked:
/// - During writer initialization (creates/opens file for current period)
/// - Before each write operation (only rotates if time period has changed)
///
/// If a log file already exists for the current time period, it will be
/// reopened and appended to rather than creating a new file.
///
/// # Automatic Cleanup
///
/// The writer performs cleanup operations during initialization and rotation:
/// - **Size limit enforcement**: When total size of all log files exceeds
/// `max_total_size_bytes`, the oldest files are removed until under the limit
/// - **Age-based cleanup**: Files older than `max_age_seconds` (based on
/// filesystem modification time) are automatically removed
/// - **File filtering**: Only files matching both the configured prefix and
/// suffix are managed; other files in the directory are left untouched
///
/// # Side Effects on Creation
///
/// When `new()` is called, the following side effects occur:
/// 1. Creates the log directory if it doesn't exist (including parent
/// directories)
/// 2. Creates or opens a log file for the current time period (appends if
/// exists)
/// 3. Performs cleanup of old files based on age (by filesystem mtime)
/// 4. Enforces the total size limit by removing oldest files if needed
///
/// # Thread Safety
///
/// This writer is safe to use from multiple threads. Internal state is
/// protected by a mutex, ensuring that file operations and rotations are
/// properly synchronized.
pub(super) struct SizeAndDateRollingWriter {
config: WriterConfig,
state: Mutex<Option<WriterState>>,
}
/// Immutable configuration for the writer - shared without locks.
///
/// This struct contains all configuration parameters that remain constant
/// throughout the writer's lifetime. Since these values never change, they
/// can be safely shared across threads without synchronization.
struct WriterConfig {
/// Directory where log files are created
base_path: PathBuf,
/// Prefix for log file names (e.g., "app" results in "app.2024-01-15.log")
file_prefix: String,
/// Suffix for log file names (typically ".log")
file_suffix: String,
/// Time period for automatic rotation (MINUTELY, HOURLY, DAILY, WEEKLY, or
/// NEVER which is treated as WEEKLY)
rotation: Rotation,
/// Maximum total size in bytes of all log files before cleanup
max_total_size_bytes: u64,
/// Maximum age in seconds before a log file is removed during cleanup
max_age_seconds: u64,
}
/// Mutable state that requires synchronization.
///
/// This struct contains the current log file handle and its path. These values
/// change when rotation occurs, so access must be protected by a mutex to
/// ensure thread safety.
///
/// The state is wrapped in `Option` because it needs to be temporarily taken
/// during rotation operations to allow mutation while holding the lock.
struct WriterState {
/// The currently open log file handle for writing
current_file: File,
/// Path to the current log file (used to identify it during cleanup)
current_path: PathBuf,
}
impl SizeAndDateRollingWriter {
/// Creates a new rolling writer with the specified configuration.
///
/// # Arguments
///
/// * `path` - Directory where log files will be created. Will be created if
/// it doesn't exist.
/// * `file_prefix` - Prefix for log file names (e.g., "app")
/// * `file_suffix` - Suffix for log file names (e.g., ".log")
/// * `rotation` - Time period for rotation (MINUTELY, HOURLY, DAILY,
/// WEEKLY, or NEVER which is treated as WEEKLY)
/// * `max_total_size_bytes` - Maximum total size of all log files. When
/// exceeded, oldest files are removed.
/// * `max_age_seconds` - Maximum age of log files in seconds. Files older
/// than this (by filesystem mtime) are removed during cleanup.
///
/// # Side Effects
///
/// This method performs several file system operations in order:
/// 1. Creates the directory at `path` if it doesn't exist
/// 2. Creates or reopens a log file for the current time period (appends if
/// exists)
/// 3. Scans the directory for existing log files matching the prefix/suffix
/// 4. Removes files older than `max_age_seconds` (by filesystem mtime)
/// 5. Removes oldest files if total size exceeds `max_total_size_bytes`
///
/// # Errors
///
/// Returns an error if:
/// - The directory cannot be created
/// - The directory cannot be read
/// - The log file cannot be created or opened
/// - File metadata cannot be read during cleanup
///
/// # Examples
///
/// ```ignore
/// let writer = SizeAndDateRollingWriter::new(
/// "/var/log/myapp",
/// "app".to_owned(),
/// ".log".to_owned(),
/// Rotation::HOURLY,
/// 100 * 1024 * 1024, // 100 MB
/// 7 * 24 * 60 * 60, // 7 days
/// )?;
/// ```
pub(super) fn new(
path: impl AsRef<Path>,
file_prefix: String,
file_suffix: String,
rotation: Rotation,
max_total_size_bytes: u64,
max_age_seconds: u64,
) -> io::Result<Self> {
let base_path = path.as_ref().to_path_buf();
fs::create_dir_all(&base_path)?;
let config = WriterConfig {
base_path,
file_prefix,
file_suffix,
rotation,
max_total_size_bytes,
max_age_seconds,
};
// Create initial state with first rotation
let mut state = None;
Self::rotate_internal(&config, &mut state, false)?;
Ok(Self { config, state: Mutex::new(state) })
}
/// Extract the timestamp from the current filename.
fn extract_timestamp_from_path(config: &WriterConfig, current_path: &Path) -> Option<String> {
let filename = current_path.file_name()?.to_str()?;
// Strip prefix and suffix to get the timestamp
// Format: "prefix.timestamp.suffix"
let without_prefix = filename.strip_prefix(&format!("{}.", config.file_prefix))?;
let timestamp = without_prefix.strip_suffix(&config.file_suffix)?;
Some(timestamp.to_owned())
}
/// Check if rotation is needed based on time period change.
fn should_rotate_by_time(config: &WriterConfig, current_path: &Path) -> bool {
let current_time = Self::format_rotation_timestamp(config);
let last_rotation_time = Self::extract_timestamp_from_path(config, current_path);
// If we can't extract the timestamp, assume rotation is needed
match last_rotation_time {
Some(last_time) => current_time != last_time,
None => true,
}
}
/// Format the current time as a timestamp string for the rotation period.
///
/// Returns a timestamp string based on the configured rotation period
/// (e.g., "2024-01-15-14" for hourly rotation).
fn format_rotation_timestamp(config: &WriterConfig) -> String {
let now = chrono::Local::now();
match config.rotation {
Rotation::MINUTELY => now.format("%Y-%m-%d-%H-%M").to_string(),
Rotation::HOURLY => now.format("%Y-%m-%d-%H").to_string(),
Rotation::DAILY => now.format("%Y-%m-%d").to_string(),
Rotation::WEEKLY | Rotation::NEVER => now.format("%Y-W%W").to_string(),
}
}
/// Rotate the log file, creating a new file with a timestamp-based name.
///
/// If `check_conditions` is true, rotation only happens if time or size
/// thresholds are met. Otherwise, rotation is forced.
///
/// This method also handles initial state creation when called with None
/// state.
fn rotate_internal(
config: &WriterConfig,
state: &mut Option<WriterState>,
check_conditions: bool,
) -> io::Result<()> {
// Check if rotation is needed (skip for uninitialized state)
if check_conditions
&& let Some(state) = state.as_ref()
&& !Self::should_rotate_by_time(config, &state.current_path)
{
return Ok(());
}
let time_str = Self::format_rotation_timestamp(config);
// Generate filename with timestamp
let filename = format!("{}.{}{}", config.file_prefix, time_str, config.file_suffix);
let new_path = config.base_path.join(filename);
// Open or create file in append mode
let new_file = OpenOptions::new().create(true).append(true).open(&new_path)?;
let new_state = WriterState { current_file: new_file, current_path: new_path };
// Clean up logs older than configured max age
Self::trim_old_logs_internal(config, &new_state)?;
// Enforce total size limit by removing oldest files if needed
Self::enforce_total_size_limit_internal(config, &new_state)?;
*state = Some(new_state);
Ok(())
}
/// Get all log files matching our prefix and suffix, sorted by modification
/// time.
///
/// Returns a vector of (path, modification_time) tuples, oldest first.
fn get_matching_log_files(config: &WriterConfig) -> io::Result<Vec<(PathBuf, SystemTime)>> {
let mut files: Vec<_> = fs::read_dir(&config.base_path)?
.filter_map(|entry| {
let entry = entry.ok()?;
let path = entry.path();
if path.is_file() {
let filename = path.file_name()?.to_str()?;
// Only process files matching our prefix and suffix
if filename.starts_with(&config.file_prefix)
&& filename.ends_with(&config.file_suffix)
{
let metadata = fs::metadata(&path).ok()?;
let modified = metadata.modified().ok()?;
return Some((path, modified));
}
}
None
})
.collect();
// Sort by modification time (oldest first)
files.sort_by_key(|(_, modified)| *modified);
Ok(files)
}
/// Remove all log files older than the configured max age.
///
/// Only files matching the configured prefix and suffix are removed.
/// Other files in the directory are left alone.
fn trim_old_logs_internal(config: &WriterConfig, state: &WriterState) -> io::Result<()> {
let now = SystemTime::now();
let files = Self::get_matching_log_files(config)?;
for (path, modified) in files {
// Skip the current file
if path == state.current_path {
continue;
}
// Check if file is older than max age
if let Ok(duration) = now.duration_since(modified)
&& duration.as_secs() > config.max_age_seconds
{
let _ = fs::remove_file(path);
}
}
Ok(())
}
/// Enforce total size limit across all log files.
///
/// If the total size of all matching log files exceeds
/// max_total_size_bytes, remove the oldest files until the total is
/// below the limit.
fn enforce_total_size_limit_internal(
config: &WriterConfig,
state: &WriterState,
) -> io::Result<()> {
let max_total = config.max_total_size_bytes;
let files = Self::get_matching_log_files(config)?;
// Calculate total size of all log files
let mut total_size: u64 = 0;
for (path, _) in &files {
if let Ok(metadata) = fs::metadata(path) {
total_size += metadata.len();
}
}
// If under limit, nothing to do
if total_size <= max_total {
return Ok(());
}
// Remove oldest files until we're under the limit
// Files are already sorted by modification time (oldest first)
for (path, _) in files {
// Don't remove the current file
if path == state.current_path {
continue;
}
if let Ok(metadata) = fs::metadata(&path) {
let file_size = metadata.len();
let _ = fs::remove_file(&path);
total_size = total_size.saturating_sub(file_size);
// Check if we're now under the limit
if total_size <= max_total {
break;
}
}
}
Ok(())
}
}
impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for SizeAndDateRollingWriter {
type Writer = SizeAndDateRollingWriterHandle<'a>;
fn make_writer(&'a self) -> Self::Writer {
SizeAndDateRollingWriterHandle {
config: &self.config,
state: &self.state,
_phantom: std::marker::PhantomData,
}
}
}
pub(super) struct SizeAndDateRollingWriterHandle<'a> {
config: &'a WriterConfig,
state: &'a Mutex<Option<WriterState>>,
_phantom: std::marker::PhantomData<&'a ()>,
}
impl<'a> Write for SizeAndDateRollingWriterHandle<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let mut state = self.state.lock().unwrap();
// Check if rotation is needed
SizeAndDateRollingWriter::rotate_internal(self.config, &mut state, true)?;
// Write to file (state must be initialized after rotation)
state.as_mut().unwrap().current_file.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
let mut state = self.state.lock().unwrap();
if let Some(s) = state.as_mut() { s.current_file.flush() } else { Ok(()) }
}
}
#[cfg(test)]
impl SizeAndDateRollingWriter {
/// Manually trigger log rotation if conditions are met.
///
/// This will rotate the current log file if the time period has changed.
fn roll(&self) -> io::Result<()> {
let mut state = self.state.lock().unwrap();
Self::rotate_internal(&self.config, &mut state, true)
}
/// Manually trigger cleanup of old log files.
///
/// This removes all log files older than the configured max age, keeping
/// only files that match the configured prefix and suffix.
fn trim(&self) -> io::Result<()> {
let state = self.state.lock().unwrap();
if let Some(ref state) = *state {
Self::trim_old_logs_internal(&self.config, state)
} else {
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use std::io::Write;
use tempfile::tempdir;
use tracing_subscriber::fmt::MakeWriter;
use super::*;
#[test]
fn test_rotation_file_naming() {
let temp_dir = tempdir().unwrap();
let log_path = temp_dir.path();
let _writer = SizeAndDateRollingWriter::new(
log_path,
"app".to_owned(),
".log".to_owned(),
Rotation::HOURLY,
10 * 1024 * 1024, // 10MB total size limit
7 * 24 * 60 * 60, // 1 week in seconds
)
.unwrap();
// Check file naming pattern
let log_files: Vec<_> = std::fs::read_dir(log_path)
.unwrap()
.filter_map(|entry| {
let entry = entry.ok()?;
let path = entry.path();
if path.is_file() {
let filename = path.file_name()?.to_str()?.to_owned();
if filename.starts_with("app") && filename.ends_with(".log") {
Some(filename)
} else {
None
}
} else {
None
}
})
.collect();
// Should have one file with proper naming
assert_eq!(log_files.len(), 1, "Expected exactly one log file");
// Check that files follow the expected naming pattern
for filename in &log_files {
assert!(
filename.starts_with("app."),
"Filename should start with prefix: {}",
filename
);
assert!(filename.ends_with(".log"), "Filename should end with suffix: {}", filename);
}
}
#[test]
fn test_prefix_suffix_filtering() {
let temp_dir = tempdir().unwrap();
let log_path = temp_dir.path();
// Create some unrelated files
std::fs::write(log_path.join("other.txt"), "unrelated").unwrap();
std::fs::write(log_path.join("app.txt"), "wrong suffix").unwrap();
std::fs::write(log_path.join("test.log"), "wrong prefix").unwrap();
let writer = SizeAndDateRollingWriter::new(
log_path,
"app".to_owned(),
".log".to_owned(),
Rotation::HOURLY,
10 * 1024 * 1024, // 10MB total size limit
7 * 24 * 60 * 60, // 1 week in seconds
)
.unwrap();
let mut handle = writer.make_writer();
handle.write_all(b"test message\n").unwrap();
handle.flush().unwrap();
// Trigger manual trim
writer.trim().unwrap();
// Check that unrelated files still exist
assert!(log_path.join("other.txt").exists(), "Unrelated file should not be removed");
assert!(log_path.join("app.txt").exists(), "File with wrong suffix should not be removed");
assert!(log_path.join("test.log").exists(), "File with wrong prefix should not be removed");
// App log file should still exist
let app_logs: Vec<_> = std::fs::read_dir(log_path)
.unwrap()
.filter_map(|entry| {
let entry = entry.ok()?;
let filename = entry.file_name().to_str()?.to_owned();
if filename.starts_with("app.") && filename.ends_with(".log") {
Some(filename)
} else {
None
}
})
.collect();
assert_eq!(app_logs.len(), 1, "Expected one app log file");
}
#[test]
fn test_manual_roll_and_trim() {
let temp_dir = tempdir().unwrap();
let log_path = temp_dir.path();
let writer = SizeAndDateRollingWriter::new(
log_path,
"manual".to_owned(),
".log".to_owned(),
Rotation::DAILY,
10 * 1024 * 1024, // 10MB total size limit
7 * 24 * 60 * 60, // 1 week in seconds
)
.unwrap();
// Manual roll should work
assert!(writer.roll().is_ok(), "Manual roll should succeed");
// Manual trim should work
assert!(writer.trim().is_ok(), "Manual trim should succeed");
// Should still have log file
let log_files: Vec<_> = std::fs::read_dir(log_path)
.unwrap()
.filter_map(|entry| {
let entry = entry.ok()?;
let path = entry.path();
if path.is_file() && path.file_name()?.to_str()?.starts_with("manual") {
Some(path)
} else {
None
}
})
.collect();
assert!(!log_files.is_empty(), "Should have at least one log file");
}
#[test]
fn test_total_size_limit_removes_oldest_files() {
// This test verifies that when the total size of all log files exceeds
// max_total_size_bytes, the oldest files are removed
let temp_dir = tempdir().unwrap();
let log_path = temp_dir.path();
// Manually create several log files with different timestamps (alphabetically
// sorted = oldest first) Total of 240 bytes
std::fs::write(log_path.join("total.2024-01-01-10-00.log"), "x".repeat(80)).unwrap();
std::thread::sleep(std::time::Duration::from_millis(10));
std::fs::write(log_path.join("total.2024-01-01-10-01.log"), "y".repeat(80)).unwrap();
std::thread::sleep(std::time::Duration::from_millis(10));
std::fs::write(log_path.join("total.2024-01-01-10-02.log"), "z".repeat(80)).unwrap();
let count_files = || {
std::fs::read_dir(log_path)
.unwrap()
.filter_map(|entry| {
let entry = entry.ok()?;
let path = entry.path();
if path.is_file() && path.file_name()?.to_str()?.starts_with("total") {
Some(path)
} else {
None
}
})
.count()
};
assert_eq!(count_files(), 3, "Should have 3 log files");
// Now create a new writer with 200 byte total limit
// Current total is 240 bytes. The writer will:
// 1. Create a new file with current timestamp
// 2. See total exceeds 200 bytes
// 3. Remove oldest file(s) until under limit
let _writer = SizeAndDateRollingWriter::new(
log_path,
"total".to_owned(),
".log".to_owned(),
Rotation::MINUTELY,
200, // 200 byte total size limit
7 * 24 * 60 * 60, // 1 week in seconds
)
.unwrap();
// After writer creation, we should still have 3 files:
// - Oldest file (10-00) was removed
// - Two middle files (10-01, 10-02) remain
// - New current file was created
let remaining_files = count_files();
assert_eq!(
remaining_files, 3,
"Should have 3 files: 2 old files + 1 new current file, but have {}",
remaining_files
);
// Calculate total size of remaining files
let total_size: u64 = std::fs::read_dir(log_path)
.unwrap()
.filter_map(|entry| {
let entry = entry.ok()?;
let path = entry.path();
if path.is_file() && path.file_name()?.to_str()?.starts_with("total") {
Some(std::fs::metadata(&path).ok()?.len())
} else {
None
}
})
.sum();
// Total should be 160 bytes (80 + 80 + 0 for new file)
assert!(total_size <= 200, "Total size should be under 200 bytes, but is {}", total_size);
// Verify the oldest file was removed by checking filenames
let filenames: Vec<String> = std::fs::read_dir(log_path)
.unwrap()
.filter_map(|entry| {
let entry = entry.ok()?;
let path = entry.path();
if path.is_file() { path.file_name()?.to_str().map(|s| s.to_owned()) } else { None }
})
.collect();
assert!(
!filenames.contains(&"total.2024-01-01-10-00.log".to_owned()),
"Oldest file should have been removed"
);
assert!(
filenames.iter().any(|f| f.starts_with("total.2024-01-01-10-01")),
"Second file should still exist"
);
assert!(
filenames.iter().any(|f| f.starts_with("total.2024-01-01-10-02")),
"Third file should still exist"
);
}
#[test]
fn test_time_based_rotation_logic() {
// Test that the rotation logic correctly identifies when rotation is needed
let temp_dir = tempdir().unwrap();
let log_path = temp_dir.path();
let writer = SizeAndDateRollingWriter::new(
log_path,
"time".to_owned(),
".log".to_owned(),
Rotation::DAILY,
10 * 1024 * 1024, // 10MB total size limit
7 * 24 * 60 * 60, // 1 week in seconds
)
.unwrap();
// Write some data
let mut handle = writer.make_writer();
handle.write_all(b"initial log entry\n").unwrap();
handle.flush().unwrap();
// Verify initial state - should have created one file
let initial_files: Vec<_> = std::fs::read_dir(log_path)
.unwrap()
.filter_map(|entry| {
let entry = entry.ok()?;
let path = entry.path();
if path.is_file() && path.file_name()?.to_str()?.starts_with("time") {
Some(path.file_name()?.to_str()?.to_owned())
} else {
None
}
})
.collect();
assert_eq!(initial_files.len(), 1, "Should have one log file initially");
// Verify the file contains our data
let file_content = std::fs::read_to_string(log_path.join(&initial_files[0])).unwrap();
assert!(file_content.contains("initial log entry"));
}
#[test]
fn test_old_files_cleanup() {
// Test that files older than one week are removed
let temp_dir = tempdir().unwrap();
let log_path = temp_dir.path();
// Create files with timestamps more than a week old
// We can't easily manipulate file mtimes without external crates,
// but we can verify the cleanup logic doesn't fail
std::fs::write(log_path.join("old.2020-01-01-10-00.log"), "old data").unwrap();
// Create a writer which will trigger cleanup
let writer = SizeAndDateRollingWriter::new(
log_path,
"old".to_owned(),
".log".to_owned(),
Rotation::HOURLY,
10 * 1024 * 1024, // 10MB total size limit
7 * 24 * 60 * 60, // 1 week in seconds
)
.unwrap();
// The old file should still exist because we can't manipulate mtime easily
// But we verify that cleanup doesn't crash
writer.trim().unwrap();
// Verify we can still write
let mut handle = writer.make_writer();
handle.write_all(b"new data").unwrap();
handle.flush().unwrap();
}
#[test]
fn test_write_operations() {
// Test that writing works correctly across multiple writes
let temp_dir = tempdir().unwrap();
let log_path = temp_dir.path();
let writer = SizeAndDateRollingWriter::new(
log_path,
"write".to_owned(),
".log".to_owned(),
Rotation::HOURLY,
10 * 1024 * 1024, // 10MB total size limit
7 * 24 * 60 * 60, // 1 week in seconds
)
.unwrap();
// Write multiple times
for i in 0..5 {
let mut handle = writer.make_writer();
let message = format!("Log entry {}\n", i);
handle.write_all(message.as_bytes()).unwrap();
handle.flush().unwrap();
}
// Verify all writes went to the same file (same hour)
let log_files: Vec<_> = std::fs::read_dir(log_path)
.unwrap()
.filter_map(|entry| {
let entry = entry.ok()?;
let path = entry.path();
if path.is_file() && path.file_name()?.to_str()?.starts_with("write") {
Some(path)
} else {
None
}
})
.collect();
assert_eq!(log_files.len(), 1, "Should have one log file for same time period");
// Verify content
let content = std::fs::read_to_string(&log_files[0]).unwrap();
for i in 0..5 {
assert!(content.contains(&format!("Log entry {}", i)));
}
}
#[test]
fn test_reopening_existing_file() {
// Test that reopening appends to existing file within same time period
let temp_dir = tempdir().unwrap();
let log_path = temp_dir.path();
// Create first writer and write
let writer1 = SizeAndDateRollingWriter::new(
log_path,
"reopen".to_owned(),
".log".to_owned(),
Rotation::DAILY,
10 * 1024 * 1024, // 10MB total size limit
7 * 24 * 60 * 60, // 1 week in seconds
)
.unwrap();
let mut handle1 = writer1.make_writer();
handle1.write_all(b"first write\n").unwrap();
handle1.flush().unwrap();
drop(writer1);
// Create second writer (simulating restart within same day)
let writer2 = SizeAndDateRollingWriter::new(
log_path,
"reopen".to_owned(),
".log".to_owned(),
Rotation::DAILY,
10 * 1024 * 1024, // 10MB total size limit
7 * 24 * 60 * 60, // 1 week in seconds
)
.unwrap();
let mut handle2 = writer2.make_writer();
handle2.write_all(b"second write\n").unwrap();
handle2.flush().unwrap();
// Should still have only one file
let log_files: Vec<_> = std::fs::read_dir(log_path)
.unwrap()
.filter_map(|entry| {
let entry = entry.ok()?;
let path = entry.path();
if path.is_file() && path.file_name()?.to_str()?.starts_with("reopen") {
Some(path)
} else {
None
}
})
.collect();
assert_eq!(log_files.len(), 1, "Should have one log file");
// Verify both writes are in the file
let content = std::fs::read_to_string(&log_files[0]).unwrap();
assert!(content.contains("first write"));
assert!(content.contains("second write"));
}
#[test]
fn test_total_size_limit_with_multiple_old_files() {
// Test that multiple old files are removed until under limit
let temp_dir = tempdir().unwrap();
let log_path = temp_dir.path();
// Create 5 files, each 50 bytes (total 250 bytes)
for i in 0..5 {
let filename = format!("multi.2024-01-01-10-0{}.log", i);
std::fs::write(log_path.join(filename), "x".repeat(50)).unwrap();
}
let count_files = || {
std::fs::read_dir(log_path)
.unwrap()
.filter(|entry| {
if let Ok(entry) = entry
&& let Some(name) = entry.file_name().to_str()
{
return name.starts_with("multi");
}
false
})
.count()
};
assert_eq!(count_files(), 5, "Should start with 5 files");
// Create writer with 100 byte limit (should keep only 2 old files + new
// current)
let _writer = SizeAndDateRollingWriter::new(
log_path,
"multi".to_owned(),
".log".to_owned(),
Rotation::MINUTELY,
100, // 100 byte total size limit
7 * 24 * 60 * 60, // 1 week in seconds
)
.unwrap();
// Should have removed oldest files
let remaining = count_files();
assert!(remaining <= 3, "Should have removed multiple old files to stay under limit");
// Verify total size is under limit
let total_size: u64 = std::fs::read_dir(log_path)
.unwrap()
.filter_map(|entry| {
let entry = entry.ok()?;
let path = entry.path();
if path.is_file() && path.file_name()?.to_str()?.starts_with("multi") {
Some(std::fs::metadata(&path).ok()?.len())
} else {
None
}
})
.sum();
assert!(total_size <= 100, "Total size should be under 100 bytes, but is {}", total_size);
}
}
@@ -1,16 +1,31 @@
// 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 that specific language governing permissions and
// limitations under the License.
#[cfg(feature = "sentry")]
use std::borrow::ToOwned;
use std::{
collections::BTreeMap,
sync::{Arc, Mutex},
sync::{Arc, Mutex, OnceLock},
};
use once_cell::sync::OnceCell;
use tracing::{callsite::DefaultCallsite, field::FieldSet, Callsite};
use tracing::{Callsite, callsite::DefaultCallsite, debug, error, field::FieldSet};
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.
@@ -52,7 +67,7 @@ fn get_or_init_metadata(
meta_kind: MetadataKind,
) -> &'static DefaultCallsite {
mutex.lock().unwrap().entry(id).or_insert_with_key(|id| {
let callsite = Box::leak(Box::new(LateInitCallsite(OnceCell::new())));
let callsite = Box::leak(Box::new(LateInitCallsite(OnceLock::new())));
let metadata = Box::leak(Box::new(tracing::Metadata::new(
Box::leak(
id.name
@@ -71,7 +86,7 @@ fn get_or_init_metadata(
FieldSet::new(field_names, identify_callsite!(callsite)),
meta_kind,
)));
callsite.0.try_insert(DefaultCallsite::new(metadata)).expect("callsite was not set before")
callsite.0.get_or_init(|| DefaultCallsite::new(metadata))
})
}
@@ -96,6 +111,8 @@ fn span_or_event_enabled(callsite: &'static DefaultCallsite) -> bool {
#[derive(uniffi::Object)]
pub struct Span(tracing::Span);
pub(crate) const BRIDGE_SPAN_NAME: &str = "<sdk_bridge_span>";
#[matrix_sdk_ffi_macros::export]
impl Span {
/// Create a span originating at the given callsite (file, line and column).
@@ -129,18 +146,41 @@ impl Span {
level: LogLevel,
target: String,
name: String,
bridge_trace_id: Option<String>,
) -> Arc<Self> {
static CALLSITES: Mutex<BTreeMap<MetadataId, &'static DefaultCallsite>> =
Mutex::new(BTreeMap::new());
let loc = MetadataId { file, line, level, target, name: Some(name) };
let callsite = get_or_init_metadata(&CALLSITES, loc, &[], MetadataKind::SPAN);
// If sentry isn't enabled, ignore bridge_trace_id's contents
let bridge_trace_id = if cfg!(feature = "sentry") { bridge_trace_id } else { None };
let callsite = if cfg!(feature = "sentry") {
get_or_init_metadata(&CALLSITES, loc, &["sentry", "sentry.trace"], MetadataKind::SPAN)
} else {
get_or_init_metadata(&CALLSITES, loc, &[], MetadataKind::SPAN)
};
let metadata = callsite.metadata();
let span = if span_or_event_enabled(callsite) {
// This function is hidden from docs, but we have to use it (see above).
let values = metadata.fields().value_set(&[]);
tracing::Span::new(metadata, &values)
let fields = metadata.fields();
if let Some(parent_trace_id) = bridge_trace_id {
debug!("Adding fields | sentry:true, sentry.trace={parent_trace_id}");
let sentry_field = fields.field("sentry").unwrap();
let sentry_trace_field = fields.field("sentry.trace").unwrap();
#[allow(trivial_casts)] // The compiler is lying, it can't infer this cast
let values = [
(&sentry_field, Some(&true as &dyn tracing::Value)),
(&sentry_trace_field, Some(&parent_trace_id as &dyn tracing::Value)),
];
tracing::Span::new(metadata, &fields.value_set(&values))
} else {
tracing::Span::new(metadata, &fields.value_set(&[]))
}
} else {
tracing::Span::none()
};
@@ -164,6 +204,27 @@ impl Span {
fn is_none(&self) -> bool {
self.0.is_none()
}
/// Creates a [`Span`] that acts as a bridge between the client spans and
/// the SDK ones, allowing them to be joined in Sentry. This function
/// will only return a valid span if the `sentry` feature is enabled,
/// otherwise it will return a noop span.
#[uniffi::constructor]
pub fn new_bridge_span(target: String, parent_trace_id: Option<String>) -> Arc<Self> {
if cfg!(feature = "sentry") {
Self::new(
"Bridge".to_owned(),
None,
LogLevel::Info,
target,
BRIDGE_SPAN_NAME.to_owned(),
parent_trace_id,
)
} else {
error!("Sentry is not enabled!");
Arc::new(Self(tracing::Span::none()))
}
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, uniffi::Enum)]
@@ -206,7 +267,7 @@ struct MetadataId {
name: Option<String>,
}
struct LateInitCallsite(OnceCell<DefaultCallsite>);
struct LateInitCallsite(OnceLock<DefaultCallsite>);
impl Callsite for LateInitCallsite {
fn set_interest(&self, interest: tracing_core::Interest) {
+723
View File
@@ -0,0 +1,723 @@
// 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 that specific language governing permissions and
// limitations under the License.
use std::sync::Arc;
use matrix_sdk::authentication::oauth::{
OAuth,
qrcode::{
self, CheckCodeSender as SdkCheckCodeSender, CheckCodeSenderError,
DeviceCodeErrorResponseType, GeneratedQrProgress, LoginFailureReason, QrProgress,
},
};
use matrix_sdk_base::crypto::types::qr_login::{self, QrCodeIntent};
use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm, stream::StreamExt};
use crate::{
authentication::OidcConfiguration, runtime::get_runtime_handle, task_handle::TaskHandle,
};
/// Handler for logging in with a QR code.
#[derive(uniffi::Object)]
pub struct LoginWithQrCodeHandler {
oauth: OAuth,
oidc_configuration: OidcConfiguration,
}
impl LoginWithQrCodeHandler {
pub(crate) fn new(oauth: OAuth, oidc_configuration: OidcConfiguration) -> Self {
Self { oauth, oidc_configuration }
}
}
#[matrix_sdk_ffi_macros::export]
impl LoginWithQrCodeHandler {
/// This method allows you to log in with a scanned QR code.
///
/// The existing device needs to display the QR code which this device can
/// scan, call this method and handle its progress updates to log in.
///
/// For the login to succeed, the [`Client`] associated with the
/// [`LoginWithQrCodeHandler`] must have been built with
/// [`QrCodeData::server_name`] as the server name.
///
/// This method uses the login mechanism described in [MSC4108]. As such,
/// it requires OAuth 2.0 support.
///
/// For the reverse flow where this device generates the QR code for the
/// existing device to scan, use [`LoginWithQrCodeHandler::generate`].
///
/// # Arguments
///
/// * `qr_code_data` - The [`QrCodeData`] scanned from the QR code.
/// * `progress_listener` - A progress listener that must also be used to
/// transfer the [`CheckCode`] to the existing device.
///
/// [MSC4108]: https://github.com/matrix-org/matrix-spec-proposals/pull/4108
pub async fn scan(
self: Arc<Self>,
qr_code_data: &QrCodeData,
progress_listener: Box<dyn QrLoginProgressListener>,
) -> Result<(), HumanQrLoginError> {
let registration_data = self
.oidc_configuration
.registration_data()
.map_err(|_| HumanQrLoginError::OidcMetadataInvalid)?;
let login =
self.oauth.login_with_qr_code(Some(&registration_data)).scan(&qr_code_data.inner);
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(get_runtime_handle().spawn(async move {
while let Some(state) = progress.next().await {
progress_listener.on_update(state.into());
}
}));
login.await?;
Ok(())
}
/// This method allows you to log in by generating a QR code.
///
/// This device needs to call this method and handle its progress updates to
/// generate a QR code which the existing device can scan and grant the
/// log in.
///
/// This method uses the login mechanism described in [MSC4108]. As such,
/// it requires OAuth 2.0 support.
///
/// For the reverse flow where the existing device generates the QR code
/// for this device to scan, use [`LoginWithQrCodeHandler::scan`].
///
/// # Arguments
///
/// * `progress_listener` - A progress listener that must also be used to
/// obtain the [`QrCodeData`] and collect the [`CheckCode`] from the user.
///
/// [MSC4108]: https://github.com/matrix-org/matrix-spec-proposals/pull/4108
pub async fn generate(
self: Arc<Self>,
progress_listener: Box<dyn GeneratedQrLoginProgressListener>,
) -> Result<(), HumanQrLoginError> {
let registration_data = self
.oidc_configuration
.registration_data()
.map_err(|_| HumanQrLoginError::OidcMetadataInvalid)?;
let login = self.oauth.login_with_qr_code(Some(&registration_data)).generate();
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(get_runtime_handle().spawn(async move {
while let Some(state) = progress.next().await {
progress_listener.on_update(state.into());
}
}));
login.await?;
Ok(())
}
}
/// Handler for granting login in with a QR code.
#[derive(uniffi::Object)]
pub struct GrantLoginWithQrCodeHandler {
oauth: OAuth,
}
impl GrantLoginWithQrCodeHandler {
pub(crate) fn new(oauth: OAuth) -> Self {
Self { oauth }
}
}
#[matrix_sdk_ffi_macros::export]
impl GrantLoginWithQrCodeHandler {
/// This method allows you to grant login with a scanned QR code.
///
/// The new device needs to display the QR code which this device can
/// scan, call this method and handle its progress updates to grant the
/// login.
///
/// This method uses the login mechanism described in [MSC4108]. As such,
/// it requires OAuth 2.0 support.
///
/// For the reverse flow where this device generates the QR code for the
/// existing device to scan, use [`GrantLoginWithQrCodeHandler::generate`].
///
/// # Arguments
///
/// * `qr_code_data` - The [`QrCodeData`] scanned from the QR code.
/// * `progress_listener` - A progress listener that must also be used to
/// transfer the [`CheckCode`] to the new device.
///
/// [MSC4108]: https://github.com/matrix-org/matrix-spec-proposals/pull/4108
pub async fn scan(
self: Arc<Self>,
qr_code_data: &QrCodeData,
progress_listener: Box<dyn GrantQrLoginProgressListener>,
) -> Result<(), HumanQrGrantLoginError> {
let grant = self.oauth.grant_login_with_qr_code().scan(&qr_code_data.inner);
let mut progress = grant.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(get_runtime_handle().spawn(async move {
while let Some(state) = progress.next().await {
progress_listener.on_update(state.into());
}
}));
grant.await?;
Ok(())
}
/// This method allows you to grant login by generating a QR code.
///
/// This device needs to call this method and handle its progress updates to
/// generate a QR code which the new device can scan to log in.
///
/// This method uses the login mechanism described in [MSC4108]. As such,
/// it requires OAuth 2.0 support.
///
/// For the reverse flow where the existing device generates the QR code
/// for this device to scan, use [`GrantLoginWithQrCodeHandler::scan`].
///
/// # Arguments
///
/// * `progress_listener` - A progress listener that must also be used to
/// obtain the [`QrCodeData`] and collect the [`CheckCode`] from the user.
///
/// [MSC4108]: https://github.com/matrix-org/matrix-spec-proposals/pull/4108
pub async fn generate(
self: Arc<Self>,
progress_listener: Box<dyn GrantGeneratedQrLoginProgressListener>,
) -> Result<(), HumanQrGrantLoginError> {
let grant = self.oauth.grant_login_with_qr_code().generate();
let mut progress = grant.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(get_runtime_handle().spawn(async move {
while let Some(state) = progress.next().await {
progress_listener.on_update(state.into());
}
}));
grant.await?;
Ok(())
}
}
/// 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())
}
/// Serialize the [`QrCodeData`] into a byte vector for encoding as a QR
/// code.
pub fn to_bytes(&self) -> Vec<u8> {
self.inner.to_bytes()
}
/// The server name contained within the scanned QR code data.
///
/// Note: This value is only present when scanning a QR code that 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.intent_data() {
qr_login::QrCodeIntentData::Msc4108 { data, .. } => match data {
qrcode::Msc4108IntentData::Login => None,
qrcode::Msc4108IntentData::Reciprocate { server_name } => {
Some(server_name.to_owned())
}
},
qr_login::QrCodeIntentData::Msc4388 { .. } => None,
}
}
/// The base URL of the homeserver contained within the scanned QR code
/// data.
///
/// Note: This value is only present when scanning a QR code conforming to
/// MSC4388.
pub fn base_url(&self) -> Option<String> {
match self.inner.intent_data() {
qrcode::QrCodeIntentData::Msc4108 { .. } => None,
qrcode::QrCodeIntentData::Msc4388 { base_url, .. } => Some(base_url.to_string()),
}
}
/// Get the [`QrCodeIntent`] of this [`QrCodeData`] object.
///
/// This tells us if the creator of the QR code wants to log in or if they
/// want to log another device in.
pub fn intent(&self) -> QrCodeIntent {
self.inner.intent()
}
}
/// 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: qrcode::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,
#[error("The check code was already sent.")]
CheckCodeAlreadySent,
#[error("The check code could not be sent.")]
CheckCodeCannotBeSent,
#[error("The rendezvous session was not found and might have expired")]
NotFound,
#[error("The QR code specifies an unsupported protocol version")]
UnsupportedQrCodeType,
}
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::UnsupportedQrCodeType => {
HumanQrLoginError::UnsupportedQrCodeType
}
SecureChannelError::SecureChannelMessage { .. }
| SecureChannelError::Ecies(_)
| SecureChannelError::InvalidCheckCode
| SecureChannelError::CannotReceiveCheckCode => {
HumanQrLoginError::ConnectionInsecure
}
SecureChannelError::InvalidIntent => HumanQrLoginError::OtherDeviceNotSignedIn,
},
QRCodeLoginError::UnexpectedMessage { .. }
| QRCodeLoginError::CrossProcessRefreshLock(_)
| QRCodeLoginError::DeviceKeyUpload(_)
| QRCodeLoginError::SessionTokens(_)
| QRCodeLoginError::UserIdDiscovery(_)
| QRCodeLoginError::SecretImport(_)
| QRCodeLoginError::ServerReset(_) => HumanQrLoginError::Unknown,
QRCodeLoginError::NotFound => HumanQrLoginError::NotFound,
}
}
}
impl From<CheckCodeSenderError> for HumanQrLoginError {
fn from(value: CheckCodeSenderError) -> Self {
match value {
CheckCodeSenderError::AlreadySent => HumanQrLoginError::CheckCodeAlreadySent,
CheckCodeSenderError::CannotSend => HumanQrLoginError::CheckCodeCannotBeSent,
}
}
}
#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi(flat_error)]
pub enum HumanQrGrantLoginError {
/// The requested device ID is already in use.
#[error("The requested device ID is already in use.")]
DeviceIDAlreadyInUse,
/// The check code was incorrect.
#[error("The check code was incorrect.")]
InvalidCheckCode,
/// The other client proposed an unsupported protocol.
#[error("Unsupported protocol: {0}")]
UnsupportedProtocol(String),
/// Secrets backup not set up properly.
#[error("Secrets backup not set up: {0}")]
MissingSecretsBackup(String),
/// The rendezvous session was not found and might have expired.
#[error("The rendezvous session was not found and might have expired")]
NotFound,
/// An unknown error has happened.
#[error("An unknown error has happened.")]
Unknown(String),
/// The requested device was not returned by the homeserver.
#[error("The requested device was not returned by the homeserver.")]
DeviceNotFound,
/// The other device is already signed in and so does not need to sign in.
#[error("The other device is already signed and so does not need to sign in.")]
OtherDeviceAlreadySignedIn,
/// The sign in was cancelled.
#[error("The sign in was cancelled.")]
Cancelled,
/// The sign in was not completed in the required time.
#[error("The sign in was not completed in the required time.")]
Expired,
/// A secure connection could not have been established between the two
/// devices.
#[error("A secure connection could not have been established between the two devices.")]
ConnectionInsecure,
/// The QR code specifies an unsupported protocol version.
#[error("The QR code specifies an unsupported protocol version")]
UnsupportedQrCodeType,
}
impl From<qrcode::QRCodeGrantLoginError> for HumanQrGrantLoginError {
fn from(value: qrcode::QRCodeGrantLoginError) -> Self {
use qrcode::{QRCodeGrantLoginError, SecureChannelError};
match value {
QRCodeGrantLoginError::DeviceIDAlreadyInUse => Self::DeviceIDAlreadyInUse,
QRCodeGrantLoginError::DeviceNotFound => Self::DeviceNotFound,
QRCodeGrantLoginError::InvalidCheckCode => Self::InvalidCheckCode,
QRCodeGrantLoginError::UnsupportedProtocol(protocol) => {
Self::UnsupportedProtocol(protocol.to_string())
}
QRCodeGrantLoginError::MissingSecretsBackup(error) => {
Self::MissingSecretsBackup(error.map_or("other".to_owned(), |e| e.to_string()))
}
QRCodeGrantLoginError::NotFound => Self::NotFound,
QRCodeGrantLoginError::SecureChannel(e) => match e {
SecureChannelError::Utf8(_)
| SecureChannelError::MessageDecode(_)
| SecureChannelError::Json(_)
| SecureChannelError::RendezvousChannel(_) => Self::Unknown(e.to_string()),
SecureChannelError::UnsupportedQrCodeType => Self::UnsupportedQrCodeType,
SecureChannelError::SecureChannelMessage { .. }
| SecureChannelError::Ecies(_)
| SecureChannelError::InvalidCheckCode
| SecureChannelError::CannotReceiveCheckCode => Self::ConnectionInsecure,
SecureChannelError::InvalidIntent => Self::OtherDeviceAlreadySignedIn,
},
QRCodeGrantLoginError::UnexpectedMessage { .. } => Self::Unknown(value.to_string()),
QRCodeGrantLoginError::Unknown(string) => Self::Unknown(string),
QRCodeGrantLoginError::LoginFailure { reason, .. } => match reason {
LoginFailureReason::UnsupportedProtocol => Self::UnsupportedProtocol(
"Other device does not support any of our protocols".to_owned(),
),
LoginFailureReason::AuthorizationExpired => Self::Expired,
LoginFailureReason::UserCancelled => Self::Cancelled,
_ => Self::Unknown(reason.to_string()),
},
}
}
}
/// Enum describing the progress of logging in by scanning a QR code that was
/// generated on an existing device.
#[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 },
/// We are syncing secrets.
SyncingSecrets,
/// 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<QrProgress>> for QrLoginProgress {
fn from(value: qrcode::LoginProgress<QrProgress>) -> Self {
use qrcode::LoginProgress;
match value {
LoginProgress::Starting => Self::Starting,
LoginProgress::EstablishingSecureChannel(QrProgress { 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::SyncingSecrets => Self::SyncingSecrets,
LoginProgress::Done => Self::Done,
}
}
}
/// Enum describing the progress of logging in by generating a QR code and
/// having an existing device scan it.
#[derive(Debug, Default, Clone, uniffi::Enum)]
pub enum GeneratedQrLoginProgress {
/// The login process is starting.
#[default]
Starting,
/// We have established the secure channel and now need to display the
/// QR code so that the existing device can scan it.
QrReady { qr_code: Arc<QrCodeData> },
/// The existing device has scanned the QR code and is displaying the
/// checkcode. We now need to ask the user to enter the checkcode so that
/// we can verify that the channel is indeed secure.
QrScanned { check_code_sender: Arc<CheckCodeSender> },
/// We are waiting for the login and for the OAuth 2.0 authorization server
/// to give us an access token.
WaitingForToken { user_code: String },
/// We are syncing secrets.
SyncingSecrets,
/// The login has successfully finished.
Done,
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait GeneratedQrLoginProgressListener: SyncOutsideWasm + SendOutsideWasm {
fn on_update(&self, state: GeneratedQrLoginProgress);
}
impl From<qrcode::LoginProgress<GeneratedQrProgress>> for GeneratedQrLoginProgress {
fn from(value: qrcode::LoginProgress<GeneratedQrProgress>) -> Self {
use qrcode::LoginProgress;
match value {
LoginProgress::Starting => Self::Starting,
LoginProgress::EstablishingSecureChannel(GeneratedQrProgress::QrReady(inner)) => {
Self::QrReady { qr_code: Arc::new(QrCodeData { inner }) }
}
LoginProgress::EstablishingSecureChannel(GeneratedQrProgress::QrScanned(inner)) => {
Self::QrScanned { check_code_sender: Arc::new(CheckCodeSender { inner }) }
}
LoginProgress::WaitingForToken { user_code } => Self::WaitingForToken { user_code },
LoginProgress::SyncingSecrets => Self::SyncingSecrets,
LoginProgress::Done => Self::Done,
}
}
}
/// Enum describing the progress of granting login in by scanning a QR code that
/// was generated on a new device.
#[derive(Debug, Default, Clone, uniffi::Enum)]
pub enum GrantQrLoginProgress {
/// 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,
},
/// The secure channel has been confirmed using the [`CheckCode`] and this
/// device is waiting for the authorization to complete.
WaitingForAuth {
/// A URI to open in a (secure) system browser to verify the new login.
verification_uri: String,
},
/// We are syncing secrets.
SyncingSecrets,
/// The login has successfully finished.
Done,
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait GrantQrLoginProgressListener: SyncOutsideWasm + SendOutsideWasm {
fn on_update(&self, state: GrantQrLoginProgress);
}
impl From<qrcode::GrantLoginProgress<QrProgress>> for GrantQrLoginProgress {
fn from(value: qrcode::GrantLoginProgress<QrProgress>) -> Self {
use qrcode::GrantLoginProgress;
match value {
GrantLoginProgress::Starting => Self::Starting,
GrantLoginProgress::EstablishingSecureChannel(QrProgress { check_code }) => {
let check_code = check_code.to_digit();
Self::EstablishingSecureChannel {
check_code,
check_code_string: format!("{check_code:02}"),
}
}
GrantLoginProgress::WaitingForAuth { verification_uri } => {
Self::WaitingForAuth { verification_uri: verification_uri.into() }
}
GrantLoginProgress::SyncingSecrets => Self::SyncingSecrets,
GrantLoginProgress::Done => Self::Done,
}
}
}
/// Enum describing the progress of granting login by generating a QR code to
/// be scanned on the new device.
#[derive(Debug, Default, Clone, uniffi::Enum)]
pub enum GrantGeneratedQrLoginProgress {
/// The login process is starting.
#[default]
Starting,
/// We have established the secure channel and now need to display the
/// QR code so that the existing device can scan it.
QrReady { qr_code: Arc<QrCodeData> },
/// The existing device has scanned the QR code and is displaying the
/// checkcode. We now need to ask the user to enter the checkcode so that
/// we can verify that the channel is indeed secure.
QrScanned { check_code_sender: Arc<CheckCodeSender> },
/// The secure channel has been confirmed using the [`CheckCode`] and this
/// device is waiting for the authorization to complete.
WaitingForAuth {
/// A URI to open in a (secure) system browser to verify the new login.
verification_uri: String,
},
/// We are syncing secrets.
SyncingSecrets,
/// The login has successfully finished.
Done,
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait GrantGeneratedQrLoginProgressListener: SyncOutsideWasm + SendOutsideWasm {
fn on_update(&self, state: GrantGeneratedQrLoginProgress);
}
impl From<qrcode::GrantLoginProgress<GeneratedQrProgress>> for GrantGeneratedQrLoginProgress {
fn from(value: qrcode::GrantLoginProgress<GeneratedQrProgress>) -> Self {
use qrcode::GrantLoginProgress;
match value {
GrantLoginProgress::Starting => Self::Starting,
GrantLoginProgress::EstablishingSecureChannel(GeneratedQrProgress::QrReady(inner)) => {
Self::QrReady { qr_code: Arc::new(QrCodeData { inner }) }
}
GrantLoginProgress::EstablishingSecureChannel(GeneratedQrProgress::QrScanned(
inner,
)) => Self::QrScanned { check_code_sender: Arc::new(CheckCodeSender { inner }) },
GrantLoginProgress::WaitingForAuth { verification_uri } => {
Self::WaitingForAuth { verification_uri: verification_uri.into() }
}
GrantLoginProgress::SyncingSecrets => Self::SyncingSecrets,
GrantLoginProgress::Done => Self::Done,
}
}
}
#[derive(Debug, uniffi::Object)]
/// Used to pass back the [`CheckCode`] entered by the user to verify that the
/// secure channel is indeed secure.
pub struct CheckCodeSender {
inner: SdkCheckCodeSender,
}
#[matrix_sdk_ffi_macros::export]
impl CheckCodeSender {
/// Send the [`CheckCode`].
///
/// Calling this method more than once will result in an error.
///
/// # Arguments
///
/// * `check_code` - The check code in digits representation.
pub async fn send(&self, code: u8) -> Result<(), HumanQrLoginError> {
self.inner.send(code).await.map_err(HumanQrLoginError::from)
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,254 @@
// 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 that specific language governing permissions and
// limitations under the License.
use std::collections::HashMap;
use anyhow::Result;
use ruma::{
OwnedUserId, UserId,
events::{TimelineEventType, room::power_levels::RoomPowerLevels as RumaPowerLevels},
};
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()
}
fn events(&self) -> HashMap<crate::event::TimelineEventType, i64> {
self.inner.events.iter().map(|(key, value)| (key.clone().into(), (*value).into())).collect()
}
/// 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,
/// The level required to change the space's children.
pub space_child: 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),
space_child: state_event_level_for(&value, &TimelineEventType::SpaceChild),
}
}
}
@@ -1,21 +1,61 @@
use std::collections::HashMap;
// 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 that specific language governing permissions and
// limitations under the License.
use matrix_sdk::{EncryptionState, RoomState};
use std::sync::Arc;
use matrix_sdk::{CallIntentConsensus, EncryptionState, RoomState};
use tracing::warn;
use crate::{
client::JoinRule,
error::ClientError,
notification_settings::RoomNotificationMode,
room::{Membership, RoomHero, RoomHistoryVisibility, SuccessorRoom},
room::{
Membership, RoomHero, RoomHistoryVisibility, SuccessorRoom, power_levels::RoomPowerLevels,
},
room_member::RoomMember,
ruma::RtcCallIntent,
};
#[derive(Clone, uniffi::Enum)]
pub enum RtcCallIntentConsensus {
Full(RtcCallIntent),
Partial { intent: RtcCallIntent, agreeing_count: u64, total_count: u64 },
None,
}
impl From<CallIntentConsensus> for RtcCallIntentConsensus {
fn from(value: CallIntentConsensus) -> Self {
match value {
CallIntentConsensus::Full(intent) => RtcCallIntentConsensus::Full(intent.into()),
CallIntentConsensus::Partial { intent, agreeing_count, total_count } => {
RtcCallIntentConsensus::Partial {
intent: intent.into(),
agreeing_count,
total_count,
}
}
CallIntentConsensus::None => RtcCallIntentConsensus::None,
}
}
}
#[derive(uniffi::Record)]
pub struct RoomInfo {
id: String,
encryption_state: EncryptionState,
creator: Option<String>,
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>,
@@ -24,11 +64,16 @@ 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,
/// If present, it means the room has been archived/upgraded.
successor_room: Option<SuccessorRoom>,
is_favourite: bool,
is_low_priority: bool,
canonical_alias: Option<String>,
alternative_aliases: Vec<String>,
membership: Membership,
@@ -42,12 +87,13 @@ pub struct RoomInfo {
active_members_count: u64,
invited_members_count: u64,
joined_members_count: u64,
user_power_levels: HashMap<String, i64>,
service_members: Vec<String>,
highlight_count: u64,
notification_count: u64,
cached_user_defined_notification_mode: Option<RoomNotificationMode>,
has_room_call: bool,
active_room_call_participants: Vec<String>,
active_room_call_consensus_intent: RtcCallIntentConsensus,
/// Whether this room has been explicitly marked as unread
is_marked_unread: bool,
/// "Interesting" messages received in that room, independently of the
@@ -65,29 +111,46 @@ pub struct RoomInfo {
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) -> 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(),
encryption_state: room.encryption_state(),
creator: room.creator().as_ref().map(ToString::to_string),
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(),
@@ -97,6 +160,7 @@ impl RoomInfo {
is_space: room.is_space(),
successor_room: room.successor_room().map(Into::into),
is_favourite: room.is_favourite(),
is_low_priority: room.is_low_priority(),
canonical_alias: room.canonical_alias().map(Into::into),
alternative_aliases: room.alt_aliases().into_iter().map(Into::into).collect(),
membership: room.state().into(),
@@ -116,7 +180,12 @@ 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,
service_members: room
.service_members()
.iter()
.flatten()
.map(|m| m.to_string())
.collect(),
highlight_count: unread_notification_counts.highlight_count,
notification_count: unread_notification_counts.notification_count,
cached_user_defined_notification_mode: room
@@ -128,13 +197,21 @@ impl RoomInfo {
.iter()
.map(|u| u.to_string())
.collect(),
active_room_call_consensus_intent: room.active_room_call_consensus_intent().into(),
is_marked_unread: room.is_marked_unread(),
num_unread_messages: room.num_unread_messages(),
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(),
})
}
}
+14
View File
@@ -1,3 +1,17 @@
// 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 that specific language governing permissions and
// limitations under the License.
use matrix_sdk::RoomDisplayName;
/// Verifies the passed `String` matches the expected room alias format:
@@ -1,4 +1,3 @@
// Copyright 2024 Mauro Romito
// Copyright 2024 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -10,7 +9,7 @@
// 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
// See the License for that specific language governing permissions and
// limitations under the License.
use std::{fmt::Debug, sync::Arc};
@@ -28,15 +27,21 @@ use crate::{error::ClientError, runtime::get_runtime_handle, task_handle::TaskHa
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:?}")),
}
}
@@ -149,11 +154,6 @@ impl RoomDirectorySearch {
}
}
#[derive(uniffi::Record)]
pub struct RoomDirectorySearchEntriesResult {
pub entries_stream: Arc<TaskHandle>,
}
#[derive(uniffi::Enum)]
pub enum RoomDirectorySearchEntryUpdate {
Append { values: Vec<RoomDescription> },
+56 -15
View File
@@ -1,31 +1,46 @@
// 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 that specific language governing permissions and
// limitations under the License.
#![allow(deprecated)]
use std::{fmt::Debug, mem::MaybeUninit, ptr::addr_of_mut, sync::Arc, time::Duration};
use eyeball_im::VectorDiff;
use futures_util::{pin_mut, StreamExt};
use futures_util::{StreamExt, pin_mut};
use matrix_sdk::{
ruma::{
api::client::sync::sync_events::UnreadNotificationsCount as RumaUnreadNotificationsCount,
RoomId,
},
Room as SdkRoom,
ruma::{
RoomId,
api::client::sync::sync_events::UnreadNotificationsCount as RumaUnreadNotificationsCount,
},
};
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_deduplicate_versions,
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,
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_identifiers, 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,
},
unable_to_decrypt_hook::UtdHookManager,
};
use crate::{
TaskHandle,
room::{Membership, Room},
runtime::get_runtime_handle,
TaskHandle,
};
#[derive(Debug, thiserror::Error, uniffi::Error)]
@@ -42,7 +57,10 @@ pub enum RoomListError {
InvalidRoomId { 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 },
}
@@ -118,7 +136,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| {
@@ -126,7 +144,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(())
}
@@ -199,7 +219,7 @@ impl RoomList {
// Get a reference to `this`. It is only borrowed, it's not moved.
let this =
// SAFETY: `ptr` is correct aligned, the `this` field is correctly aligned,
// SAFETY: `ptr` is correctly aligned, the `this` field is correctly aligned,
// is dereferenceable and points to a correctly initialized value as done
// in the previous line.
unsafe { addr_of_mut!((*ptr).this).as_ref() }
@@ -225,7 +245,12 @@ impl RoomList {
listener.on_update(
diffs
.into_iter()
.map(|room| RoomListEntriesUpdate::from(utd_hook.clone(), room))
.map(|diff| {
RoomListEntriesUpdate::from(
utd_hook.clone(),
diff.map(|room| room.into_inner()),
)
})
.collect(),
);
}
@@ -449,10 +474,18 @@ impl RoomListDynamicEntriesController {
pub enum RoomListEntriesDynamicFilterKind {
All { filters: Vec<RoomListEntriesDynamicFilterKind> },
Any { filters: Vec<RoomListEntriesDynamicFilterKind> },
Identifiers { identifiers: Vec<String> },
NonSpace,
Space,
NonLeft,
// Not { filter: RoomListEntriesDynamicFilterKind } - requires recursive enum
// support in uniffi https://github.com/mozilla/uniffi-rs/issues/396
Joined,
Unread,
Favourite,
LowPriority,
NonLowPriority,
NonFavorite,
Invite,
Category { expect: RoomListFilterCategory },
None,
@@ -487,10 +520,18 @@ impl From<RoomListEntriesDynamicFilterKind> for BoxedFilterFn {
Kind::Any { filters } => Box::new(new_filter_any(
filters.into_iter().map(|filter| BoxedFilterFn::from(filter)).collect(),
)),
Kind::Identifiers { identifiers } => Box::new(new_filter_identifiers(
identifiers.into_iter().map(|id| RoomId::parse(id).unwrap()).collect(),
)),
Kind::NonSpace => Box::new(new_filter_not(Box::new(new_filter_space()))),
Kind::Space => Box::new(new_filter_space()),
Kind::NonLeft => Box::new(new_filter_non_left()),
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::NonFavorite => Box::new(new_filter_not(Box::new(new_filter_favourite()))),
Kind::Invite => Box::new(new_filter_invite()),
Kind::Category { expect } => Box::new(new_filter_category(expect.into())),
Kind::None => Box::new(new_filter_none()),
+55 -9
View File
@@ -1,5 +1,5 @@
use matrix_sdk::room::{RoomMember as SdkRoomMember, RoomMemberRole};
use ruma::UserId;
use ruma::{UserId, events::room::power_levels::UserPowerLevel};
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.
@@ -83,8 +92,7 @@ pub struct RoomMember {
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 is_ignored: bool,
pub suggested_role_for_power_level: RoomMemberRole,
pub membership_change_reason: Option<String>,
@@ -100,8 +108,7 @@ 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()?,
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()),
@@ -130,3 +137,42 @@ impl TryFrom<matrix_sdk::room::RoomMemberWithSenderInfo> for RoomMemberWithSende
})
}
}
#[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:?}")),
})?)
}
})
}
}
+48 -33
View File
@@ -1,10 +1,23 @@
// 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 that specific language governing permissions and
// limitations under the License.
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 matrix_sdk::{Client, room_preview::RoomPreview as SdkRoomPreview};
use ruma::room::{JoinRuleSummary, RoomType as RumaRoomType};
use crate::{
client::JoinRule,
client::{AllowRule, JoinRule},
error::ClientError,
room::{Membership, RoomHero},
room_member::{RoomMember, RoomMemberWithSenderInfo},
@@ -22,9 +35,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,20 +45,16 @@ 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
@@ -114,30 +123,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() },
}
}
}
@@ -152,8 +167,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 {
+80 -40
View File
@@ -21,9 +21,13 @@ use std::{
use extension_trait::extension_trait;
use matrix_sdk::attachment::{BaseAudioInfo, BaseFileInfo, BaseImageInfo, BaseVideoInfo};
use ruma::{
assign,
KeyDerivationAlgorithm as RumaKeyDerivationAlgorithm, MatrixToUri, MatrixUri as RumaMatrixUri,
OwnedRoomId, OwnedUserId, UInt, UserId, assign,
events::{
call::notify::NotifyType as RumaNotifyType,
GlobalAccountDataEvent as RumaGlobalAccountDataEvent,
GlobalAccountDataEventType as RumaGlobalAccountDataEventType,
RoomAccountDataEvent as RumaRoomAccountDataEvent,
RoomAccountDataEventType as RumaRoomAccountDataEventType,
direct::DirectEventContent,
fully_read::FullyReadEventContent,
identity_server::IdentityServerEventContent,
@@ -37,6 +41,8 @@ use ruma::{
poll::start::PollKind as RumaPollKind,
push_rules::PushRulesEventContent,
room::{
ImageInfo as RumaImageInfo, MediaSource as RumaMediaSource,
ThumbnailInfo as RumaThumbnailInfo,
message::{
AudioInfo as RumaAudioInfo,
AudioMessageEventContent as RumaAudioMessageEventContent,
@@ -54,8 +60,9 @@ use ruma::{
VideoInfo as RumaVideoInfo,
VideoMessageEventContent as RumaVideoMessageEventContent,
},
ImageInfo as RumaImageInfo, MediaSource as RumaMediaSource,
ThumbnailInfo as RumaThumbnailInfo,
},
rtc::notification::{
CallIntent as RumaCallIntent, NotificationType as RumaNotificationType,
},
secret_storage::{
default_key::SecretStorageDefaultKeyEventContent,
@@ -70,10 +77,6 @@ use ruma::{
TagEventContent, TagInfo as RumaTagInfo, TagName as RumaTagName,
UserTagName as RumaUserTagName,
},
GlobalAccountDataEvent as RumaGlobalAccountDataEvent,
GlobalAccountDataEventType as RumaGlobalAccountDataEventType,
RoomAccountDataEvent as RumaRoomAccountDataEvent,
RoomAccountDataEventType as RumaRoomAccountDataEventType,
},
matrix_uri::MatrixId as RumaMatrixId,
push::{
@@ -81,8 +84,6 @@ use ruma::{
Ruleset as RumaRuleset, SimplePushRule as RumaSimplePushRule,
},
serde::JsonObject,
KeyDerivationAlgorithm as RumaKeyDerivationAlgorithm, MatrixToUri, MatrixUri as RumaMatrixUri,
OwnedRoomId, OwnedUserId, UInt, UserId,
};
use tracing::info;
@@ -389,11 +390,7 @@ pub enum MessageType {
/// is its own field.
/// - if a media only has a filename, then body is the filename.
fn get_body_and_filename(filename: String, caption: Option<String>) -> (String, Option<String>) {
if let Some(caption) = caption {
(caption, Some(filename))
} else {
(filename, None)
}
if let Some(caption) = caption { (caption, Some(filename)) } else { (filename, None) }
}
impl TryFrom<MessageType> for RumaMessageType {
@@ -470,11 +467,7 @@ impl TryFrom<RumaMessageType> for MessageType {
geo_uri: c.geo_uri,
description,
zoom_level: zoom_level.and_then(|z| z.get().try_into().ok()),
asset: c.asset.and_then(|a| match a.type_ {
RumaAssetType::Self_ => Some(AssetType::Sender),
RumaAssetType::Pin => Some(AssetType::Pin),
_ => None,
}),
asset: c.asset.map(|a| a.type_).into(),
},
}
}
@@ -487,25 +480,50 @@ impl TryFrom<RumaMessageType> for MessageType {
}
#[derive(Clone, uniffi::Enum)]
pub enum NotifyType {
pub enum RtcNotificationType {
Ring,
Notify,
Notification,
}
impl From<RumaNotifyType> for NotifyType {
fn from(val: RumaNotifyType) -> Self {
impl From<RumaNotificationType> for RtcNotificationType {
fn from(val: RumaNotificationType) -> Self {
match val {
RumaNotifyType::Ring => Self::Ring,
_ => Self::Notify,
RumaNotificationType::Ring => Self::Ring,
_ => Self::Notification,
}
}
}
impl From<NotifyType> for RumaNotifyType {
fn from(value: NotifyType) -> Self {
impl From<RtcNotificationType> for RumaNotificationType {
fn from(value: RtcNotificationType) -> Self {
match value {
NotifyType::Ring => RumaNotifyType::Ring,
NotifyType::Notify => RumaNotifyType::Notify,
RtcNotificationType::Ring => RumaNotificationType::Ring,
RtcNotificationType::Notification => RumaNotificationType::Notification,
}
}
}
#[derive(Clone, uniffi::Enum)]
pub enum RtcCallIntent {
Video,
Audio,
}
impl From<RumaCallIntent> for RtcCallIntent {
fn from(val: RumaCallIntent) -> Self {
match val {
RumaCallIntent::Audio => Self::Audio,
// No support for custom intents, so we can just use video as default
_ => Self::Video,
}
}
}
impl From<RtcCallIntent> for RumaCallIntent {
fn from(value: RtcCallIntent) -> Self {
match value {
RtcCallIntent::Video => RumaCallIntent::Video,
RtcCallIntent::Audio => RumaCallIntent::Audio,
}
}
}
@@ -543,7 +561,7 @@ impl TryFrom<RumaImageMessageEventContent> for ImageMessageContent {
fn try_from(value: RumaImageMessageEventContent) -> Result<Self, Self::Error> {
Ok(Self {
filename: value.filename().to_owned(),
caption: value.caption().map(ToString::to_string),
caption: value.caption().map(str::to_owned),
formatted_caption: value.formatted_caption().map(Into::into),
source: Arc::new(value.source.try_into()?),
info: value.info.as_deref().map(TryInto::try_into).transpose()?,
@@ -582,7 +600,7 @@ impl TryFrom<RumaAudioMessageEventContent> for AudioMessageContent {
fn try_from(value: RumaAudioMessageEventContent) -> Result<Self, Self::Error> {
Ok(Self {
filename: value.filename().to_owned(),
caption: value.caption().map(ToString::to_string),
caption: value.caption().map(str::to_owned),
formatted_caption: value.formatted_caption().map(Into::into),
source: Arc::new(value.source.try_into()?),
info: value.info.as_deref().map(Into::into),
@@ -619,7 +637,7 @@ impl TryFrom<RumaVideoMessageEventContent> for VideoMessageContent {
fn try_from(value: RumaVideoMessageEventContent) -> Result<Self, Self::Error> {
Ok(Self {
filename: value.filename().to_owned(),
caption: value.caption().map(ToString::to_string),
caption: value.caption().map(str::to_owned),
formatted_caption: value.formatted_caption().map(Into::into),
source: Arc::new(value.source.try_into()?),
info: value.info.as_deref().map(TryInto::try_into).transpose()?,
@@ -654,7 +672,7 @@ impl TryFrom<RumaFileMessageEventContent> for FileMessageContent {
fn try_from(value: RumaFileMessageEventContent) -> Result<Self, Self::Error> {
Ok(Self {
filename: value.filename().to_owned(),
caption: value.caption().map(ToString::to_string),
caption: value.caption().map(str::to_owned),
formatted_caption: value.formatted_caption().map(Into::into),
source: Arc::new(value.source.try_into()?),
info: value.info.as_deref().map(TryInto::try_into).transpose()?,
@@ -736,7 +754,7 @@ impl TryFrom<&AudioInfo> for BaseAudioInfo {
let size = UInt::try_from(value.size.ok_or(MediaInfoError::MissingField)?)
.map_err(|_| MediaInfoError::InvalidField)?;
Ok(BaseAudioInfo { duration: Some(duration), size: Some(size) })
Ok(BaseAudioInfo { duration: Some(duration), size: Some(size), waveform: None })
}
}
@@ -900,13 +918,14 @@ pub struct LocationContent {
pub geo_uri: String,
pub description: Option<String>,
pub zoom_level: Option<u8>,
pub asset: Option<AssetType>,
pub asset: AssetType,
}
#[derive(Clone, uniffi::Enum)]
pub enum AssetType {
Sender,
Pin,
Unknown,
}
impl From<AssetType> for RumaAssetType {
@@ -914,6 +933,26 @@ impl From<AssetType> for RumaAssetType {
match value {
AssetType::Sender => Self::Self_,
AssetType::Pin => Self::Pin,
_ => panic!("Invalid asset type"),
}
}
}
impl From<RumaAssetType> for AssetType {
fn from(value: RumaAssetType) -> Self {
match value {
RumaAssetType::Self_ => Self::Sender,
RumaAssetType::Pin => Self::Pin,
_ => Self::Unknown,
}
}
}
impl From<Option<RumaAssetType>> for AssetType {
fn from(value: Option<RumaAssetType>) -> Self {
match value {
None => Self::Sender,
Some(asset_type) => asset_type.into(),
}
}
}
@@ -1486,17 +1525,17 @@ impl From<RumaSecretStorageV1AesHmacSha2Properties> for SecretStorageV1AesHmacSh
#[derive(Clone, uniffi::Record, Default)]
pub struct MediaPreviewConfig {
/// The media previews setting for the user.
pub media_previews: MediaPreviews,
pub media_previews: Option<MediaPreviews>,
/// The invite avatars setting for the user.
pub invite_avatars: InviteAvatars,
pub invite_avatars: Option<InviteAvatars>,
}
impl From<MediaPreviewConfigEventContent> for MediaPreviewConfig {
fn from(value: MediaPreviewConfigEventContent) -> Self {
Self {
media_previews: value.media_previews.into(),
invite_avatars: value.invite_avatars.into(),
media_previews: value.media_previews.map(Into::into),
invite_avatars: value.invite_avatars.map(Into::into),
}
}
}
@@ -1670,6 +1709,7 @@ pub enum RoomAccountDataEvent {
/// The name of a tag.
#[derive(Clone, PartialEq, Eq, Hash, uniffi::Enum)]
#[uniffi::export(Eq, Hash)]
pub enum TagName {
/// `m.favourite`: The user's favorite rooms.
Favorite,
+1 -1
View File
@@ -39,7 +39,7 @@ mod sys {
mod sys {
use std::future::Future;
use crate::executor::{spawn, JoinHandle};
use matrix_sdk_common::executor::{JoinHandle, spawn};
/// A dummy guard that does nothing when dropped.
/// This is used for the Wasm implementation to match
+193
View File
@@ -0,0 +1,193 @@
// Copyright 2026 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 that specific language governing permissions and
// limitations under the License.
use matrix_sdk::{
deserialized_responses::TimelineEvent,
message_search::{
GlobalSearchIterator as SdkGlobalSearchIterator,
RoomSearchIterator as SdkRoomSearchIterator, SearchError as SdkSearchError,
},
};
use matrix_sdk_ui::timeline::TimelineDetails;
use tokio::sync::Mutex;
use crate::{
client::Client,
error::ClientError,
room::Room,
timeline::{ProfileDetails, TimelineItemContent},
utils::Timestamp,
};
#[derive(uniffi::Error, thiserror::Error, Debug)]
pub enum SearchError {
#[error("Failed to search through the index: {0}")]
IndexError(String),
#[error("Failed to load event content for search result: {0}")]
EventLoadError(String),
}
impl From<SdkSearchError> for SearchError {
fn from(err: SdkSearchError) -> Self {
match err {
SdkSearchError::IndexError(err) => SearchError::IndexError(err.to_string()),
SdkSearchError::EventLoadError(err) => SearchError::EventLoadError(err.to_string()),
}
}
}
#[matrix_sdk_ffi_macros::export]
impl Room {
/// Search for messages in this room matching the given query, returning an
/// iterator over the results that yields `num_results_per_batch` results at
/// a time.
pub fn search_messages(&self, query: String, num_results_per_batch: u32) -> RoomSearchIterator {
RoomSearchIterator {
sdk_room: self.inner.clone(),
inner: Mutex::new(self.inner.search_messages(query, num_results_per_batch as usize)),
}
}
}
#[derive(uniffi::Object)]
pub struct RoomSearchIterator {
sdk_room: matrix_sdk::room::Room,
inner: Mutex<SdkRoomSearchIterator>,
}
#[matrix_sdk_ffi_macros::export]
impl RoomSearchIterator {
/// Return a list of events for the next batch of search results, or `None`
/// if there are no more results.
pub async fn next_events(&self) -> Result<Option<Vec<RoomSearchResult>>, SearchError> {
let Some(events) = self.inner.lock().await.next_events().await? else {
return Ok(None);
};
let mut results = Vec::with_capacity(events.len());
for event in events {
if let Some(result) = RoomSearchResult::from(&self.sdk_room, event).await {
results.push(result);
}
}
results.shrink_to_fit();
Ok(Some(results))
}
}
#[derive(Clone, uniffi::Record)]
pub struct RoomSearchResult {
event_id: String,
sender: String,
sender_profile: ProfileDetails,
content: TimelineItemContent,
timestamp: Timestamp,
}
impl RoomSearchResult {
async fn from(room: &matrix_sdk::room::Room, event: TimelineEvent) -> Option<Self> {
let sender = event.sender()?;
let event_id = event.event_id().unwrap().to_string();
let timestamp =
event.timestamp().unwrap_or_else(ruma::MilliSecondsSinceUnixEpoch::now).into();
let content = matrix_sdk_ui::timeline::TimelineItemContent::from_event(room, event).await?;
let profile = TimelineDetails::from_initial_value(
matrix_sdk_ui::timeline::Profile::load(room, &sender).await,
);
Some(Self {
event_id,
sender: sender.to_string(),
sender_profile: ProfileDetails::from(profile),
content: TimelineItemContent::from(content),
timestamp,
})
}
}
#[derive(Clone, uniffi::Enum)]
pub enum SearchRoomFilter {
/// All the joined rooms (= DMs + non-DMs).
Rooms,
/// Only joined DM rooms.
Dms,
/// Only joined non-DM (group) rooms.
NonDms,
}
#[matrix_sdk_ffi_macros::export]
impl Client {
/// Search across all all rooms for the given query, returning an iterator
/// over the results.
pub async fn search_messages(
&self,
query: String,
filter: SearchRoomFilter,
num_results_per_batch: u32,
) -> Result<GlobalSearchIterator, ClientError> {
let sdk_client = (*self.inner).clone();
let mut search = sdk_client.search_messages(query, num_results_per_batch as usize);
match filter {
SearchRoomFilter::Rooms => {}
SearchRoomFilter::Dms => search = search.only_dm_rooms().await?,
SearchRoomFilter::NonDms => search = search.no_dms().await?,
}
Ok(GlobalSearchIterator { sdk_client, inner: Mutex::new(search.build()) })
}
}
#[derive(uniffi::Record)]
pub struct GlobalSearchResult {
room_id: String,
result: RoomSearchResult,
}
#[derive(uniffi::Object)]
pub struct GlobalSearchIterator {
sdk_client: matrix_sdk::Client,
inner: Mutex<SdkGlobalSearchIterator>,
}
#[matrix_sdk_ffi_macros::export]
impl GlobalSearchIterator {
/// Return a list of events for the next batch of search results, or `None`
/// if there are no more results.
pub async fn next_events(&self) -> Result<Option<Vec<GlobalSearchResult>>, SearchError> {
let Some(events) = self.inner.lock().await.next_events().await? else {
return Ok(None);
};
let mut results = Vec::with_capacity(events.len());
for (room_id, event) in events {
let Some(room) = self.sdk_client.get_room(&room_id) else {
continue;
};
if let Some(result) = RoomSearchResult::from(&room, event).await {
results.push(GlobalSearchResult { room_id: room_id.to_string(), result });
}
}
results.shrink_to_fit();
Ok(Some(results))
}
}
@@ -1,14 +1,28 @@
// 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 that specific language governing permissions and
// limitations under the License.
use std::sync::{Arc, RwLock};
use futures_util::StreamExt;
use matrix_sdk::{
Account,
encryption::{
Encryption,
identities::UserIdentity,
verification::{SasState, SasVerification, VerificationRequest, VerificationRequestState},
Encryption,
},
ruma::events::key::verification::VerificationMethod,
Account,
};
use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
use ruma::UserId;
@@ -116,11 +130,8 @@ impl SessionVerificationController {
/// Request verification for the current device
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.set_ongoing_verification_request(verification_request)
}
@@ -141,10 +152,7 @@ impl SessionVerificationController {
let methods = vec![VerificationMethod::SasV1];
let verification_request = user_identity
.request_verification_with_methods(methods)
.await
.map_err(anyhow::Error::from)?;
let verification_request = user_identity.request_verification_with_methods(methods).await?;
self.set_ongoing_verification_request(verification_request)
}
@@ -238,13 +246,15 @@ impl SessionVerificationController {
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;
}
}
if sender != self.user_identity.user_id()
&& let Some(status) = self.encryption.cross_signing_status().await
&& !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 {
@@ -257,21 +267,17 @@ impl SessionVerificationController {
return;
};
let Ok(sender_profile) = self.account.fetch_user_profile_of(sender).await else {
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: UserProfile {
user_id: request.other_user_id().to_string(),
display_name: sender_profile.displayname,
avatar_url: sender_profile.avatar_url.as_ref().map(|url| url.to_string()),
},
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),
device_display_name: other_device_data.display_name().map(str::to_owned),
first_seen_timestamp: other_device_data.first_time_seen_ts().into(),
});
}
@@ -283,15 +289,10 @@ impl SessionVerificationController {
) -> Result<(), ClientError> {
if let Some(ongoing_verification_request) =
self.verification_request.read().unwrap().clone()
&& !ongoing_verification_request.is_done()
&& !ongoing_verification_request.is_cancelled()
{
if !ongoing_verification_request.is_done()
&& !ongoing_verification_request.is_cancelled()
{
return Err(ClientError::from_str(
"There is another verification flow ongoing.",
None,
));
}
return Err(ClientError::from_str("There is another verification flow ongoing.", None));
}
*self.verification_request.write().unwrap() = Some(verification_request.clone());
+550
View File
@@ -0,0 +1,550 @@
// 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::{StreamExt, pin_mut};
use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
use matrix_sdk_ui::spaces::{
SpaceFilter as UISpaceFilter, SpaceRoom as UISpaceRoom, SpaceRoomList as UISpaceRoomList,
SpaceService as UISpaceService,
leave::{LeaveSpaceHandle as UILeaveSpaceHandle, LeaveSpaceRoom as UILeaveSpaceRoom},
room_list::SpaceRoomListPaginationState,
};
use ruma::RoomId;
use crate::{
TaskHandle,
client::JoinRule,
error::ClientError,
room::{Membership, RoomHero},
room_preview::RoomType,
runtime::get_runtime_handle,
};
/// 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 top_level_joined_spaces(&self) -> Vec<SpaceRoom> {
self.inner.top_level_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_top_level_joined_spaces(
&self,
listener: Box<dyn SpaceServiceJoinedSpacesListener>,
) -> Arc<TaskHandle> {
let (initial_values, mut stream) = self.inner.subscribe_to_top_level_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());
}
})))
}
/// Space filters provide access to a custom subset of the space graph that
/// can be used in tandem with the [`crate::RoomListService`] to narrow
/// down the presented rooms.
///
/// They are limited to the first 2 levels of the graph, with the first
/// level only containing direct descendants while the second holds the rest
/// of them recursively.
pub async fn space_filters(&self) -> Vec<SpaceFilter> {
self.inner.space_filters().await.into_iter().map(|s| s.into()).collect()
}
/// Subscribe to changes or updates to the space filters.
pub async fn subscribe_to_space_filters(
&self,
listener: Box<dyn SpaceServiceSpaceFiltersListener>,
) -> Arc<TaskHandle> {
let (initial_values, mut stream) = self.inner.subscribe_to_space_filters().await;
listener.on_update(vec![SpaceFilterUpdate::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 flattened list containing all the spaces where the user has
/// permission to send `m.space.child` state events.
///
/// Note: Unlike [`Self::top_level_joined_spaces()`], this method does not
/// recompute the space graph, nor does it notify subscribers about changes.
pub async fn editable_spaces(&self) -> Vec<SpaceRoom> {
self.inner.editable_spaces().await.into_iter().map(Into::into).collect()
}
/// Returns a `SpaceRoomList` for the given space ID.
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).await)))
}
/// Returns all known direct-parents of a given space room ID.
pub async fn joined_parents_of_child(
&self,
child_id: String,
) -> Result<Vec<SpaceRoom>, ClientError> {
let child_id = RoomId::parse(child_id)?;
let parents = self.inner.joined_parents_of_child(&child_id).await;
Ok(parents.into_iter().map(Into::into).collect())
}
/// Returns the corresponding `SpaceRoom` for the given room ID, or `None`
/// if it isn't known.
pub async fn get_space_room(&self, room_id: String) -> Result<Option<SpaceRoom>, ClientError> {
let room_id = RoomId::parse(room_id.as_str())?;
Ok(self.inner.get_space_room(&room_id).await.map(Into::into))
}
pub async fn add_child_to_space(
&self,
child_id: String,
space_id: String,
) -> Result<(), ClientError> {
let space_id = RoomId::parse(space_id)?;
let child_id = RoomId::parse(child_id)?;
self.inner.add_child_to_space(child_id, space_id).await.map_err(ClientError::from)
}
pub async fn remove_child_from_space(
&self,
child_id: String,
space_id: String,
) -> Result<(), ClientError> {
let space_id = RoomId::parse(space_id)?;
let child_id = RoomId::parse(child_id)?;
self.inner.remove_child_from_space(child_id, space_id).await.map_err(ClientError::from)
}
/// Start a space leave process returning a [`LeaveSpaceHandle`] from which
/// rooms can be retrieved in reversed BFS order starting from the requested
/// `space_id` graph node. If the room is unknown then an error will be
/// returned.
///
/// Once the rooms to be left are chosen the handle can be used to leave
/// them.
pub async fn leave_space(
&self,
space_id: String,
) -> Result<Arc<LeaveSpaceHandle>, ClientError> {
let space_id = RoomId::parse(space_id)?;
let handle = self.inner.leave_space(&space_id).await.map_err(ClientError::from)?;
Ok(Arc::new(handle.into()))
}
}
/// 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 the space of the room list if known.
pub fn space(&self) -> Option<SpaceRoom> {
self.inner.space().map(Into::into)
}
/// Subscribe to space updates.
pub fn subscribe_to_space_updates(
&self,
listener: Box<dyn SpaceRoomListSpaceListener>,
) -> Arc<TaskHandle> {
let space_updates = self.inner.subscribe_to_space_updates();
Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
pin_mut!(space_updates);
while let Some(space) = space_updates.next().await {
listener.on_update(space.map(Into::into));
}
})))
}
/// 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)
}
/// Clears the room list back to its initial state so that any new changes
/// to the hierarchy will be included the next time [`Self::paginate`] is
/// called.
///
/// This is useful when you've added or removed children from the space as
/// the list is based on a cached state that lives server-side, meaning
/// the /hierarchy request needs to be restarted from scratch to pick up
/// the changes.
pub async fn reset(&self) {
self.inner.reset().await;
}
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait SpaceRoomListSpaceListener: SendOutsideWasm + SyncOutsideWasm + Debug {
fn on_update(&self, space: Option<SpaceRoom>);
}
#[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>);
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait SpaceServiceSpaceFiltersListener: SendOutsideWasm + SyncOutsideWasm + Debug {
fn on_update(&self, filter_updates: Vec<SpaceFilterUpdate>);
}
/// 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 room's name from the room state event if received from sync, or one
/// that's been computed otherwise.
pub display_name: String,
/// Room name as defined by the room state event only.
pub raw_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,
/// Whether this room is a direct room.
///
/// Only set if the room is known to the client otherwise we
/// assume DMs shouldn't be exposed publicly in spaces.
pub is_direct: Option<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>>,
/// The via parameters of the room.
pub via: Vec<String>,
}
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()),
display_name: room.display_name,
raw_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,
is_direct: room.is_direct,
children_count: room.children_count,
state: room.state.map(Into::into),
heroes: room.heroes.map(|heroes| heroes.into_iter().map(Into::into).collect()),
via: room.via.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() }
}
}
}
}
#[derive(uniffi::Enum)]
pub enum SpaceFilterUpdate {
Append { values: Vec<SpaceFilter> },
Clear,
PushFront { value: SpaceFilter },
PushBack { value: SpaceFilter },
PopFront,
PopBack,
Insert { index: u32, value: SpaceFilter },
Set { index: u32, value: SpaceFilter },
Remove { index: u32 },
Truncate { length: u32 },
Reset { values: Vec<SpaceFilter> },
}
impl From<VectorDiff<UISpaceFilter>> for SpaceFilterUpdate {
fn from(diff: VectorDiff<UISpaceFilter>) -> 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() }
}
}
}
}
/// The `LeaveSpaceHandle` processes rooms to be left in the order they were
/// provided by the [`SpaceService`] and annotates them with extra data to
/// inform the leave process e.g. if the current user is the last room admin.
///
/// Once the upstream client decides what rooms should actually be left, the
/// handle provides a method to execute that too.
#[derive(uniffi::Object)]
pub struct LeaveSpaceHandle {
inner: UILeaveSpaceHandle,
}
#[matrix_sdk_ffi_macros::export]
impl LeaveSpaceHandle {
/// A list of rooms to be left which next to normal [`SpaceRoom`] data also
/// include leave specific information.
pub fn rooms(&self) -> Vec<LeaveSpaceRoom> {
let rooms = self.inner.rooms();
rooms.iter().map(|room| room.clone().into()).collect()
}
/// Bulk leave the given rooms. Stops when encountering an error.
pub async fn leave(&self, room_ids: Vec<String>) -> Result<(), ClientError> {
let room_ids = room_ids.iter().map(RoomId::parse).collect::<Result<Vec<_>, _>>()?;
self.inner
.leave(|room| room_ids.contains(&room.space_room.room_id))
.await
.map_err(ClientError::from)
}
}
impl From<UILeaveSpaceHandle> for LeaveSpaceHandle {
fn from(handle: UILeaveSpaceHandle) -> Self {
LeaveSpaceHandle { inner: handle }
}
}
/// Space leaving specific room that groups normal [`SpaceRoom`] details with
/// information about the leaving user's role.
#[derive(uniffi::Record)]
pub struct LeaveSpaceRoom {
/// The underlying [`SpaceRoom`]
pub space_room: SpaceRoom,
/// Whether the user is the last owner in the room. This helps clients
/// better inform the user about the consequences of leaving the room.
pub is_last_owner: bool,
/// If the room creators have infinite PL.
pub are_creators_privileged: bool,
}
impl From<UILeaveSpaceRoom> for LeaveSpaceRoom {
fn from(room: UILeaveSpaceRoom) -> Self {
LeaveSpaceRoom {
space_room: room.space_room.into(),
is_last_owner: room.is_last_owner,
are_creators_privileged: room.are_creators_privileged,
}
}
}
#[derive(uniffi::Record)]
pub struct SpaceFilter {
/// The underlying [`SpaceRoom`]
space_room: SpaceRoom,
/// The level of the space filter in the tree/hierarchy.
/// At this point in time the filters are limited to the first 2 levels.
level: u8,
/// The room identifiers of the descendants of this space.
/// For top level spaces (level 0) these will be direct descendants while
/// for first level spaces they will be all other descendants, recursively.
descendants: Vec<String>,
}
impl From<UISpaceFilter> for SpaceFilter {
fn from(filter: UISpaceFilter) -> Self {
SpaceFilter {
space_room: filter.space_room.into(),
level: filter.level,
descendants: filter.descendants.into_iter().map(|id| id.to_string()).collect(),
}
}
}
+281
View File
@@ -0,0 +1,281 @@
// 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 that specific language governing permissions and
// limitations under the License.
#[cfg(feature = "sqlite")]
use std::path::PathBuf;
#[cfg(feature = "sqlite")]
use matrix_sdk::SqliteStoreConfig;
#[cfg(doc)]
use crate::client_builder::ClientBuilder;
/// The outcome of building a [`StoreBuilder`], with data that can be passed
/// directly to a [`ClientBuilder`].
pub enum StoreBuilderOutcome {
/// An SQLite store configuration successfully built.
#[cfg(feature = "sqlite")]
Sqlite { config: SqliteStoreConfig, cache_path: PathBuf, store_path: PathBuf },
/// An IndexedDB store configuration successfully built.
#[cfg(feature = "indexeddb")]
IndexedDb { name: String, passphrase: Option<String> },
/// An in-memory store configuration successfully built.
InMemory,
}
#[cfg(feature = "sqlite")]
mod sqlite {
use std::{fs, path::Path, sync::Arc};
use matrix_sdk::SqliteStoreConfig;
use tracing::debug;
use zeroize::Zeroizing;
use super::StoreBuilderOutcome;
use crate::{client_builder::ClientBuildError, helpers::unwrap_or_clone_arc};
/// The store paths the client will use when built.
#[derive(Clone)]
struct StorePaths {
/// The path that the client will use to store its data.
data_path: String,
/// The path that the client will use to store its caches. This path can
/// be the same as the data path if you prefer to keep
/// everything in one place.
cache_path: String,
}
/// A builder for configuring a Sqlite session store.
#[derive(Clone, uniffi::Object)]
pub struct SqliteStoreBuilder {
paths: StorePaths,
passphrase: Zeroizing<Option<String>>,
pool_max_size: Option<usize>,
cache_size: Option<u32>,
journal_size_limit: Option<u32>,
system_is_memory_constrained: bool,
}
impl SqliteStoreBuilder {
pub(crate) fn raw_new(data_path: String, cache_path: String) -> Self {
Self {
paths: StorePaths { data_path, cache_path },
passphrase: Zeroizing::new(None),
pool_max_size: None,
cache_size: None,
journal_size_limit: None,
system_is_memory_constrained: false,
}
}
}
#[matrix_sdk_ffi_macros::export]
impl SqliteStoreBuilder {
/// Construct a [`SqliteStoreBuilder`] and set the paths that the client
/// will use to store its data and caches.
///
/// Both paths **must** be unique per session as the SDK stores aren't
/// capable of handling multiple users, however it is valid to use the
/// same path for both stores on a single session.
#[uniffi::constructor]
pub fn new(data_path: String, cache_path: String) -> Arc<Self> {
Arc::new(Self::raw_new(data_path, cache_path))
}
/// Set the passphrase for the stores.
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)
}
/// Set the pool max size for the stores.
///
/// 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 pool_max_size(self: Arc<Self>, pool_max_size: Option<u32>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.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 stores.
///
/// 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 cache_size(self: Arc<Self>, cache_size: Option<u32>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.cache_size = cache_size;
Arc::new(builder)
}
/// Set the size limit for the SQLite WAL files of stores.
///
/// 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 journal_size_limit(self: Arc<Self>, limit: Option<u32>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.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`]. 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)
}
}
impl SqliteStoreBuilder {
#[allow(clippy::result_large_err)]
pub fn build(&self) -> Result<StoreBuilderOutcome, ClientBuildError> {
let data_path = Path::new(&self.paths.data_path);
let cache_path = Path::new(&self.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.",
);
fs::create_dir_all(data_path)?;
fs::create_dir_all(cache_path)?;
let mut sqlite_store_config = if self.system_is_memory_constrained {
SqliteStoreConfig::with_low_memory_config(data_path)
} else {
SqliteStoreConfig::new(data_path)
};
sqlite_store_config = sqlite_store_config.passphrase(self.passphrase.as_deref());
if let Some(size) = self.pool_max_size {
sqlite_store_config = sqlite_store_config.pool_max_size(size);
}
if let Some(size) = self.cache_size {
sqlite_store_config = sqlite_store_config.cache_size(size);
}
if let Some(limit) = self.journal_size_limit {
sqlite_store_config = sqlite_store_config.journal_size_limit(limit);
}
Ok(StoreBuilderOutcome::Sqlite {
config: sqlite_store_config,
store_path: data_path.to_owned(),
cache_path: cache_path.to_owned(),
})
}
}
}
#[cfg(feature = "indexeddb")]
mod indexeddb {
use std::sync::Arc;
use super::StoreBuilderOutcome;
use crate::{client_builder::ClientBuildError, helpers::unwrap_or_clone_arc};
#[derive(Clone, uniffi::Object)]
pub struct IndexedDbStoreBuilder {
name: String,
passphrase: Option<String>,
}
#[matrix_sdk_ffi_macros::export]
impl IndexedDbStoreBuilder {
#[uniffi::constructor]
pub fn new(name: String) -> Arc<Self> {
Arc::new(Self { name, passphrase: None })
}
/// Set the passphrase for the stores.
pub fn passphrase(self: Arc<Self>, passphrase: Option<String>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.passphrase = passphrase;
Arc::new(builder)
}
}
impl IndexedDbStoreBuilder {
pub fn build(&self) -> Result<StoreBuilderOutcome, ClientBuildError> {
Ok(StoreBuilderOutcome::IndexedDb {
name: self.name.clone(),
passphrase: self.passphrase.clone(),
})
}
}
}
#[cfg(feature = "indexeddb")]
pub use indexeddb::*;
#[cfg(feature = "sqlite")]
pub use sqlite::*;
use crate::client_builder::ClientBuildError;
/// Represent the kind of store the client will configure.
#[derive(Clone)]
pub enum StoreBuilder {
/// Represents the builder for the SQLite store.
#[cfg(feature = "sqlite")]
Sqlite(SqliteStoreBuilder),
/// Represents the builder for the IndexedDB store.
#[cfg(feature = "indexeddb")]
IndexedDb(IndexedDbStoreBuilder),
/// Represents the builder for in-memory store.
InMemory,
}
impl StoreBuilder {
#[allow(clippy::result_large_err)]
pub(crate) fn build(&self) -> Result<StoreBuilderOutcome, ClientBuildError> {
match self {
#[cfg(feature = "sqlite")]
Self::Sqlite(config) => config.build(),
#[cfg(feature = "indexeddb")]
Self::IndexedDb(config) => config.build(),
Self::InMemory => Ok(StoreBuilderOutcome::InMemory),
}
}
}
+40 -9
View File
@@ -26,8 +26,8 @@ use matrix_sdk_ui::{
};
use crate::{
error::ClientError, helpers::unwrap_or_clone_arc, room_list::RoomListService,
runtime::get_runtime_handle, TaskHandle,
TaskHandle, error::ClientError, helpers::unwrap_or_clone_arc, room_list::RoomListService,
runtime::get_runtime_handle,
};
#[derive(uniffi::Enum)]
@@ -45,7 +45,7 @@ impl From<MatrixSyncServiceState> for SyncServiceState {
MatrixSyncServiceState::Idle => Self::Idle,
MatrixSyncServiceState::Running => Self::Running,
MatrixSyncServiceState::Terminated => Self::Terminated,
MatrixSyncServiceState::Error => Self::Error,
MatrixSyncServiceState::Error(_error) => Self::Error,
MatrixSyncServiceState::Offline => Self::Offline,
}
}
@@ -90,6 +90,15 @@ 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)]
@@ -106,12 +115,6 @@ impl SyncServiceBuilder {
#[matrix_sdk_ffi_macros::export]
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 { builder, ..this })
}
/// Enable the "offline" mode for the [`SyncService`].
pub fn with_offline_mode(self: Arc<Self>) -> Arc<Self> {
let this = unwrap_or_clone_arc(self);
@@ -119,6 +122,34 @@ impl SyncServiceBuilder {
Arc::new(Self { builder, ..this })
}
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 })
}
/// Set a custom Sliding Sync connection ID for the room list service.
///
/// By default [`matrix_sdk_ui::room_list_service::DEFAULT_CONNECTION_ID`]
/// is used. Set a different value for secondary processes such as iOS
/// Share Extensions that are not meant to reuse the main app's
/// connection.
pub fn with_room_list_connection_id(self: Arc<Self>, connection_id: String) -> Arc<Self> {
let this = unwrap_or_clone_arc(self);
let builder = this.builder.with_room_list_conn_id(connection_id);
Arc::new(Self { builder, ..this })
}
/// Set a custom timeline limit for the room list service.
///
/// When set, overrides the default timeline limit of
/// [`matrix_sdk_ui::room_list_service::DEFAULT_LIST_TIMELINE_LIMIT`].
pub fn with_room_list_timeline_limit(self: Arc<Self>, limit: u32) -> Arc<Self> {
let this = unwrap_or_clone_arc(self);
let builder = this.builder.with_room_list_timeline_limit(limit);
Arc::new(Self { builder, ..this })
}
pub async fn finish(self: Arc<Self>) -> Result<Arc<SyncService>, ClientError> {
let this = unwrap_or_clone_arc(self);
Ok(Arc::new(SyncService {
+89
View File
@@ -0,0 +1,89 @@
// Copyright 2026 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::time::Duration;
use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
/// A listener for the sync loop.
///
/// Called after each successful sync response when using
/// [`Client::sync_v2`](crate::client::Client::sync_v2).
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait SyncListenerV2: SyncOutsideWasm + SendOutsideWasm {
/// Called after each successful sync response.
fn on_update(&self, response: SyncResponseV2);
}
/// Settings for a sync v2 call.
#[derive(uniffi::Record)]
pub struct SyncSettingsV2 {
/// Timeout in milliseconds for the server long-poll.
/// If not set, defaults to 30 seconds.
#[uniffi(default = None)]
pub timeout_ms: Option<u64>,
/// Whether to request full state on the first sync.
#[uniffi(default = false)]
pub full_state: bool,
}
impl From<SyncSettingsV2> for matrix_sdk::config::SyncSettings {
fn from(value: SyncSettingsV2) -> Self {
let mut settings = matrix_sdk::config::SyncSettings::new();
if let Some(timeout_ms) = value.timeout_ms {
settings = settings.timeout(Duration::from_millis(timeout_ms));
}
if value.full_state {
settings = settings.full_state(true);
}
settings
}
}
/// The response from a sync v2 call.
#[derive(uniffi::Record)]
pub struct SyncResponseV2 {
/// The batch token to supply in the `since` param of the next `/sync`
/// request.
pub next_batch: String,
/// Updates to rooms.
pub rooms: SyncResponseRoomsV2,
}
/// Room updates from a sync v2 response.
#[derive(uniffi::Record)]
pub struct SyncResponseRoomsV2 {
/// Room IDs of rooms the user has been invited to.
pub invited: Vec<String>,
/// Room IDs of joined rooms that had updates.
pub joined: Vec<String>,
/// Room IDs of rooms the user has left.
pub left: Vec<String>,
/// Room IDs of rooms the user has knocked on.
pub knocked: Vec<String>,
}
impl From<matrix_sdk::sync::SyncResponse> for SyncResponseV2 {
fn from(value: matrix_sdk::sync::SyncResponse) -> Self {
Self {
next_batch: value.next_batch,
rooms: SyncResponseRoomsV2 {
invited: value.rooms.invited.keys().map(ToString::to_string).collect(),
joined: value.rooms.joined.keys().map(ToString::to_string).collect(),
left: value.rooms.left.keys().map(ToString::to_string).collect(),
knocked: value.rooms.knocked.keys().map(ToString::to_string).collect(),
},
}
}
}
+15 -1
View File
@@ -1,4 +1,18 @@
use tokio::task::JoinHandle;
// 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 that specific language governing permissions and
// limitations under the License.
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
@@ -1,9 +1,26 @@
// 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 that specific language governing permissions and
// limitations under the License.
use std::sync::Arc;
use matrix_sdk_ui::timeline::event_type_filter::TimelineEventTypeFilter as InnerTimelineEventTypeFilter;
use matrix_sdk_ui::timeline::{
TimelineEventFocusThreadMode, TimelineReadReceiptTracking,
event_filter::{TimelineEventCondition, TimelineEventFilter as InnerTimelineEventFilter},
};
use ruma::{
events::{AnySyncTimelineEvent, TimelineEventType},
EventId,
events::{AnySyncTimelineEvent, TimelineEventType},
};
use super::FocusEventError;
@@ -12,31 +29,50 @@ use crate::{
event::{MessageLikeEventType, RoomMessageEventMessageType, StateEventType},
};
/// A timeline filter that includes or excludes events based on their type or
/// content.
#[derive(uniffi::Object)]
pub struct TimelineEventTypeFilter {
inner: InnerTimelineEventTypeFilter,
pub struct TimelineEventFilter {
inner: InnerTimelineEventFilter,
}
#[matrix_sdk_ffi_macros::export]
impl TimelineEventTypeFilter {
impl TimelineEventFilter {
#[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) })
pub fn include(conditions: Vec<FilterTimelineEventCondition>) -> Arc<Self> {
let conditions: Vec<TimelineEventCondition> =
conditions.iter().map(|t| t.clone().into()).collect();
Arc::new(Self { inner: InnerTimelineEventFilter::Include(conditions) })
}
#[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) })
pub fn include_event_types(event_types: Vec<FilterTimelineEventType>) -> Arc<Self> {
let conditions = event_types
.iter()
.map(|t| TimelineEventCondition::EventType(t.clone().into()))
.collect();
Arc::new(Self { inner: InnerTimelineEventFilter::Include(conditions) })
}
#[uniffi::constructor]
pub fn exclude(conditions: Vec<FilterTimelineEventCondition>) -> Arc<Self> {
let conditions: Vec<TimelineEventCondition> =
conditions.iter().map(|t| t.clone().into()).collect();
Arc::new(Self { inner: InnerTimelineEventFilter::Exclude(conditions) })
}
#[uniffi::constructor]
pub fn exclude_event_types(event_types: Vec<FilterTimelineEventType>) -> Arc<Self> {
let conditions = event_types
.iter()
.map(|t| TimelineEventCondition::EventType(t.clone().into()))
.collect();
Arc::new(Self { inner: InnerTimelineEventFilter::Exclude(conditions) })
}
}
impl TimelineEventTypeFilter {
/// Filters an [`event`] to decide whether it should be part of the timeline
/// based on [`AnySyncTimelineEvent::event_type()`].
impl TimelineEventFilter {
/// Filters an `event` to decide whether it should be part of the timeline.
pub(crate) fn filter(&self, event: &AnySyncTimelineEvent) -> bool {
self.inner.filter(event)
}
@@ -61,6 +97,31 @@ impl From<FilterTimelineEventType> for TimelineEventType {
}
}
/// A condition that matches on an event's type or content.
#[derive(uniffi::Enum, Clone)]
pub enum FilterTimelineEventCondition {
/// The event has the specified event type.
EventType { event_type: FilterTimelineEventType },
/// The event is an `m.room.member` event that represents a membership
/// change (join, leave, etc.).
MembershipChange,
/// The event is an `m.room.member` event that represents a profile
/// change (displayname or avatar URL).
ProfileChange,
}
impl From<FilterTimelineEventCondition> for TimelineEventCondition {
fn from(value: FilterTimelineEventCondition) -> Self {
match value {
FilterTimelineEventCondition::EventType { event_type } => {
Self::EventType(event_type.into())
}
FilterTimelineEventCondition::MembershipChange => Self::MembershipChange,
FilterTimelineEventCondition::ProfileChange => Self::ProfileChange,
}
}
}
#[derive(uniffi::Enum)]
pub enum TimelineFocus {
Live {
@@ -73,18 +134,14 @@ pub enum TimelineFocus {
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,
/// How to handle threaded events.
thread_mode: TimelineEventFocusThreadMode,
},
Thread {
/// The thread root event ID to focus on.
root_event_id: String,
num_events: u16,
},
PinnedEvents {
max_events_to_load: u16,
max_concurrent_requests: u16,
},
PinnedEvents,
}
impl TryFrom<TimelineFocus> for matrix_sdk_ui::timeline::TimelineFocus {
@@ -95,20 +152,16 @@ impl TryFrom<TimelineFocus> for matrix_sdk_ui::timeline::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 } => {
TimelineFocus::Event { event_id, num_context_events, thread_mode } => {
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,
})
Ok(Self::Event { target: parsed_event_id, num_context_events, thread_mode })
}
TimelineFocus::Thread { root_event_id, num_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(),
@@ -116,11 +169,9 @@ impl TryFrom<TimelineFocus> for matrix_sdk_ui::timeline::TimelineFocus {
}
})?;
Ok(Self::Thread { root_event_id: parsed_root_event_id, num_events })
}
TimelineFocus::PinnedEvents { max_events_to_load, max_concurrent_requests } => {
Ok(Self::PinnedEvents { max_events_to_load, max_concurrent_requests })
Ok(Self::Thread { root_event_id: parsed_root_event_id })
}
TimelineFocus::PinnedEvents => Ok(Self::PinnedEvents),
}
}
}
@@ -152,8 +203,8 @@ pub enum TimelineFilter {
/// appear in the timeline.
types: Vec<RoomMessageEventMessageType>,
},
/// Show only events which match this filter.
EventTypeFilter { filter: Arc<TimelineEventTypeFilter> },
/// Show only events which match this event filter.
EventFilter { filter: Arc<TimelineEventFilter> },
}
/// Various options used to configure the timeline's behavior.
@@ -174,11 +225,11 @@ pub struct TimelineConfiguration {
pub date_divider_mode: DateDividerMode,
/// Should the read receipts and read markers be tracked for the timeline
/// items in this instance?
/// items in this instance and on which event types?
///
/// As this has a non negligible performance impact, make sure to enable it
/// only when you need it.
pub track_read_receipts: bool,
pub track_read_receipts: TimelineReadReceiptTracking,
/// Whether this timeline instance should report UTDs through the client's
/// delegate.
+229 -32
View File
@@ -16,9 +16,14 @@ use std::collections::HashMap;
use matrix_sdk::room::power_levels::power_level_user_changes;
use matrix_sdk_ui::timeline::RoomPinnedEventsChange;
use ruma::events::FullStateEventContent;
use ruma::events::{
StateEventContentChange, room::history_visibility::HistoryVisibility as RumaHistoryVisibility,
};
use crate::{timeline::msg_like::MsgLikeContent, utils::Timestamp};
use crate::{
client::JoinRule, event::TimelineEventType, ruma::AssetType,
timeline::msg_like::MsgLikeContent, utils::Timestamp,
};
impl From<matrix_sdk_ui::timeline::TimelineItemContent> for TimelineItemContent {
fn from(value: matrix_sdk_ui::timeline::TimelineItemContent) -> Self {
@@ -35,11 +40,13 @@ impl From<matrix_sdk_ui::timeline::TimelineItemContent> for TimelineItemContent
Content::CallInvite => TimelineItemContent::CallInvite,
Content::CallNotify => TimelineItemContent::CallNotify,
Content::RtcNotification { call_intent } => TimelineItemContent::RtcNotification {
call_intent: call_intent.map(|s| s.to_string()),
},
Content::MembershipChange(membership) => {
let reason = match membership.content() {
FullStateEventContent::Original { content, .. } => content.reason.clone(),
StateEventContentChange::Original { content, .. } => content.reason.clone(),
_ => None,
};
TimelineItemContent::RoomMembership {
@@ -95,6 +102,51 @@ impl From<matrix_sdk_ui::timeline::TimelineItemContent> for TimelineItemContent
}
}
#[derive(Debug, Clone, uniffi::Enum)]
pub enum HistoryVisibility {
/// Previous events are accessible to newly joined members from the point
/// they were invited onwards.
///
/// Events stop being accessible when the member' state changes to
/// something other than *invite* or *join*.
Invited,
/// Previous events are accessible to newly joined members from the point
/// they joined the room onwards.
/// Events stop being accessible when the member' state changes to
/// something other than *join*.
Joined,
/// Previous events are always accessible to newly joined members.
///
/// All events in the room are accessible, even those sent when the member
/// was not a part of the room.
Shared,
/// All events while this is the `HistoryVisibility` value may be shared by
/// any participating homeserver with anyone, regardless of whether they
/// have ever joined the room.
WorldReadable,
/// A custom history visibility, up for interpretation by the consumer.
Custom {
/// The string representation for this custom history visibility.
repr: String,
},
}
impl From<&RumaHistoryVisibility> for HistoryVisibility {
fn from(value: &RumaHistoryVisibility) -> Self {
match value {
RumaHistoryVisibility::Invited => Self::Invited,
RumaHistoryVisibility::Joined => Self::Joined,
RumaHistoryVisibility::Shared => Self::Shared,
RumaHistoryVisibility::WorldReadable => Self::WorldReadable,
_ => Self::Custom { repr: value.to_string() },
}
}
}
#[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
@@ -109,7 +161,9 @@ pub enum TimelineItemContent {
content: MsgLikeContent,
},
CallInvite,
CallNotify,
RtcNotification {
call_intent: Option<String>,
},
RoomMembership {
user_id: String,
user_display_name: Option<String>,
@@ -195,41 +249,123 @@ impl From<matrix_sdk_ui::timeline::MembershipChange> for MembershipChange {
}
}
#[derive(Clone, uniffi::Record)]
pub struct PowerLevelChanges {
ban: i64,
kick: i64,
events_default: i64,
invite: i64,
redact: i64,
state_default: i64,
users_default: i64,
notifications: i64,
}
#[derive(Clone, uniffi::Enum)]
#[allow(clippy::large_enum_variant)]
// Added because the RoomPowerLevels variant is quite large.
// This is the same issue than for TimelineItemContent.
pub enum OtherState {
PolicyRuleRoom,
PolicyRuleServer,
PolicyRuleUser,
RoomAliases,
RoomAvatar { url: Option<String> },
RoomAvatar {
url: Option<String>,
},
RoomCanonicalAlias,
RoomCreate,
RoomCreate {
federate: bool,
},
RoomEncryption,
RoomGuestAccess,
RoomHistoryVisibility,
RoomJoinRules,
RoomName { name: Option<String> },
RoomPinnedEvents { change: RoomPinnedEventsChange },
RoomPowerLevels { users: HashMap<String, i64>, previous: Option<HashMap<String, i64>> },
RoomHistoryVisibility {
history_visibility: HistoryVisibility,
},
RoomJoinRules {
join_rule: Option<JoinRule>,
},
RoomName {
name: Option<String>,
},
RoomPinnedEvents {
change: RoomPinnedEventsChange,
},
RoomPowerLevels {
events: HashMap<TimelineEventType, i64>,
previous_events: Option<HashMap<TimelineEventType, i64>>,
users: HashMap<String, i64>,
previous_users: Option<HashMap<String, i64>>,
thresholds: PowerLevelChanges,
previous_thresholds: Option<PowerLevelChanges>,
},
RoomServerAcl,
RoomThirdPartyInvite { display_name: Option<String> },
RoomThirdPartyInvite {
display_name: Option<String>,
},
RoomTombstone,
RoomTopic { topic: Option<String> },
RoomTopic {
topic: Option<String>,
},
SpaceChild,
SpaceParent,
Custom { event_type: String },
Custom {
event_type: String,
},
}
impl From<&matrix_sdk_ui::timeline::AnyOtherFullStateEventContent> for OtherState {
fn from(content: &matrix_sdk_ui::timeline::AnyOtherFullStateEventContent) -> Self {
use matrix_sdk::ruma::events::FullStateEventContent as FullContent;
use matrix_sdk_ui::timeline::AnyOtherFullStateEventContent as Content;
/// FFI representation of a single location update from a beacon event.
#[derive(Clone, uniffi::Record)]
pub struct BeaconInfo {
/// The geo URI carrying the user's coordinates
/// (e.g. `"geo:51.5008,0.1247;u=35"`).
pub geo_uri: String,
/// Timestamp (ms since Unix Epoch) of this location update.
pub ts: Timestamp,
/// An optional human-readable description of the location.
pub description: Option<String>,
}
/// FFI representation of a live location sharing session (MSC3489).
///
/// Corresponds to a `org.matrix.msc3672.beacon_info` state event in the
/// timeline. Location updates are aggregated here as they arrive.
#[derive(Clone, uniffi::Record)]
pub struct LiveLocationContent {
/// Whether this sharing session is currently active.
pub is_live: bool,
/// The timestamp when this live location sharing session started
/// (from the `org.matrix.msc3488.ts` field of the originating
/// `beacon_info` state event).
///
/// This marks the *beginning* of the session. The session expires at
/// `ts + timeout_ms`.
pub ts: Timestamp,
/// An optional human-readable label for this sharing session.
pub description: Option<String>,
/// Duration of the session in milliseconds.
pub timeout_ms: u64,
/// The asset type of the beacon (e.g. `Sender` for the user's own
/// location, `Pin` for a fixed point of interest).
pub asset_type: AssetType,
/// All location updates received so far, sorted oldest-first.
pub locations: Vec<BeaconInfo>,
}
impl From<&matrix_sdk_ui::timeline::AnyOtherStateEventContentChange> for OtherState {
fn from(content: &matrix_sdk_ui::timeline::AnyOtherStateEventContentChange) -> Self {
use matrix_sdk::ruma::events::StateEventContentChange as FullContent;
use matrix_sdk_ui::timeline::AnyOtherStateEventContentChange as Content;
match content {
Content::PolicyRuleRoom(_) => Self::PolicyRuleRoom,
Content::PolicyRuleServer(_) => Self::PolicyRuleServer,
Content::PolicyRuleUser(_) => Self::PolicyRuleUser,
Content::RoomAliases(_) => Self::RoomAliases,
Content::RoomAvatar(c) => {
let url = match c {
FullContent::Original { content, .. } => {
@@ -240,11 +376,36 @@ impl From<&matrix_sdk_ui::timeline::AnyOtherFullStateEventContent> for OtherStat
Self::RoomAvatar { url }
}
Content::RoomCanonicalAlias(_) => Self::RoomCanonicalAlias,
Content::RoomCreate(_) => Self::RoomCreate,
Content::RoomCreate(c) => {
let federate = match c {
FullContent::Original { content, .. } => content.federate,
FullContent::Redacted(content) => content.federate,
};
Self::RoomCreate { federate }
}
Content::RoomEncryption(_) => Self::RoomEncryption,
Content::RoomGuestAccess(_) => Self::RoomGuestAccess,
Content::RoomHistoryVisibility(_) => Self::RoomHistoryVisibility,
Content::RoomJoinRules(_) => Self::RoomJoinRules,
Content::RoomHistoryVisibility(c) => {
let history_visibility = match c {
FullContent::Original { content, .. } => &content.history_visibility,
FullContent::Redacted(content) => &content.history_visibility,
};
Self::RoomHistoryVisibility { history_visibility: history_visibility.into() }
}
Content::RoomJoinRules(c) => {
let ruma_join_rule = match c {
FullContent::Original { content, .. } => &content.join_rule,
FullContent::Redacted(content) => &content.join_rule,
};
let join_rule = match ruma_join_rule.clone().try_into() {
Ok(jr) => Some(jr),
Err(err) => {
tracing::error!("Failed to convert join rule: {}", err);
None
}
};
Self::RoomJoinRules { join_rule }
}
Content::RoomName(c) => {
let name = match c {
FullContent::Original { content, .. } => Some(content.name.clone()),
@@ -253,20 +414,56 @@ impl From<&matrix_sdk_ui::timeline::AnyOtherFullStateEventContent> for OtherStat
Self::RoomName { name }
}
Content::RoomPinnedEvents(c) => Self::RoomPinnedEvents { change: c.into() },
Content::RoomPowerLevels(c) => match c {
FullContent::Original { content, prev_content } => Self::RoomPowerLevels {
users: power_level_user_changes(content, prev_content)
Content::RoomPowerLevels(c) => {
let (content, prev_content) = match c.clone() {
FullContent::Original { content, prev_content } => (content, prev_content),
FullContent::Redacted(content) => (content.into(), None),
};
Self::RoomPowerLevels {
events: content
.events
.iter()
.map(|(k, &v)| (k.clone().into(), v.into()))
.collect(),
previous_events: prev_content.as_ref().map(|prev_content| {
prev_content
.events
.iter()
.map(|(k, &v)| (k.clone().into(), v.into()))
.collect()
}),
thresholds: PowerLevelChanges {
ban: content.ban.into(),
kick: content.kick.into(),
events_default: content.events_default.into(),
invite: content.invite.into(),
redact: content.redact.into(),
state_default: content.state_default.into(),
users_default: content.users_default.into(),
notifications: content.notifications.room.into(),
},
previous_thresholds: prev_content.as_ref().map(|prev_content| {
PowerLevelChanges {
ban: prev_content.ban.into(),
kick: prev_content.kick.into(),
events_default: prev_content.events_default.into(),
invite: prev_content.invite.into(),
redact: prev_content.redact.into(),
state_default: prev_content.state_default.into(),
users_default: prev_content.users_default.into(),
notifications: prev_content.notifications.room.into(),
}
}),
users: power_level_user_changes(&content, &prev_content)
.iter()
.map(|(k, v)| (k.to_string(), *v))
.collect(),
previous: prev_content.as_ref().map(|prev_content| {
previous_users: prev_content.as_ref().map(|prev_content| {
prev_content.users.iter().map(|(k, &v)| (k.to_string(), v.into())).collect()
}),
},
FullContent::Redacted(_) => {
Self::RoomPowerLevels { users: Default::default(), previous: None }
}
},
}
Content::RoomServerAcl(_) => Self::RoomServerAcl,
Content::RoomThirdPartyInvite(c) => {
let display_name = match c {
File diff suppressed because it is too large Load Diff
@@ -14,15 +14,16 @@
use std::{collections::HashMap, sync::Arc};
use matrix_sdk::crypto::types::events::UtdCause;
use ruma::events::{room::MediaSource as RumaMediaSource, EventContent};
use matrix_sdk_base::crypto::types::events::UtdCause;
use ruma::events::{MessageLikeEventContent, room::MediaSource as RumaMediaSource};
use super::{
content::Reaction,
content::{BeaconInfo, LiveLocationContent, Reaction},
reply::{EmbeddedEventDetails, InReplyToDetails},
};
use crate::{
error::ClientError,
event::MessageLikeEventType,
ruma::{ImageInfo, MediaSource, MediaSourceExt, Mentions, MessageType, PollKind},
timeline::content::ReactionSenderData,
utils::Timestamp,
@@ -50,6 +51,15 @@ pub enum MsgLikeKind {
/// An `m.room.encrypted` event that could not be decrypted.
UnableToDecrypt { msg: EncryptedMessage },
/// A custom message like event.
Other { event_type: MessageLikeEventType },
/// A live location sharing session (MSC3489).
///
/// Represents a `org.matrix.msc3672.beacon_info` state event with all
/// aggregated location updates from `org.matrix.msc3672.beacon` events.
LiveLocation { content: LiveLocationContent },
}
/// A special kind of [`super::TimelineItemContent`] that groups together
@@ -182,6 +192,43 @@ impl TryFrom<matrix_sdk_ui::timeline::MsgLikeContent> for MsgLikeContent {
thread_root,
thread_summary,
},
Kind::Other(other) => Self {
kind: MsgLikeKind::Other {
event_type: MessageLikeEventType::Other(other.event_type().to_string()),
},
reactions,
in_reply_to,
thread_root,
thread_summary,
},
Kind::LiveLocation(state) => {
let locations = state
.locations()
.iter()
.map(|location| BeaconInfo {
geo_uri: location.geo_uri().to_owned(),
ts: location.ts().into(),
description: location.description().map(ToOwned::to_owned),
})
.collect();
Self {
kind: MsgLikeKind::LiveLocation {
content: LiveLocationContent {
is_live: state.is_live(),
ts: state.ts().into(),
description: state.description().map(ToOwned::to_owned),
timeout_ms: state.timeout().as_millis() as u64,
asset_type: state.asset_type().into(),
locations,
},
},
reactions,
in_reply_to,
thread_root,
thread_summary,
}
}
})
}
}
@@ -241,7 +288,12 @@ pub struct PollAnswer {
#[derive(Clone, uniffi::Object)]
pub struct ThreadSummary {
pub latest_event: EmbeddedEventDetails,
pub num_replies: usize,
pub num_replies: u32,
/// The user's own public read receipt event id, for this particular thread.
pub public_read_receipt_event_id: Option<String>,
/// The user's own private read receipt event id, for this particular
/// thread.
pub private_read_receipt_event_id: Option<String>,
}
#[matrix_sdk_ffi_macros::export]
@@ -249,6 +301,10 @@ 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 {
@@ -256,6 +312,10 @@ impl From<matrix_sdk_ui::timeline::ThreadSummary> for ThreadSummary {
Self {
latest_event: EmbeddedEventDetails::from(value.latest_event),
num_replies: value.num_replies,
public_read_receipt_event_id: value.public_read_receipt_event_id.map(|v| v.to_string()),
private_read_receipt_event_id: value
.private_read_receipt_event_id
.map(|v| v.to_string()),
}
}
}
+14 -3
View File
@@ -14,7 +14,8 @@
use matrix_sdk_ui::timeline::{EmbeddedEvent, TimelineDetails};
use super::{content::TimelineItemContent, ProfileDetails};
use super::{ProfileDetails, content::TimelineItemContent};
use crate::{event::EventOrTransactionId, utils::Timestamp};
#[derive(Clone, uniffi::Object)]
pub struct InReplyToDetails {
@@ -50,8 +51,16 @@ impl From<matrix_sdk_ui::timeline::InReplyToDetails> for InReplyToDetails {
pub enum EmbeddedEventDetails {
Unavailable,
Pending,
Ready { content: TimelineItemContent, sender: String, sender_profile: ProfileDetails },
Error { message: String },
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 {
@@ -63,6 +72,8 @@ impl From<TimelineDetails<Box<EmbeddedEvent>>> for EmbeddedEventDetails {
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() },
}
@@ -0,0 +1,345 @@
// Copyright 2026 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::StreamExt;
use matrix_sdk::room::{
ListThreadsOptions as SdkListThreadsOptions, ThreadSubscription as SdkThreadSubscription,
};
use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
use matrix_sdk_ui::timeline::{
RoomExt,
thread_list_service::{
ThreadListItem as UIThreadListItem, ThreadListItemEvent as UIThreadListItemEvent,
ThreadListPaginationState as UIThreadListPaginationState,
ThreadListService as UIThreadListService,
ThreadListServiceError as UIThreadListServiceError,
},
};
use ruma::api::client::threads::get_threads::v1::IncludeThreads as SdkIncludeThreads;
use crate::{
TaskHandle,
error::ClientError,
runtime::get_runtime_handle,
timeline::{ProfileDetails, TimelineItemContent},
utils::Timestamp,
};
/// A thread subscription (MSC4306).
#[derive(uniffi::Record)]
pub struct ThreadSubscription {
/// Whether the thread subscription happened automatically (e.g. after a
/// mention) or if it was manually requested by the user.
pub automatic: bool,
}
impl From<ThreadSubscription> for SdkThreadSubscription {
fn from(subscription: ThreadSubscription) -> Self {
Self { automatic: subscription.automatic }
}
}
/// Options for [`Room::load_thread_list`].
#[derive(Debug, Clone, uniffi::Record)]
pub struct ListThreadsOptions {
/// An extra filter to select which threads should be returned.
pub include_threads: IncludeThreads,
/// The token to start returning events from.
///
/// This token can be obtained from a [`ThreadList::prev_batch_token`]
/// returned by a previous call to [`Room::load_thread_list()`].
///
/// If `from` isn't provided the homeserver shall return a list of thread
/// roots from end of the timeline history.
pub from: Option<String>,
/// The maximum number of events to return.
///
/// Default: 10.
pub limit: Option<u64>,
}
impl From<ListThreadsOptions> for SdkListThreadsOptions {
fn from(opts: ListThreadsOptions) -> Self {
Self {
include_threads: opts.include_threads.into(),
from: opts.from,
limit: opts.limit.and_then(ruma::UInt::new),
}
}
}
/// Which threads to include in the response.
#[derive(Debug, Clone, uniffi::Enum)]
pub enum IncludeThreads {
/// `all`
///
/// Include all thread roots found in the room.
///
/// This is the default.
All,
/// `participated`
///
/// Only include thread roots for threads where
/// [`current_user_participated`] is `true`.
///
/// [`current_user_participated`]: https://spec.matrix.org/latest/client-server-api/#server-side-aggregation-of-mthread-relationships
Participated,
}
impl From<IncludeThreads> for SdkIncludeThreads {
fn from(include_threads: IncludeThreads) -> Self {
match include_threads {
IncludeThreads::All => Self::All,
IncludeThreads::Participated => Self::Participated,
}
}
}
/// Each `ThreadListItem` represents one thread root event in the room. The
/// fields are pre-resolved from the raw homeserver response: the sender's
/// profile is fetched eagerly and the event content is parsed into a
/// `TimelineItemContent` so that consumers can render the item without any
/// additional work.
///
/// `ThreadListItem`s are produced page by page via `Room::load_thread_list()`
/// and are accumulated inside the `ThreadListService` as pages are fetched
/// through `ThreadListService::paginate()`.
#[derive(uniffi::Record)]
pub struct ThreadListItem {
/// The thread root event.
///
/// Contains the event ID, timestamp, sender, sender profile, and parsed
/// content of the thread's root message. Use `root_event.event_id` to open
/// a per-thread `Timeline` or to navigate the user to the thread view.
root_event: ThreadListItemEvent,
/// The latest event in the thread (i.e. the most recent reply), if
/// available.
///
/// Initially populated from the server's bundled thread summary and
/// updated in real time as new events arrive via sync or back-pagination.
latest_event: Option<ThreadListItemEvent>,
/// The number of replies in this thread (excluding the root event).
///
/// Initially populated from the server's bundled thread summary and
/// updated in real time as new events arrive via sync.
num_replies: u32,
}
impl From<UIThreadListItem> for ThreadListItem {
fn from(item: UIThreadListItem) -> Self {
Self {
root_event: item.root_event.into(),
latest_event: item.latest_event.map(Into::into),
num_replies: item.num_replies,
}
}
}
/// Information about an event in a thread (either the root or the latest
/// reply).
#[derive(uniffi::Record)]
pub struct ThreadListItemEvent {
/// The event ID.
pub event_id: String,
/// The timestamp of the event.
pub timestamp: Timestamp,
/// The sender of the event.
pub sender: String,
/// The sender's profile details.
pub sender_profile: ProfileDetails,
/// Whether the event was sent by the current user.
pub is_own: bool,
/// The content of the event, if available.
pub content: Option<TimelineItemContent>,
}
impl From<UIThreadListItemEvent> for ThreadListItemEvent {
fn from(event: UIThreadListItemEvent) -> Self {
Self {
event_id: event.event_id.to_string(),
timestamp: event.timestamp.into(),
sender: event.sender.to_string(),
is_own: event.is_own,
sender_profile: event.sender_profile.into(),
content: event.content.map(Into::into),
}
}
}
/// Listener for changes to the [`ThreadListService`] pagination state.
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait ThreadListPaginationStateListener: SendOutsideWasm + SyncOutsideWasm + Debug {
fn on_update(&self, state: UIThreadListPaginationState);
}
/// Listener for changes to the [`ThreadListService`] item list.
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait ThreadListEntriesListener: SendOutsideWasm + SyncOutsideWasm + Debug {
fn on_update(&self, diff: Vec<ThreadListUpdate>);
}
/// A diff applied to the observable thread list.
///
/// Mirrors [`eyeball_im::VectorDiff`] for [`ThreadListItem`].
#[derive(uniffi::Enum)]
pub enum ThreadListUpdate {
/// New items were appended at the back.
Append { values: Vec<ThreadListItem> },
/// The list was cleared.
Clear,
/// A new item was prepended at the front.
PushFront { value: ThreadListItem },
/// A new item was appended at the back.
PushBack { value: ThreadListItem },
/// The first item was removed.
PopFront,
/// The last item was removed.
PopBack,
/// An item was inserted at the given position.
Insert { index: u32, value: ThreadListItem },
/// The item at the given position was replaced.
Set { index: u32, value: ThreadListItem },
/// The item at the given position was removed.
Remove { index: u32 },
/// The list was truncated to the given length.
Truncate { length: u32 },
/// The whole list was replaced with new items.
Reset { values: Vec<ThreadListItem> },
}
impl From<VectorDiff<UIThreadListItem>> for ThreadListUpdate {
fn from(diff: VectorDiff<UIThreadListItem>) -> Self {
match diff {
VectorDiff::Append { values } => {
Self::Append { values: values.into_iter().map(Into::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(Into::into).collect() }
}
}
}
}
/// A high-level, reactive, paginated list of threads for a room.
///
/// `ThreadListService` is the FFI-facing wrapper around
/// [`matrix_sdk_ui::timeline::thread_list_service::ThreadListService`]. It
/// maintains an observable list of [`ThreadListItem`]s and exposes a
/// pagination state publisher, making it straightforward to build reactive UIs
/// on top of the thread list.
///
/// Obtain an instance via [`Room::thread_list_service`].
#[derive(uniffi::Object)]
pub struct ThreadListService {
inner: UIThreadListService,
}
impl ThreadListService {
pub(crate) fn new(room: &matrix_sdk::Room) -> Self {
Self { inner: room.thread_list_service() }
}
}
#[matrix_sdk_ffi_macros::export]
impl ThreadListService {
/// Returns a snapshot of the current pagination state.
pub fn pagination_state(&self) -> UIThreadListPaginationState {
self.inner.pagination_state()
}
/// Subscribes to changes in the pagination state.
///
/// The `listener` is called once for every state transition. The returned
/// [`TaskHandle`] keeps the subscription alive
pub fn subscribe_to_pagination_state_updates(
&self,
listener: Box<dyn ThreadListPaginationStateListener>,
) -> Arc<TaskHandle> {
let mut subscriber = self.inner.subscribe_to_pagination_state_updates();
Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
while let Some(state) = subscriber.next().await {
listener.on_update(state);
}
})))
}
/// Returns a snapshot of the current thread list items.
pub fn items(&self) -> Vec<ThreadListItem> {
self.inner.items().into_iter().map(Into::into).collect()
}
/// Subscribes to changes in the thread list.
///
/// The `listener` receives an initial `Reset` diff containing all currently
/// loaded items, followed by subsequent diffs as the list changes.
pub fn subscribe_to_items_updates(
&self,
listener: Box<dyn ThreadListEntriesListener>,
) -> Arc<TaskHandle> {
let (initial_values, mut stream) = self.inner.subscribe_to_items_updates();
// Emit the current snapshot immediately so the caller starts with a
// consistent view of the list.
listener.on_update(vec![ThreadListUpdate::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());
}
})))
}
/// Fetches the next page of threads and appends the results to the list.
///
/// This is a no-op when the list is already loading or the end has been
/// reached.
pub async fn paginate(&self) -> Result<(), ClientError> {
self.inner.paginate().await.map_err(|e| match e {
UIThreadListServiceError::Sdk(sdk_err) => ClientError::from(sdk_err),
})
}
/// Resets the service back to its initial, empty state.
///
/// Clears all loaded items, discards the pagination token, and sets the
/// state to `Idle { end_reached: false }`. The next call to
/// [`Self::paginate`] will restart from the beginning of the thread list.
pub async fn reset(&self) {
self.inner.reset().await;
}
}
+5 -5
View File
@@ -14,7 +14,7 @@
use std::{fmt::Debug, sync::Arc, time::Duration};
use matrix_sdk::crypto::types::events::UtdCause;
use matrix_sdk_base::crypto::types::events::UtdCause;
use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
use matrix_sdk_ui::unable_to_decrypt_hook::{
UnableToDecryptHook, UnableToDecryptInfo as SdkUnableToDecryptInfo,
@@ -41,10 +41,10 @@ impl UnableToDecryptHook for UtdHook {
// 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;
}
if let Some(duration) = &info.time_to_decrypt
&& *duration < IGNORE_UTD_PERIOD
{
return;
}
// Report the UTD to the client.

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